[PATCH] Negative Transition Aggregate Functions (WIP)

Started by David Rowleyabout 12 years ago216 messages
#1David Rowley
dgrowleyml@gmail.com
1 attachment(s)

I've been working on speeding up aggregate functions when used in the
context of a window's with non fixed frame heads.

Currently if the top of the window is not in a fixed position then when the
window frame moves down the aggregates must be recalculated by looping over
each record in the new scope of the window frame.

Take the following as an example:

SELECT SUM(n::int) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND UNBOUNDED
FOLLOWING)
FROM generate_series(1,20000) g(n);

Here the frame head moves along with the current row and always finishes
with the last row in the partition or the last row in the frame if no
partitioning exists.
Currently PostgreSQL must recalculate the aggregate each time, this means
looping from the current row to the end of the frame for each row
produced... So the first row needs 20000 iterations, the 2nd row 19999, 3rd
row 19998 etc.

The above query on the unpatched head, on my laptop takes 36676 ms. With
the attached patch it takes 73 ms. So a speed increase of about 500 times
for 20,000 rows. Of course this performance gap will increase with more
rows and decrease with less rows, but it's even seeing a performance
improvement with as little as 50 rows.

The attached patch is not quite finished yet, I've not yet fully covered
SUM and AVG for all types. I just need to look into numeric a bit more
before I figure that one out.
Here is a list of things still todo:

1. Fully implement negative transition functions for SUM and AVG.
2. CREATE AGGREGATE support for user defined aggregates.
3. Documentation.
4. Look to see which other aggregates apart from COUNT, SUM and AVG that
can make use of this.

Please note that if the aggregate function does not have a negative
transition function setup, e.g MAX and MIN, then the old behaviour will
take place.

One thing that is currently on my mind is what to do when passing volatile
functions to the aggregate. Since the number of times we execute a volatile
function will much depend on the window frame options, I think we should
include some sort of warning in the documentation that "The number of times
that the expression is evaluated within the aggregate function when used in
the context of a WINDOW is undefined". The other option would be to disable
this optimisation if the aggregate expression contained a volatile
function, but doing this, to me seems a bit weird as is anyone actually
going to be depending on a volatile function being executed so many times?

If anyone can see any road blocking issues to why this optimisation cannot
be done please let me know.

Otherwise I'll continue to work on this so that it is ready for CF4.

Regards

David Rowley

Attachments:

negative_transition_funcs_v0.6.patch.gzapplication/x-gzip; name=negative_transition_funcs_v0.6.patch.gzDownload
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#1)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

David Rowley <dgrowleyml@gmail.com> writes:

The attached patch is not quite finished yet, I've not yet fully covered
SUM and AVG for all types.

I think you *can't* cover them for the float types; roundoff error
would mean you don't get the same answers as before.

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

#3Greg Stark
stark@mit.edu
In reply to: Tom Lane (#2)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 14 Dec 2013 15:40, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

The attached patch is not quite finished yet, I've not yet fully covered
SUM and AVG for all types.

I think you *can't* cover them for the float types; roundoff error
would mean you don't get the same answers as before.

I was going to say the same thing. But then I started to wonder.... What's
so special about the answers we used to give? They are also subject to
round off and the results are already quite questionable in those cases.

#4David Rowley
dgrowleyml@gmail.com
In reply to: Greg Stark (#3)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 15, 2013 at 12:48 PM, Greg Stark <stark@mit.edu> wrote:

On 14 Dec 2013 15:40, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

The attached patch is not quite finished yet, I've not yet fully

covered

SUM and AVG for all types.

I think you *can't* cover them for the float types; roundoff error
would mean you don't get the same answers as before.

I was going to say the same thing. But then I started to wonder.... What's
so special about the answers we used to give? They are also subject to
round off and the results are already quite questionable in those cases.

I guess they probably shouldn't be, subject to rounding / influenced by
errors from tuples that are out of scope of the aggregate context.
Though saying that it would be a shame to have this optimisation for all
but float and double. I can imagine the questions in [GENERAL].. Why is
SUM(<int>) OVER ().. fast but SUM(<float>) OVER () slow? I wonder what
other RDBMS' do here...

Regards

David Rowley

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#3)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Greg Stark <stark@mit.edu> writes:

On 14 Dec 2013 15:40, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

I think you *can't* cover them for the float types; roundoff error
would mean you don't get the same answers as before.

I was going to say the same thing. But then I started to wonder.... What's
so special about the answers we used to give? They are also subject to
round off and the results are already quite questionable in those cases.

Well, we can't easily do better than the old answers, and the new ones
might be arbitrarily worse. Example: sum or average across single-row
windows ought to be exact in any case, but it might be arbitrarily wrong
with the negative-transition technique.

More generally, this is supposed to be a performance enhancement only;
it's not supposed to change the results.

This consideration also makes me question whether we should apply the
method for NUMERIC. Although in principle numeric addition/subtraction
is exact, such a sequence could leave us with a different dscale than
is returned by the existing code. I'm not sure if changing the number of
trailing zeroes is a big enough behavior change to draw complaints.

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

#6Josh Berkus
josh@agliodbs.com
In reply to: David Rowley (#1)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 12/14/2013 05:00 PM, Tom Lane wrote:

This consideration also makes me question whether we should apply the
method for NUMERIC. Although in principle numeric addition/subtraction
is exact, such a sequence could leave us with a different dscale than
is returned by the existing code. I'm not sure if changing the number of
trailing zeroes is a big enough behavior change to draw complaints.

If we're going to disqualify NUMERIC too, we might as well bounce the
feature. Without a fast FLOAT or NUMERIC, you've lost most of the
target audience.

I think even the FLOAT case deserves some consideration. What's the
worst-case drift? In general, folks who do aggregate operations on
FLOATs aren't expecting an exact answer, or one which is consistent
beyond a certain number of significant digits.

And Dave is right: how many bug reports would we get about "NUMERIC is
fast, but FLOAT is slow"?

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7David Rowley
dgrowleyml@gmail.com
In reply to: Josh Berkus (#6)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 15, 2013 at 2:27 PM, Josh Berkus <josh@agliodbs.com> wrote:

If we're going to disqualify NUMERIC too, we might as well bounce the
feature. Without a fast FLOAT or NUMERIC, you've lost most of the
target audience.

I don't agree with this. I'm going with the opinion that the more types and
aggregates we can support (properly) the better. I'd rather delay
implementations of the ones which could change results than see them as a
roadblock for the ones we can implement today without this danger.

I think the feature is worth it alone if we could improve COUNT(*).
It's a bit silly to have to loop through all the tuples in a tuplestore to
see how many there are after removing one, when we already knew the count
before removing it. Something like, 10 - 1 .... ummm I dunno, let's count
again.. 1, 2, 3, 4, 5, 6, 7, 8, 9.... It's 9!! Where with this patch it's
just 10 - 1 *result*. Feels a bit like asking a kid, if you have 10 beans
and you take 1 away, how many will there be. You and I know 9, but the kid
might have to count them again. PostgreSQL counts them again.

Regards

David Rowley

--

Show quoted text

Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Josh Berkus (#6)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Josh Berkus <josh@agliodbs.com> writes:

I think even the FLOAT case deserves some consideration. What's the
worst-case drift?

Complete loss of all significant digits.

The case I was considering earlier of single-row windows could be made
safe (I think) if we apply the negative transition function first, before
incorporating the new row(s). Then for example if you've got float8 1e20
followed by 1, you compute (1e20 - 1e20) + 1 and get the right answer.
It's not so good with two-row windows though:

Table correct sum of negative-transition
this + next value result
1e20 1e20 1e20 + 1 = 1e20
1 1 1e20 - 1e20 + 0 = 0
0

In general, folks who do aggregate operations on
FLOATs aren't expecting an exact answer, or one which is consistent
beyond a certain number of significant digits.

Au contraire. People who know what they're doing expect the results
to be what an IEEE float arithmetic unit would produce for the given
calculation. They know how the roundoff error ought to behave, and they
will not thank us for doing a calculation that's not the one specified.
I will grant you that there are plenty of clueless people out there
who *don't* know this, but they shouldn't be using float arithmetic
anyway.

And Dave is right: how many bug reports would we get about "NUMERIC is
fast, but FLOAT is slow"?

I've said this before, but: we can make it arbitrarily fast if we don't
have to get the right answer. I'd rather get "it's slow" complaints
than "this is the wrong answer" complaints.

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

#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#7)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

David Rowley <dgrowleyml@gmail.com> writes:

On Sun, Dec 15, 2013 at 2:27 PM, Josh Berkus <josh@agliodbs.com> wrote:

If we're going to disqualify NUMERIC too, we might as well bounce the
feature. Without a fast FLOAT or NUMERIC, you've lost most of the
target audience.

I think the feature is worth it alone if we could improve COUNT(*).

Agreed, count(*) and operations on integers are alone enough to be worth
the trouble.

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

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#8)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

I wrote:

It's not so good with two-row windows though:

Actually, carrying that example a bit further makes the point even more
forcefully:

Table correct sum of negative-transition
this + next value result
1e20 1e20 1e20 + 1 = 1e20
1 1 1e20 - 1e20 + 0 = 0
0 0 0 - 1 + 0 = -1
0 1 -1 - 0 + 1 = 0
1

Those last few answers are completely corrupt.

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

#11David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#10)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 15, 2013 at 3:08 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

It's not so good with two-row windows though:

Actually, carrying that example a bit further makes the point even more
forcefully:

Table correct sum of negative-transition
this + next value result
1e20 1e20 1e20 + 1 = 1e20
1 1 1e20 - 1e20 + 0 = 0
0 0 0 - 1 + 0 = -1
0 1 -1 - 0 + 1 = 0
1

Those last few answers are completely corrupt.

I guess the answer for the people that complain about slowness could be
that they create their own aggregate function which implements float4pl as
the trans function and float4mi as the negative trans function. They can
call it SUMFASTBUTWRONG() if they like. Perhaps it would be worth a note in
the documents for this patch?

I've removed the negative trans function setups for float4 and float8 with
SUM and AVG stuff in my working copy now.

As for numeric, I did start working on this just after I posted the
original patch and before I saw your comment about it. I did end up making
do_numeric_desperse() which was to be the reverse of do_numeric_accum(),
but I got stuck on the equivalent of when do_numeric_accum()
does mul_var(&X, &X, &X2, X.dscale * 2);

If it is decided that we don't want to implement a negative trans function
for numeric, then I guess I could leave in my trans function to allow users
to create their own fast version, maybe?

Regards

David Rowley

Show quoted text

regards, tom lane

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#11)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

David Rowley <dgrowleyml@gmail.com> writes:

I guess the answer for the people that complain about slowness could be
that they create their own aggregate function which implements float4pl as
the trans function and float4mi as the negative trans function. They can
call it SUMFASTBUTWRONG() if they like. Perhaps it would be worth a note in
the documents for this patch?

I think it would be an absolutely perfect documentation example to show
how to set up such an aggregate (and then point out the risks, of course).

As for numeric, I did start working on this just after I posted the
original patch and before I saw your comment about it. I did end up making
do_numeric_desperse() which was to be the reverse of do_numeric_accum(),
but I got stuck on the equivalent of when do_numeric_accum()
does mul_var(&X, &X, &X2, X.dscale * 2);

Ummm ... why doesn't it work to just use numeric_add and numeric_sub,
exactly parallel to the float case?

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

#13David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#10)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 15, 2013 at 3:08 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

It's not so good with two-row windows though:

Actually, carrying that example a bit further makes the point even more
forcefully:

Table correct sum of negative-transition
this + next value result
1e20 1e20 1e20 + 1 = 1e20
1 1 1e20 - 1e20 + 0 = 0
0 0 0 - 1 + 0 = -1
0 1 -1 - 0 + 1 = 0
1

Those last few answers are completely corrupt.

For sake of the archives I just wanted to reproduce this...
I used the following query with the patch which was attached upthread to
confirm this:

SELECT sum(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1
FOLLOWING)
FROM (VALUES(1,1e20),(2,1)) n(i,n);

sum
--------
1e+020
0
(2 rows)

SUM(1) should equal 1 not 0.

But unpatched I get:

sum
--------
1e+020
1
(2 rows)

This discovery seems like good information to keep around, so I've added a
regression test in my local copy of the patch to try to make sure nobody
tries to add a negative trans for float or double in the future.

Regards

David Rowley

Show quoted text

regards, tom lane

#14David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#12)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 15, 2013 at 9:29 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I guess the answer for the people that complain about slowness could be
that they create their own aggregate function which implements float4pl

as

the trans function and float4mi as the negative trans function. They can
call it SUMFASTBUTWRONG() if they like. Perhaps it would be worth a note

in

the documents for this patch?

I think it would be an absolutely perfect documentation example to show
how to set up such an aggregate (and then point out the risks, of course).

I've attached an updated patch which includes some documentation.
I've also added support for negfunc in CREATE AGGREGATE. Hopefully that's
an ok name for the option, but if anyone has any better ideas please let
them be known.

For the checks before the aggregate is created, I put these together quite
quickly and I think I'm still missing a check to ensure that the strict
property for the trans and negtrans functions are set to the same thing,
though I do have a runtime check for this, it's likely bad to wait until
then to tell the user about it.

As for numeric, I did start working on this just after I posted the

original patch and before I saw your comment about it. I did end up

making

do_numeric_desperse() which was to be the reverse of do_numeric_accum(),
but I got stuck on the equivalent of when do_numeric_accum()
does mul_var(&X, &X, &X2, X.dscale * 2);

Ummm ... why doesn't it work to just use numeric_add and numeric_sub,
exactly parallel to the float case?

I've not quite got back to this yet and I actually pulled out my initial
try at this thinking that we didn't want it because it was affecting the
scale. The transition function for SUM numeric does not seem to use
numeric_add, it uses numeric_avg_accum as the transition function which
lets do_numeric_accum do the hard work and that just does add_var. I
changes these add_var's to sub_var's and altered the initial value to flip
the sign so that NULL,10 would be -10 instead of 10. I think that's all it
needs, and I guess I leave the dscale as is in this situation then?

Regards

David Rowley

Show quoted text

regards, tom lane

Attachments:

negative_transition_funcs_v0.8.patch.gzapplication/x-gzip; name=negative_transition_funcs_v0.8.patch.gzDownload
��|�Rnegative_transition_funcs_v0.8.patch�kS�D���W�����(oD;"T��8���&�M�1����=�l6�m,wt�"s���9g������� ��$�������~'����LFzb:�M�k&7��W/Bz��Q��tz�D�AO�vw�6��v-��f�YO���D{���u$�����}�!��g�������Y�c�b>���F�M�s������,�yY�����$V��@�<��]@�����j�������V�Fa�R�C���?*�?��w�N���,�r���d����G�~G�><2�U�^����'%�Q5���/,�ZQa,��r��I�rZ��
�^�d�F�K���?��|�w|�;��NG�'r���k�r~R�|f�\��zq78��/��|	O�Y�D�Sr)�E��O7���@@�}�X�������a�������B|������������f���U��r?�� ��&}��M�J�(o-q{~�6r&���>�hZ���/��!�����m�t~1�>�Z�dPOix=|�����{Ba�.���c�<5�������5��l���L��(bS�>Y���������oqu�w�O�����Fw?�\<OG�����E&����rp'>��F��^)��N��������mp|�i��f&�6i���������,5$�����"����&�O�b��I�B�["{TF���O��	 E�6;�j�A������ny\���G���iQd�$��2����0��Si&�Se����qy�W�iDh��(_�b)��d�%:R���������t����[��g�/R5�O����`bK���MXK��b�����L��x����j8���w��)WREgo6���*���s�=;i�g��,��0^&���=�"�I�Z7v���Y�5v�?�C�� L����N�VMLN�E4��'�-%S��$�����)�r�Zjdzd���l<U�MV�,+2���&7W�@��g������E�^����"U1������O'����LN,����9�u��xi8f���jO�/�
\����C ��y��~k����|������������JCe
��-�)�R������A�vDVb�*'������;,����������N'88>��k����f)�zTt������K>MD#���N�7J"U:_�'�Eddt�_�B��&�t=7D�9]-ID��4�.[�(0�����T���s��XtR�eG���T�G�0fb�7D�<R��m#�c�e>L���H{���b�KT
XS{�[8�!��t�����{�)��x6�����_�rFj��=jMLk��aV��HfU�6z�\��*���p���$J�@H�����\����
$�c!�JA����p�fHN>����y�z�� ��`/!�6Q�~C�����`Q���5��hc�q��������
�"���W��:5����*3�2X�\�db&6�QR��_�5��2��\��4��F9��HO���Pc����lp�5(<(�y��"�`�0*�21
7��g�Aw�Dpq��Qrf���:M������U�[�~T�2�0+a���Q����K��D�T���v*�"�Xz��l���LJ�O���y�+�p���N�N���=�5�p�X����&��'t'�j(5<�Nn.��� 68@���K����h�����B��)�.U A��Y9��U���L��:[E$�.��S\i�&�Qo�~[n���p�G�o�4���;>��x-������� �NGy�Q�D&��|G����%��0��3g��c�����nA�v��]-
K�
B����	��f�Mh�Y
g\p�S�m���7�O��S��F�����U#�%3 n!�2�����Z" \%I����������_��q|:3f�b���2��������qiM�S]�b��q�'�i��P�UGZ�2K�\F��"�-K�7*����?�R�a<��Z�� 6��� �4�������Tf v��\�$�s ����s���n*U	XZcpwws�,�O���}��7������96�F_��>��j�A�@:5��fu����s�3x�,(�CabTx��Y
ID]1ISF���X5*��T�� o��S�k��W����3u�����Q��m^bc�9Xk%��S���#7����gP���Re����a5T�lV���Q�tb���H=WV�5�h~
:W����d�v��P�x88�����~��;|��S�?��������>,�{F��N�K�@v��M���������r�-q9�\_�/~]��}}~��+c��:c+����[����IY�6��9�7q}�j�(_"������M�vd�F4���p�]{�R��"�6�}��u*?�k����?��
!�-v��
����Sx�p**q�H1�p�a�qk3�i�����E���X$,PT���al,Mo\�����dN�����M}���z0[6���=��yw��+e�z�\�\jK�#*��p~�@8��u����:?K�� _	�I";��e�*���4L"=�>�EwtN|'�(�'FT���P���q=��v���E�
	Ro�<J�}��`���K��;�~{u�q��j�����?,,[X;�}@D�f&#�������"�;%��!��,���&�����I${���S4�o^���J���X����i_��+[��~1���A'�JB�w�B��0��"/W���e��T��h���0�J\��\rb�(���B�j~]Dw+Z5�y�����$
;�)�u�c�r���i,�cu�V/����n�}���z����<
�N�t�wtp�W"u-	����TzL�?��8��`�+v�%��G�Q?[�u*����gT�#��@H/<D.1xI�R �|�c��nB�Y(_����� j>Y5������A`�w
�������D�&|��z���s�������6�Z�	�d�y:��l^K�iG]�%�B�g8+�	[9Te]YbkA�<\u�$O�c8L'�(��(s,��u&�L��,��_�9��+�����X��t:{���;Qk��#�<����.��.{	9>�o���	'C�U)��	J��A���,���*�n�sl/��;Hj*���r��d/V�b�@u�Z�kR�g�N�1�r�q4D�.{�K4�i���
����W���$#S����`k�Vj������ZY���{��x�������U�>���>�Q*�>��C�Hm1c�Jl�/i�n��;`��t�����I��E[
z�2�yG���#l���k��?�T������������������4����Z$,/�8Cs
l�g������;�����^�������oeQ���GZ��X�Q� �'{oBg�����;@����������*����i��Ms-n����ls�ZW:��!�w;|������9s[��U��[aw���}�FU
�4'��Jl��D��"���=�:���(����l��� R��5���?�����Z��C8�V~���	��7���yY.����S]���W*�����/��QZ+*C��l�)}��:�_����!\"<����|(�����'��z�
�;�8�����3p
�|�����"(�/�xX�m����e�p7<�D���xV|;�^��Ey�Lj<=L]\�`�j�$�O>����b��-������H���'�l6����8a�$�T��QC�I�\���iL{<o�*�r�*u����9oF=s��w������SJ�T)N��������H'(���Z�)>m��yf�R��6�Z(\_�qy�`��vj������	����\#��aX'��������<\�,�J0pWT�,Ke/�$<K��@S�QB�\I�����J��h��Yw���{�Ax��+ZZR�v�Ma��:�l��ID��p���d�A;^�O��
�-�8<�����Cr,��X�a��.�������	�H��H;M<���g���K�9�8��[
��S�j����lb�����mc���%L
w�����F��kj����.�h�������K�����b�T���83^���9���?�=h�}5�*����������'���v���������b6����<h	����b�p������VEa0`��*��	����l��bR�B)��%k	9������b��2'�c`���+���%;A�������w�����k�fkz���b��>2B�� ���~���R����$�O�.?���EI C�kQU�B��^`ee�!��b���Dn���I��6�
���b-�g.9>z���`2]���iT�'1w���A�s�>>���	}������8�S_����x{g5����T�gfogm�N�	(��w�����3S�_P��N�.�fMD3��������h��5������hS%�j�����YZH6hl�XN%�����
����%�^K������Z�LiY!��?'s��g�`\R��a2���Q����@�dy�Q��K���Zd�$�@�S����ln(�����~3uo�3�'D��)�jSsSx�7n��4�mK�������n�'+&i1mF���:P1a�F��N���	.�~�m�<�-�f�����X<��S�������O!���p��>��A�/K\���MW��4�~�����M���T�{�PA����������_9W�o������o���v�Sq��%�L��s�YAm��K-��W5���'�t
"���5`�����q�����c�q��kt��2~�������X����/V�E}�{�@���I�l��[�7���
N��?j���M�+�A��^h4+k���zx/dH�/W��W�o�{���������moi�����tk�C�P�pa�LO�@�����h�H��,��K��HK�!"�������@v�K�����������@e-���85"�Q��K-�D��9���J�
� ���G��A/�@�b�"�LeKcK��r +>��WQ}��6LTE�ql'a�J��|�B2�N�����(����f�1�F��[�7���<�9u����Y4�S�x�
s�AC���U���9vB�4h�0w�������]��>Ju����&������H�gq��,��	#-��V�9j����fIb&��0@)\E(��_�Z�"^������)~����0!����rx5Yj���X�pj�\h�����E'��{Z:��m�fY�vM��W���WM�OfLV�4�K��L�T'RWB2�kE�"��p����2�Uo�>��H���`��(�`k��� W�K@�LA"���m{;��F{��l�'�Us�(����#�(������=t��zn���AF_:8[F���~�����>��eg���
��kk+g���O���8���%R��}�����$4bA�U.X�aYXOn�J`����� ����J�P8�����h���f��#�\���X����of���<�e�*�"��q���P�3��O�����x�Upnrc�<].6x&�!�j�2��	�����lz7����������W�8d�8�~�rIZ`���]w ���]Y�+!"d~�f�$�3�������u8�����a�.��v�~�~O�����	=��9�HS��*e��o��e�|E�uO�M0�,������-��0w�0�x[�6����������	�Z�*H���c3������,-OIdbY����	�4HD�����E�B�
Y��o�5j�lb��#m��(u�������f:��!�~�8�e�Ex���r��<�$$�W�89�5�i&i��<�����c>B\����=L&�Um/���QJ����v��3�u2-�w	F�3�Dy��:�H�LSk8�����+�\p��mw�L/��`r�k�b]@:���P����p{|�� �'f��
�kV�u)����Dz�����=
�\I:[��H���MsL������;�l���W���l������;�46 �1S�b��.d�B�1	�0����d���&��5a�2n
\�o��V�I��P�+�W��b=\+�!��&f0@V� ����YZ��\����U���A�� �%}�="$?��������`P�9�	�[� ��m��Fn_�������i>j�1Q��J���������<A�/���
-���)����e���EN��������FL9
��{��t�����R��nRI��H�y���v�y��>7f��t�8��fh��������o=��7�S��k�b�?*x&ddkv�n����r��<���b�%�A�������$eE��
��m}3�
6\���JB\
K�j��8�H.������Q�lD5��������P#�GSs�,�f:i�
�����R��pi{�'���_��v��.��0JS��
�������<�Z����b�_=���0%��aG�y����s��Q����"�p�E����*�W�Q������KSq�^'����-3�]���������i��7ZXxQ��$�:�������w�7oF���4&��9��{^����cb�B;��BE��A�/����������r|>z�/���yY;T�������R8"��.w�,���&-m��Ln��]V�����$i�9v�
�ak^����-BAk"D��K"~�%:�&G'6�?�`4\�%�}�Q�e|���f�������1��<�����������]���v���d=E����_7gA�<7�U��L.���[�@B�/6�"
��T7�16G��G�|���.��B�Dw�+\����_k���3T���l���������]�p��0������(��t�vlR���/qqm&)����������`h��Z�&���F��l����i��;$!H����n�F�Ue��D+�4a7��4y��=�h�
��i����t��H�b\�s���
:	�A��JH:�H�*��-:p2��8�G�������:�p�3}>��2o�@��J�vv�P�xxrf��xvd����wd8�M�5N*�F.�.�QC���>�����% �d��� ��2~qMV*~��cY�X������W��lmy4���Q7����65������w�Ixw�����Q������o
�����bf�a�l����Z�9�jZ1�07��F���������g^��9V��Z��������MD���&�:	�~(T"���?.��ecZ��w�Qc��A�aQK��HH�j&5X��ix_]_\�_�>zsu������}nX���}��~-!��!��f�,U�:^���:�lXP=V�l��������}Z�:��:��l�Qw�O���9�
L��0���b���7!�I���>l�cEn��VQ+���
�PVK�����r[5	��=MX���.	�>������A������t�=R�fp4|3������������-�1'���3�"!��h�H���������~���0H���a�lg:v��M�qD��dG'�O�W�'�F��
��^2��d��x�#�{at7���������p4�k���\�$aNC��CJ�����i�x;��qi�F��Y��>oi��|m������K�Kk��������!�zgi$�X�K;@�:��A�m��� ��5�p�
	T�M��I]�>n�S�f�vP�@�-^�'�(MJ��*��F�G��:(��\�f)��0�����C��,��|`�gf��ro��E)\g�FH[x���E��gd�0�97b��8�+e�������`�)����@S!�e|"8���k6�m��==#���x�J����{Q���T3hg�9����Iw���;]��2y� f>���v�4��7K�����������<okr|�h��9@{�N�_����5��yYk��
����7�����
���A��k>!a�r���r=��_?�gB�x�x�y-5��7�>oh}�T��
�7�a}����7�I��P{�-���"��Z[�h[%>��S�v�����6��l�Q<)C��W�V�F\�/�o���K7�0��O���\H-7i��&/Yk�K�uh#�p5������5��T��jp�K��������
5*�$��������5�����������C�7��`��!������B
�,N&����X;:���"�A��E�2�Ls(W8����N�{W��4������Jl7)������+G
!��T���f������"�RUM��sv�������9ixa�(P��R�-$A�l����l���M�v$�+�z\f���!+�rvy�]�6�������
&M�k��\���bJx��$}�X}��#�����
���������0�$&Vr�x�y��x������(��(��bpQJ�������xC��q?z�f���FO?�AI��R��/eR��4l�	���KM�{��G8b�9~�U�BH���8/�l~t>�!�����5M����R�_�ip@������=q���/M��M�zi��m^'|���m4x&�(�p:��/�Q��e��6�r�����	�px>]0������%���'���n���g��
�bI��O�i�����ZJ�-�M�V��>o��@������7`�u�����2&����p�����������/�#c��D�{�a��N6
zV}�3�0��q�5>���vO�m0��������^Dr�yP����� �*�)q6� �k7�dJ?�&�������X��m@K�z�w5��5����Av�es;�s�(���jiV���&�������-b��"` ��Y�N5t��t,���2����������W��,h������	�K�Gm�4i���H�9�,��X�!j���2�K�{�����~^�u���{�'���q+����x/gQ��$5�3��(��&k�m��S�Qc��*;p�+i�zGn���a�Ac�DQ0���G�����6�-"d���S�-���"N!*V���_d�+����;2��UD1�UV��0���F�
v��qEO�|��-�L�B��dKB?)qWO��0���0�"��M���G�G�3��h��������Zfb�������X|�Ei[&�(e��y$�������D�iF���>�s�~C�A�
Qs5d�]�C��X2�����y
{������}�
�L��b�_�	Y���V�GP��,@/�lO�P�����;���PU����@��;'9�Y��[�M���� ����P���v9��m��+�i��3*���I�v@�`���m�T���R���m9r&n����R���W��Lb,%DKR�����9�m�A9��k�	��� ?�}�T���'�a�D�����Ne4�^�QL{���#};|��_�"������W��s$u��c�&�m]B{���s{JK����R^���b������$�,8)���[F���=���������K����cP����q^v+G+Hp�Dp5W;����Z��\
�C��G\=�����HWK��4��	IV����?����13��6�������L���n�=���D�!����a��_��+������>}=��&��1~�|,�C��rM���Z��v�Q�f����+����bSSM2�
9�x����C)��F���;T�W�eY����
o�7��7��'"w����[Z�����v�Vy5����	�e�u��z
V�	��X;'ujrM�]Q�z��6�*�c=wk�;D�:�>A�z��A���J
�`x���B2I�[��@	����
���������9I�~�L/��������ysN��P�3�$���r���_S����O���4����nOo����S��,�s���������7>�v%����=����������A��!�",}������%99��4�l��$�>c,�c��8y����oJZ�0�/��^?�o�����3/f�����?R����`����sC��3�P�h��E&�(
�����Qap�u-��HL��I�0���skS�$�_R������o�z�����E�� �}�������X���.VAd2��{j�x��r�Y^2����rx�ld.���1���0#�J�3<�������B�{��A3��8NU����.U�d_���K�CJ<��cRd�4����*�pB=���q�=�%ld���'��;���1I�
��i�����*�q�lt���~���������g8������_�w\����Dg9b��W����"=�%6>�w}I�������K�"4����7���g������/��2����Q=���7������W#���v���cJ��'>U��D�M�VW���c36�Z�����{��������y�&�e2&���	kY�,�)j����6
G�pJ��#�9�M������8Oh�|�����?��� �Z���.�yr��������H��B�v$hG�MGp���)	�Rq��d�RY���)���^��xY.AD���0���L5���M#�V�'B��	������#Y��h�h���i�c[B�(�F�
��]/vV.G7ud�������q�C�3a���#�s%"<�O�����������0��i93��Ua��
S:xA�'|A|V��9S�x��{�����4UU�9mj��s��	�m�0l���0���3�aU�u���]U�HhB[Z#�5�-������(NPKt�D���;B�S���	m�����6H@�J�$�A�M����iZ��<#��Q=�#������$ I�-UV��[�$$L�Mm����<	�S`K���%R-����B1�b�\z���|���8!���<���B��&E��$B����^����x��V��&�y�3��I:Sgg_J;��]Op����++���0j�l�m8��!]�W�M�o��_�/���1���z+A�BAH�"
"9@d"D���:
}�1�J�a��9y��,�����������Y�g�,�u[�<q���x��(;]
N����
��A�s=<�K��y^*���!b�r���&r�P""��R�U �ib���B�K��dD�A~?��(w��%3�n���P��1���u��|�9��k�nC0uE�$�.RpIN����RDr�"GZ�D�JM]����
�,�\:,p%��nL��GR��w�����zB+_F:!�� *����
���}�yN�|b�X�dRk-��uX���.p`:����xu QiW0���<������z���+HmN����I��t2��,��g���t:�&m�Q�Gd-1e,��t���K"q	0����zC�n=�Rg�T���HLqG�Kz:�.���:5������6=���S<��t������\�l�"����\�_G�c����.�M��nD�y���K��Et�Z�aL������8��x��Fr
zs����a(< }
U4�&Z�Q�[_RS�c��GS+�jn�A��Xp����o,�!�����6D������O��\g�rDc�	��T��d�M� K���MV�7���$�J
.	�[���&��^t�0�����C<�HDz�pg�4���@����F��B)���{~65G������r,Ec��q)�nn#�f��?�0�A�-�6V���lj�@���.����R��"9���T���H����p��k��0�����p��Fn#�$��b4QV��S 7�a1��
�UO��I�����v��eC�\
sJ�95^�y��~������������-�i?��Y��J!	�N�<��uv|�(U��0��>�{�V��O��(�F�����GUU�&�fh�y���he)���@.�����y�-��l��7��N���88Z�}�>#D]f��bx�V�&�to��X�������/]�{(\y^���Z/�)s^�*����c�e^�]_�=t��k����������O����r��`�m����e�nz�_��U��VWr��b��?���I�n��3v�kt[7�1]^��*K����RB;���O��}'���J9c�\&�����b���o>�����<joC���o>��������yz��r~���.�%�hu$�Vp�����}|��~�{��D�����g�����������m� �f��FS
te>M%��v�=h������i�;M}s*^�������J��~�uEh���_]���q��T��'^�9��{�� "����q�n�i�e��hBp�/+jU������D��������[��B�~n�h/[���w�	���F�k��h����F�e�wG���Q��K6"8]�A�t#b>���GW��v��_�Wa[�N/g���;m�]7d�I�{?�$:�
~nRv��D�X�	�<����2jyQ��b}ym�������E1m����0#�#pq\��<������&V=����� �v9Nz2��HN7pSP����������h�rI[s�����"b3��
����}�m�K	��r�}�b[�
�q���Y��������v�1��\��Z�H�k�.>&�9
6������9�l:����9�l
g��
|�����D���A�
�W�o�~w�g�����^�u��_�������)v��}�'�N��77�=�Awcpy����|Av��/P��<��{8��rZ�|��py������������'����q�0���j�,�&C��w�������SO�����jM�c� �%e����4�����O{���&D���P�vC��8���&$M����4�S��uA���(R?�3�`H�e���*5���gfo���b�y�X�om�hsprr�y��E�C2#6!�i,���nt�=�������q]��D���B�{�����v����+�7���w�H�D�E��e�n4?4#
�3%i���kK���w��e$���,�\^��=����������K���C�&WimX^�sco����H�G
q^V�w��2)��cvw���^�E��>kS�S����#�����i��=9A|d�:A���F"�$������<i�+&h0	��k5R-aq�.����,���[>��)�rWJ,[`��F������v]��*�RE�2J��ydm��,��GSL��3��6T0�
����U�X(P���sJV�O?������������{�t��
�"
o�*�SDT|�V����?tcC
A7t�k/�:��9�Gcx��(�pdEa@+H0

������zoI
0E��&�l�t�b�ZM���%/���	�)�
���K�� (j�����r�)N�A�(G���,�����Ul���E�L����<��]�|��(��0���^*
���/��R����X��=W���Q�0�������Gg�I��E����}���Ie�h�p�Y��j�;���s��t.�	������E����u�<r���uv��<m�T���0�^I3�Z��L�[=&l��BR��I�L�#�ts'�_����(uB�^���0��S��2����M�����l��

>Zn�hd�S����.k�ua<��"|�E���H�C�5��A����A^�<-g�� eC|��A�b2�`�S	�LT�AX)��ZD`��L9T*�u+���JH�0`wkkm�Q��n�d�Z\�+K���)���p��-w(�S��|\�5c}�JZ_�G�X��t������\���������:J��B�� Pi�F���B�o�hZ�f�A����&W;c�
�d���*�����%��U�1������?+
�	|k;��>�CqX��p;���?����Z�'0�z�6� ��Y��G����Z<��$�5�(�,��1�Y�"v��<�}��S/z��f=R���<J�OR��ea��ke�(��S�����x��6a*d(D����������$a�zn�H���'9YB~
����J2�F���R�����z�m��?�M����!0��ob���y|z�!T�^��=�����W������AF�b��$�U
s���������J��B�O^&%~��g�L���<r�`j�W(%O+o=���L����$pc���8���	��D�+O�IJ����~V�z�A+�A�4BY9vWM�Uq�S}RM�P9�OE�Ni�M}�LmjK-nJ-"�E�>��6
�.�������Z����
#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#14)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

David Rowley <dgrowleyml@gmail.com> writes:

I've attached an updated patch which includes some documentation.
I've also added support for negfunc in CREATE AGGREGATE. Hopefully that's
an ok name for the option, but if anyone has any better ideas please let
them be known.

I'd be a bit inclined to build the terminology around "reverse" instead of
"negative" --- the latter seems a bit too arithmetic-centric. But that's
just MHO.

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

#16Ants Aasma
ants.aasma@eesti.ee
In reply to: David Rowley (#1)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Dec 15, 2013 6:44 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I've attached an updated patch which includes some documentation.
I've also added support for negfunc in CREATE AGGREGATE. Hopefully

that's

an ok name for the option, but if anyone has any better ideas please let
them be known.

I'd be a bit inclined to build the terminology around "reverse" instead of
"negative" --- the latter seems a bit too arithmetic-centric. But that's
just MHO.

To contribute to the bike shedding, inverse is often used in similar
contexts.

--
Ants Aasma

#17David Rowley
dgrowleyml@gmail.com
In reply to: Ants Aasma (#16)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mon, Dec 16, 2013 at 6:00 AM, Ants Aasma <ants.aasma@eesti.ee> wrote:

On Dec 15, 2013 6:44 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I've attached an updated patch which includes some documentation.
I've also added support for negfunc in CREATE AGGREGATE. Hopefully

that's

an ok name for the option, but if anyone has any better ideas please

let

them be known.

I'd be a bit inclined to build the terminology around "reverse" instead

of

"negative" --- the latter seems a bit too arithmetic-centric. But that's
just MHO.

To contribute to the bike shedding, inverse is often used in similar
contexts.

I guess it's not really bike shedding, most of the work I hope is done, so
I might as well try to get the docs polished up and we'd need a consensus
on what we're going to call them before I can get that done.

I like both of these better than negative transition function and I agree
negative implies arithmetic rather than opposite.
Out of these 2 I do think inverse fits better than reverse, so I guess that
would make it "inverse aggregate transition function".
Would that make the CREATE AGGREGATE option be INVFUNC ?

Any other ideas or +1's for any of the existing ones?

Regards

David Rowley

Show quoted text

--
Ants Aasma

#18David Fetter
david@fetter.org
In reply to: David Rowley (#17)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mon, Dec 16, 2013 at 08:39:33PM +1300, David Rowley wrote:

On Mon, Dec 16, 2013 at 6:00 AM, Ants Aasma <ants.aasma@eesti.ee> wrote:

On Dec 15, 2013 6:44 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I've attached an updated patch which includes some documentation.
I've also added support for negfunc in CREATE AGGREGATE. Hopefully

that's

an ok name for the option, but if anyone has any better ideas please

let

them be known.

I'd be a bit inclined to build the terminology around "reverse" instead

of

"negative" --- the latter seems a bit too arithmetic-centric. But that's
just MHO.

To contribute to the bike shedding, inverse is often used in similar
contexts.

I guess it's not really bike shedding, most of the work I hope is done, so
I might as well try to get the docs polished up and we'd need a consensus
on what we're going to call them before I can get that done.

I like both of these better than negative transition function and I agree
negative implies arithmetic rather than opposite.
Out of these 2 I do think inverse fits better than reverse, so I guess that
would make it "inverse aggregate transition function".
Would that make the CREATE AGGREGATE option be INVFUNC ?

Any other ideas or +1's for any of the existing ones?

+1 for inverse.

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
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Hannu Krosing
hannu@2ndQuadrant.com
In reply to: David Rowley (#17)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 12/16/2013 08:39 AM, David Rowley wrote:

On Mon, Dec 16, 2013 at 6:00 AM, Ants Aasma <ants.aasma@eesti.ee
<mailto:ants.aasma@eesti.ee>> wrote:

On Dec 15, 2013 6:44 PM, "Tom Lane" <tgl@sss.pgh.pa.us
<mailto:tgl@sss.pgh.pa.us>> wrote:

David Rowley <dgrowleyml@gmail.com

<mailto:dgrowleyml@gmail.com>> writes:

I've attached an updated patch which includes some documentation.
I've also added support for negfunc in CREATE AGGREGATE.

Hopefully that's

an ok name for the option, but if anyone has any better ideas

please let

them be known.

I'd be a bit inclined to build the terminology around "reverse"

instead of

"negative" --- the latter seems a bit too arithmetic-centric.

But that's

just MHO.

To contribute to the bike shedding, inverse is often used in
similar contexts.

I guess it's not really bike shedding, most of the work I hope is
done, so I might as well try to get the docs polished up and we'd need
a consensus on what we're going to call them before I can get that done.

I like both of these better than negative transition function and I
agree negative implies arithmetic rather than opposite.
Out of these 2 I do think inverse fits better than reverse, so I guess
that would make it "inverse aggregate transition function".
Would that make the CREATE AGGREGATE option be INVFUNC ?

Any other ideas or +1's for any of the existing ones?

+1, inverse good :)

Regards

David Rowley

--
Ants Aasma

--
Hannu Krosing
PostgreSQL Consultant
Performance, Scalability and High Availability
2ndQuadrant Nordic O�

#20Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Tom Lane (#8)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 12/15/2013 03:57 AM, Tom Lane wrote:

Josh Berkus <josh@agliodbs.com> writes:

I think even the FLOAT case deserves some consideration. What's the
worst-case drift?

Complete loss of all significant digits.

The case I was considering earlier of single-row windows could be made
safe (I think) if we apply the negative transition function first, before
incorporating the new row(s). Then for example if you've got float8 1e20
followed by 1, you compute (1e20 - 1e20) + 1 and get the right answer.
It's not so good with two-row windows though:

Table correct sum of negative-transition
this + next value result
1e20 1e20 1e20 + 1 = 1e20
1 1 1e20 - 1e20 + 0 = 0
0

In general, folks who do aggregate operations on
FLOATs aren't expecting an exact answer, or one which is consistent
beyond a certain number of significant digits.

Au contraire. People who know what they're doing expect the results
to be what an IEEE float arithmetic unit would produce for the given
calculation. They know how the roundoff error ought to behave, and they
will not thank us for doing a calculation that's not the one specified.
I will grant you that there are plenty of clueless people out there
who *don't* know this, but they shouldn't be using float arithmetic
anyway.

And Dave is right: how many bug reports would we get about "NUMERIC is
fast, but FLOAT is slow"?

I've said this before, but: we can make it arbitrarily fast if we don't
have to get the right answer. I'd rather get "it's slow" complaints
than "this is the wrong answer" complaints.

There's another technique we could use which doesn't need a negative
transition function, assuming the order you feed the values to the
aggreate function doesn't matter: keep subtotals. For example, if the
window first contains values 1, 2, 3, 4, you calculate 3 + 4 = 7, and
then 1 + 2 + 7 = 10. Next, 1 leaves the window, and 5 enters it. Now you
calculate 2 + 7 + 5 = 14. By keeping the subtotal (3 + 4 = 7) around,
you saved one addition compared to calculating 2 + 3 + 4 + 5 from scratch.

The negative transition function is a lot simpler and faster for
count(*) and integer operations, so we probably should implement that
anyway. But the subtotals technique could be very useful for other data
types.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21David Rowley
dgrowleyml@gmail.com
In reply to: Heikki Linnakangas (#20)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mon, Dec 16, 2013 at 9:39 PM, Heikki Linnakangas <hlinnakangas@vmware.com

wrote:

There's another technique we could use which doesn't need a negative
transition function, assuming the order you feed the values to the aggreate
function doesn't matter: keep subtotals. For example, if the window first
contains values 1, 2, 3, 4, you calculate 3 + 4 = 7, and then 1 + 2 + 7 =
10. Next, 1 leaves the window, and 5 enters it. Now you calculate 2 + 7 +
5 = 14. By keeping the subtotal (3 + 4 = 7) around, you saved one addition
compared to calculating 2 + 3 + 4 + 5 from scratch.

The negative transition function is a lot simpler and faster for count(*)
and integer operations, so we probably should implement that anyway. But
the subtotals technique could be very useful for other data types.

- Heikki

That's quite interesting. I guess we would need another flag in
pg_aggregate to mark if the order of the tuples matters, string_agg would
be an example of one that would have to skip this.

At least for ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING if the
aggregate order did not matter than it would likely be quite efficient just
to aggregate from the bottom up and materialise the results at each tuple
and store it in the tuple store, then just use that materialised value when
that tuple is processed. This method won't work when the frame base is not
fixed.

I had been thinking ahead on how to improve MIN and MAX cases too. I came
up with something called "tuple store indexes" that could be build as
binary search trees with a composite index on the tuple position and the
aggregate's sort operator... Something similar to how the following query
could use an index on (id,value) to calculate max()
select max(value) from test where id between 1 and 100;
It's certainly not something for this patch, but it was an idea I came up
with which I think would be possible without adding any more columns to
pg_aggregate.

Regards

David Rowley

#22David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#1)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 15, 2013 at 12:17 PM, David Rowley <dgrowleyml@gmail.com> wrote:

One thing that is currently on my mind is what to do when passing volatile
functions to the aggregate. Since the number of times we execute a volatile
function will much depend on the window frame options, I think we should
include some sort of warning in the documentation that "The number of times
that the expression is evaluated within the aggregate function when used in
the context of a WINDOW is undefined". The other option would be to disable
this optimisation if the aggregate expression contained a volatile
function, but doing this, to me seems a bit weird as is anyone actually
going to be depending on a volatile function being executed so many times?

I just wanted to bring this back into people's minds.
During writing this patch I found and removed a comment which was a todo
item to implement these negative transition functions. This comment said
about maybe disallowing the use of these if the expression of the function
contained a volatile function. I wondered why this was important and I
still don't really see why we would disallow this only to enforce that we
call that function an undefined number of times anyway.

nextval was the only volatile function that I could think of that would
allow me to give an example which was easy to understand what is going on
here.

CREATE SEQUENCE test_seq;
SELECT currval('test_seq'),
COUNT(*) OVER (ORDER BY x.x ROWS BETWEEN CURRENT ROW AND UNBOUNDED
FOLLOWING),
SUM(nextval('test_seq')) OVER (ORDER BY x.x ROWS BETWEEN CURRENT ROW
AND UNBOUNDED FOLLOWING)
FROM generate_series(1,2) x (x);
DROP SEQUENCE test_seq;

The results are:
currval | count | sum
---------+-------+-----
2 | 2 | 3
3 | 1 | 3

I've not looked to see if the spec has anything about this but, the first
row will have sum as 1+2 then the 2nd row will just have 1 row to aggregate
and the value will be 3 due to nextval returning 3, I could see an argument
that the results for this should actually be:

currval | count | sum
---------+-------+-----
2 | 2 | 3
3 | 1 | 2

If it was then the solution would have to be to materalise the expression
by evaluating it once for each tuple which sounds like a big change. I
thought maybe if we're going to be playing around with the number of times
these expressions are evaluated then we should stick a node in the docs to
tell our users not to depend on this.

Something like the attached maybe.

Attachments:

aggfuncwindow_note_doc.patchapplication/octet-stream; name=aggfuncwindow_note_doc.patchDownload
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 4f50f43..59419ae 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1882,6 +1882,14 @@ UNBOUNDED FOLLOWING
     list and the <literal>ORDER BY</> clause of the query.
    </para>
 
+   <note>
+     The number of evaluations of any expression within an aggregate function
+     parameters when called as a window function is undefined and this may 
+     change between releases of <productname>PostgreSQL</productname>. If your
+     expression calls a function, especially a volatile function then it may
+     be desirable to put the function call into a sub query.
+   </node>
+   
    <para>
     More information about window functions can be found in
     <xref linkend="tutorial-window">,
#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#22)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

David Rowley <dgrowleyml@gmail.com> writes:

One thing that is currently on my mind is what to do when passing volatile
functions to the aggregate. Since the number of times we execute a volatile
function will much depend on the window frame options, I think we should
include some sort of warning in the documentation that "The number of times
that the expression is evaluated within the aggregate function when used in
the context of a WINDOW is undefined". The other option would be to disable
this optimisation if the aggregate expression contained a volatile
function, but doing this, to me seems a bit weird as is anyone actually
going to be depending on a volatile function being executed so many times?

Once again: this patch has no business changing any user-visible behavior.
That would include not changing the number of evaluations of volatile
functions. The planner is full of places where optimizations are disabled
for volatile subexpressions, and I don't see why this should be different.

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

#24Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#5)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, Dec 14, 2013 at 8:00 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Greg Stark <stark@mit.edu> writes:

On 14 Dec 2013 15:40, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

I think you *can't* cover them for the float types; roundoff error
would mean you don't get the same answers as before.

I was going to say the same thing. But then I started to wonder.... What's
so special about the answers we used to give? They are also subject to
round off and the results are already quite questionable in those cases.

Well, we can't easily do better than the old answers, and the new ones
might be arbitrarily worse. Example: sum or average across single-row
windows ought to be exact in any case, but it might be arbitrarily wrong
with the negative-transition technique.

More generally, this is supposed to be a performance enhancement only;
it's not supposed to change the results.

This consideration also makes me question whether we should apply the
method for NUMERIC. Although in principle numeric addition/subtraction
is exact, such a sequence could leave us with a different dscale than
is returned by the existing code. I'm not sure if changing the number of
trailing zeroes is a big enough behavior change to draw complaints.

I tend to think it is. I'm not sure if it's worth it, but you could
finesse this problem with a more complex transition state - keep track
of how many values with any given scale are part of the current
window.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#23)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Dec 17, 2013 at 1:18 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Once again: this patch has no business changing any user-visible behavior.
That would include not changing the number of evaluations of volatile
functions. The planner is full of places where optimizations are disabled
for volatile subexpressions, and I don't see why this should be different.

My point was meant to be more along the lines of that I thought it was
already broken and it perhaps should be fixed or at the very least we could
warn the users about it.
I would imagine that most of those other places in the planner are to
prevent extra evaluations of volatile functions? In this particular case
we're already evaluating these multiple extra times when a tuple moves of
the top of the frame. I would have thought that we should only evaluate the
volatile function once per tuple. This is not what the current
implementation does.

I don't have an issue skipping this optimisation when the aggregate's
expression contain any volatile functions. I just wanted to raise my
concerns about the current behaviour, which I find a bit bizarre.

Regards

David Rowley

Show quoted text

regards, tom lane

#26David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#25)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Dec 17, 2013 at 11:06 AM, David Rowley <dgrowleyml@gmail.com> wrote:

On Tue, Dec 17, 2013 at 1:18 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Once again: this patch has no business changing any user-visible behavior.
That would include not changing the number of evaluations of volatile
functions. The planner is full of places where optimizations are disabled
for volatile subexpressions, and I don't see why this should be different.

My point was meant to be more along the lines of that I thought it was
already broken and it perhaps should be fixed or at the very least we could
warn the users about it.
I would imagine that most of those other places in the planner are to
prevent extra evaluations of volatile functions? In this particular case
we're already evaluating these multiple extra times when a tuple moves of
the top of the frame. I would have thought that we should only evaluate the
volatile function once per tuple. This is not what the current
implementation does.

I don't have an issue skipping this optimisation when the aggregate's
expression contain any volatile functions. I just wanted to raise my
concerns about the current behaviour, which I find a bit bizarre.

To improve on the example I used to try and get my point across:

These were all run on an unpatched copy of HEAD. Ignore the actual results
from sum() and look at what currval() is set to after each query.

create sequence seq;
select sum(nextval('seq')) over (order by n rows between current row and
unbounded following)
from generate_series(1,10) n(n);
select currval('seq');

drop sequence seq;
create sequence seq;
select sum(nextval('seq')) over (order by n)
from generate_series(1,10) n(n);
select currval('seq');

nextval() is executed 55 times with the first query and 10 times with the
2nd query. Of course this is because the current implementation requires
that when a tuple moves out of scope that the whole frame be re-aggregated.
I had thought that all of the places that disabled optimisations due to
there being volatile somewhere were to stop the number of executions being
undefined, this case seems undefined already, or at least I can't find
anywhere in the docs that says the expression will be executed this number
of times.

Once again, I'm not fighting to have inverse transitions uses when volatile
functions are involved, I'll happily disable that. I just wanted to raise
this to find out if it's intended or not and it seemed like a good thread
to do it on.

Regards

David Rowley

#27David Rowley
dgrowleyml@gmail.com
In reply to: Hannu Krosing (#19)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mon, Dec 16, 2013 at 9:36 PM, Hannu Krosing <hannu@2ndquadrant.com>wrote:

On 12/16/2013 08:39 AM, David Rowley wrote:

Any other ideas or +1's for any of the existing ones?

+1, inverse good :)

In the attached patch I've renamed negative to inverse. I've also disabled
the inverse functions when an expression in an aggregate contains a
volatile function.

Regards

David Rowley

Attachments:

inverse_aggregate_functions_v1.0.patch.gzapplication/x-gzip; name=inverse_aggregate_functions_v1.0.patch.gzDownload
#28David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#27)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Dec 17, 2013 at 10:51 PM, David Rowley <dgrowleyml@gmail.com> wrote:

On Mon, Dec 16, 2013 at 9:36 PM, Hannu Krosing <hannu@2ndquadrant.com>wrote:

On 12/16/2013 08:39 AM, David Rowley wrote:

Any other ideas or +1's for any of the existing ones?

+1, inverse good :)

In the attached patch I've renamed negative to inverse. I've also disabled
the inverse functions when an expression in an aggregate contains a
volatile function.

I've attached an updated patch in which I've done some more work to the
documents and also added some more sanity checking around CREATE AGGREGATE.
The previous patch allowed CREATE AGGREGATE to accept a transition function
which was strict and an inverse transition function which was not, or vise
versa.

I've also added a bunch of regression tests around create aggregate which
were missing in the previous patch.

Regards

David Rowley

Attachments:

inverse_transition_functions_v1.1.patch.gzapplication/x-gzip; name=inverse_transition_functions_v1.1.patch.gzDownload
�qc�Rinverse_transition_functions_v1.1.patch�[{o�4�{�f����{�
X��
T��
!T����$��v��sI��M�!�0���?���s}���h+��{�&��i��I+�xb:�K�Wtn��W���������t�dpzr$������v{%�v��\�����}tr�:M����?�����l:�
Smu�`y���>wmg�����A��
�g��3�Z�~ON&:z��R�+�]�n����j�����>��j�R�C����;������ ;9I���^21�k|������G�[t=��(aWp��8U�`���T�#��Il�&�_���R�{��V�`�@3<9sX;��F��Y���;����� ��F=�����d�b�����W��a_\~��C�+x��$����P	/��\�$2��vz�9�7�'�'7�����w�W�b)�����7�a�a�gP��h�`��}���A�#T�*!m-��xy�68����Q�pp��f;o1"���:@G������� �z$&m0������6m�G^�d����w���
�v'`I6N��a�#}���9��������q�4������I�S����_
������o��cq�p�_�H��=n��'�f��`(��ns|�i���#�V��fU:��mYatB�
��U���%��2�>�OFb��I������O�(j7�Y!����G���X����|�_nAO��|��D�XF<�p�L�R#Y�S���-�2��IE���W�i�6bf�/4
��@[�zoE)�����������{1|����<�����������gehL%m"'�f��P"�C�	�����i��A�_A�>��S$L�����6F|G�U���av�F�Q*��l
�Vp{:3VL����~pF,]���
�dl�=+@/e���M�H��*#S;_�|=�6�Q�"�j�^�}`�:��xg:�:Z��-����&�j���=/�E�����~���J
��5~�L&�mFm��F�IU�V�o�����lu�����������{~!UD�-4!��AIBG��8�?���8���7y�z�7Q���-4|F��x�rUWRY��f�!^4�0�-�`��k�"
���]X��[���d�k!���W������^�Q(A�|e�T��7��J�S!���
�I����j����^�f��GV�����a:��I��T��)�iWnO��r�\�Mn�6���vP���h-�W)����r������??�t������k�n.���)M�v)3�������|��F����n�X.��:tY��.	��i�|���T7�9�&s��f1� �jn�.�h�/,2�W��SU8+B�&BL���c�7��UuKb1x���o�0R�!�e�A���������2��6=�t����sT�>�|r�"�M�*���w��������EbOqLD�"��J
�(u���S���Ta^6��'+d�(���<����1lc��|LU)�����������<��N;�x�!�kcq�6��yb����SR��������Lo;���cj��td�;�]d`����,R�z>�Ti�����������4�;
C/�D@�����l��|!@i>���&�q�j���e/�@c�8��l�6�<q!���5"Ecr3���1����O���p�s�����b�V��$��8M���c9�_�A��jY6�4��iiB���;��)�F���
v*!�\�Xz����~2)��<1�`P���N����Y50�����@
'�-��8��WTvlx�V&�3o�mPf���kigSQ��d]7��]�*���r/�e�w�lV�@�*���$��]|�/��i�6D�!�r�6}z��vE�.�6�6�yk���?�Jk�����?�A�F���L-�� &��D���	V3�X��������1w<{�������S�/o�!��2� ���p���v�����q���$�=(����A�������G:S:��������Sj*�Df>�l���:�l���ur�6
�j�+� 6�Ww����.o�#��
w���>T����OB�=5������r��*b��ay������G�&���x�H��!s�Q2�4bgwwY�'�P�|�=�~�B�r�zj��{���U����(����"t3�2��#�y��Y�� �o��S���T��Q�ss%�'���wC;�u"6�,���1�$	��yhh,x�]���1;�"�� >O9�]���(�`7���K�@ugu
%/\v���)�P4<G��V��up�*�Q.��g�*���5�;ev���:������0��%��F9�"�Z"r��`8;��2c�^�n?b�q�)\]W�`�U���}�<�|�(o�$� >��x�]"O�*���{�/7����%^��<~s9��z7���qu+���d
O;���0F�&KT�
������h����_�l;C_��d�=���m�?�]�
�[�n��m��,���[� �|�d2�%�������e��l ����>S��*��n���V�2y	4r �Q�w�����q��������U����� �`�D��a@���V��jT�S��?j����aQ�V���kZ`�4u�b'_h7����Q�Y��)����/q���VM�v���zy��������������Kp��\	��(�>�GJM"��Y��ie-NIk�q�f�A�!k�b>�Cq�z��ja�{3����z"����wr�[��7�=UXT�)��2�����E>�^/N�k����A�����tnT��GnjK\��������G�w�\��d�g�,=����K@�2_Xe�A�	�b!}���n�L"��GMZ�%�v�<�����y����6��,�Y�������w�w ������?�r>�-��%pL��v
O�O9�h.��cL��a��3���K��}#0�W����J������(�4�������/�������������;R����
��j��@|��h(�O�����ZsT!�l�e��,1-xvq��#	��z�7������I�G'�K/|�
��������@GrB��,0���
���GQ.�@�x���O���!���G��Q��z���a7xN�����Yf�^��W8�og2��}lm@�
��a�f���f226���&<���/&c� �;�����Wjn�RV�+�jrQ�_�W!������_�5&_����3~�����	!�cA�K�*�����wV$��\���"��)(���������$)T��Us�F�+R��[�L�y�2�[u����V�Ig�;m�^y3���b���z���Q��Cy�(�:?
*�z
��u������3~���a� ��4����	-����2���
�rb�0�5�Q��\?H.�D���%?)���}]���H��k
����y.&��y���N��w	5��1�Z��*���a�{��w���v�Q'Z�=TX��0t�y��f��
����)yE ���J��Vx]Y��Nss���	%~R,z:IG:
�F�b���dB�i��qe3{���U��l���x,��:���wp*6�
Zo!n���6��2|p���<��^�0
TPr�B����9�U:�n�r^U��F���9��vB���@D�xAd�$�(�,%%��2B��q�Q�9��?�������+���K"����*w��Y�%�5]+�a�����
�y�i�?&��O�c������������`Pv/��(���i#�x��=
@��/ab�n�@;�<=����faH�$�<o������Z2���vr{u�fm2�'aUO�N��j��*��>�1��������������Ey�������]tu!�������3�c~g�W#~qA�Lak���~v���?��{�t�\Y�,zu�W�����������1�
�47���5U�������Zm��� 7��j������{��6sM��V�	��Y��w
�,�*hi�������[�����,��e�������l�������e�|=?���[��X���7U �������+�������s���6n�[�;��1l-�ECW�������l���Q�8�_�;c�/�zO���~�����B��'���y�{�w�����2��+��	���F�a&��]q�;�p���8$,^2�(�N���*����y��E�S\�b�jhb�����^�|�����i�`mD�I�#�M:�����W����bh6i&�:��D����
(��D�w����7�^8��;S�������HI�*E��T���:u���w�����Ux���Y��������7�-d������~a1��%l�����K�gP"?�[���j�u1,���93���W��^]6���;���m���W�����m���[���C)�)0�b1�E�D�i������hI~��
(V�<��{��V{w��B��L��O#6$��r����d��V��a��� �eeE��bB@3U�'�HoE���1����|��i�<�d�6��?��c� q��7@�-&���3*���^�<��&�c^���b��[����0X�C�3���K�	�vY*�����jh��N�ix_D�9�J�;n�*����mcL���^[�g��K�!JPWb��
���P~���v�/�y���m�"�����< x{{�j_�WS[�:�i����J����vg`�l�Y��%�x.98���l}��"�8�q�
�����+�^� ���1I��$�� �&�A��cV)n�U%@�SSu�	��/��@]�'9��_�	>���de9S�%�gZ�V�Mg"���' �g�\Y[���+X�5U=�^�hML��*E�$�x�?x�N�H7�X���Y�����Z��H�I�x{2��a�c�Y2�t�&�1�V>c��2O�cs�2�SlZ��c��MM�VT�@�O�T	-L�RZr#_]�i�N pfxJ�P-�S���H����������oJ�
I���C5_<5c��'���4������wwgI�P�t�&��K�"�A���,�{�f���9[Z����c[v8|2w6�3x&���P1�R{Q�
K���iJ�%��Z��z�a1���%�4��6��*��"wsj�N4ire����^_2���#�f���U�6�t���%]���N�S\#��8�D�<"��J\�t5.5��6�7��v�������@�����A�����7�~,&yzx�1M�����>_k��n���q|(i3L{��������	��T?�[�I6�S&���:hY�
��L�m4-:D��U������U�l�����n�����;�[k9�!�=h Hc� �����O�=�X$�?u`[3�hI�(6�������D�p��72��������#.���<x��>������5qW�mp�.����3`}��-�ag8?R*A��HK����D�	T��e���v��_�{9~8@F���xAwLA�m�#<Q�\�!�;.�(��T<nR���^�`Y�q�s}��&�F2H@zm�W�Cm���{��iTJ�	��F��)�����V�2HXLme��I���d��q�M��������������^�ar0�M���D0��X	�a�&K����2�V]$�����N�#�4�k&�)f�F�
�:CT�Bk�"aU^r�I��n�evj���[*�
��R��)���J��[\H���z?�����������)r��#��F%�v�Nb�����y��^;�vM�U�h���hkg��2C�*���;��������k�@�m�_�v�6r���L{8�Y��0���m`$8�o�\},e
���l����68���������<,W��
�A�n���[#���
7������pEaXO��'����,7+�����z��]�?��^�k�������h<��yC�!l��p(��W���@��s���b5��P4���x���p�#�1�����+#�E�oa���2��*�"�q������0@��99zq��yoKc�����C��MH�jA%a����00�_�����=>:}{��e�8B��qv~�5����3�|j�����oI�")��p,]H�T�<�TN3�8����3���et��BLh3
.�gX�r�!�A����FU�K�N-�f��`��R&�rl��6�igE�E%uy��?*{]�Mv�as����=\\�1]Z	�\~�uy)A2�����-�L�q�48UH�A��C�O7��E�b��0d�]��k����d�"���sS�f-1w��&�������l�e�j.���\jh���P�*6D9��'�]����A�.���.�)��<��K�nm��E�)��B�+�����W�:��w1Ga�����z�(�q�D&�����+n��{Bv	�$4�/�]�#�������k�]�$]&|�E�x^�����y^�X�O���UZ,���T�1��Q�=}f1��dan�"r=��@S�7��k�����/������Cx���9u���xe3��+�����|j��hC��R����%.��$��Y��6� +�L0n���cV��`��nS8�SEa��[�w5N����G����F#�k�A���B���0i+���`;�y �1D���|����K���#L��<(/�j��m:&��%fuw�"��T����Pxh��
�������%���7��@E��
�)s�7X��=�	
������**��}H��2���A��<���
�+H��i��bj������y��.7J�K4B{�k��1[���z�!�B�O9D[d��RpU���X�����6�0�~'�;�9F����B/L�n�I;B<��u��	��<��}��[/m>�����@��-X2�~����m�iBB#K]9P#�G�r�,���4�zK�����imz)Pjr-D�r����j�p���Q���4Z�4PnN����u�������0�������A�C�j��^��t�,��|�}�D�(��YE@�Tqom1����$CE�<�M��aw�{e
;<�z��2k`���m���:WH���9}�����������3h,N��mN������U�d?OmQikK���,72���O�����=��?:��0*E������s����!
!�@�?�F�d���uo����vzE�.}��~���n��$5��%Xm4�-�'d��)�� X�.���+h
p���`�vI����
��O������0xQm����
�����GY��z����i����,��k��W���y@��
KpvZ�����=~4x|��hTi�'<����>y���*��Z�r�k<�>��',cJ490���u��n����GXc���?����+�<�D�nU���=.��4G���YRq7��a�2���QZh��a?��L��w��?*���� �hq����hi5V%A�%���3tbu,L>|�@�H�T�e^����>Z�UL�M
���
n����0!�Y���
)^�/L�k��$��YZ#)��\�@�O��g,�D��������m.e|�������u�����q~
���mp��$�[������������
I2NQp �����PT*z`�	�������M�����2�_Ka�����ll�6{���o?q��>�k�1���2����I�C<g{�7���]��������loS��W����������?-�(�J�7}3��������a�D��\�'NDV��im!���;t#Q���@@�^���!}C4,dG�y�d��"#F��K�����yF�8��f�=W��m]�q���U�\q~��+?�1t#S�\^�	�U���	������.��yDO��4d~gf���x�'w�����1�!�������-�5�=��,ky��)�e�'���L����a�`�y�w�)v��aG��Z�Yj����$_��H����I2���"�a����������!4w����o�q��c������8���������Q(J�R���g����~l�2�d���	b��{),U��7�-�%C�N�c{�$��<C������`x�|�o ��10����;*�g���W����Gk=��$$N�d�b)�)��9�V�p)!����0��Ik{>�"P�����Nd�s�(2pf���D���'����M�
�<������@�p�8
/���p<�f�=�,.�I2'���c�v����x�x�;���4 GI�(��7{��	�.��l���i���Ng��5���'�����(
���W`��( ��4>.>�pw����*��������3��0]�W�D��%&=����t^�;� '"`H��J����u�e���{��o'yo�]��CU�!J�I���������Q�h��{�G�Qr�n��7���dn�v/ZKlc��u[�9jw�F�Q��H\���C8/� �=���#������
�����c�J;�`Z������m��/)������>��1�)d'RS����~���r.k����V���ovp)<��J|��Z�����rqQ����P�z�{H�Y��H����	G������z��x�s�m��D[{:!�����/=`�Je���Z2"g���*�q�7�t���mB�h��0.�p"�����S���DC7���5yZ�i-���
�ig���V����Y�^���G�Y��%��~������x7�_V���n�2z�)y��a���UW)�X���&y� �S��`�� O3�j��|��	Sq~����������D��)���7���3�5+��i��v:��p��#��j��j�������X����C���[j���s"�E6]�r�xQ���O�S��+`Yi(�&���[���A��0Ea�M�SE��4������VF������z���uG@e�L�Mc"��n���\�Q	MKK������0y3�8Ex��-�J��u�x�,�-��,aEK�S������"��������GD��NH�J:��c_�NS5��e���/�_o>L'���gRO���q��4��6YfJ�L��q ���j@&��Ysr����yZ~`�&��
������'��q�+*I���h�L�������KZ�:!�Z���%E�q���)��������+����`�&�M>�}���-�3_~�uk="�/�l��/�%����g�/.g��oO]_W�:����RE��C�F���c
�Y����=��E+Vb����'��H��S����X�����bn.����-�N��q�S9��'7"���4� ����l���~���u�yR�J�QJ��0�Z�������~I�t�[!G�zI��u
H�]#v��S�l1�� ��N�Jx���3B!b�A<D��H���C���P������hD*�?-��{U�����?�����<��)�=H�H2*]�i�K��[5��\%V��'�'�hG�0T��r�^O�WC��Y���=��N��,�IT4�d�-6�E+�FI�6?WJO��T~n>CZoT*6��/�E�,r�]F����q$���@�����6\:jSv��4	�����7����0��1p�2��+�v;�h�*�&/����!!�Y� ����6�q~9��5�^��]�l�a�\�]��r���b���Q�y</�r=6���l~�u���Z#�\K�H#���%��1��!�P�dZYI��x<��WRw��Q��#�6�}_�}�����XM�����Y/*Y��7@���}�����&�u�L���<��+�tv9�����)��J�
c-_�����;&�#X
�n�U���(��K	��(�p���
�oT�~���������L�n��l�g~��-I�4#$�m����L��N)}MQ?b������l���`����d���d�:c���"�o��)-}[�O�sd��A��|�8C$�����1`!�he,K|C������*���a1��b����A�d6�|���g�p�A�W���U,��w0��oR�8C�����Jq�?E�5�&��QpCEAQQ��pE�
��(~w+��3�S����w�7��_e����d�����OoT�4X�����n\��G�e�!�f��-�]~|}z�O�J��~��^M����R6��B�<!�����e��p��?z�,��%}�r����Ka|�[p���Z�2�7v���Fp��d�U��XM0��?V����������?V��X��tmQ*w��cLi�b�W^`��	/,�=��{���<�t��������4@}rf��uS~�&IY����5����n�^&�@4�Lw����'�������?���uXJ���/V7�����z%b
�����r^�����T��7�w:��
�#��A��
������vu�"�������*?�=E�Opk�g_�l+P�_:�����$8|f�R�OV�R�\ ��f��t�Y��|���UhIIG�vQH���`D/�=�������)�\�P����%�\o��\��tD����C�fc��t��w]������>�0~k����XcT=���J�[e�WG���Q�k+V��#d����#�;��l���,@F�O,�R���t�u��f��h��,KPJ��%�lDE;Y��LV�[��fl�F3�6o:�rS^L&�����5i�s-����J��e?��7[�}�T]�-y�h���5�(�5%�K1v\���]���	��������I��+O
���������_T22f�n�0+b�p
��#�fl�sss?��������j���*-����U�)���������
��3�8�f`�\�����[�#��s�!�?t�.:I������J�
�1�`�KNE�X������"�D4�3��Q+���qvEF��(���H#<�X�%39R'�a+�v��"e"����`���$�)\i�[v�	����#�Wb��@S�<�Z�"����{+.3}.���4�u���l�-�s��x�x��b��eb��1F�C�j�L����S�x1��M0�}��IN�����z�	�j>�����O�!�{��c�!�uv�z�����;�e����0�Y%���������XV�4�j����%m#�U8-)���i^���NjG4�yZ�<�y����������`^u�
����v-��aE��"N8]��.������pV#���D�J�E�$��K��%��jjA5	f���~X:KB�}�&�� ��P����u"�H=w���Q�u�&��"&�,��Z�t�����r��Jc�H0��O��f�q
�������X���6)L�����?=��^���pO"}��E�8��H��������#�x�o9�/2�#JT����,�>bC}8���9FD��p���:n��D�����c~���
w�1���Sg�x�����ca8����L��\8b�l|�������t���r2��1��f�d�
A�ZMk4��&7��DQ����Ewb��&=1�BQ���P�P4(���2C� %J����(�bT��4�JY�bIDU�b��&L1�	C�����STy2�(T,�B@��c"~+�^e>��������|;l��.��^��}��_����jo�
�X�{��^��)�;$�[=�- ��D$�$:!�x��y�Ld��X�����e��ig����������*Gw�����@��`�[��U���O���Jd��-P"��(��(�XC0v����:9�ZA�M$+���6���'��%�����9:pJ������o�1����o���o��X�������{�_���o�l�Z��fWu��deD�>JpV�ru��_�<2?,O�S�������f\I�i�}<o�����6�R#���Y@#	{AW����R5����3�n���j�k?&���v���G���MJQ�(���������k�
:�X�+S��y�I�����RYP�MW���I-�qU��3Zkl��1����B�h��C�h�������Z��lU<��]qH(�OB�~���8��i?jqT\��_ bt�4_������WT|���O����m ���/��P]�~��}����B��g�A� �l���u�����7U����������B����m$ZQq�G��Z_Q���"l��.K�����\iw;�-����/�@��}8��L�U����j�Mh���������`�%@��Q��P��4�K�������7��Bm��*�]����ZOf�:��|��Q�cd9���
!s�#����`� �N�A`�����	=�ncE.2������~s��p�j{M���������L��a�|���$0d�����c&k]7c�r�q0u2��}(�R�)��p�6��$�t��!RzI���S��R���J���n�
2K��ihWZ��	3K�������n���h6�-��dW7�f�D�8NF"� �����XCce�-��;�@�.���.�l��/����}������^��TQ�\Ip`d�<�������1�:;�0������F�W�����0��J�<Tu��������/A�����������o�Ev��3�xs�����x����>'d�E46��/���{\�:�$������t�I�r([�R���2��A��dW�lcj/��o�m*�|�bP���J��3��n�c�����p��c����]�^Q���Q'��c&n��i����.�o��gx��b�,s�K���HYjz�E��	�77"{�^���R!I8�Lj�Sg%z=t*���x��j�5&��R�g|���[��7�?���E�-��32SF�K*Y�L�V�K���������!�3�����������D�Pb�)�}a`� %1�anj�
qM|L�`���E�������rL}k�
�2S>;��b��E�K��'X_#z�����=(�p��������|��)@(�:��{-���s{�l"������$Z��������O��?&��3�J�s�d{VZ��?E���������|�
_�����D��a����R��\.�$���C��!����\p���B�Q�>./n���I�c�x�P�����x�M�T=�.���3]��4�.�chs������� �Y~���� �h��+Vm�:~�rH�� 	Vg�����H0�\-�-3
!��w�����M���\����h5-�o��g�.�*����8���B$����s��?i���f��vG{9���Zf����wa�C��|Z6��V�C6b���*M�d���
���1�����O�4dc�����p��Z�e[)��-��"|(y]��qm��"6�n��f�����Q�k���94��4������[MC�##X��;P}�P�-_�H�6 5a�2f������Y������jn����W:�jU�Z�P������z�����@*�� ����l,��)�R���S� ���a�!"�e��(`[��ZX�HAi��h���1;�
��J���%���'P�3h�-��o��h�F)��m�m'����(�`�R���-�s��AW(@���C0�������c����F�A�3��q7#����i6��f�;�?��Z�n5i�+��N��12���y�;i$]#��t��V�2����T�-��6_�R|��Z�2����/���R��,���T'���s���`����il
���N8^C�7�{4W�I���Ix��f�pk�����	��<k�)[��GT���d2�NW���a-���1=-��X�����z�8�E����eh����c�����L��21�<�d��([b����\ch[M�5J}�l������<������B��Sp&g�,6Gtv�`\	�WjCt&�0"�e����7/�%�[)�@���*�t�O����E��8�IN�T9�u;�.�����zf7���g�-�Z��
�Z��U���V�0����R��+U�C�=e9��S����Y`����7tNJ��8����a���c>���R�-v�v���GY��3���nM�S���p6mX�r�6�XA��/bnE$wN�EV���&����aO��+Gm��~9�)]���|��p����O�~Y{J�D�x��31�B,���Wv��%X�����CC�+M
�r�hU��{�M�,-����-�\��Z#L3O�-�R]o~.w�����T�xd��3�vKX,u
F���H9(P�3�O�oSn��0�x�%�?O]1*G
�@������ki@�?���v���Uy^�����h�#X%(c��<�-���r��E���.�!���
�����u8�"�X,�4���6Es�'`���y����%�<�4�#����8���j�v����$n)��U�O��tm(��Os�����^@��)��s�"���[n*b��8G ~�3���$g87��U�j��]yj��[zU�l��j����W�|�
Ga��������	�������\���v>������78���/���?���n�S�{z��Q�.J�>=?�p���^�}{������b������� yUc�Z6�&�*uOl>����U��{e�?�w^<�AR�@F�Q_�`��f��) o�G��e[i0��[��r�s��v[��:vw[��5X��9����4��k�������{=�sO*����ZJI�U�32�����?��FH�����v�,hZB+
�����n�h
��H��?�6�jF]M��Y��ki����X5K9�b�'b�}J"h��XWs'j]�������^km�8��C�b�_����~�M��(�S���
)��Y4>_+��bh��R�)
0����N�6�
���+�{�(t�C�h�Le�&������a���J�������u�/������B�����0���L�2F�E0���	p"1�R�_�LD��GH��_���1q�� ���``
�cz�e��\���K���: ���x�)�1fX-���������x����1����q7BJ�LH��+��d8vf(����(	��8��6�57m����h�U������K\�����p&���D;����l�u�|��OBql���`_���78�r"��!�f��"�iU�Z8��
�1��1�����q�Y�^k[P��m+'X�����o03x������]���2n��O���(��2[{�i�j����=�5\��qP�J�%s�&�D_��3`>���Y�Bg�	���3j��l��O$�j�y���}���@�}�S�;J	����M&�n�g����c�������$*x�����bf:
��(b���������/VT�X!�1^]��s"����
��k������$�.
#29Erik Rijkers
er@xs4all.nl
In reply to: David Rowley (#28)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, December 21, 2013 10:53, David Rowley wrote:

[inverse_transition_functions_v1.1.patch.gz ]

Hi,

I know, $subject says "WIP", but I assumed it's supposed to compile, so I tried to get this to run on linux (Centos 5.0).
gcc 4.8.2,
with

./configure --prefix=/home/aardvark/pg_stuff/pg_installations/pgsql.inverse --with-pgport=6554 --enable-depend
--enable-cassert --enable-debug --with-openssl --with-perl --with-libxml --with-libxslt --with-ossp-uuid

but although applying the patch and configure were OK, the compile went awry:

[...]
make[4]: Entering directory `/home/aardvark/pg_stuff/pg_sandbox/pgsql.inverse/src/backend'
make[4]: Nothing to be done for `submake-errcodes'.
make[4]: Leaving directory `/home/aardvark/pg_stuff/pg_sandbox/pgsql.inverse/src/backend'
make[3]: Leaving directory `/home/aardvark/pg_stuff/pg_sandbox/pgsql.inverse/src/common'
make -C catalog schemapg.h
make[3]: Entering directory `/home/aardvark/pg_stuff/pg_sandbox/pgsql.inverse/src/backend/catalog'
cd ../../../src/include/catalog && '/home/aardvark/perl-5.19/bin/perl' ./duplicate_oids
1220
1221
1800
1801
1970
1971
make[3]: Leaving directory `/home/aardvark/pg_stuff/pg_sandbox/pgsql.inverse/src/backend/catalog'
make[2]: Leaving directory `/home/aardvark/pg_stuff/pg_sandbox/pgsql.inverse/src/backend'
make[1]: Leaving directory `/home/aardvark/pg_stuff/pg_sandbox/pgsql.inverse/src'
-- [2013.12.21 11:35:29 inverse] make contrib
dblink.c:55:28: fatal error: utils/fmgroids.h: No such file or directory
#include "utils/fmgroids.h"
^
compilation terminated.
make[1]: *** [dblink.o] Error 1
make: *** [all-dblink-recurse] Error 2
-- make returned 2 - abort

( although that output mentions perl 5,19, I also tried with regular perl 5.18, cassert on/off, etc.; every combination
yielded this same compile error )

Thanks,

Erik Rijkers

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30David Rowley
dgrowleyml@gmail.com
In reply to: Erik Rijkers (#29)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, Dec 21, 2013 at 11:47 PM, Erik Rijkers <er@xs4all.nl> wrote:

On Sat, December 21, 2013 10:53, David Rowley wrote:

[inverse_transition_functions_v1.1.patch.gz ]

Hi,

I know, $subject says "WIP", but I assumed it's supposed to compile, so I
tried to get this to run on linux (Centos 5.0).
gcc 4.8.2,
with

<snip>

cd ../../../src/include/catalog && '/home/aardvark/perl-5.19/bin/perl'
./duplicate_oids
1220
1221
1800
1801
1970
1971

Thanks for the report. It seems the windows build does not check for
duplicate OIDs and the linux one does.
I'll just need to change the Oids of the new functions I've added, only it
seems the unused_oids perl script does not like windows.

I'll try to get you a working patch shortly.

Regards

David Rowley

#31David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#30)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 22, 2013 at 12:12 AM, David Rowley <dgrowleyml@gmail.com> wrote:

On Sat, Dec 21, 2013 at 11:47 PM, Erik Rijkers <er@xs4all.nl> wrote:

On Sat, December 21, 2013 10:53, David Rowley wrote:

[inverse_transition_functions_v1.1.patch.gz ]

Hi,

I know, $subject says "WIP", but I assumed it's supposed to compile, so I
tried to get this to run on linux (Centos 5.0).
gcc 4.8.2,
with

<snip>

cd ../../../src/include/catalog && '/home/aardvark/perl-5.19/bin/perl'
./duplicate_oids
1220
1221
1800
1801
1970
1971

Thanks for the report. It seems the windows build does not check for
duplicate OIDs and the linux one does.
I'll just need to change the Oids of the new functions I've added, only it
seems the unused_oids perl script does not like windows.

I'll try to get you a working patch shortly.

Please find attached an updated patch which should remove the duplicate OID
problem you saw.

Regards

David Rowley

Show quoted text

Regards

David Rowley

Attachments:

inverse_transition_functions_v1.2.patch.gzapplication/x-gzip; name=inverse_transition_functions_v1.2.patch.gzDownload
�	}�Rinverse_transition_functions_v1.2.patch�[{o�4�{�f����{�
X��
T��
!T����$��v��sI��M�!�0���?���s}���h+��{�&��i��I+�xb:�K�Wtn��W���������t�dpzr$������v{%�v��\�����}tr�:M����?�����l:�
Smu�`y���>wmg�����A��
�g��3�Z�~ON&:z��R�+�]�n����j�����>��j�R�C����;������ ;9I���^21�k|������G�[t=��(aWp��8U�`���T�#��Il�&�_���R�{��V�`�@3<9sX;��F��Y���;����� ��F=�����d�b�����W��a_\~��C�+x��$����P	/��\�$2��vz�9�7�'�'7�����w�W�b)�����7�a�a�gP��h�`��}���A�#T�*!m-��xy�68����Q�pp��f;o1"���:@G������� �z$&m0������6m�G^�d����w���
�v'`I6N��a�#}���9��������q�4������I�S����_
������o��cq�p�_�H��=n��'�f��`(��ns|�i���#�V��fU:��mYatB�
��U���%��2�>�OFb��I������O�(j7�Y!����G���X����|�_nAO��|��D�XF<�p�L�R#Y�S���-�2��IE���W�i�6bf�/4
��@[�zoE)�����������{1|����<�����������gehL%m"'�f��P"�C�	�����i��A�_A�>��S$L�����6F|G�U���av�F�Q*��l
�Vp{:3VL����~pF,]���
�dl�=+@/e���M�H��*#S;_�|=�6�Q�"�j�^�}`�:��xg:�:Z��-����&�j���=/�E�����~���J
��5~�L&�mFm��F�IU�V�o�����lu�����������{~!UD�-4!��AIBG��8�?���8���7y�z�7Q���-4|F��x�rUWRY��f�!^4�0�-�`��k�"
���]X��[���d�k!���W������^�Q(A�|e�T��7��J�S!���
�I����j����^�f��GV�����a:��I��T��)�iWnO��r�\�Mn�6���vP���h-�W)����r������??�t������k�n.���)M�v)3�������|��F����n�X.��:tY��.	��i�|���T7�9�&s��f1� �jn�.�h�/,2�W��SU8+B�&BL���c�7��UuKb1x���o�0R�!�e�A���������2��6=�t����sT�>�|r�"�M�*���w��������EbOqLD�"��J
�(u���S���Ta^6��'+d�(���<����1lc��|LU)�����������<��N;�x�!�kcq�6��yb����SR��������Lo;���cj��td�;�]d`����,R�z>�Ti�����������4�;
C/�D@�����l��|!@i>���&�q�j���e/�@c�8��l�6�<q!���5"Ecr3���1����O���p�s�����b�V��$��8M���c9�_�A��jY6�4��iiB���;��)�F���
v*!�\�Xz����~2)��<1�`P���N����Y50�����@
'�-��8��WTvlx�V&�3o�mPf���kigSQ��d]7��]�*���r/�e�w�lV�@�*���$��]|�/��i�6D�!�r�6}z��vE�.�6�6�yk���?�Jk�����?�A�F���L-�� &��D���	V3�X��������1w<{�������S�/o�!��2� ���p���v�����q���$�=(����A�������G:S:��������Sj*�Df>�l���:�l���ur�6
�j�+� 6�Ww����.o�#��
w���>T����OB�=5������r��*b��ay������G�&���x�H��!s�Q2�4bgwwY�'�P�|�=�~�B�r�zj��{���U����(����"t3�2��#�y��Y�� �o��S���T��Q�ss%�'���wC;�u"6�,���1�$	��yhh,x�]���1;�"�� >O9�]���(�`7���K�@ugu
%/\v���)�P4<G��V��up�*�Q.��g�*���5�;ev���:������0��%��F9�"�Z"r��`8;��2c�^�n?b�q�)\]W�`�U���}�<�|�(o�$� >��x�]"O�*���{�/7����%^��<~s9��z7���qu+���d
O;���0F�&KT�
������h����_�l;C_��d�=���m�?�]�
�[�n��m��,���[� �|�d2�%�������e��l ����>S��*��n���V�2y	4r �Q�w�����q��������U����� �`�D��a@���V��jT�S��?j����aQ�V���kZ`�4u�b'_h7����Q�Y��)����/q���VM�v���zy��������������Kp��\	��(�>�GJM"��Y��ie-NIk�q�f�A�!k�b>�Cq�z��ja�{3����z"����wr�[��7�=UXT�)��2�����E>�^/N�k����A�����tnT��GnjK\��������G�w�\��d�g�,=����K@�2_Xe�A�	�b!}���n�L"��GMZ�%�v�<�����y����6��,�Y�������w�w ������?�r>�-��%pL��v
O�O9�h.��cL��a��3���K��}#0�W����J������(�4�������/�������������;R����
��j��@|��h(�O�����ZsT!�l�e��,1-xvq��#	��z�7������I�G'�K/|�
��������@GrB��,0���
���GQ.�@�x���O���!���G��Q��z���a7xN�����Yf�^��W8�og2��}lm@�
��a�f���f226���&<���/&c� �;�����Wjn�RV�+�jrQ�_�W!������_�5&_����3~�����	!�cA�K�*�����wV$��\���"��)(���������$)T��Us�F�+R��[�L�y�2�[u����V�Ig�;m�^y3���b���z���Q��Cy�(�:?
*�z
��u������3~���a� ��4����	-����2���
�rb�0�5�Q��\?H.�D���%?)���}]���H��k
����y.&��y���N��w	5��1�Z��*���a�{��w���v�Q'Z�=TX��0t�y��f��
����)yE ���J��Vx]Y��Nss���	%~R,z:IG:
�F�b���dB�i��qe3{���U��l���x,��:���wp*6�
Zo!n���6��2|p���<��^�0
TPr�B����9�U:�n�r^U��F���9��vB���@D�xAd�$�(�,%%��2B��q�Q�9��?�������+���K"����*w��Y�%�5]+�a�����
�y�i�?&��O�c������������`Pv/��(���i#�x��=
@��/ab�n�@;�<=����faH�$�<o������Z2���vr{u�fm2�'aUO�N��j��*��>�1��������������Ey�������]tu!�������3�c~g�W#~qA�Lak���~v���?��{�t�\Y�,zu�W�����������1�
�47���5U�������Zm��� 7��j������{��6sM��V�	��Y��w
�,�*hi�������[�����,��e�������l�������e�|=?���[��X���7U �������+�������s���6n�[�;��1l-�ECW�������l���Q�8�_�;c�/�zO���~�����B��'���y�{�w�����2��+��	���F�a&��]q�;�p���8$,^2�(�N���*����y��E�S\�b�jhb�����^�|�����i�`mD�I�#�M:�����W����bh6i&�:��D����
(��D�w����7�^8��;S�������HI�*E��T���:u���w�����Ux���Y��������7�-d������~a1��%l�����K�gP"?�[���j�u1,���93���W��^]6���;���m���W�����m���[���C)�)0�b1�E�D�i������hI~��
(V�<��{��V{w��B��L��O#6$��r����d��V��a��� �eeE��bB@3U�'�HoE���1����|��i�<�d�6��?��c� q��7@�-&���3*���^�<��&�c^���b��[����0X�C�3���K�	�vY*�����jh��N�ix_D�9�J�;n�*����mcL���^[�g��K�!JPWb��
���P~���v�/�y���m�"�����< x{{�j_�WS[�:�i����J����vg`�l�Y��%�x.98���l}��"�8�q�
�����+�^� ���1I��$�� �&�A��cV)n�U%@�SSu�	��/��@]�'9��_�	>���de9S�%�gZ�V�Mg"���' �g�\Y[���+X�5U=�^�hML��*E�$�x�?x�N�H7�X���Y�����Z��H�I�x{2��a�c�Y2�t�&�1�V>c��2O�cs�2�SlZ��c��MM�VT�@�O�T	-L�RZr#_]�i�N pfxJ�P-�S���H����������oJ�
I���C5_<5c��'���4������wwgI�P�t�&��K�"�A���,�{�f���9[Z����c[v8|2w6�3x&���P1�R{Q�
K���iJ�%��Z��z�a1���%�4��6��*��"wsj�N4ire����^_2���#�f���U�6�t���%]���N�S\#��8�D�<"��J\�t5.5��6�7��v�������@�����A�����7�~,&yzx�1M�����>_k��n���q|(i3L{��������	��T?�[�I6�S&���:hY�
��L�m4-:D��U������U�l�����n�����;�[k9�!�=h Hc� �����O�=�X$�?u`[3�hI�(6�������D�p��72��������#.���<x��>������5qW�mp�.����3`}��-�ag8?R*A��HK����D�	T��e���v��_�{9~8@F���xAwLA�m�#<Q�\�!�;.�(��T<nR���^�`Y�q�s}��&�F2H@zm�W�Cm���{��iTJ�	��F��)�����V�2HXLme��I���d��q�M��������������^�ar0�M���D0��X	�a�&K����2�V]$�����N�#�4�k&�)f�F�
�:CT�Bk�"aU^r�I��n�evj���[*�
��R��)���J��[\H���z?�����������)r��#��F%�v�Nb�����y��^;�vM�U�h���hkg��2C�*���;��������k�@�m�_�v�6r���L{8�Y��0���m`$8�o�\},e
���l����68���������<,W��
�A�n���[#���
7������pEaXO��'����,7+�����z��]�?��^�k�������h<��yC�!l��p(��W���@��s���b5��P4���x���p�#�1�����+#�E�oa���2��*�"�q������0@��99zq��yoKc�����C��MH�jA%a����00�_�����=>:}{��e�8B��qv~�5����3�|j�����oI�")��p,]H�T�<�TN3�8����3���et��BLh3
.�gX�r�!�A����FU�K�N-�f��`��R&�rl��6�igE�E%uy��?*{]�Mv�as����=\\�1]Z	�\~�uy)A2�����-�L�q�48UH�A��C�O7��E�b��0d�]��k����d�"���sS�f-1w��&�������l�e�j.���\jh���P�*6D9��'�]����A�.���.�)��<��K�nm��E�)��B�+�����W�:��w1Ga�����z�(�q�D&�����+n��{Bv	�$4�/�]�#�������k�]�$]&|�E�x^�����y^�X�O���UZ,���T�1��Q�=}f1��dan�"r=��@S�7��k�����/������Cx���9u���xe3��+�����|j��hC��R����%.��$��Y��6� +�L0n���cV��`��nS8�SEa��[�w5N����G����F#�k�A���B���0i+���`;�y �1D���|����K���#L��<(/�j��m:&��%fuw�"��T����Pxh��
�������%���7��@E��
�)s�7X��=�	
������**��}H��2���A��<���
�+H��i��bj������y��.7J�K4B{�k��1[���z�!�B�O9D[d��RpU���X�����6�0�~'�;�9F����B/L�n�I;B<��u��	��<��}��[/m>�����@��-X2�~����m�iBB#K]9P#�G�r�,���4�zK�����imz)Pjr-D�r����j�p���Q���4Z�4PnN����u�������0�������A�C�j��^��t�,��|�}�D�(��YE@�Tqom1����$CE�<�M��aw�{e
;<�z��2k`���m���:WH���9}�����������3h,N��mN������U�d?OmQikK���,72���O�����=��?:��0*E������s����!
!�@�?�F�d���uo����vzE�.}��~���n��$5��%Xm4�-�'d��)�� X�.���+h
p���`�vI����
��O������0xQm����
�����GY��z����i����,��k��W���y@��
KpvZ�����=~4x|��hTi�'<����>y���*��Z�r�k<�>��',cJ490���u��n����GXc���?����+�<�D�nU���=.��4G���YRq7��a�2���QZh��a?��L��w��?*���� �hq����hi5V%A�%���3tbu,L>|�@�H�T�e^����>Z�UL�M
���
n����0!�Y���
)^�/L�k��$��YZ#)��\�@�O��g,�D��������m.e|�������u�����q~
���mp��$�[������������
I2NQp �����PT*z`�	�������M�����2�_Ka�����ll�6{���o?q��>�k�1���2����I�C<g{�7���]��������loS��W����������?-�(�J�7}3��������a�D��\�'NDV��im!���;t#Q���@@�^���!}C4,dG�y�d��"#F��K�����yF�8��f�=W��m]�q���U�\q~��+?�1t#S�\^�	�U���	������.��yDO��4d~gf���x�'w�����1�!�������-�5�=��,ky��)�e�'���L����a�`�y�w�)v��aG��Z�Yj����$_��H����I2���"�a����������!4w����o�q��c������8���������Q(J�R���g����~l�2�d���	b��{),U��7�-�%C�N�c{�$��<C������`x�|�o ��10����;*�g���W����Gk=��$$N�d�b)�)��9�V�p)!����0��Ik{>�"P�����Nd�s�(2pf���D���'����M�
�<������@�p�8
/���p<�f�=�,.�I2'���c�v����x�x�;���4 GI�(��7{��	�.��l���i���Ng��5���'�����(
�g�+0>(
��	�4�^���>5��Jl��$���;3�����K��\b�����N�kw��K��i��� h��� �`sq����%��\���jH�����)�\�N��
j��������?B���v�V�Q��O��n/Dk�m��n�*E���?�r��K��}������QZs�ps!���B����v�Y�`g�L��t��9�
3�%�^:���8se
�"5Ep�Y��|����p���Y����.��wV�Y�A��,�4���C\d!<y4T�����z6xx>�nUv�"!�`�t�w:A/z{N��H�U�	�/G]6��W*������)\UY�#���#�FnG��f�
'r�]-`��5u������5�ik����
�i����N��-�Y�^���G�Y�&H�����i��?��*�n*��,��/�A�,�e�.S����bgS���RT`
�O����x�^�.���<������'L���~rH;���#�9�\�c�un��}1f>�jV.:Y���7�t���0�UUv�I/������������&�&��W8D��l��>�����������gW���P6$���[���A�6?Fa�m�SE��8	�����VF������z����#��_&��1�n�v�H�{������%����y��4��<NRe�-�J��u�Y8[P�I�����w�Ui�e��; ��I#!��-���t(������j���h������7�������'�hK��W�bQ�$1�J����8Q�p5 �y��99�Wy�<-?0S	Pe�������2e<��J�<v"Z�S-�i#c/rN��D�`c"_kC^6���1�E��;�6.��f��q��M�����7�\�=cE���Y�>�����gT�������d��������b�����u��C,��,U_1:�jTi:��K�eHy���8Q�b%�|�|xr�o���xC3�KS<��\��5����E�I7 �"�}*����F����f"b�6���|����w�4OJ��i�3����` �vD�������Q�^Rqk]=�l��x*�-�XX�iQ	/{�-����c�Qvk�����w�=T�u��x1����F���V�h����d�����9�O�Q�QOG��Q���M#_��������J��%��N����a����������������'�{�����X���h:"��[l>6��,�"c~�(=��gP���i�Q��@3�bA,�g�C�2r�4�E�#��F���rq�|���y��D�`j.y|3K���Y"n��%� 	c�Q4��b��J�����L�a����������4n���\6��Y~����r���b���Q�y</�|=6���l�:��	X��F ���
� ��g�P����C�}Xje%�.��D�_H��VG1�f�(���})��R���c9!��.[e��dA��R|��W���w�p�$�N�i��'}����>���:F-(��R�
c-_�����;&�#X
�j�����*��K	��(�[p���
�oU@=vj���t���3�a��m�H�k����`�6����P��f'��&�1�����Dw�C��#X�j%1�l�\�F�1|��_m��r�����+�'�9����EZ��8C$�w����1`>�h%,K|C�lV���*���e1��b����A�d6�|�����p�Q����O�-��w0��oR�8C�����J
q�9~��/���&i�"
�PQPT|Y���
�Q��NfZ������O�y����7�$3�M���>���F�N�����xp�����e_�bo6��p�����G��\*0�k{\w�?���.3�V�W$d�\��3�.������z^\��.O�5,�R_j�9e��.�	i�#��I����jrs��c�j�����\������"W�&]]��]�vG������Xil���j��i�W�nf�������ip���5f��z����Aa|�5����n�^��h�������'�������?���"y�1����'�����yb�����R�S���BSr#�M�����[0���s�7g����E��������=E�O�����|����t���f*px�hcZ��������i6^)��jL���mm����Ej��y9f��������;P9�o�*��'��o�G�c���8u�����7�;���!}a����>�0~k�����Ci1��Y�Bc�s}kZ��m���mpbm�Jk���L�����n7���\F��X�S���ij}	�f���7��87�D�K7bb#z��
[����l���iv��s/7���$��m��&5}z���R�A���}�U=6�u=B@��T�� �qM�����6,8�Z�tH0�����e7V�@�G�(�I�v��O�~Q��X�X_`��9�k��l���A����i� wV�� ��Tu>�Uq��^��,�v��M/ub�gL�9!@~����D���hH����� a)x�����E.X���c�V�&'#EE�v�E��h�`�C-�zn����LF��@bxd��5J�,�}���V�����ta����c-+��w�
����1�3�58����3�uC,F��(c�+��!u������V\^��r�����/��z�-���~NU���{�U��eb��1F�����v����S�`������Q�6y�\J2�B0�?�T��=:Z<'�i��^�����.�>��E�
�.��w���}�A�0�E%Q���D=cVT5�T�QQ�@�6�E�E��uD�cXV;���iY�@�<�e�M��D�cXVA�UA<�B��B<VD�q4 �"��������#��F���L�J	�R2.�%Hz���� �&��v����V��%!���	��h������/�!!�U��z��������%��hT�n���f�9RK��L�%�����V��;����6
Qt�-�lkC��6#9�x1�H����1�[?B��������?J!�YY��G��/jG��F���E���e%LaS�.�!�v��1|��0B�pG����D��6�E�V���~�U�%�V���=|����i-����M���p,�����i1�����1/�e3���2\�6�7C.4�&Z�5����
�8!��<��D��Tw�t��p��4W�4���T�B��P
Be(\�:��(%s$F1��=�I���%)
I�4S�4T	�0a�A�)T��Py
��S�JC� @��5Q~�.�)w?J{����_C�v�<\�Xw4#���vO�qN��r}�
���gp���k� �y��~W_&�-@�M�>HVHtBB��)(��'0�L�0���q��vXBS*i�X�����,d�c��S���[�@"@���8w���W���B��:R�V(��y)��y)�^C�4�XF���qE5���X�i���N�%^F�Y�8'9X����������Z���~���By<�d�����o��\�m��
ivT(�A!c%����\�%Y�
���0$w�_G����F�����������BS���:�*9"ahB�>P�U�'����e�MZ�h���`"�l�.!�y��<^&EmG!$T�D{x���^As���b�^�pA�NRB�N�H*�(k��T���LK�,,?��~����"��#)�!iE�fCu�������x��� A�^d�%eo�������_q���8��D�'���<Z?p9��������jz���9�"�	)�����
V{Y!�h��@���Frrh��z��	���AU�Vd2�o<�PR�F0&����4�aq��z?��E]�2r��
<u�W6zs�a|�=��-����/�@O�pPN2�� @��c0��AO��)�Dx�5�oZ%:5'P��z� 0�X~n���P�����Z��*Q�J��U����������	��|:�#s?�_@2��Pd��|
FN}�������A0tN@�� ����\�xu�&"}��O����@7���F�>/d���}����!34��v9f�4���:W>�U���7q�B�P H�R�R�l�;�I��Z;S$df����P����B��Q�n%����$�W�F������p�����Q�n�|2�rF�I�k��[��;[��H���Y�;K�S����;3�����E_�<az	_\���7�b��ss��*b\+� ��"����uv\L���f�A��c�����:�Fcj4 r��w�2�e�v��u��������k����N�i��|4xr��1��G��m� x�E`��4L�\luA�kL;���� \9�-�R�32���L���V��������Z�^<k�.�����������1�O(%�v0����\?L��n��>YQr��L�d�i�:q4��2Y�k�~A�Y�^�R����Rn;�Jq�H�������,�E�!IrH����J�>t*���p��b�)�mP��{|���[����?����M_-���2SZ��^�J��L���_w����>C��cf������,�����t�Y������ORF
�&s�2-2���>S^a�L_jd��SQ��0�
��d�%��D��q��������F���Lb��O|�L���- �H�oP�����}j�RM���Zf�����R97D_�g~M�}g2�
���Q�Ye(��E'x��V'6����ZJ_)�e���9�M#/��(��t�!��{�C�Q��N�n�zI����e�ua0()�Q9D$�'#g��Yt)G�3�
���,:g�Q���koN���X�3�`�O#V�:�:u��H��%A������{��`$9�����r{{�{{{��4?����������M��L�%������"N-o�B�k1t�p�c��O��.�~�k��	���P2[���x��.
c�)������R��fU�"Z�)��
�_(c����!)��i�������p��J�e�([��< ���`TD!x[�f��4m�iw������F���E<x�XD�[��
5<�K���^���~�;���<���R��f�����}�����������#�4�l�;v9.���O����-#���~�k��q�h�t�-R���D�'�*��g�b�$���"�z��Z57���L;�z.��%�P�2�q/]���.M��JB�J�a���N�t���KQz�)g���
{1NS8H��Rp$���E
?<aF~!\'O�����~l4�H4YN>�4�Q���b��[�Ng�1�l;ED�D!<z*F<W��\�UF������|z����r��aI�8��g��q7F���f��V�L����Q�%a��N���&����3�C�oq2��'��kd7�[�Qk(��92�rO���V�ko��M����0:��zu)U����KLu�)��n<=�#$j|�XCq��Ms��&#u�&��"9	[
�2	����
���n�*�L@.f�]�^���L<�r}%sdM�p�j��e���"Gd*q���f��QW��a?%Z�J%C��(�O�����&�c�CJ��Gz�ey����14�5�����F���Mu�J���_P��-��/"�A��r�pj�I�'B���J��<'��wU�����������`�T+ ��A�y"�����]S��PYp�U"�R;�{�>��=��zf7<�;�*[B�T+5H����|Q6-���������_�a�d{�r���A5aI��x3'�o��Tj{{p��?��wa��{{��KD)�;��S���2Qg�3��0�NN`�t!O�0#@����%�K��!�b��p",�e���hB�|��U��N�
�v�;V�a�����,�3����kOJ�.��������+���	�P���9��\7��(!���{�]�,-����-\�I����/��Q��7�"v�����dz|b��H�t��YyF���H9(�������;9��v	��S�Tc
��������sa@����n�����k���G�*P�&��!��'[��v��Q�E9b1s�����U���'8#�VKd���is4Gz��A�7�����+XC��3,@c?B�@�QH;�^��5�T��y
x������'�z�6
�b�9�{��Aw�!��;z���[n*b��8G ��3�����pn�!��������������J����/n��9�
.���:�{�	`��X��������z��a�s�;�Q�Xz����BO�����	<����Gp�f0�
��������7GO�O���������^X�����n��W���G�N��OnC\��������'�3/�� )y Ck��,0����Ly+<�t��R�Y5\����+Et�2�`�������`}���4��o�H��5"��� e�2���[B!�L>WPJZ!YE<"� ^+/���Xl��.�� Yi����%��L5����v*P���]�4����6���d�L]e���K��gi�t0{�LM�����Y�b��;���]7.nYI�U�m��]�*{��V?���'^�+�d����!��s�E��\��[@;%���L	h���]D<u��(*|	1wO�B��y��Y��(����}!n�����(�r� ��������rXB{{	�KC�,���1R?���aJ(��	�9�}E��������V�4`"&ND:7�`8�'_����A(s��Q`��?��)�1X-�������$yL��	�1����n��4��,��G�'C�p�,P����*TB�t����Iz�N�)V�G0#���^��.��31�8%b�yL�3\�7X���/�$�V|��ez\{��� ���l�
/�H�V��=��\�b���6F��74��K4qjx��r�K�����_�^n���sw��o�{7���'vZE�U����45m�nO������G���T�-�m��}�7g��>�x���b�?���r����?���������g�}T��Or�sG)�s�p��	���Y9�g�X*�ty�r&�J��)������e*��i(u���A�r��U/W�_�W�&�\���U��/�z�����f��/
#32Erik Rijkers
er@xs4all.nl
In reply to: David Rowley (#31)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, December 21, 2013 12:38, David Rowley wrote:

[ inverse_transition_functions_v1.2.patch.gz ]
Please find attached an updated patch which should remove the duplicate OID
problem you saw.

That fixes it, thanks

There is 1 of 141 failed tests:

window ... FAILED

but that's easily ignored for now.

I'll do some testing later,

Thanks

Erik Rijkers

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33David Rowley
dgrowleyml@gmail.com
In reply to: Erik Rijkers (#32)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 22, 2013 at 12:49 AM, Erik Rijkers <er@xs4all.nl> wrote:

On Sat, December 21, 2013 12:38, David Rowley wrote:

[ inverse_transition_functions_v1.2.patch.gz ]
Please find attached an updated patch which should remove the duplicate

OID

problem you saw.

That fixes it, thanks

There is 1 of 141 failed tests:

window ... FAILED

That's strange, it passes here.

Would you be able to send me the regression diff file?

Regards

David Rowley

Show quoted text

but that's easily ignored for now.

I'll do some testing later,

Thanks

Erik Rijkers

#34Erik Rijkers
er@xs4all.nl
In reply to: David Rowley (#33)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, December 21, 2013 12:52, David Rowley wrote:

On Sun, Dec 22, 2013 at 12:49 AM, Erik Rijkers <er@xs4all.nl> wrote:

On Sat, December 21, 2013 12:38, David Rowley wrote:

[ inverse_transition_functions_v1.2.patch.gz ]
Please find attached an updated patch which should remove the duplicate

OID

problem you saw.

That fixes it, thanks

There is 1 of 141 failed tests:

window ... FAILED

That's strange, it passes here.

Would you be able to send me the regression diff file?

attached...

Attachments:

regression.diffsapplication/octet-stream; name=regression.diffsDownload
*** /home/aardvark/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/window.out	2013-12-21 12:57:36.814939706 +0100
--- /home/aardvark/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/window.out	2013-12-21 12:59:49.827750904 +0100
***************
*** 1078,1084 ****
    INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
    INNER JOIN pg_proc AS pntrns ON agg.agginvtransfn = pntrns.oid
    WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pntrns.proargtypes;
!  proname | proname
  ---------+---------
  (0 rows)
  
--- 1078,1084 ----
    INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
    INNER JOIN pg_proc AS pntrns ON agg.agginvtransfn = pntrns.oid
    WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pntrns.proargtypes;
!  proname | proname 
  ---------+---------
  (0 rows)
  
***************
*** 1089,1212 ****
    INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
    INNER JOIN pg_proc AS pntrns ON agg.agginvtransfn = pntrns.oid
    WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pntrns.proisstrict;
!  proname | proname
  ---------+---------
  (0 rows)
  
  -- test inverse transition funtions handle NULLs properly
  SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg
  ---+--------------------
   1 | 1.5000000000000000
   2 | 2.0000000000000000
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg
  ---+--------------------
   1 | 1.5000000000000000
   2 | 2.0000000000000000
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg
  ---+--------------------
   1 | 1.5000000000000000
   2 | 2.0000000000000000
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,AVG(v::numeric(10,2)) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg
  ---+--------------------
   1 | 2.0000000000000000
   2 | 2.5000000000000000
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
!  i |    avg
  ---+------------
   1 | @ 1.5 secs
   2 | @ 2 secs
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!   i | sum
  ---+-----
   1 |   3
   2 |   2
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!   i | sum
  ---+-----
   1 |   3
   2 |   2
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum
  ---+-----
   1 |   3
   2 |   2
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
!  i |  sum
  ---+-------
   1 | $3.30
   2 | $2.20
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
!  i |   sum
  ---+----------
   1 | @ 3 secs
   2 | @ 2 secs
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,SUM(v::numeric(4,1)) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum
  ---+-----
   1 | 3.3
   2 | 2.2
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | count
  ---+-------
   1 |     2
   2 |     1
--- 1089,1212 ----
    INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
    INNER JOIN pg_proc AS pntrns ON agg.agginvtransfn = pntrns.oid
    WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pntrns.proisstrict;
!  proname | proname 
  ---------+---------
  (0 rows)
  
  -- test inverse transition funtions handle NULLs properly
  SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg         
  ---+--------------------
   1 | 1.5000000000000000
   2 | 2.0000000000000000
!  3 |                   
!  4 |                   
  (4 rows)
  
  SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg         
  ---+--------------------
   1 | 1.5000000000000000
   2 | 2.0000000000000000
!  3 |                   
!  4 |                   
  (4 rows)
  
  SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg         
  ---+--------------------
   1 | 1.5000000000000000
   2 | 2.0000000000000000
!  3 |                   
!  4 |                   
  (4 rows)
  
  SELECT i,AVG(v::numeric(10,2)) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg         
  ---+--------------------
   1 | 2.0000000000000000
   2 | 2.5000000000000000
!  3 |                   
!  4 |                   
  (4 rows)
  
  SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
!  i |    avg     
  ---+------------
   1 | @ 1.5 secs
   2 | @ 2 secs
!  3 | 
!  4 | 
  (4 rows)
  
  SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
  ---+-----
   1 |   3
   2 |   2
!  3 |    
!  4 |    
  (4 rows)
  
  SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
  ---+-----
   1 |   3
   2 |   2
!  3 |    
!  4 |    
  (4 rows)
  
  SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
  ---+-----
   1 |   3
   2 |   2
!  3 |    
!  4 |    
  (4 rows)
  
  SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
!  i |  sum  
  ---+-------
   1 | $3.30
   2 | $2.20
!  3 |      
!  4 |      
  (4 rows)
  
  SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
!  i |   sum    
  ---+----------
   1 | @ 3 secs
   2 | @ 2 secs
!  3 | 
!  4 | 
  (4 rows)
  
  SELECT i,SUM(v::numeric(4,1)) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
  ---+-----
   1 | 3.3
   2 | 2.2
!  3 |    
!  4 |    
  (4 rows)
  
  SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | count 
  ---+-------
   1 |     2
   2 |     1
***************
*** 1216,1222 ****
  
  SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | count
  ---+-------
   1 |     4
   2 |     3
--- 1216,1222 ----
  
  SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | count 
  ---+-------
   1 |     4
   2 |     3
***************
*** 1227,1253 ****
  -- test that inverse transition functions work with various frame options
  SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum
  ---+-----
   1 |   1
   2 |   2
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum
  ---+-----
   1 |   3
   2 |   2
!  3 |
!  4 |
  (4 rows)
  
  SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
!  i | sum
  ---+-----
   1 |   3
   2 |   6
--- 1227,1253 ----
  -- test that inverse transition functions work with various frame options
  SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
  ---+-----
   1 |   1
   2 |   2
!  3 |    
!  4 |    
  (4 rows)
  
  SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
  ---+-----
   1 |   3
   2 |   2
!  3 |    
!  4 |    
  (4 rows)
  
  SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
!  i | sum 
  ---+-----
   1 |   3
   2 |   6
***************
*** 1261,1280 ****
  -- hard about it.
  SELECT SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
    FROM (VALUES(1,1e20),(2,1)) n(i,n);
!   sum
! --------
!  1e+020
!       1
  (2 rows)
  
  -- inverse transition function with filter
  SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
!  i | sum
  ---+-----
   1 |   3
   2 |   2
!  3 |
!  4 |
  (4 rows)
  
--- 1261,1280 ----
  -- hard about it.
  SELECT SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
    FROM (VALUES(1,1e20),(2,1)) n(i,n);
!   sum  
! -------
!  1e+20
!      1
  (2 rows)
  
  -- inverse transition function with filter
  SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
!  i | sum 
  ---+-----
   1 |   3
   2 |   2
!  3 |    
!  4 |    
  (4 rows)
  

======================================================================

#35David Rowley
dgrowleyml@gmail.com
In reply to: Erik Rijkers (#34)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 22, 2013 at 1:01 AM, Erik Rijkers <er@xs4all.nl> wrote:

On Sat, December 21, 2013 12:52, David Rowley wrote:

On Sun, Dec 22, 2013 at 12:49 AM, Erik Rijkers <er@xs4all.nl> wrote:

On Sat, December 21, 2013 12:38, David Rowley wrote:

[ inverse_transition_functions_v1.2.patch.gz ]
Please find attached an updated patch which should remove the

duplicate

OID

problem you saw.

That fixes it, thanks

There is 1 of 141 failed tests:

window ... FAILED

That's strange, it passes here.

Would you be able to send me the regression diff file?

attached...

Thanks

This was just down to some missing trailing white space on the expected
results. I've fixed these up and attached another patch.

Regards

David Rowley

Attachments:

inverse_transition_functions_v1.3.patch.gzapplication/x-gzip; name=inverse_transition_functions_v1.3.patch.gzDownload
����Rinverse_transition_functions_v1.3.patch�[{o�4�{�f����{�
X��
T��
!T����$��v��sI��M�!�0���?���s}���h+��{�&��i��I+�xb:�K�Wtn��W���������t�dpzr$������v{%�v��\�����}tr�:M����?�����l:�
Smu�`y���>wmg�����A��
�g��3�Z�~ON&:z��R�+�]�n����j�����>��j�R�C����;������ ;9I���^21�k|������G�[t=��(aWp��8U�`���T�#��Il�&�_���R�{��V�`�@3<9sX;��F��Y���;����� ��F=�����d�b�����W��a_\~��C�+x��$����P	/��\�$2��vz�9�7�'�'7�����w�W�b)�����7�a�a�gP��h�`��}���A�#T�*!m-��xy�68����Q�pp��f;o1"���:@G������� �z$&m0������6m�G^�d����w���
�v'`I6N��a�#}���9��������q�4������I�S����_
������o��cq�p�_�H��=n��'�f��`(��ns|�i���#�V��fU:��mYatB�
��U���%��2�>�OFb��I������O�(j7�Y!����G���X����|�_nAO��|��D�XF<�p�L�R#Y�S���-�2��IE���W�i�6bf�/4
��@[�zoE)�����������{1|����<�����������gehL%m"'�f��P"�C�	�����i��A�_A�>��S$L�����6F|G�U���av�F�Q*��l
�Vp{:3VL����~pF,]���
�dl�=+@/e���M�H��*#S;_�|=�6�Q�"�j�^�}`�:��xg:�:Z��-����&�j���=/�E�����~���J
��5~�L&�mFm��F�IU�V�o�����lu�����������{~!UD�-4!��AIBG��8�?���8���7y�z�7Q���-4|F��x�rUWRY��f�!^4�0�-�`��k�"
���]X��[���d�k!���W������^�Q(A�|e�T��7��J�S!���
�I����j����^�f��GV�����a:��I��T��)�iWnO��r�\�Mn�6���vP���h-�W)����r������??�t������k�n.���)M�v)3�������|��F����n�X.��:tY��.	��i�|���T7�9�&s��f1� �jn�.�h�/,2�W��SU8+B�&BL���c�7��UuKb1x���o�0R�!�e�A���������2��6=�t����sT�>�|r�"�M�*���w��������EbOqLD�"��J
�(u���S���Ta^6��'+d�(���<����1lc��|LU)�����������<��N;�x�!�kcq�6��yb����SR��������Lo;���cj��td�;�]d`����,R�z>�Ti�����������4�;
C/�D@�����l��|!@i>���&�q�j���e/�@c�8��l�6�<q!���5"Ecr3���1����O���p�s�����b�V��$��8M���c9�_�A��jY6�4��iiB���;��)�F���
v*!�\�Xz����~2)��<1�`P���N����Y50�����@
'�-��8��WTvlx�V&�3o�mPf���kigSQ��d]7��]�*���r/�e�w�lV�@�*���$��]|�/��i�6D�!�r�6}z��vE�.�6�6�yk���?�Jk�����?�A�F���L-�� &��D���	V3�X��������1w<{�������S�/o�!��2� ���p���v�����q���$�=(����A�������G:S:��������Sj*�Df>�l���:�l���ur�6
�j�+� 6�Ww����.o�#��
w���>T����OB�=5������r��*b��ay������G�&���x�H��!s�Q2�4bgwwY�'�P�|�=�~�B�r�zj��{���U����(����"t3�2��#�y��Y�� �o��S���T��Q�ss%�'���wC;�u"6�,���1�$	��yhh,x�]���1;�"�� >O9�]���(�`7���K�@ugu
%/\v���)�P4<G��V��up�*�Q.��g�*���5�;ev���:������0��%��F9�"�Z"r��`8;��2c�^�n?b�q�)\]W�`�U���}�<�|�(o�$� >��x�]"O�*���{�/7����%^��<~s9��z7���qu+���d
O;���0F�&KT�
������h����_�l;C_��d�=���m�?�]�
�[�n��m��,���[� �|�d2�%�������e��l ����>S��*��n���V�2y	4r �Q�w�����q��������U����� �`�D��a@���V��jT�S��?j����aQ�V���kZ`�4u�b'_h7����Q�Y��)����/q���VM�v���zy��������������Kp��\	��(�>�GJM"��Y��ie-NIk�q�f�A�!k�b>�Cq�z��ja�{3����z"����wr�[��7�=UXT�)��2�����E>�^/N�k����A�����tnT��GnjK\��������G�w�\��d�g�,=����K@�2_Xe�A�	�b!}���n�L"��GMZ�%�v�<�����y����6��,�Y�������w�w ������?�r>�-��%pL��v
O�O9�h.��cL��a��3���K��}#0�W����J������(�4�������/�������������;R����
��j��@|��h(�O�����ZsT!�l�e��,1-xvq��#	��z�7������I�G'�K/|�
��������@GrB��,0���
���GQ.�@�x���O���!���G��Q��z���a7xN�����Yf�^��W8�og2��}lm@�
��a�f���f226���&<���/&c� �;�����Wjn�RV�+�jrQ�_�W!������_�5&_����3~�����	!�cA�K�*�����wV$��\���"��)(���������$)T��Us�F�+R��[�L�y�2�[u����V�Ig�;m�^y3���b���z���Q��Cy�(�:?
*�z
��u������3~���a� ��4����	-����2���
�rb�0�5�Q��\?H.�D���%?)���}]���H��k
����y.&��y���N��w	5��1�Z��*���a�{��w���v�Q'Z�=TX��0t�y��f��
����)yE ���J��Vx]Y��Nss���	%~R,z:IG:
�F�b���dB�i��qe3{���U��l���x,��:���wp*6�
Zo!n���6��2|p���<��^�0
TPr�B����9�U:�n�r^U��F���9��vB���@D�xAd�$�(�,%%��2B��q�Q�9��?�������+���K"����*w��Y�%�5]+�a�����
�y�i�?&��O�c������������`Pv/��(���i#�x��=
@��/ab�n�@;�<=����faH�$�<o������Z2���vr{u�fm2�'aUO�N��j��*��>�1��������������Ey�������]tu!�������3�c~g�W#~qA�Lak���~v���?��{�t�\Y�,zu�W�����������1�
�47���5U�������Zm��� 7��j������{��6sM��V�	��Y��w
�,�*hi�������[�����,��e�������l�������e�|=?���[��X���7U �������+�������s���6n�[�;��1l-�ECW�������l���Q�8�_�;c�/�zO���~�����B��'���y�{�w�����2��+��	���F�a&��]q�;�p���8$,^2�(�N���*����y��E�S\�b�jhb�����^�|�����i�`mD�I�#�M:�����W����bh6i&�:��D����
(��D�w����7�^8��;S�������HI�*E��T���:u���w�����Ux���Y��������7�-d������~a1��%l�����K�gP"?�[���j�u1,���93���W��^]6���;���m���W�����m���[���C)�)0�b1�E�D�i������hI~��
(V�<��{��V{w��B��L��O#6$��r����d��V��a��� �eeE��bB@3U�'�HoE���1����|��i�<�d�6��?��c� q��7@�-&���3*���^�<��&�c^���b��[����0X�C�3���K�	�vY*�����jh��N�ix_D�9�J�;n�*����mcL���^[�g��K�!JPWb��
���P~���v�/�y���m�"�����< x{{�j_�WS[�:�i����J����vg`�l�Y��%�x.98���l}��"�8�q�
�����+�^� ���1I��$�� �&�A��cV)n�U%@�SSu�	��/��@]�'9��_�	>���de9S�%�gZ�V�Mg"���' �g�\Y[���+X�5U=�^�hML��*E�$�x�?x�N�H7�X���Y�����Z��H�I�x{2��a�c�Y2�t�&�1�V>c��2O�cs�2�SlZ��c��MM�VT�@�O�T	-L�RZr#_]�i�N pfxJ�P-�S���H����������oJ�
I���C5_<5c��'���4������wwgI�P�t�&��K�"�A���,�{�f���9[Z����c[v8|2w6�3x&���P1�R{Q�
K���iJ�%��Z��z�a1���%�4��6��*��"wsj�N4ire����^_2���#�f���U�6�t���%]���N�S\#��8�D�<"��J\�t5.5��6�7��v�������@�����A�����7�~,&yzx�1M�����>_k��n���q|(i3L{��������	��T?�[�I6�S&���:hY�
��L�m4-:D��U������U�l�����n�����;�[k9�!�=h Hc� �����O�=�X$�?u`[3�hI�(6�������D�p��72��������#.���<x��>������5qW�mp�.����3`}��-�ag8?R*A��HK����D�	T��e���v��_�{9~8@F���xAwLA�m�#<Q�\�!�;.�(��T<nR���^�`Y�q�s}��&�F2H@zm�W�Cm���{��iTJ�	��F��)�����V�2HXLme��I���d��q�M��������������^�ar0�M���D0��X	�a�&K����2�V]$�����N�#�4�k&�)f�F�
�:CT�Bk�"aU^r�I��n�evj���[*�
��R��)���J��[\H���z?�����������)r��#��F%�v�Nb�����y��^;�vM�U�h���hkg��2C�*���;��������k�@�m�_�v�6r���L{8�Y��0���m`$8�o�\},e
���l����68���������<,W��
�A�n���[#���
7������pEaXO��'����,7+�����z��]�?��^�k�������h<��yC�!l��p(��W���@��s���b5��P4���x���p�#�1�����+#�E�oa���2��*�"�q������0@��99zq��yoKc�����C��MH�jA%a����00�_�����=>:}{��e�8B��qv~�5����3�|j�����oI�")��p,]H�T�<�TN3�8����3���et��BLh3
.�gX�r�!�A����FU�K�N-�f��`��R&�rl��6�igE�E%uy��?*{]�Mv�as����=\\�1]Z	�\~�uy)A2�����-�L�q�48UH�A��C�O7��E�b��0d�]��k����d�"���sS�f-1w��&�������l�e�j.���\jh���P�*6D9��'�]����A�.���.�)��<��K�nm��E�)��B�+�����W�:��w1Ga�����z�(�q�D&�����+n��{Bv	�$4�/�]�#�������k�]�$]&|�E�x^�����y^�X�O���UZ,���T�1��Q�=}f1��dan�"r=��@S�7��k�����/������Cx���9u���xe3��+�����|j��hC��R����%.��$��Y��6� +�L0n���cV��`��nS8�SEa��[�w5N����G����F#�k�A���B���0i+���`;�y �1D���|����K���#L��<(/�j��m:&��%fuw�"��T����Pxh��
�������%���7��@E��
�)s�7X��=�	
������**��}H��2���A��<���
�+H��i��bj������y��.7J�K4B{�k��1[���z�!�B�O9D[d��RpU���X�����6�0�~'�;�9F����B/L�n�I;B<��u��	��<��}��[/m>�����@��-X2�~����m�iBB#K]9P#�G�r�,���4�zK�����imz)Pjr-D�r����j�p���Q���4Z�4PnN����u�������0�������A�C�j��^��t�,��|�}�D�(��YE@�Tqom1����$CE�<�M��aw�{e
;<�z��2k`���m���:WH���9}�����������3h,N��mN������U�d?OmQikK���,72���O�����=��?:��0*E������s����!
!�@�?�F�d���uo����vzE�.}��~���n��$5��%Xm4�-�'d��)�� X�.���+h
p���`�vI����
��O������0xQm����
�����GY��z����i����,��k��W���y@��
KpvZ�����=~4x|��hTi�'<����>y���*��Z�r�k<�>��',cJ490���u��n����GXc���?����+�<�D�nU���=.��4G���YRq7��a�2���QZh��a?��L��w��?*���� �hq����hi5V%A�%���3tbu,L>|�@�H�T�e^����>Z�UL�M
���
n����0!�Y���
)^�/L�k��$��YZ#)��\�@�O��g,�D��������m.e|�������u�����q~
���mp��$�[������������
I2NQp �����PT*z`�	�������M�����2�_Ka�����ll�6{���o?q��>�k�1���2����I�C<g{�7���]��������loS��W����������?-�(�J�7}3��������a�D��\�'NDV��im!���;t#Q���@@�^���!}C4,dG�y�d��"#F��K�����yF�8��f�=W��m]�q���U�\q~��+?�1t#S�\^�	�U���	������.��yDO��4d~gf���x�'w�����1�!�������-�5�=��,ky��)�e�'���L����a�`�y�w�)v��aG��Z�Yj����$_��H����I2���"�a����������!4w����o�q��c������8���������Q(J�R���g����~l�2�d���	b��{),U��7�-�%C�N�c{�$��<C������`x�|�o ��10����;*�g���W����Gk=��$$N�d�b)�)��9�V�p)!����0��Ik{>�"P�����Nd�s�(2pf���D���'����M�
�<������@�p�8
/���p<�f�=�,.�I2'���c�v����x�x�;���4 GI�(��7{��	�.��l���i���Ng��5���'�����(
�g�+0>(
��	�4�^���>5��Jl��$���;3�����K��\b�����N�kw��K��i��� h��� �`sq����%��\���jH�����)�\�N��
j��������?B���v�V�Q��O��n/Dk�m��n�*E���?�r��K��}������QZs�ps!���B����v�Y�`g�L��t��9�
3�%�^:���8se
�"5Ep�Y��|����p���Y����.��wV�Y�A��,�4���C\d!<y4T�����z6xx>�nUv�"!�`�t�w:A/z{N��H�U�	�/G]6��W*������)\UY�#���#�FnG��f�
'r�]-`��5u������5�ik����
�i����N��-�Y�^���G�Y�&H�����i��?��*�n*��,��/�A�,�e�.S����bgS���RT`
�O����x�^�.���<������'L���~rH;���#�9�\�c�un��}1f>�jV.:Y���7�t���0�UUv�I/������������&�&��W8D��l��>�����������gW���P6$���[���A�6?Fa�m�SE��8	�����VF������z����#��_&��1�n�v�H�{������%����y��4��<NRe�-�J��u�Y8[P�I�����w�Ui�e��; ��I#!��-���t(������j���h������7�������'�hK��W�bQ�$1�J����8Q�p5 �y��99�Wy�<-?0S	Pe�������2e<��J�<v"Z�S-�i#c/rN��D�`c"_kC^6���1�E��;�6.��f��q��M�����7�\�=cE���Y�>�����gT�������d��������b�����u��C,��,U_1:�jTi:��K�eHy���8Q�b%�|�|xr�o���xC3�KS<��\��5����E�I7 �"�}*����F����f"b�6���|����w�4OJ��i�3����` �vD�������Q�^Rqk]=�l��x*�-�XX�iQ	/{�-����c�Qvk�����w�=T�u��x1����F���V�h����d�����9�O�Q�QOG��Q���M#_��������J��%��N����a����������������'�{�����X���h:"��[l>6��,�"c~�(=��gP���i�Q��@3�bA,�g�C�2r�4�E�#��F���rq�|���y��D�`j.y|3K���Y"n��%� 	c�Q4��b��J�����L�a����������4n���\6��Y~����r���b���Q�y</�|=6���l�:��	X��F ���
� ��g�P����C�}Xje%�.��D�_H��VG1�f�(���})��R���c9!��.[e��dA��R|��W���w�p�$�N�i��'}����>���:F-(��R�
c-_�����;&�#X
�j�����*��K	��(�[p���
�oU@=vj���t���3�a��m�H�k����`�6����P��f'��&�1�����Dw�C��#X�j%1�l�\�F�1|��_m��r�����+�'�9����EZ��8C$�w����1`>�h%,K|C�lV���*���e1��b����A�d6�|�����p�Q����O�-��w0��oR�8C�����J
q�9~��/���&i�"
�PQPT|Y���
�Q��NfZ������O�y����7�$3�M���>���F�N�����xp�����e_�bo6��p�����G��\*0�k{\w�?���.3�V�W$d�\��3�.������z^\��.O�5,�R_j�9e��.�	i�#��I����jrs��c�j�����\������"W�&]]��]�vG������Xil���j��i�W�nf�������ip���5f��z����Aa|�5����n�^��h�������'�������?���"y�1����'�����yb�����R�S���BSr#�M�����[0���s�7g����E��������=E�O�����|����t���f*px�hcZ��������i6^)��jL���mm����Ej��y9f��������;P9�o�*��'��o�G�c���8u�����7�;���!}a����>�0~k�����Ci1��Y�Bc�s}kZ��m���mpbm�Jk���L�����n7���\F��X�S���ij}	�f���7��87�D�K7bb#z��
[����l���iv��s/7���$��m��&5}z���R�A���}�U=6�u=B@��T�� �qM�����6,8�Z�tH0�����e7V�@�G�(�I�v��O�~Q��X�X_`��9�k��l���A����i� wV�� ��Tu>�Uq��^��,�v��M/ub�gL�9!@~����D���hH����� a)x�����E.X���c�V�&'#EE�v�E��h�`�C-�zn����LF��@bxd��5J�,�}���V�����ta����c-+��w�
����1�3�58����3�uC,F��(c�+��!u������V\^��r�����/��z�-���~NU���{�U��eb��1F�����v����S�`������Q�6y�\J2�B0�?�T��=:Z<'�i��^�����.�>��E�
�.��w���}�A�0�E%Q���D=cVT5�T�QQ�@�6�E�E��uD�cXV;���iY�@�<�e�M��D�cXVA�UA<�B��B<VD�q4 �"��������#��F���L�J	�R2.�%Hz���� �&��v����V��%!���	��h������/�!!�U��z��������%��hT�n���f�9RK��L�%�����V��;����6
Qt�-�lkC��6#9�x1�H����1�[?B��������?J!�YY��G��/jG��F���E���e%LaS�.�!�v��1|��0B�pG����D��6�E�V���~�U�%�V���=|����i-����M���p,�����i1�����1/�e3���2\�6�7C.4�&Z�5����
�8!��<��D��Tw�t��p��4W�4���T�B��P
Be(\�:��(%s$F1��=�I���%)
I�4S�4T	�0a�A�)T��Py
��S�JC� @��5Q~�.�)w?J{����_C�v�<\�Xw4#���vO�qN��r}�
���gp���k� �y��~W_&�-@�M�>HVHtBB��)(��'0�L�0���q��vXBS*i�X�����,d�c��S���[�@"@���8w���W���B��:R�V(��y)��y)�^C�4�XF���qE5���X�i���N�%^F�Y�8'9X����������Z���~���By<�d�����o��\�m��
ivT(�A!c%����\�%Y�
���0$w�_G����F�����������BS���:�*9"ahB�>P�U�'����e�MZ�h���`"�l�.!�y��<^&EmG!$T�D{x���^As���b�^�pA�NRB�N�H*�(k��T���LK�,,?��~����"��#)�!iE�fCu�������x��� A�^d�%eo�������_q���8��D�'���<Z?p9��������jz���9�"�	)�����
V{Y!�h��@���Frrh��z��	���AU�Vd2�o<�PR�F0&����4�aq��z?��E]�2r��
<u�W6zs�a|�=��-����/�@O�pPN2�� @��c0��AO��)�Dx�5�oZ%:5'P��z� 0�X~n���P�����Z��*Q�J��U����������	��|:�#s?�_@2��Pd��|
FN}�������A0tN@�� ����\�xu�&"}��O����@7���F�>/d���}����!34��v9f�4���:W>�U���7q�B�P H�R�R�l�;�I��Z;S$df����P����B��Q�n%����$�W�F������p�����Q�n�|2�rF�I�k��[��;[��H���Y�;K�S����;3�����E_�<az	_\���7�b��ss��*b\+� ��"����uv\L���f�A��c�����:�Fcj4 r��w�2�e�v��u��������k����N�i��|4xr��1��G��m� x�E`��4L�\luA�kL;���� \9�-�R�32���L���V��������Z�^<k�.�����������1�O(%�v0����\?L��n��>YQr��L�d�i�:q4��2Y�k�~A�Y�^�R����Rn;�Jq�H�������,�E�!IrH����J�>t*���p��b�)�mP��{|���[����?����M_-���2SZ��^�J��L���_w����>C��cf������,�����t�Y������ORF
�&s�2-2���>S^a�L_jd��SQ��0�
��d�%��D��q��������F���Lb��O|�L���- �H�oP�����}j�RM���Zf�����R97D_�g~M�}g2�
���Q�Ye(��E'x��V'6����ZJ_)�e���9�M#/��(��t�!��{�C�Q��N�n�zI����e�ua0()�Q9D$�'#g��Yt)G�3�
���,:g�Q���koR���X�f�Rk_��kD����p��_7��x�H��?������������I6�m6�f���Y�������������*��9�-�qjy��U�eZ�S��>������.T^N�������v�F��R0��J2�|����i7�����1e;�E�K1����bx��!o����+��7V��|���(T�M`TVBF��t�]�1�I��k�G�M��Z�M,����R�U�]h�U*~&���Q�Hp�yV�p��R/�AG�$77/�h_�=?��\�a8v����"�m��p��a���E���=T?����x��z�oz�.���`�o���*�4��@c/��-�0����U��((�E���i�S�fV-M��z�=w�4�K��X3Kpm��R�4�^���dN>+�o�cH)�4��d4_2�(-�H(��3b�����7\��B���&��^@����w}1W��V�;���,;e�8��T�h��������,��r�����
0�O����'[���<q����-��M|�m�m�:m[�R^B�G}k�P�m�p7�����g��'�Y{��Z#����N���CQ�3G���U��V���D��JWC&������wA�c��m��|y����NOFK���d�P^k�%�-��&#u�&bQ����V�����6-����[��,��h�.Ex�c77����3YZ����~����R��&����Y��,\Y��������RY�I�J��Iz#{����lrc,|H����H�v� ����x�a���u�A^(��dQ����h�4���'�����r�������%�LK��^�mM����!L������� �#���	=������$Q��|p���F������A�1��f��%=yj,A�Fk4Ai���|S6-��������Y�hb�d{RrV�'�����Y�x3��5����8�?�?��������s�R����Ooy��c��x���7h'�1|n�6���"Ne\A\��,�8$A*v^�Y+��Gr�~M���J���w<�2�a��u56~^`8<d�����:�$���Q�/�{�`�?���w�DD
��I�E��j��a^	�0D�.�bM�'y��X���j<�*�*�r,���?��4��J��{|b[���0��oN&����	I}]�M6
��������Ns��]"��	��4@��H^���f�T��w0����:`�.���,��GG�i��M,����M,�n�v�q�pQNX_��t<%� G���agb4��y�k�1Gq��`��$^�/_��B��#��3�6
�k�K� Gh���}
����~Q�cn]����J����������`B���d�Zl�)�i�wq�@��3�~����c���1[����5ue�|��j������[��;#G*�@	�KPS/�OZ0!��`q�u��P��y�}�ys"�T�������� �����p�?|������A�hH7��G�_�>z��^>������WW#�a����c�����f�6�:�'.�\�T�O��!D�Y"��L�8�OX���e����6f���� ��O��dh�dx�k�,��ebcu����&-�����<�����=0��T����(����X�����+6�������.��{$vD0a^�
�/�C��"���V����������km��������j�,��,���{���e��h�f��H(W0U���HEu@�$X�v�5�@��A�8��{������V�L��_g��b��8?��O}����7�/%;�F�R��d||����#t����VZ�0��J�^������t\�S �u�L`��]Z<�bIR�����3^c�Z�?Y�j���v�4�/@i��tWP:9�
S����)�@��l*���x��x���}��u�^�4&Jl&�r~�8j�pJoM���Q,����P`�������3�X-�����&iL���	�u�L�c�vJ%i~��.��'�cAp�-����te9���1fi4����l��!��t7���+�;�Mq��E8���,����;*LT�27$&�O�A���2Q�c�5A3_�(�
��>����mp�R�����%�}tS���xF�����v��0L������"5��W//C�;������1[C���8g��{�l��g�@={��_t{��/����*+!U���m�0Z�k��RU%i�((YJ�%��%�D?��n�n�[�Cg�����wk�������X����v�Q�q�pYb��t�(y������y������F���WOOZ��T����SS;K=#��h���Po?A��������v����k`�>�/����oYz����s1
#36David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#35)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 22, 2013 at 2:42 AM, David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, Dec 22, 2013 at 1:01 AM, Erik Rijkers <er@xs4all.nl> wrote:

On Sat, December 21, 2013 12:52, David Rowley wrote:

On Sun, Dec 22, 2013 at 12:49 AM, Erik Rijkers <er@xs4all.nl> wrote:

On Sat, December 21, 2013 12:38, David Rowley wrote:

[ inverse_transition_functions_v1.2.patch.gz ]
Please find attached an updated patch which should remove the

duplicate

OID

problem you saw.

That fixes it, thanks

There is 1 of 141 failed tests:

window ... FAILED

That's strange, it passes here.

Would you be able to send me the regression diff file?

attached...

Thanks

This was just down to some missing trailing white space on the expected
results. I've fixed these up and attached another patch.

I've attached an updated patch which is re-merged to current head.

I've also done a bit more work to the patch:
Added inverse transition functions for all the stddev() type aggregates for
all int types.
Added inverse transition function for SUM with bigint
Added pg_dump support.

I've yet to do SUM or AVG for numeric types due to some concern with having
more digits after the decimal point after inverse transitions take place.

Regards

David Rowley

Regards

Show quoted text

David Rowley

Attachments:

inverse_transition_functions_v1.5.patch.gzapplication/x-gzip; name=inverse_transition_functions_v1.5.patch.gzDownload
�<��Rinverse_transition_functions_v1.5.patch�[yo�D�;�K% m�6M/�����
��6�:58�����wg�k�.��^����vfv������Vk�!���9�	����#GDS��ob�ar�]�"��.'��I�}�qN.��9>>?=�k�Z����f��>���y�\4�O�D|�����7*4��7Jd�}�G�����o�xj/#<J���}nT �h����|�=9����AT��S��r3�U/Q�8��7G�m-i���?��
��B���L�V<m!�~�R(g�O��l�������G]��0|R�V�l�T�j���Z��)�e�VMbw5��BM�] Q���(i�����u����5����Ld������:U��e�d;�u��3��}!�����?���_���Oo�Qr(�R�O�c���@A�=�X����������z����+��J���}��`4���i4�*Z3�x7�����4r�&S�"$��)��W������r��������v����\klE����/�7���Az���������z���e0v��%�_�p{?���A�Qb�Q�d�d�������rw����������6Y\���'����a�vp5N�/�o�	#'���zp/>��F�}�|)9������Ht>���$���&�L<Zl��Q�����
�"�h�E�iCr�������q��PL���r|��nS�G��k���A�����]�
���o��9*�/�f�Q>��T�HF<� U:�q�#[�Q���Wj����
q{���S_�T+W�D�#���bD�	)���������7G��=��J��4pE�f�������
34����Y�U2�%����c�|��4�7�YjP���W�p������);��gv!>�#����Pc&MB��8��4���������4�#�mXZ���s��V��I|���X�9�.Y2�
eb������m�Ho����o���p��?\e�M'/�����j���='JCS?<xsT}���}��7�.��6�����y��$j��6�o�����|u�����9�+E���B��SXB[���:sq��F�� p�#'
x�vS4Q���=,<�X�0�j(�l�J7�M�HFL�Pw��������_��
�[�����B���o�7ev��{��r(��\����plv��~��CzI�gob&�0
y��L���k�<4�%#k;�Zk����W�'��L,`5_V�6	�����i�[���B�V����/x���<c/���By��v[)uqy���h��-q=2�I��	�I��{��6�OSQ��H���=�*���"�jC�j�E�.C�[��Nu7�Chr7�j�+������&���BZn��"Xb�b>V�%���D{#T5,�a��)���v,<�a��S�eJ[�.�������f2t�7B��&����	dp)��0�MT�S �s���%|�h�)��$�c�0��f~�4B�uyKG3e��Da�6���F�8V2)��y^Ns���H�XF�R�����n��g�yL����Q��"N��5��:P�������\��I�
7U�=vi�O|�g�KZ�L� =O9`Hl��T���b2mR(P*�+|h��4t��J�(��b������~�����N~��L�}�I
��!�4��x6���[�]��3@��������AL��)V�S0���0�Zvu��On�<����b0x
�21���GK,�3f�d-W�E���`>����w��o��=�5F���#{Z�J;��8|��*S���}�Rr�v%�P��H3g�=?�����#&;e�����R�_��gNW~tzQ�=�a8
�@H��Z��V�W���;���V�����S���>L=�k�I��l��w):J�w��t4���+��3d�NU��K����)�&����G�FL�����NG4�?jv����Z�tV��
��i��5��'@�x6��� ��d2�SdVNp�a��3������vxT����Kj��^^�B�<;:
+����V����M�(��������<��x��YC�@�V��D8�T%]
I��-���������wj�wO���s�^4�._g��:Djro}pu{=C'��vx=��F�����zyC�x�/��>���>��1�s:��o�euF-.�2t�(0���bi|���;N
Q��Z��
g	������E���1��� �+*t3�+� �����p�6xfXZ����~W�V|J���o_�
���^Jj�0�W�yTu��(
I��.l�����4�_����"�L��X�Wp<|{������lP�����K�@�:�U����n3�(%��@�nr���2S���x>|���d������N��3��j\	���4�(5����"p-���P;{[�6����M1�Wt=��lg�@���",�*�Z���1�E��ht���O+���dP��!��#���X����?�����������������;uU�����3�O�P
��i�;��k��f`�q�x����'S�I1�?(�8���t�w��W������Br�~�%���� X�}�d<J�@
�wx��~���L�t'���Hx`���{�<�o����@�&��Lx ��F��^�p������bt�
<��ut���0����g) �U�)��M��{�X6�
v���!�����o��R��P[��V��7���\.����V-�`�~��Q��4����~�K�>��J��~���]�����`aa�0�5���`����e9�!��
H����V�<�2�����=��6(K��+>�
P<��k��0<���0x��F4��N�-��(��^a>���3CG����yH'C6=F6��}�b�@4�
���Z$/��)�w�����������w�����ZD�g���lK��YL-e^����'1}�D0��U"��EKZ���-,mRO�}]� Q���J�&h������Y�����O�~B�h��U����P��'����|��L",�Xa�,2��+�h|'0�{����z�#@{++0��f���nr���p6g�j�'l'+:
���c���m�{)���Q�	�{
;��
�~SV�V�U�u&|���,������I�S���NpO}��t�������j�!�����5t�3[F
�k-���Ho�0���p��$U:�
�3�m�*	?A���
����oa�t�A�����UW�-��*��7������
0b-����i:�&q��#�����?��>Av���v^�Sc���&c�U��(��\�1WD��~3��|'nh\v��}����G������
<;U�xy���m)�\QZ,�,�SU���j�!i��!mZSuC��E>�b��k�!dE�/�}W��A\�>d����}F.��E�>V'��c���\�����J���zG�s~���]
�p�h��O���<P�Pi�����r���=�q��d���y��%NJ�7T���E6��	�9����A�x@M���l��G�����(	�4,c��B��Gg� ��������(�y����3��s� ��Y�X#-wX�/�.�+�yT�5���+[|��i������O*�@������^�X|��fZ={Y�����[��o\v��Q�����w������C,���c����r|���mLN�r�X&(�X�C�7�X9��K4����,t��A���96��uB����������S�
R�gX��b���\u������;���k.����)�a�U��5[+=#�c�+�%e]����u/�~�9�����n����!z0(��g�J}BI�����ZG�@��/��P��+�Ok5p�0
z������;����aT
y9��s��k_g�O�U��Z�B��4IThJs��	8H����0��l\��Dy�P��YD=;�i���f_g��=O`��s�w5�W/dQ���Z��P�WA����W-g�����^�����0��F
�27�2xI}7��i��MU7`���mfV�jGld���]�C��e���������he��u��{��}>��Xi��������Uu��U��9U�n��M7�U�������2�{��sq}�
����c�n�T�yT	���P�q���3��G������S���oU������d��nT���n��H��,J�W�����o��QT�7�x�GK��On�w���'�G��rB�\3_d�|%�*��WU�����+=�	h%	���?��C'QH���,<CM���.c������7���_����=������'�g�l4�F��'|!z��0��
Z���K�[�y+QU;T��DN�Y������i��aaW��b/Q������9�k����h1��*WH~L%�2������6����u�ES<�w#l�|��f������m����_��C��JB��H4DjU �TU���!�8w�[�
��3��f��l� ��T��������p������c7"eB�l��0�
��LZ������OV6z������R�3 �+�Q�|TJSr��t��8n�1�}��b��
��J���VdL��	�G�R���BLK��q���@W��K��<���FF�����.���.���_�Z�&�� �����x�{�3b�������X����a��c;�8�'B��+�D�y}��7�Dux�(������5X;��xS�o?���(������	�!�������E������x,���}j^Gu������q�H1U5���#�
��7�`���3������lp��M�.��_z� ����Iv�LDC
H�d?��&f �-X5��k��9a����;���$��4�m��4$	����ZR^��.8�9�2��n;��. ��l0d]Z[v��NX�5U=�^AYmL��X��b�"����@�a��� A�5#hG���Kh6���]�|����V��#�T�������ji�~��9;7��u:��m��MKM�NT=e�/�T	��������>�S]�����s��NA�P��B�3)��n���V~SZVHa��d������S�<�N��3��8���<psw��K�(�:%���kCzW��=~������,%����-;=j�
r����Smf��Qil8�6�`�R@�Zp��h������E��L�i��
 ���HP��f�]Z����`'���I�H6_ ��"���3]`�����Zy����y������#�����OW�Rc�j�xS���n|���hJ#��
��.�
������ �1���k���6�j��}>G��r���q~(�2Lw�d�i�(���\=���Vt�3�P4h��[�T��L�=6-zD��U������u�l����������+��[:�1��i!Hc�$a4��}�U���Y6?�`[��d�pQ0�W����@�r�^^�9$���E�eL�W�C�n�Yml�������k��8��
W����
�>���s�7-��".�3"-����'jI�Bj�*����sLl���j� J��:l�F�`yLA�m�#�� �!
w��L�R�7D�~�I2�O�0���@���j&���A�<�hWQ_%����"�i��IjZH����@�4���uv�$�t������l����e��8����p�,��0�hd�����5n��t)�
LcL�����]C(,34c�~�T�7R�q��9������p4zQ��?\�$�bHa��B
K��n��vj�K�k ���R��1���W�����b_��s��e"?�H���)���#�3�TB����-:Y�����������(���I��
����t�e�dU����	��!�����j4{@*,^����=���Y���p��~�a�

-����h�&����T� 8��Do�����������^��8�n���H�&:�t��tn\���8[G���q�Ex��]���%��J�����~T�?"xyJw+ R�����x#4bC�u.���XXO/�N`��������:s�P2�.�6��h�]�v���oB'�!\�������bQ
�<���������7#$�1'�O�2�lEb��P���w���D(�-����~s�\q���J�������-\������ZK�i���os��������o� "d}�Y�/$q�1�9��o���p��)n����`_�	m��v����f�����A.B���y�����2��zoC��.��KJby��?����������w3}�A]�xt-h��t���*�TKXY�[��������3B:
���|�9�(�A
����n}��Q�f�a3`�9h���R7k��j�hR�&2����e�k.�������!O#����%�Y�P?�(�"m4���-�Pc��u�G�Ky��e��R}��S���� Vd�O'��W�:�������z��fac*�t����
��9�����A�'a!4����1�{ ����������2������_�������r��u�����y�NZ�3�1�~F� ����40��ye^���h��K���������&z����XI���������y�K��="���.B
a�l��6�! k�L4n���1�xM4p=�	#{�&Ih|F���]
���p����v���xQy1���P�����,m���tH�{���")����P���`Jq~�AE�&o���k/1���#�_����y���C�}T�����e���0��(*q 7���Ao@��=�	�������*)�O}H���*������<���t^H�wI���[��r��l�q���\n�&l�z���g�>{���C��p�r�6��J�U����� [3��wmi��*�&�8Fx�E[��r7%����1�k��w���]
�x�>����C���<b y�X2�~�W!0���������A8P+�G�r�,`�b��B����O��t��P"�VG�j����n�p
��Q���"Z�N�0ZH����u��������~����z<�C��L�b!��k����<d�{�C�4�R���U�K���.��*�2T�����<pw�W���'�=�Af
Ms_F���iSAP����W�����?{v��Z�K/H}�~}����  ��S[��9��St�^�
�'���^�8��:~�qT
�:����s�Z^�E ��@�q+W1�R���hk�vz)�:�;��6�������
�:
hB���YS z7� X���B���-l
 q>�h��Hh���%��u����/��_Te|"0j�3W��������8��+>@������Y�����W��=s�,��pZ��P����^�����d���8�����W���zXl�����S���=�`I0f�D��,��Ifq�ux:}�5R�x��WRX����Z��{\��i�.���l��\���e���QY���s��s���E��[��9o�i#@������#���X�`�h\���u&o��v����������-�Y�U���2=�s�	AiB��.��RzP8]8�\W��RYZcC)�o�����~�d��X�D��������Q���������-!cU/��aqE����:�R��2��N�����"��(�q���@�k8�
Q����'�p�t���F�����K�|o����������~?�~���Y^�����*������T\���qa����i������nv����"���������;Q��y\�J�i�f�W�������/"�s���Y�?�u@D����w�G������������!������I���K8�0r/�O��G�9���;W���]�q�����\y~��?��-}��J.�����*i�������R���<�'vN2���a8�?��������E*e�xU;����-�5���!k���)�e�O��:�P�����`�pO:R��CG�Z�(Xj����$_�4��#wd{��6	��)�<�`�}=��z��zB�qGw��f����(Au�����8�vi��������Q,J�R�:te��$��E?�
n��0	���/BP�j�!��d�W�s����Y�9[p�x0�}��=2<��������HK��
���g���]L�Cl
'Orn����u�p�$!��F���X���|�"P������N���$2pL���DGo�OO�xy2=2�7<�����?&����#�k�i�����1�������Bp�H���@[����:W�S�����C\���q�y����x�_��k�������h����-����e�Jw���o}�Q�u��*�F��>~8f@�����V�7E����v��}T�U�]]G��&`H���F+@P?���
����_�pessS
	��zS���&�]�O����7�~�(^��O���=���j�
&=�dYw�Ek-X��G�Y$�
20*��bPD�`||>����Pq�FG)�\����x��};d/t8g�L��t��9�3}��4����V�8���Yj
��k�Y���=���ms�e�9q�����}K�p������a���\�&<�4T��z;��,���t�������nD8L'�`����n�mU������/��+�C��0�D�[��L��&�|r&��Gf!��fP��������������y�����������Tm��3H
���T�@�����z����g�/��T��[��dr�D����K����=���\kU����
�aj�I���F8��&O�����$� cJ%y��&���$�q��,��m�:I'�-���l�����-���������w��W�U�YoO\���)��G:���F�8J
�0v�k��\4�����l�J�v,�!3f��h�q��E����Fp�U,��j�F��x*Q*I<,���OwN�T�u�l��1h��ZEf�n��EL��a	5K]b��x�&��e�SOb���a��vf�1���f���R��/4�.�x��=Z�x���[� mT���k�w��#�,f{����0�~�������3bP��s���	:��cc�t8���"�+��d2��yN�JO�%����Eh��v����WF�+LI=�	i�'L=�L>#���������8���@�K^�X=�]����c������UQ���w���G7�`�n@��bN��X��y��������!Q
�����������HF��V�Pp13g������+m���K�����u�0':���M>5~zPk��s����4c����3�9V7��5��t��WD�O�?9�6Y]3!

V�0����,�����SJ!��5}����r���=C���Cn���\*���Q�
dW�xJ���X&���"�^�Q���vhD�X���-����U����������u� ��O�^��a��.rd�K!�����AZ&��������Hg���*;�`Ze(���*&�mk�&>&��O��~�%���?jr	2&�n4��o���
��3��P*�&R$�y��G
t��[\��
���aI�2~�j�^�nD:����|�X���3m�n�O���Z��=}��,��`yu6`�������m�z�(%����(Mhy����v��*G���h
����T��HT�x����"�5�h��+GK�����kD�u�L:D�/50��B��U�"�Q�'4'M7��6�q�`I^:o��,SEgk!��|l*<��:0���>��P�c�v��^{�	��h����j/,'�y�����:��;f�����S���km���V)1~�
����E�c�A���^;}if#�m*�9>��.��g��G��W9��.��8�P��|:���wa�yy�Yq4U�X=#Y)�@L�����EZ��xP�W�^���G�E�{t���Xf���Tb���C�Z�
��+�"w��s`K'	�[�X�$z�����d�K	[J��o�P<���b�����m��k�>��ok�����^��9�0A��J�\~��;�K#��-��5��~���U�4'�������TQ�Ag~�f���x�;����{>�D���m��w*W��Tb����m�fF���g^���4[K�M��6rE*��<�PL�z4��y�l\nG�w��^�k�~x������5���y$�����iIV�	�X�����_kI|fm�@����eei��4Q��DO�����1�I��e�=h�~�<�B���#i0�RYh-*�,</g��</-7.7�����Y�������bk@����Sl
P�MvM5���k�z�#���A�>mQ�(��p�3�Q�Q��R������n���X.lF�*y�R�,�����qV^}��F�����[��b
:y�;;��Z�@�K��9b�r]�}��t���P*�����}&�w1��_�	���}�SY@��g��S��dV,=�a+�����$f�)-h�|�$,������^l:b�~���}zR��v���zw�zw���r�������x�0B�y+?��P������`��h�J�@�����"����.
��p}3-I&x�t��L$������7��6�������~����0���jU���}���Sfps�b�����9����������~������6���h5�vB����QX�f����F�S�t������J3��a��@���&��������n?��������*h|�s8���L���g�
��	n�h������w�pu�M�:����7�j����}\�'pU����zI�)��ny�"�|�zRYF�O�������O��4�OO�P>1(P?��.�����{-i��R +]������8_�jx���TdJze}!�L�f�`���?��4I��s�w��Bv��������k�2O�yw�Z���4S�r�;�*G����B�*C�������h�����
�E����������#N�{s���RG������O���e%�sLr?������G���=�7���X\>'����O�cb�,�B��Ua��@�[t5\E�mC�+��O"v|��6�5�$�c����(��8�5�);�A\cI�u�v{��1�c�b�D2�]�H�RC���0����Q(�F'���BX��:X�Q����F� ���U.UC�m��"��f����o�i����o����J`\C����,��+�7��b2�����Y�.���jf�����0zS_-�|�X�P��U�
�*gsy �t���;I������k���������	����hu~V|�"��O���M������o��������cRG�c��i!���)-Y���x~^^k�5�w���������-&���$^�3K7����1&������������e;�X���R�1�`��W�(;���6�|���X
#-����I�X>���/�o�I�osl��b�1#u�0b�n���0���H��h����T�lw��(8��n�{�����G�y	Z~"��`x{x��g�Di4O�V3i�b���#���s�h�O�<L�C���BM�s�(\��lq��{EJD���cM�������0�}	z�_�|��h�Qna�\z���8~�"*BM�q�,HH�xI\��*u���[|w��$vBv<����6�b��/�q�}����|.�,$N��?~;$�n�.��?�������t�� \��U1!	9��Q������<������O3z�}�M���K���G;N�����!�
0�St����:*ct�LC��	������P2��iO
����>\4�]�J���q�����a]�|�������zp���;�y����� |Tz�h15�������p����'y�%z�2aG�?�����}��pa{@E�.�I{�	������������]?�L>�l}�'�?������|%h�Wo���[���xk����IE!�����!|\K�x��S,���
���sGy�����=���a��d(0��������05�t����`�	����������c/N���q�Yw��:�#�fW�CR�?�����K7/�^b��O�u����<����;w�|��~��6v�����?�s�hQ�s���&����=��7d���c3�5�������IE���6$�`��i��j����E�$M��/_c��kE��R R�@�{{d�w�@�{��{������n��{�c�����k����	C��K��l>��j�E�����������)FU�f��j����*]�������U��������FJ5�-��n,,
Uh�VE�����C}Q����jP2�	�6�����:R����AE����J��B�Ji�L���84���#>"��#���	:m���:�K�E���KU�#�r��:�9��� �����%���(���J�$C�:C�
"�F�t(�$�HM	�FT�X��W��l� �~�� �!��jC�m��@c�_Y�U���YV*����h�T����71��������/�2���{��nvL��)k������C��q�X�d,��2�u/$���d�(aB$���$��{�9�"$�]�g��a��P"�l^~_J��qu����sd; ����fb��
����#ff��<����Qebm+om���(�H����0I�(�&�2�j����t��"��R������V�(Jq�g.�Y����v?	Rh��hmJ��mr+UDk��q>>���]1)
7��v���Wi�*�4�I����k#9eh�IS��!�Z�O��Z���:�5_��%T�I�<�T@�	�/�
���S�f�`��DX�U��Y^�V!�?u�G�a:�uS��e> a"�E�`�%#H���pd�2>��"YJ���<(AJ�� b%�K��%&H��C����	g�&N���Kp�;Y|��,AAFP� �(�Qp�E)�P��4�����������A����z��#��+'�7�z���������:��j%)8�&j�0�T�*��F8�*�N!��g�E5g���
#GI�Y�G�U�$5�QDgM�%E��F���@iQ�i�����O-);M=	�){|jI�i���P��ZRw�&�sW�H�NccpF� lQ�Yl�C�iQ��D��K4���li �)�[V���`�#-[����2D��0	"Lr�	)YX��,/A�K� B&OM�Q�'������d�	"|�9A9����P��8
2�r(9J9����,PA
T�� a*�U�a�#+���l��J��d���^O
�=P�����c�b�Hv�AW����@���w��4�!��O��U�(8�������Z����j��B��)��$�MvZ�6Sj2_z�B�y���\��&)�|������Pf�M�)t�^��|O��>�|���,&�5���L8OlM�&s�BU��d��F)q&Z�D����\V��0��������Jk&�3�k&�3�
��A����V����'���*�k��c������������������c��@e�L
�p&�Tj�s��l
���Zu�R��i>x'�S x=������C��������U�6��;��NnB4�����|1�/�c5q��F��W[1��I�W#��X\��0	��n���WwL����P��\�R?VM'�����4��d)O���b#_�D����� 9y�V6v{��K#@~�'��S*_s��7��B�$ �E��2�oW���
��7��M4�W�-�E��`��b�u�}2d�N�&�!�M��w��L9�5�c������CId8��0�����hj29��<�.` q�`
2`�]�0��Gh�����6��0������fi���*Dv�v�n�<!��r�2�:�Dm� �Z�vI��X/U3�7�k�??�;Z����������~6�5��\#��Kn�<�@��s��L��~�y^��7�:���n�Q�E����e���np�t�"��
�
4�����.	���1���l�k���VF68�1�N�@@P�<$�� B����$�~��qv�g�������v2��N����t>x���2����i�!/^D:�!��A���(�H$2eEY.����8n�Vs����x�`�~j��=�������M��S-z��IG_���&I���r����������zzM����h���1�E���L���<aW�3i�3Y�}����	s����,�����A�B����<�\���!t:�V���m�x���wS7g��������OY�!�uj�����wKqJ�UD�L#*�e�&�x�o��k��A6�n��*�%��/��0�y�M'���~�
����](<���Hx���������aa��/�-�9s�)����>0��xW�U�=9%G8��������1AO��'�h���hv_R	�Y���&�K#���V��.!w?��IiRB�wSz����������H.��������~������	�c���J�k��4|�����i�� BM�'	�A��2&�aC�NH�1�i���4)����4x\h�k��q��!x�nB�}B��A��	"a����9E��>��?�d�s����fE��>q�����)�S��n�e�_��TY�����@��������������8�C�9�[�NJ5�(_g��;X���3u{���t�Q���p8�G�����t�eY$����� d~��+���~;9?��U}��MR��>��'l��UYX�v18`�A�������1)
>]�
���Pr�-�.���OE�k�S�>��%�~4��J�����9V�W������������pj��&\q�M�nh��S�+_���
���c�cE�@���fy���<B��~��_����GI��j��{v#T<+��M�c�l	]�#����7���=9K���1>�x�5�ma9���T�Fh�
�k�T���In��s�C�-s��Hv�WRc�1�(Iv���CI6w��-�CH��h��9f	�S2_���%�H��/(�zBR4����l��j\�b���R9��r:�^H��fJ2i=�yN���8�a)�4 E��K���J��k���%���2 >5:c��������O��(����_��W�*�D�[&�gsE����
��<Q�Mu��7C�_f~�fN�DZ��a$������9�8���)��v�7��1*���.�=w�\�n3o��ZEyHy��{��<���+\�'?I��7nW$6_D,r���Z~[4���iQ)%�"p��?��T�
s���t�QD����r��lu���kB�1��c�z��w�fo3�.@�L���S�5����cZ�>(��1^-���s�v�������5E�����'e���=sO���}w���l�u���UK�61"@���lU�d�g4���^���`%�p�������h$�4H��hJrYc�3.R!��W"Sqyy���;���VG,�)t�'���?����k8����n��1�%��Z��(��)l0�p�a;}�L���^�GX�����'l��7��5���~|��z�5g�B�a/�q�� ��J3�h��-�3������	���^��f���v�G�
&Y���U��U��Beg�mXm���%N�%�d�7��U��=/��WE��J,�)
kv�������`"C�fs	'��Pa������C4�]��d4���;�=/i�I}�����E�*���Z���g��>i��[d�iY�����k�0,W���^�U��S��[Vv�d?H�w��7���Y��x�����*�E�
�(�N�w2���[��*T�/;
LY%U����1�������jz�����^�������#H���je�8�lB�gGN�����hu��=��->�ew����*���u��"n~��m��m���D��o�>T��x��������5�~�RG�/�������`����h��;�I�XDR���4���N[j�Vi`x_�7�
.%�����&R��P�����1M���l5��W���^wqy���
���
���&�#���x8�m'�L����}�8�9\����VI���xV�5|�����^�K8m����X����}t7���l�>�G[�O���[�����X�v8��N@��B�~GR�>w�G�q���PZ=�h�L�>t{���Z3Tu�����^�W'�?�ZQA���Y�����'���v�Vr�|U��a��g�qd�I4����G�1�D'2��*�����'����z���,����q9�p���[v
���}�������b����0�\�0O���������Q�
O������B'��g��<��N�.o�<:L���7������\F�oO������OL��B[.�e1R��xHO,���^�[��iv^���,�\��>�t]��.�O��3v}����
;s�;sdgg'���O��|������	�����h�M3)��d�y��S9�������D��
&�����5���v����!�V�9|�O���^^������F�zj|,��u0�5�oj��V	W[mY�yZB���,�Jx�������U���NW�:Ba>��h���������z���@��N�a��[G����Q��SQ�l�l&_��b>G�|1�:�4���tS�d8���o�������C�X�i�����D�sx�r���"_�T�����a����+~@ v��H,^z�]���,��Jp#�{W��6E��+�0�n���q�f��16&HQ?����PX# am`B����vR�K[��a�I��9��5�9d,��e?9bV�F��s����oRlW�(W0�,@���k�^�����,��{A|m�$�������le(wy��S����w�>�:��=}����v�>�lM�P�/��P�O�B��.�3�
��� 8�1{��1������#6,H��'�W#�%x5r������9I�c23$����?���%uq;�>�o�\�y�V.��|0Yx���:��O~�����k�����`/���M�Iv[�����������Rq��wj�N_�k����U��.w^���o0�����K�E�?�(2D2��mN8��3��L\���5�^dmC��)�ox�7o��e�t�"��%}�]	���"���	��(Xw[�J[��-&�Kq(�S�9���;#W�����!��<����Va�B���H���#�I\SC��.yx��4�X^	�K�2��,
jf�h0�T,"J3��eXY�z��)�
�b�g [/d�q�G}t�:]�'W:���Og����}��\`�v��A*�9s�ba)6,T�#��G��_k���Z9�,���B�_/�8l��'\�4V��-�-��n��e��y�#�9-� ���]�z�S�,YO���0\�P�O6U\��1{�R�X���q�!t�������e���>�5�S�NiR]j��Hmp�N�'��,�op�,K*YD���_}F����������F�x�huI�0�LFu��
���6��7mW�J�����"�c��
�aVZK]�ul��-jyi���s
3�C�Sre. R��~UY�0�+G����\��7\tt{������Z��5�����cim1�BF���hP����h0�d���j��4����R���h�����a�1j��1/%�~�P�M~��s0%C�wF|
>�*��+q|����Ws�����6���o	@����	�����(��7����H�������0�/�P�(��_���0�������=���{�eh7(�
���������y�@���3�A��I<����4����"��>����0�G���)\���'��$��":G<I��� 9�ca�T�I���`@1J��c&i}��U�����>e2+�-���[���8�,HM(e�M��15'Pd�:�Q3'v���H����������W�wH���.�f$�$���f*�i4X�0����[�}�����|{)�j{�p����P'��N	#��A_��~�FC��z|OO�|Z�3i��������QT";Ic��Iw����sOf�8���wd"���?w��O���upC->�����	����������������%68��o�:
]�2�8R����EG��Q�����;Hw�3s�h�������D���.��:��v����[���v^/����t����k��������V{m��:�
h+��]�W	�[�d�������\[��jG{=�sT�h��@��Qd���x^�����p��
#37Erik Rijkers
er@xs4all.nl
In reply to: David Rowley (#36)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Wed, December 25, 2013 14:49, David Rowley wrote:

[ inverse_transition_functions_v1.5.patch.gz ]

I ran into the following problem which is, I think, NOT a problem with your patch but with my setup. Still, if anyone can
enlighten me on its cause I'd be thankful (it shows up every now and then for me).

I have a 200 GB dev database running under 9.4devel that I thought I could now, for test purposes, compile a patched
postgres binary for (i.e.: a HEAD + inverse_transition_functions_v1.5.patch.gz binary), so as to avoid an initdb and use
the existing 200 GB data.

I know the patch is valid, because a separately built, newly initdb-ed instance runs the below statement without fail.

But running from existing 200 gb dev database I get:

$ echo "\\set VERBOSITY verbose \\\\ SELECT SUM(n::integer) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND UNBOUNDED
FOLLOWING) FROM generate_series(1, 100) g(n)" | psql
ERROR: XX000: cache lookup failed for type 0
LOCATION: get_typlenbyval, lsyscache.c:1895
Time: 2.752 ms

Is there a way I can use the existing database and avoid both initdb and this error?

Thanks,

Erik Rijkers

See also:
[1]: /messages/by-id/E5F4C5A18CAB7A4DA23080DE9CE8158603E67AA1@eaubrmw001.eapac.ericsson.se
/messages/by-id/E5F4C5A18CAB7A4DA23080DE9CE8158603E67AA1@eaubrmw001.eapac.ericsson.se

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Tom Lane
tgl@sss.pgh.pa.us
In reply to: Erik Rijkers (#37)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

"Erik Rijkers" <er@xs4all.nl> writes:

I have a 200 GB dev database running under 9.4devel that I thought I could now, for test purposes, compile a patched
postgres binary for (i.e.: a HEAD + inverse_transition_functions_v1.5.patch.gz binary), so as to avoid an initdb and use
the existing 200 GB data.

That's not going to work for any patch that touches the initial contents
of the system catalogs.

Is there a way I can use the existing database and avoid both initdb and this error?

Use pg_upgrade to move the data into a newly initdb'd directory. Note
you will need both unpatched and patched executables for this.

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

#39David Rowley
dgrowleyml@gmail.com
In reply to: Josh Berkus (#6)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 15, 2013 at 2:27 PM, Josh Berkus <josh@agliodbs.com> wrote:

On 12/14/2013 05:00 PM, Tom Lane wrote:

This consideration also makes me question whether we should apply the
method for NUMERIC. Although in principle numeric addition/subtraction
is exact, such a sequence could leave us with a different dscale than
is returned by the existing code. I'm not sure if changing the number of
trailing zeroes is a big enough behavior change to draw complaints.

If we're going to disqualify NUMERIC too, we might as well bounce the
feature. Without a fast FLOAT or NUMERIC, you've lost most of the
target audience.

I think even the FLOAT case deserves some consideration. What's the
worst-case drift? In general, folks who do aggregate operations on
FLOATs aren't expecting an exact answer, or one which is consistent
beyond a certain number of significant digits.

And Dave is right: how many bug reports would we get about "NUMERIC is
fast, but FLOAT is slow"?

From what I can tell adding an inverse transition function to support AVG
for numeric does not affect the number of trailing zeros in the results, so
I've attached a patch which now has inverse transition functions to support
numeric types in AVG and all of the STDDEV* aggregates.

The function numeric_avg_accum_inv is the inverse transition function for
AVG. In the attached patch I've set this to be the inverse transition
function for SUM too. I know it was mentioned that having extra trailing
zeros here is a change of results and could be unwanted, but I've left it
in for now and I've included a failing regression test to demonstrate
exactly what has changed in the hope that it may seed a discussion on what
the best solution is.

Here's a quick example of what I'm talking about:

With this query I'll include a call to MAX to force the executor to not use
inverse transitions.
SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND
UNBOUNDED FOLLOWING),
MAX(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
FOLLOWING)
FROM (VALUES(1,1.00000000000001),(2,2),(3,3)) v(i,n);
sum | max
------------------+-----
6.00000000000001 | 3
5 | 3
3 | 3
(3 rows)

Notice lack of trailing zeros for the sum results in rows 2 and 3.

Here an inverse transition will be performed... Notice the extra trailing
zeros on rows 2 and 3.
SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND
UNBOUNDED FOLLOWING)
FROM (VALUES(1,1.00000000000001),(2,2),(3,3)) v(i,n);
sum
------------------
6.00000000000001
5.00000000000000
3.00000000000000
(3 rows)

My ideas so far on what to do about this:

1. Don't allow inverse transition for SUM with numeric, but
since numeric_avg_accum_inv is already written for AVG numeric support we
could include a note in the docs to tell users how to create a SUM
aggregate for numeric that supports inverse transitions.
2. Don't worry about the trailing zeros... After all SELECT AVG(n) FROM
(VALUES(2::numeric)) v(n); adds a whole bunch of trailing zeros. Only the
above 2 queries shows that this behaviour can be a bit weird.
3. Invent some way to remove trailing zeros, perhaps in numeric_out. Is
there a reason they are there? Note that this would affect numeric globally.

What I don't currently know is: Are there any rules about trailing zeros on
numeric? I see that AVG with a single numeric produces many trailing zeros
after the decimal point. Are these meant to be there or is it just a side
effect of dividing on numerics?

Please note that I'm not trying to push for any of the above points. I just
want to get the information I currently have out there as perhaps someone
can think of something better or show a good reason for or against any of
the 3 points.

Regards

David Rowley

--

Show quoted text

Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

Attachments:

inverse_transition_functions_v1.7.patch.gzapplication/x-gzip; name=inverse_transition_functions_v1.7.patch.gzDownload
#40David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#39)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Fri, Dec 27, 2013 at 1:36 AM, David Rowley <dgrowleyml@gmail.com> wrote:

From what I can tell adding an inverse transition function to support AVG
for numeric does not affect the number of trailing zeros in the results, so
I've attached a patch which now has inverse transition functions to support
numeric types in AVG and all of the STDDEV* aggregates.

here's a slightly updated patch in which I've fixed up an comment that had
become out-dated because of the patch. I also changed the wording in quite
a few of my comments and made some changes to the docs. The only other
change was an extra error check just in case window_gettupleslot was to
fail to get a tuple that should always be in the tuple store.

I'm now classing the patch is not WIP anymore. I think it's ready for the
commitfest.

Regards

David Rowley

Attachments:

inverse_transition_functions_v1.8.patch.gzapplication/x-gzip; name=inverse_transition_functions_v1.8.patch.gzDownload
��U�Rinverse_transition_functions_v1.8.patch�[�r�D��}�!U@�q�87W
�pIH�U�K#G�,��YC��|�-i,��(������t��5=^?��?3���;�)����������m�75j�|��y�������������<P�����������/�����?�X������}t����P����Y:��:�a&�
������E1� M��/��W���fi�eAh"�R��a�:#�H�]���l65��O���������IuD�+Eo&�?�.����@�K�b=1���P�]�i�e;	}<
��E��k�Z��I�9IU_Dj��
�XGk��6��/���Rx����K����94t��wNo������#}tV3�
���4v&stJ��s��W�������}v��O�Y3��g�(2����mOu
�A@��$9h�s�v��n�l�����\��V��u��_����KK�VGk��zd��������	�����]��yp6�u���H����f�[����U�(����r3��	��W�7���{���Kb�-=������n&i6L���,I�X�N�I�N.6�>��Z�����5�I��;�q�p�i�R��z_���K"����W*�#u{��W�����<�o>S��L������������:�B���IZ��{�o9��{� �����;7�K�by7V��8�<��Qvj�Q�o���X��V�6UJ�����.�b���_~x0���4��G�:��*KTa�k��`h�!��<�f�>�5V==���w���U�5�
yD	e���L%���
���o�o��������u �1�#_�f��6��8|m�M�dJ
x��i����K�zu��<iE
}��]�K�������NpM���aY���S��q>��U���6S�y��0R����K����$*�2���l&���&���62���������B�l�����Q�z�$���a���
��-��W���L#���)�/�$�����TM�6R�l���g1�n��W�7Lj�	�i��o��Sm�U1��cke�`��[H
�U��`i(I�pr���C���<a. h��#/�0k#�31�K��,����T��Jj�=�L7�V�q�A+qX+Q�v*���BP%�=����7���6��;Y�Q��~��^��7��<�S�IoR��X���"������5�,���b�~�"&��Z3�������W��0�q�Y_��j����j!\+'��3��zw�\�q�Fs~n��Tt�$8o85��tLl@�<�s|Dy�L�����	�Dg�/X��>CYP]��u��	����5V���Vh68Ik�S��g�V��V)��{c��T��������3a��w��&��ANo9{F�c�
��k�,��z��p 0���Y{�.����E34H����.]��ddR���;Us*5]��X�lSN��$af�L�ZzD��b�M&���)� !
����������fe�8<���en����c�	4�-C��D��T��S?��WLS���`L$'���pkj�L�H�Bas�%�k���$W4Y�
����y:M���X��(���B�
�u�2
�U�C�����c�D��ExR^R�/ED@���N������2Y��A��D<���������L��#�$y����=��E[l)�"�{4@"� |C*�U��A��(�n+�I��@�����"�:2����}2'2�1����aVK���s�{;	-��hio�D`�8��Y6��1	_��~
�H��U��<��K���$����(�+T�	���@�P�0�JE���F�;jC���x�V�$��'�'<L�%x'��9:|�}Z�3dd�����j�g4
d��	�/a�:-�m���������F���L
+��q%��)I_�]�Y��"6>�.��� ����e�]��ylmr�r�g�Dv$����h�#��s�f4�u���][����Y�Xa@��*�����.���T�O����e@����(Bdr*�t�4�&�d�����"p�����y��DJLg��{4����YC[�h]���T���}+i��dlVya!ZV���	���P��(�W���(���nv
�F��ca}1�wR�!�i2O�����0:�?�N��E�1�xD�J�o�y<n������/����r
��."����({��R����3a�zH�BL����@/T� ����@��n�4��U6�*��T�,�1Y���e�kl�k~�U�*�&�qq
e��R�Z}��o�{����&�2e�\���I����B�e.�l��<|�i�Nr�e��t<wK�����������9;���7:�����&08m���N���ts������G���N�%f��	Hm��e������	4y���6�9S�~�u��`6��Vq�,�{��v�����]�%�m�qt�}@hL�|v��t���{���3���Y>������7:��p^�?}�A8L�����74 ��	�G���Dr�����@�,����_T0������)"�g�P�9/��bf����)R �FImm=j{���Z[y���=iA��WQ�|R�������BS$�#����~?�����
��I�����u����yk�����f�wu{��b��e�zH
��7�������[NR�4���m��������;.�>��g�f�	=������HN�=�%�����%��N.{�7�R��e�!6r�zm���7�*���NmU�3L}2�A��F�|��vn�����6������#���K��V(�f��{m���^i����S����
�!8�i�6�N����v���.:~�����
�L*��nw�w�
$t7B�Pmhm��e����!A����-J��`�a�Aa
��~n����Z��W�(li.��������}�k�Ihceg<X�$��)��a9�������!
;�6S��5��Z���!�a��������;!����9'�"4>C�X�6��>�E�]���P
�J.��l�v$��A����_����>���x����C#�o��3�i��"�R$dd�x���\���82;5��*���/�N���$;;w��W����������L�aq�-�"��>7z:��<�����l������S��
��������[s�/K�#�;�1wW}�<����bkt��5�@��R�sdD��������"�g����|!��&e�<*-�U�-��%A��`�\h�������{C��������p���VO�v������������9�j��u��k�=��C�J�O��+L�t}_��7����K��DB��9��0����{kt9q�lds���#����4SU�G�o�#Y�Z��LF����=���'G����V������{(�&��C>��X���E���6S�4��w�����/7���{w������������_��Ff[�4^��U�� B>u~+�lg�p��c���
p0VM�}�4i�K�`iP�(���d��DM�W+E�DP���'�I�9��N�b�
��n�+���U�N���O�D~YB<9�Q��J&I���Z�J,n����*`���T����be&��"�j�[��������o]��qXU���������/��oq7B�b�5�,H��w�
�|eS�u�|L�+y��f�9T%�G�k���U���smK{Q�Od��1[[!��t]�n]
]��h�3��Co���s����!u���~���3��r~��Q#m
�s�7_��/�;r1�9n]�����Z��7�������z���6��G.h�"���G�i�-����K|����U���>{���W*.�����\�/�Ums�5n���)W����1�z���7#~A	�+��s�T��joQ"�g�.�%�8,VS���I���<�-�R�-(MMs�f(+r�*[WL�������.���P�.�X������FU���"g�8�r����sv����txr����!�A7s&���>E�z�Gycv��^����=p%����I�.S&����!����$�Y?J�&��n��<ol�]�E������`��KO�����mrXf�e�.������C����S*E����9������^�����c����^EN6��+[]��%P��+����S���kK��$ii��z~��<9	V����a��<���d��_���f� ?��YE�0g��w|�n��������m8 g!�C����l%l�p�J�S����(w&�4���d��?��W/��w���P[/U^s;�B�����u���7����EB*���*B!W�md ��/�'�KEV���<_�i���F���S����������#�*��6�O�;���E����9yxU�Of�*����'p�am�j����W��C�����yv�rj��y���y?o�jvF�7+/E�c�����8����f���N��*OS*e�>����$��o�YX�'������0w��mA���l��-	qN�
��'��E#��`�ULXY��k�y�~3�����,�h{�������{���K�Q���������G�w0{�@�!��J�sf:�[�e��<��w���"��
)���N������)��w�8�����]�
�b��b7Gws4h71n@����'���Iz7d����CZcT?!�K�fzT�TQ��f�Y�c���T����9;p�>�
�Hj�w��~��r�g�������}�5������_n!�v.�5^���>���S�9nh���S�e���?��!�~�V4���������������~r���9a���a��������CK���|��}�
��9+w�]c3�yz>A4����3����m��?.���j$���u��@J��a�3��r��-�,bj�)Z�,��K	�~�]����c�g�k�_��0��H-z�'���xM���k8:��Tux�Rz��fu\�U�y"����0����d�!�x5�����9�I��lFN���7��b�b��JB-3%�Z���������X�;G��������R����.�?V�x�x�a�#�g�K�����f�,[�� �B��j��F���Bx�&K��D��zm��Ci�X5r �[���=�1h���*A�4������;hN��)�3^��_��-�
���N<���g��y�����3�g���_�MN�:�V��"���m1���-1��xl�Q�?����0����U'O����h<W�=��H^\f��I�
�)*��\jc,d���!���^d�uk��R��������q����������E�������<�_c��X�y��82�����q_i-����E�x	�M�u��F�����)��C�M..�x�i����e����[��A�T�'�Rj�����p���tw���s`\D��;�d���SX�������Di��Q��*'5]H������R���.��?�'0�]:W�KQ��&#e����1!8b(E�$%x�p�N��6B��$A�<�s'9��%����j����v�_Q�sT��`�Q6��qjK�����t�&$[�e����?��W�lE�7"�U����_FK���gZ+E����q�Sa�l�a��5�,�J	�����
RX�*���0��%=���f��������c��V��%7A)5J����	���r�]t��C���+��	g���g�Lt��P���nQ�:C���c���
���3I�Z�RnV��l.�9����A)�0K�&j�\�����AH�����Y�aRK%	�����yKYW���2��X���8�D#id�C���X7�����xU��h7�s�[mJ��d��1���I&��$�G������&aj��]��~�]�0.��6�tt��K������*������I�$	���0�"����H��+�Ek���l����h��m/�_3E�����k��V�h��>�;���4S���������l~���%M�����H���l�H�n�7����~w5�J��#��37TP��~�`O�v4?���mp��
��i�f`}�t���'��j ��K��
C��[�Zs��_z��P������=?���^�]=������8���
|Q�4�q�bF�p������q��2����V��l8/��Add���1p��x�:��0���o�r��C	
�|K�.���z�����V�Y�z���l9���|�u���*g	g���
%#c����l{�&�wP�"Z���b���n���^;�BP��6]_n����HsL[3���)/0zS���R����K*a��Bf��n�Z��+�t}���0
�<���:y�5.����	?�9�
Q?��nGv[���xN�.]+m��$�h����"����k�����I��J��l�~�YK�����}!:���B�m�:b>,����3(r���GI�;���i$�L����<�7E�EL���0
8��lo��������Y-�<��c��/��������[=�e9-��
'��0��PfhY����F�,�WHU	�����t��h�0��/��!�D,"��S`���v@#74Oq;�V��������k��%�D���,����d��L[q���nl�X%2�����|����ddO�bbg�J����b�����'8a���m����NI��W����8�[�x�N�Snf��+��r%R�
?��87�m��A��!��c��  3�{(��uZ {W�0u��,�E��niP)>�(q���%��������u����-�<�^
}3WNz�R4�!{�0R�����Z[�EN�>�"0#��)K!��>��J�����N���.���ck��	MO���#,������K �Lz���L��Wbm�W����}�-��$��z�8���j?�����B#�K\���48&���q�R ���H�tX���69 /0\�ux�)a��^8|x��7��d��y-de���Q�3UVC~������P'��!�
��}l��Z
�@�\O���������ON����������C������a��'+�W�U���&g
3O�1`���$�L6|v���+�O_"1����(6�%���w�&DXW��`��f���T!��y��(������������`7����)�RWW;�]4i,T;8?�T�	v���hY[B�t�h��	��*g;���6�["?fp�X�#�!MQ�4��g�EN�
�J�kW�����������J���2�P�m�!�ee��k���5����/r����<-��6^A)�����S1'j�8Q��s>�������^���������y��(�*N��[�"i �Q�8��6K��~����t
C8����i:#	b��4 ,���st��p���U]��'@�;[g<L�-5���"��������~�����!�%b<��3��oJ��,&�$X������+a.�P%4,��u}��0(��������Y�M���_���L�a��#����\dw�
Y`��f�?5P���[��'�r���~�q�`�@F�8y4�)������6+3�b(v�4P�\�����t��-��a���A4l����m�To��<��YTw0�f>d(lO.]�z
�v���D��Q��&�QX;7U�7@~��/�A����K>�	�}3���v�M�;�w�a��^���e~�oR�y�4�#���k�	P�Qb�x��	��(�W���T��)���k�=^Lsn^���E��486p�T�vpab�N>L�x�(�:���c�����C�!~b/�~i@��������d[]K�(��e�M��s�t����.�d���'Y��u}�DW�7��V�i��x�lU{@�����A^�Q]�#B"E���p�(�2�s�7 X��oF^����7���l�_�?@j�{ �9*J������{|��N�gd7
�O��M��x����Vs.���5��L!��o�!H�������"n�@��s�(�m�h"�W�����g���F�O��������������u����3W�_v�b
�2Y
H�O��?��������l!q�P�b�P�D6s��h�KN� �C��em����N���u�{
(�=(����<+Q
����Ch�YZ����X��X�������'��=�������i���t��_Hq������j����4�hF|��'�t����y0�W>i�~t�9Ld�4v
��C���4a3W�x=�E�;;A���:������3(Z�bzX�����Mt?��?��\"A@�8��Y=�0]��oB6�c��BIuhl[�42w��D�i�������MV�W�J����9�h~���ZhU�Rt�r��c%A
0�sB�hW��V���G���K�.������/��Rk�~�����\n�8����}q�����W������;���7{W��4��S�P�4ii�-@�P����lS�`�p����gw�kZn�&�5����o��R�ENJ�^��zGW�&�swR[�G�=�� 
G��mXq�,;�k�~[���E�X�P�Z�G;��|��
+�,�T��b��a�����E>6��%#@TQ������Jj<�~`U�H}����e���Q�A�E:<t�M�:��_|��-�RLE�}��9*��S����`i�-+����1
F4��NKA�9���!�r�w2Bq��*f�����np2�!4�{e�:�hUi�W��,+L��4�b�q�a���Dt�U�AH��v<�?dAl���N������(
����F�	����J��]J>(gp8:�����ry��1L�ole���)z�9%��0�pJ�C�y���cL.���8�/C���1������s������������N3���
Y��z����=|t�����KO�%�)'�
zd�����"��FJ�����E�<��>"���0����
��-� �PO��������x4������h��?�W,JB��)!|-����- ���O�*<\�R U��W�Z������2�9D��>^�X�[�,z���7)�����(yA�����;��kwr�dte��UBY������=W����Q+n�
�[� h�~�"P�Q��*���^1���G��_2������Gn�L�L������������vs��m��H��x��s@���E��\����2>��[��]�Q;H����a.���8�y��FXH�e_�G��������-�W�\��rN��*����`�����M��G�xM��8S
Oj�x��b5����cr�<�C�q��^�d�V���2?!�@
��|�y �2��P}�T�p7B���\�>�����`�����k�g���b#/�9�/����g�J��?����+{�����6d
��<�����Dy�������qzm$�����e^��B
�}2���*t��|;����O>`�����^A8��(0���?����g�����+����9�D���y�H������	����;���G���>�������h���m}X_y��+�n��j�6���yk�V6��Gv����{����$
�;-���t�Y\��H,����}�boj�Z����9�����+UU�*=����y��h�9�*
�1��Vo�b;��k�=�,	�{��4ic���k��/��������p��:�(^?VQ�
���]���f�w����C����� ����u�$:/B���,���R-�C_P{X�8��r�����A��w%|LMX-'����b?�����;��l�U�K���9m��y��e���\�e	5K]��W
������/<K�^u�
K����3�5����j�sR�l�t�[���	������Qi�����m�=��r����N��w�]y4��>�
��%%4/�T�9���J����8:��AA�hm���i�	�iy���X�2��������|O�U� lB������Q�)��p'S��r��:��B�KI��z�����%�������[�p���{�(�w=xl�nJ2�yI�����y^"c�o�.I�i�&�?�nFO��|Ziv�1����������:�dC4����H�}L%+z������F��N��R�+�1Nt���~�@@�U��e3l���>#�X���H]�yk�P��T,�ru�o��a��C���F��
�;7�[���q;s������xr�pV���9I�}��U��pV�um�E�T�O��UiD���fg�b��}���n6~1��7�Y/�-/V��z���sI��<��Q�en��%F�5�Ozz�x��%��X�z��]wZ4�}M��\��O(���y��,����Q�N���lj�o�]�m^?��=���*z�piv��z�O�L[��n��jC�@N���y��9��N�Y�o����I�<s�]cz������s��A��y�uuL�Uo@��������6����C����������{z���MV��D�i6J��-�z�0�q>�K��h��kJ���n���	#Hv����3Q��h�WD�6�[f�������������T������#��R�	��g7�u���Ik���~�X���e�12��zv�`W{p�]��}��:���WH���MWW�k������t���P�[Y�$�$�4�s�/�":�E�����]VEV*Y'h��{,D�4�P�o9�vM�tC��y$y��VEaf�< �!�upV�!(KE&� IN�v�����c��>�< �Be���![�x����M�,��:���qA1�SYX�esB�2k�`�c5�������p��rVoV3���3���V����%�#a�Ym�My_^t���A'��zb�4���������.PU�p��Z��AV
=�s8��t��%�0?$
f>k!����x��3q���P�^�lLq|�VT3A��7^B�����������_����>���iU�I�!�8=�FW����R�Kp�v�=��B
���,�_�T���Y�
�U���xv$��~��c�"��UD�r��*�Y�p�W���]�&�����J�����v]����4��m�����V-�������w��{�")��|�To��*)����E���^����h�E}b�6\/_�z�vl���k���tI7���vA}|ue�N;9U�-�C"���U<J����%���l���xq^���SB�1^*������A�,���LM����c��.��\l�YX��/j���<[�w��P�/�������<+#��uP:.O���
6���"�"v�Y���Z�������f���
ec�7+��e�����b{x�E�������3���q����1u��7	
 ��V Ot���!�Q6t�8��Z"�������7o��������a��
����0��i�a������V���G/��a�:���|S��c��g��m�
�O����?{������X��?��o�|a�j{������+�"X*�-I��c�@�&�6�	B��$�*�v4�eB�w��%97
!��b_������;����r9�������1%���)z+^���g��:"���5nq��w���3Fx�4�BqhU���|���|I��"9�L�����19�
��%�4U���_��>�N�N��*=>>y������>���"8R�������3H?~98=���������������Z�B��Gc
a�	'�k���UW���d#�d�H�S[��Ctrvj�s�,�|(�����(s9$&kj�d���+7+���k$l���������j{#[m�[myb�y���V���?a���mu+-��j��(2'�]���i��{~����s<?l��*G���yx�����]^-���\�PtU$��=Pe�9L�+*�P�S,�A�nJ�J���7�r����o�	���*���k�y8�"|�������\�����IC����'/�j+��"w
���W�~���!�M��U�]�T��]�`����i���i
?��x~!�k5��u`�
�y��`|'�@l�������d�R���S���L�}�G��(����,#��l���br�=l
�PxQd�8Nw���`c����h_�I8���,:�	x)NQa��������N�,'m�IC����~�������L�M��0�5w��_'�,�]ag����C5�(�(����>���E���9����7���mM�<;��)�o!6+
6������(���-�w�M�I��+s�hE=(f��WW$/��{��������D���_/����lf��-������|a�.D��2���|N��������v�q�m�ge�e+��0<w�������)(�P��K8��!�.��It}���'���V`��E��Q<���_��>fgo�a�V�'$5%!��-��5/o����x�c��	�K���T���Mc��:��!����HV�
p0%�����b"iA�H�hN���=B���������Nq�������
B���$���4br��{��"P�=��8`���epp����4�
��%���p�|��L���=mV�5)o�&z$2���#��z3�6�������<)��/���6���B��.���M3����c:�>�J��;F.������R
N2���
O�l.���|H~��^�5rT/�9��� '�z#FXV����2
��g
y��bj�K�Mu���(�j�_	����6����,���8�`|������i(���E��e�H�B�XRFSHVe	=h�����"&Q]/	0���L�]���*�L���c�{>�aqW���� N����@Q$']�������f�v�P��e��Z��\�h
���D�F��^��!5K��2�G�j���nT��~���;�;�g�L.����jR�(YQ�;���� ��wZ^�5V����EB�<��E;�|�?�B��m6����������gb�7LV�i������f���%�#^��Ls����+9�y'&�n1�����:{	����/8���Rb�S�J���~���!QC{5ok��1oq���o���P��-�����
z��.DXJ���WF���UF-�dYy���9 �-!���v�	�-�[�4����������)&���B��&}��������=�V�BU���]�unx3�,��~�����t����\C|���it����S�CHn4�8-z�i����)���G�^�b�M�V��5WN���eZ�,����k�^O���Ha]��M9�
N�������/N��Z�/���
���"b������t�v;aC?�y^���6�
��������W���+d1+�[���>$l�U��	�o���n�0���E��w���Tm�C��]5=T}�����;�U/hW��~�@�����w��,�p�Y�;o�h�z"]��k��Eb�!8e��qi����?�
�rU�L���x0���6El�-Tn��������������V�k'�Si=��W	)J�n���~�u��B��:����}FdDLd���~<<����t��#�pZ��F`j��G��Y�?�>/��0QR8�v Z�� N�����%vll�{)����1�`��������Gg��O��Ej��"�Sy�v�3�S'���T��RX
}8C.gN�rX~9{g����n��	~�lg`���fu3��r{h4���~f�������c(QLi��Y��`3~a@�j���� �Ia?�0���G(���e���=���:���������_��J�]���7=��1��1���w�z��70����
\���v�/���M��2o�z��[MA\�s�����2	��h��g@&B,
�EHFa��(!��#D�p������B��R(Vb\
�KHLa���)446�l"p
���S(vB|
�OHP�
!*$D!G��(D�P(4������nz���V.����c��w��~�L���Qu�G�o����^���N�u��8�8}:��w����>�����9��(=G#�9���
�5L����"F���`B�G����V�V;S;��M�h>�tL�������1�3Deg����RN=���@O�����<�	��E\���~ �F���t���<-W�=1 !#�b$��P�D����B��RH^Bd
�LLM��	�)8!;�e'��P�������CE��(��pE(���B�UX�B�
�T�U����Z{h}��e
�������|0����#��.�t��rx�v��6��O��?�z�����������vm����CE�<�vk��?������0���{7R�8}�.Z�����*D�}3p&m�97N���u||vlc�!�<��I���U�F��+4�x�<�$K���- K��l���>?I����k� ��}���L��%Br�b��r�0�%a�JrB~{�����*CY��2hr���l���X�es\�N�$������^��>����|9�������_���q~U����}&8_O���W�I���2�@�7D )�7B()	v�F��J�A�0�������w������t}G8w�C������g�������f���}!V<���4zt^�V��*A���:.��p��	�N�����p��+�3+�
�"���/�"����|�a��[��/��2��c�7�%7��
A}��t��4&�O%C�;����`	������b�)������9�H2��y�&�#�4i ��k�p�PB����C�u}������_�<������������Ol��~��}{���`~S<�7e%~���P<' ��$���b?���/�7�;)�J3��UN�/[�?pQ�S|U�n��%2��4�j59'�~v����i��i^�W��$�>���j����9���n3�S�`Xs��A� �I��	]�pK�aJx���j�#�0
��l���st��6@1���&��iJ	�#�Y��
������vj��n����O��qE�&��e��+��]O(���P1�JYC_D�(��R1�J[(w����G
��\6�h����=l~]��
=����x8v2�-�I�d
:�;0���$0BR�/�k0��dy<W2��lZ����R���z2�eMy��+SX�V�����e�E�$���H���SI\��"��_����� 9a��Tx���%�S�I�����p���<x����6X����65~=\�����}��wJE�Kr�yb�����kW�'k�|��>����7�+�����|;7��kI
��?��_ ���"�#���������'�v��/�?��Ua7�6�cv}���3��0*=u��?�V#�su��;?����Q��.�c�.J�k��8�����b�����"�eM6
�E:@�<���P�����7_�^�������K��,��>;�P����m��G�4���u�y��Fk��2��2�%�a��-�
���h�q�qD�u��X���a����a��GQ�K����B"���%&)ld{���x�����6
��W������I��4A���x@�J�V���)����l��fi�#��$���������C��x�-;���},�Z���`Eo�}~m5�A��w�J�oQ,���t��ObdJd��Kg�&������_�Gk�j����F�k/�j��������>f����ii]���lI_�O�Q�w�a���7j^��0X��DEi�������z�����g��g�z#�b��GI��-!��,f����51\����:Z}���*�>��B %l�;�~S�����
g�}����2��B2�LZ�}^$�V�S�������i�k8�2NN��kS7����>3f����
z
����y�r�E��!����������p�	D$LTa �~����P�H�cM��F/l��������&�{%{:JxSkl�E�qC��{x=W��'=����<i�`���F�U�=�3*���&�V �L���o�"�f_#a(b�DUn%���H*3�C �j%���� ��o���H���^Y���G�Z�!���m��u�x���I��:;R��x��P�Q��$4n$�Q�h����u�E�ia-�M�d���$k��N(Y>.'�mZ���U�r��>�U���SNI���u���9Aa�!�&����E3��NU��m�k��vd �
�����"����?
p�HyK��(�o�TQ�(M>��99^���� ��@��)�Y���A����%2���4c`f�Jt�=q�22g��n�icM���t��X��s��u(�7m(5"-�/r�3����#���vnI$�6'�D�
"R5z��D��5	F��8 �m�JG�����h���f��\��>��@�������?�g�2���>����n3��mC�H�_�/�M��p�h�a�X>�0t�)������
���E��v��Y�eZ�P<5�/3p�l�K8�G#���*e�~��X&\<��X]��������#�����1?q��6`�,���~��<���s������>#/���n�J&���|7�x.�a�"}tKn$��f��Sb/`&E�Ng��b�Z�\��Z�W���'������}1�h��o~w�!�9�K@(�kf?p�����B.U�P��O��!��nVs.9�7�5_��?Q�Q�4��a��2d�E�E��Ts���p+��>��P��
��tX$�x��F�o{^R0�Zd;u@�x�H&���wr9~-g	�6�y��Q�d������"T����M:����mP�z���ua������y,��P�%n�L����t�����x�~9���U���FE!�+*��H�n���c@��pqy�^�|9|U��
�P�����T�QSLz������py2��IA�5H�� Z�Q��v�R����q���=����b9\�?���?����?�~?^\����)��>��P���i1cx������S�6�@A��
U1�D��`���)���F��@���6	�������9�0i�k����������������
�5��)������f���%P<3���Jh3�LM�o.�9
�?���!��t:m��yj�eOc1��n
��=
���<>�\����z��5��L!��X�_�:Xx�J�'���a#�#a�I�f���Y���fE�&�`%`c3+���Q��,��r����F��U�g&����&�n�{8�
L�0������Z��jx*��v3������0��O�����M���cZs���5x�O����"R�W6����<yH��u��F�����a2	}�]������
�b��l�\���,uv}iA���<����o����_~:����{0Y/����������� �l����m8w`��}�BQ���@��������Z�H�G����Ni.��*��T[��m]����}�U���
��/d">���D����;�wb�d����	o��@�Bc��	��o�IFg�U��4�b}�C(t9a�������)�p1xN���j�,�HJ��.���7����������v�i���SXh�R����C��a�4Z�� $$��)��h��I<<�vR7���g�Mr������c��ZI%M��[����C/J�����Vd-�[2��|���a� �-X�������l8������Im���3u��lZf���W����7��	�n�i��(����/��ZI���t{'G�	�W�������*�������<���A���'0����i�T�r(|�Y�1��C�<��V�h�uG�}��wL���d��Hb�'hz	z��tMf�3#�le�>gf�������i�X}_>I�r�DRVmfj��7�g�5��V��e��@C��~�Z��n�x�=����(�D�������%b�y��`�UANI�n���iz���@�����A7��e"��L���Q�l$e�AV�u=U�I`k��O_D/��;�Z
�
$`	���OK,A��8��Y��
j���A^5`@�������"�"f?����B��xV�	����V��.������X�V�\c8��lr�(�9�}�����"�����-.[#LN��E$�y3G���ik0��:��0�������lg�r��d����
IC��S�Or W����!oh��@���?>=M��r���j�������lz��p5�h8�x��x����GG����������7�q����������I���Z���u:��$M����e��LNv�F��W�������[D��rju(<�b��2���2(���
���V���!�5dt�c���q�6"w�b2����8Be��C�����C��i)~���������+c��t8Ra��f�!��M����	5�`��r�����9�f~���;Qx��	4!����8
�w6M����:c�z��\s�@W���Hu�Y�n��3u�;^b�~���I+����{2��j���a�=��������h���M���_�(]���w�h
�x}�� �u}����Q0xgg0XP�Lv��g}C��W����I��ao�(�#��kk�U%m��%�F�
�������!^�P��!��}y?x/E;�_,U���J�����
�MY��6Z��d��p���rSbw`��(�*�]��Ql�d��e)O6�cs����X�1�0��]@��u��6Q�s����z�}'*�fau�A�5]��c�Z<f�\�T�1L�.��
T!���
I���UDNm,�'���'.���]�\P��nQ�9���
��
D�l��y�}��b�JP3�[�1�m�qL��U��2-�5�����h�aB���`�j.��
 �|�o*��gS��1�����k��HZ�bk�c������YuSqC�l�50���<C���b�q4�4�	
�`�d��F�w���(.M6Kay�1P
I�9�p���AA���0���
@1��N/��R�~��o DO/�B�L@�H��w��������D}i��	�V�!
�zG^��C:wp�Z���\�KqL!������_~�:�Y�@I|�c�O�����+>�0��)?�����PX�Tr/�S�����r?!�&P�����.'�nKi��G��[eu�H+�CW�5�&�c2�J���r}#��qL+����9�S���_&�%eAYL�r�N*��R�=���f��A+G��X���j�,����nj
dI��'*�q�,$B��������JC�Ie�d�W+���u�:\��:�2�����o���;�E-j�z�\�NI�V2���4�e
� t?��Sr��f�������|*�/i������������	��J@%�9���esa�m9����M
_/�Q��8%�/�d�LE	��� -�UPsI<����S�Y��:��F����8x�������R�;1�4�����l�����U]��j
&�up��,����AX�4C�Q�����X�Y}%w��Rp����y�.��7-�c�Q�c`�0�lH��������)���a��rVC,�BWL�p�WU�B�M�W�
��|�K���b	E)
-�_<'�MxQ�������/�[P�q��B��D�oD�{B���-|�s3��+x�����n���
�	 �o�'rP��
��
�7j��
#41Erik Rijkers
er@xs4all.nl
In reply to: David Rowley (#40)
2 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, January 2, 2014 13:05, David Rowley wrote:

here's a slightly updated patch
[inverse_transition_functions_v1.8.patch.gz ]

patch applies, and compiles (although with new warnings).
But make check complains loudly: see attached.

warnings:

In file included from gram.y:13871:0:
scan.c: In function �:
scan.c:10167:23: warning: unused variable � [-Wunused-variable]
struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */
^
In file included from ../../../../src/include/utils/probes.h:6:0,
from ../../../../src/include/pg_trace.h:15,
from tuplesort.c:109:
tuplesort.c: In function �:
tuplesort.c:935:44: warning: comparison between pointer and integer [enabled by default]
TRACE_POSTGRESQL_SORT_DONE(state->tapeset != NULL, spaceUsed);
^
tuplesort.c:935:2: note: in expansion of macro �
TRACE_POSTGRESQL_SORT_DONE(state->tapeset != NULL, spaceUsed);

I didn't yet use the patch you sent after this patch.

Thanks,

Erik Rijkers

Attachments:

regression.outapplication/octet-stream; name=regression.outDownload
regression.diffsapplication/octet-stream; name=regression.diffsDownload
*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/window.out	2014-01-02 13:19:49.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/window.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 19,1532 ****
  ('develop', 8, 6000, '2006-10-01'),
  ('develop', 11, 5200, '2007-08-15');
  SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
!   depname  | empno | salary |  sum  
! -----------+-------+--------+-------
!  develop   |     7 |   4200 | 25100
!  develop   |     9 |   4500 | 25100
!  develop   |    11 |   5200 | 25100
!  develop   |    10 |   5200 | 25100
!  develop   |     8 |   6000 | 25100
!  personnel |     5 |   3500 |  7400
!  personnel |     2 |   3900 |  7400
!  sales     |     3 |   4800 | 14600
!  sales     |     4 |   4800 | 14600
!  sales     |     1 |   5000 | 14600
! (10 rows)
! 
! SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
!   depname  | empno | salary | rank 
! -----------+-------+--------+------
!  develop   |     7 |   4200 |    1
!  develop   |     9 |   4500 |    2
!  develop   |    11 |   5200 |    3
!  develop   |    10 |   5200 |    3
!  develop   |     8 |   6000 |    5
!  personnel |     5 |   3500 |    1
!  personnel |     2 |   3900 |    2
!  sales     |     3 |   4800 |    1
!  sales     |     4 |   4800 |    1
!  sales     |     1 |   5000 |    3
! (10 rows)
! 
! -- with GROUP BY
! SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
! GROUP BY four, ten ORDER BY four, ten;
!  four | ten | sum  |          avg           
! ------+-----+------+------------------------
!     0 |   0 |    0 | 0.00000000000000000000
!     0 |   2 |    0 |     2.0000000000000000
!     0 |   4 |    0 |     4.0000000000000000
!     0 |   6 |    0 |     6.0000000000000000
!     0 |   8 |    0 |     8.0000000000000000
!     1 |   1 | 2500 | 1.00000000000000000000
!     1 |   3 | 2500 |     3.0000000000000000
!     1 |   5 | 2500 |     5.0000000000000000
!     1 |   7 | 2500 |     7.0000000000000000
!     1 |   9 | 2500 |     9.0000000000000000
!     2 |   0 | 5000 | 0.00000000000000000000
!     2 |   2 | 5000 |     2.0000000000000000
!     2 |   4 | 5000 |     4.0000000000000000
!     2 |   6 | 5000 |     6.0000000000000000
!     2 |   8 | 5000 |     8.0000000000000000
!     3 |   1 | 7500 | 1.00000000000000000000
!     3 |   3 | 7500 |     3.0000000000000000
!     3 |   5 | 7500 |     5.0000000000000000
!     3 |   7 | 7500 |     7.0000000000000000
!     3 |   9 | 7500 |     9.0000000000000000
! (20 rows)
! 
! SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
!   depname  | empno | salary |  sum  
! -----------+-------+--------+-------
!  develop   |    11 |   5200 | 25100
!  develop   |     7 |   4200 | 25100
!  develop   |     9 |   4500 | 25100
!  develop   |     8 |   6000 | 25100
!  develop   |    10 |   5200 | 25100
!  personnel |     5 |   3500 |  7400
!  personnel |     2 |   3900 |  7400
!  sales     |     3 |   4800 | 14600
!  sales     |     1 |   5000 | 14600
!  sales     |     4 |   4800 | 14600
! (10 rows)
! 
! SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
!   depname  | empno | salary | rank 
! -----------+-------+--------+------
!  develop   |     7 |   4200 |    1
!  personnel |     5 |   3500 |    1
!  sales     |     3 |   4800 |    1
!  sales     |     4 |   4800 |    1
!  personnel |     2 |   3900 |    2
!  develop   |     9 |   4500 |    2
!  sales     |     1 |   5000 |    3
!  develop   |    11 |   5200 |    3
!  develop   |    10 |   5200 |    3
!  develop   |     8 |   6000 |    5
! (10 rows)
! 
! -- empty window specification
! SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
!  count 
! -------
!     10
!     10
!     10
!     10
!     10
!     10
!     10
!     10
!     10
!     10
! (10 rows)
! 
! SELECT COUNT(*) OVER w FROM tenk1 WHERE unique2 < 10 WINDOW w AS ();
!  count 
! -------
!     10
!     10
!     10
!     10
!     10
!     10
!     10
!     10
!     10
!     10
! (10 rows)
! 
! -- no window operation
! SELECT four FROM tenk1 WHERE FALSE WINDOW w AS (PARTITION BY ten);
!  four 
! ------
! (0 rows)
! 
! -- cumulative aggregate
! SELECT sum(four) OVER (PARTITION BY ten ORDER BY unique2) AS sum_1, ten, four FROM tenk1 WHERE unique2 < 10;
!  sum_1 | ten | four 
! -------+-----+------
!      0 |   0 |    0
!      0 |   0 |    0
!      2 |   0 |    2
!      3 |   1 |    3
!      4 |   1 |    1
!      5 |   1 |    1
!      3 |   3 |    3
!      0 |   4 |    0
!      1 |   7 |    1
!      1 |   9 |    1
! (10 rows)
! 
! SELECT row_number() OVER (ORDER BY unique2) FROM tenk1 WHERE unique2 < 10;
!  row_number 
! ------------
!           1
!           2
!           3
!           4
!           5
!           6
!           7
!           8
!           9
!          10
! (10 rows)
! 
! SELECT rank() OVER (PARTITION BY four ORDER BY ten) AS rank_1, ten, four FROM tenk1 WHERE unique2 < 10;
!  rank_1 | ten | four 
! --------+-----+------
!       1 |   0 |    0
!       1 |   0 |    0
!       3 |   4 |    0
!       1 |   1 |    1
!       1 |   1 |    1
!       3 |   7 |    1
!       4 |   9 |    1
!       1 |   0 |    2
!       1 |   1 |    3
!       2 |   3 |    3
! (10 rows)
! 
! SELECT dense_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!  dense_rank | ten | four 
! ------------+-----+------
!           1 |   0 |    0
!           1 |   0 |    0
!           2 |   4 |    0
!           1 |   1 |    1
!           1 |   1 |    1
!           2 |   7 |    1
!           3 |   9 |    1
!           1 |   0 |    2
!           1 |   1 |    3
!           2 |   3 |    3
! (10 rows)
! 
! SELECT percent_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!    percent_rank    | ten | four 
! -------------------+-----+------
!                  0 |   0 |    0
!                  0 |   0 |    0
!                  1 |   4 |    0
!                  0 |   1 |    1
!                  0 |   1 |    1
!  0.666666666666667 |   7 |    1
!                  1 |   9 |    1
!                  0 |   0 |    2
!                  0 |   1 |    3
!                  1 |   3 |    3
! (10 rows)
! 
! SELECT cume_dist() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!      cume_dist     | ten | four 
! -------------------+-----+------
!  0.666666666666667 |   0 |    0
!  0.666666666666667 |   0 |    0
!                  1 |   4 |    0
!                0.5 |   1 |    1
!                0.5 |   1 |    1
!               0.75 |   7 |    1
!                  1 |   9 |    1
!                  1 |   0 |    2
!                0.5 |   1 |    3
!                  1 |   3 |    3
! (10 rows)
! 
! SELECT ntile(3) OVER (ORDER BY ten, four), ten, four FROM tenk1 WHERE unique2 < 10;
!  ntile | ten | four 
! -------+-----+------
!      1 |   0 |    0
!      1 |   0 |    0
!      1 |   0 |    2
!      1 |   1 |    1
!      2 |   1 |    1
!      2 |   1 |    3
!      2 |   3 |    3
!      3 |   4 |    0
!      3 |   7 |    1
!      3 |   9 |    1
! (10 rows)
! 
! SELECT ntile(NULL) OVER (ORDER BY ten, four), ten, four FROM tenk1 LIMIT 2;
!  ntile | ten | four 
! -------+-----+------
!        |   0 |    0
!        |   0 |    0
! (2 rows)
! 
! SELECT lag(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!  lag | ten | four 
! -----+-----+------
!      |   0 |    0
!    0 |   0 |    0
!    0 |   4 |    0
!      |   1 |    1
!    1 |   1 |    1
!    1 |   7 |    1
!    7 |   9 |    1
!      |   0 |    2
!      |   1 |    3
!    1 |   3 |    3
! (10 rows)
! 
! SELECT lag(ten, four) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!  lag | ten | four 
! -----+-----+------
!    0 |   0 |    0
!    0 |   0 |    0
!    4 |   4 |    0
!      |   1 |    1
!    1 |   1 |    1
!    1 |   7 |    1
!    7 |   9 |    1
!      |   0 |    2
!      |   1 |    3
!      |   3 |    3
! (10 rows)
! 
! SELECT lag(ten, four, 0) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!  lag | ten | four 
! -----+-----+------
!    0 |   0 |    0
!    0 |   0 |    0
!    4 |   4 |    0
!    0 |   1 |    1
!    1 |   1 |    1
!    1 |   7 |    1
!    7 |   9 |    1
!    0 |   0 |    2
!    0 |   1 |    3
!    0 |   3 |    3
! (10 rows)
! 
! SELECT lead(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!  lead | ten | four 
! ------+-----+------
!     0 |   0 |    0
!     4 |   0 |    0
!       |   4 |    0
!     1 |   1 |    1
!     7 |   1 |    1
!     9 |   7 |    1
!       |   9 |    1
!       |   0 |    2
!     3 |   1 |    3
!       |   3 |    3
! (10 rows)
! 
! SELECT lead(ten * 2, 1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!  lead | ten | four 
! ------+-----+------
!     0 |   0 |    0
!     8 |   0 |    0
!       |   4 |    0
!     2 |   1 |    1
!    14 |   1 |    1
!    18 |   7 |    1
!       |   9 |    1
!       |   0 |    2
!     6 |   1 |    3
!       |   3 |    3
! (10 rows)
! 
! SELECT lead(ten * 2, 1, -1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!  lead | ten | four 
! ------+-----+------
!     0 |   0 |    0
!     8 |   0 |    0
!    -1 |   4 |    0
!     2 |   1 |    1
!    14 |   1 |    1
!    18 |   7 |    1
!    -1 |   9 |    1
!    -1 |   0 |    2
!     6 |   1 |    3
!    -1 |   3 |    3
! (10 rows)
! 
! SELECT first_value(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!  first_value | ten | four 
! -------------+-----+------
!            0 |   0 |    0
!            0 |   0 |    0
!            0 |   4 |    0
!            1 |   1 |    1
!            1 |   1 |    1
!            1 |   7 |    1
!            1 |   9 |    1
!            0 |   0 |    2
!            1 |   1 |    3
!            1 |   3 |    3
! (10 rows)
! 
! -- last_value returns the last row of the frame, which is CURRENT ROW in ORDER BY window.
! SELECT last_value(four) OVER (ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
!  last_value | ten | four 
! ------------+-----+------
!           0 |   0 |    0
!           0 |   0 |    2
!           0 |   0 |    0
!           1 |   1 |    1
!           1 |   1 |    3
!           1 |   1 |    1
!           3 |   3 |    3
!           0 |   4 |    0
!           1 |   7 |    1
!           1 |   9 |    1
! (10 rows)
! 
! SELECT last_value(ten) OVER (PARTITION BY four), ten, four FROM
! 	(SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s
! 	ORDER BY four, ten;
!  last_value | ten | four 
! ------------+-----+------
!           4 |   0 |    0
!           4 |   0 |    0
!           4 |   4 |    0
!           9 |   1 |    1
!           9 |   1 |    1
!           9 |   7 |    1
!           9 |   9 |    1
!           0 |   0 |    2
!           3 |   1 |    3
!           3 |   3 |    3
! (10 rows)
! 
! SELECT nth_value(ten, four + 1) OVER (PARTITION BY four), ten, four
! 	FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s;
!  nth_value | ten | four 
! -----------+-----+------
!          0 |   0 |    0
!          0 |   0 |    0
!          0 |   4 |    0
!          1 |   1 |    1
!          1 |   1 |    1
!          1 |   7 |    1
!          1 |   9 |    1
!            |   0 |    2
!            |   1 |    3
!            |   3 |    3
! (10 rows)
! 
! SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER (PARTITION BY two ORDER BY ten) AS wsum
! FROM tenk1 GROUP BY ten, two;
!  ten | two | gsum  |  wsum  
! -----+-----+-------+--------
!    0 |   0 | 45000 |  45000
!    2 |   0 | 47000 |  92000
!    4 |   0 | 49000 | 141000
!    6 |   0 | 51000 | 192000
!    8 |   0 | 53000 | 245000
!    1 |   1 | 46000 |  46000
!    3 |   1 | 48000 |  94000
!    5 |   1 | 50000 | 144000
!    7 |   1 | 52000 | 196000
!    9 |   1 | 54000 | 250000
! (10 rows)
! 
! SELECT count(*) OVER (PARTITION BY four), four FROM (SELECT * FROM tenk1 WHERE two = 1)s WHERE unique2 < 10;
!  count | four 
! -------+------
!      4 |    1
!      4 |    1
!      4 |    1
!      4 |    1
!      2 |    3
!      2 |    3
! (6 rows)
! 
! SELECT (count(*) OVER (PARTITION BY four ORDER BY ten) +
!   sum(hundred) OVER (PARTITION BY four ORDER BY ten))::varchar AS cntsum
!   FROM tenk1 WHERE unique2 < 10;
!  cntsum 
! --------
!  22
!  22
!  87
!  24
!  24
!  82
!  92
!  51
!  92
!  136
! (10 rows)
! 
! -- opexpr with different windows evaluation.
! SELECT * FROM(
!   SELECT count(*) OVER (PARTITION BY four ORDER BY ten) +
!     sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS total,
!     count(*) OVER (PARTITION BY four ORDER BY ten) AS fourcount,
!     sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS twosum
!     FROM tenk1
! )sub
! WHERE total <> fourcount + twosum;
!  total | fourcount | twosum 
! -------+-----------+--------
! (0 rows)
! 
! SELECT avg(four) OVER (PARTITION BY four ORDER BY thousand / 100) FROM tenk1 WHERE unique2 < 10;
!           avg           
! ------------------------
!  0.00000000000000000000
!  0.00000000000000000000
!  0.00000000000000000000
!  1.00000000000000000000
!  1.00000000000000000000
!  1.00000000000000000000
!  1.00000000000000000000
!      2.0000000000000000
!      3.0000000000000000
!      3.0000000000000000
! (10 rows)
! 
! SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER win AS wsum
! FROM tenk1 GROUP BY ten, two WINDOW win AS (PARTITION BY two ORDER BY ten);
!  ten | two | gsum  |  wsum  
! -----+-----+-------+--------
!    0 |   0 | 45000 |  45000
!    2 |   0 | 47000 |  92000
!    4 |   0 | 49000 | 141000
!    6 |   0 | 51000 | 192000
!    8 |   0 | 53000 | 245000
!    1 |   1 | 46000 |  46000
!    3 |   1 | 48000 |  94000
!    5 |   1 | 50000 | 144000
!    7 |   1 | 52000 | 196000
!    9 |   1 | 54000 | 250000
! (10 rows)
! 
! -- more than one window with GROUP BY
! SELECT sum(salary),
! 	row_number() OVER (ORDER BY depname),
! 	sum(sum(salary)) OVER (ORDER BY depname DESC)
! FROM empsalary GROUP BY depname;
!   sum  | row_number |  sum  
! -------+------------+-------
!  14600 |          3 | 14600
!   7400 |          2 | 22000
!  25100 |          1 | 47100
! (3 rows)
! 
! -- identical windows with different names
! SELECT sum(salary) OVER w1, count(*) OVER w2
! FROM empsalary WINDOW w1 AS (ORDER BY salary), w2 AS (ORDER BY salary);
!   sum  | count 
! -------+-------
!   3500 |     1
!   7400 |     2
!  11600 |     3
!  16100 |     4
!  25700 |     6
!  25700 |     6
!  30700 |     7
!  41100 |     9
!  41100 |     9
!  47100 |    10
! (10 rows)
! 
! -- subplan
! SELECT lead(ten, (SELECT two FROM tenk1 WHERE s.unique2 = unique2)) OVER (PARTITION BY four ORDER BY ten)
! FROM tenk1 s WHERE unique2 < 10;
!  lead 
! ------
!     0
!     0
!     4
!     1
!     7
!     9
!      
!     0
!     3
!      
! (10 rows)
! 
! -- empty table
! SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 WHERE FALSE)s;
!  count 
! -------
! (0 rows)
! 
! -- mixture of agg/wfunc in the same window
! SELECT sum(salary) OVER w, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
!   sum  | rank 
! -------+------
!   6000 |    1
!  16400 |    2
!  16400 |    2
!  20900 |    4
!  25100 |    5
!   3900 |    1
!   7400 |    2
!   5000 |    1
!  14600 |    2
!  14600 |    2
! (10 rows)
! 
! -- strict aggs
! SELECT empno, depname, salary, bonus, depadj, MIN(bonus) OVER (ORDER BY empno), MAX(depadj) OVER () FROM(
! 	SELECT *,
! 		CASE WHEN enroll_date < '2008-01-01' THEN 2008 - extract(YEAR FROM enroll_date) END * 500 AS bonus,
! 		CASE WHEN
! 			AVG(salary) OVER (PARTITION BY depname) < salary
! 		THEN 200 END AS depadj FROM empsalary
! )s;
!  empno |  depname  | salary | bonus | depadj | min  | max 
! -------+-----------+--------+-------+--------+------+-----
!      1 | sales     |   5000 |  1000 |    200 | 1000 | 200
!      2 | personnel |   3900 |  1000 |    200 | 1000 | 200
!      3 | sales     |   4800 |   500 |        |  500 | 200
!      4 | sales     |   4800 |   500 |        |  500 | 200
!      5 | personnel |   3500 |   500 |        |  500 | 200
!      7 | develop   |   4200 |       |        |  500 | 200
!      8 | develop   |   6000 |  1000 |    200 |  500 | 200
!      9 | develop   |   4500 |       |        |  500 | 200
!     10 | develop   |   5200 |   500 |    200 |  500 | 200
!     11 | develop   |   5200 |   500 |    200 |  500 | 200
! (10 rows)
! 
! -- window function over ungrouped agg over empty row set (bug before 9.1)
! SELECT SUM(COUNT(f1)) OVER () FROM int4_tbl WHERE f1=42;
!  sum 
! -----
!    0
! (1 row)
! 
! -- window function with ORDER BY an expression involving aggregates (9.1 bug)
! select ten,
!   sum(unique1) + sum(unique2) as res,
!   rank() over (order by sum(unique1) + sum(unique2)) as rank
! from tenk1
! group by ten order by ten;
!  ten |   res    | rank 
! -----+----------+------
!    0 |  9976146 |    4
!    1 | 10114187 |    9
!    2 | 10059554 |    8
!    3 |  9878541 |    1
!    4 |  9881005 |    2
!    5 |  9981670 |    5
!    6 |  9947099 |    3
!    7 | 10120309 |   10
!    8 |  9991305 |    6
!    9 | 10040184 |    7
! (10 rows)
! 
! -- window and aggregate with GROUP BY expression (9.2 bug)
! explain (costs off)
! select first_value(max(x)) over (), y
!   from (select unique1 as x, ten+four as y from tenk1) ss
!   group by y;
!                  QUERY PLAN                  
! ---------------------------------------------
!  WindowAgg
!    ->  HashAggregate
!          Group Key: (tenk1.ten + tenk1.four)
!          ->  Seq Scan on tenk1
! (4 rows)
! 
! -- test non-default frame specifications
! SELECT four, ten,
! 	sum(ten) over (partition by four order by ten),
! 	last_value(ten) over (partition by four order by ten)
! FROM (select distinct ten, four from tenk1) ss;
!  four | ten | sum | last_value 
! ------+-----+-----+------------
!     0 |   0 |   0 |          0
!     0 |   2 |   2 |          2
!     0 |   4 |   6 |          4
!     0 |   6 |  12 |          6
!     0 |   8 |  20 |          8
!     1 |   1 |   1 |          1
!     1 |   3 |   4 |          3
!     1 |   5 |   9 |          5
!     1 |   7 |  16 |          7
!     1 |   9 |  25 |          9
!     2 |   0 |   0 |          0
!     2 |   2 |   2 |          2
!     2 |   4 |   6 |          4
!     2 |   6 |  12 |          6
!     2 |   8 |  20 |          8
!     3 |   1 |   1 |          1
!     3 |   3 |   4 |          3
!     3 |   5 |   9 |          5
!     3 |   7 |  16 |          7
!     3 |   9 |  25 |          9
! (20 rows)
! 
! SELECT four, ten,
! 	sum(ten) over (partition by four order by ten range between unbounded preceding and current row),
! 	last_value(ten) over (partition by four order by ten range between unbounded preceding and current row)
! FROM (select distinct ten, four from tenk1) ss;
!  four | ten | sum | last_value 
! ------+-----+-----+------------
!     0 |   0 |   0 |          0
!     0 |   2 |   2 |          2
!     0 |   4 |   6 |          4
!     0 |   6 |  12 |          6
!     0 |   8 |  20 |          8
!     1 |   1 |   1 |          1
!     1 |   3 |   4 |          3
!     1 |   5 |   9 |          5
!     1 |   7 |  16 |          7
!     1 |   9 |  25 |          9
!     2 |   0 |   0 |          0
!     2 |   2 |   2 |          2
!     2 |   4 |   6 |          4
!     2 |   6 |  12 |          6
!     2 |   8 |  20 |          8
!     3 |   1 |   1 |          1
!     3 |   3 |   4 |          3
!     3 |   5 |   9 |          5
!     3 |   7 |  16 |          7
!     3 |   9 |  25 |          9
! (20 rows)
! 
! SELECT four, ten,
! 	sum(ten) over (partition by four order by ten range between unbounded preceding and unbounded following),
! 	last_value(ten) over (partition by four order by ten range between unbounded preceding and unbounded following)
! FROM (select distinct ten, four from tenk1) ss;
!  four | ten | sum | last_value 
! ------+-----+-----+------------
!     0 |   0 |  20 |          8
!     0 |   2 |  20 |          8
!     0 |   4 |  20 |          8
!     0 |   6 |  20 |          8
!     0 |   8 |  20 |          8
!     1 |   1 |  25 |          9
!     1 |   3 |  25 |          9
!     1 |   5 |  25 |          9
!     1 |   7 |  25 |          9
!     1 |   9 |  25 |          9
!     2 |   0 |  20 |          8
!     2 |   2 |  20 |          8
!     2 |   4 |  20 |          8
!     2 |   6 |  20 |          8
!     2 |   8 |  20 |          8
!     3 |   1 |  25 |          9
!     3 |   3 |  25 |          9
!     3 |   5 |  25 |          9
!     3 |   7 |  25 |          9
!     3 |   9 |  25 |          9
! (20 rows)
! 
! SELECT four, ten/4 as two,
! 	sum(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row),
! 	last_value(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row)
! FROM (select distinct ten, four from tenk1) ss;
!  four | two | sum | last_value 
! ------+-----+-----+------------
!     0 |   0 |   0 |          0
!     0 |   0 |   0 |          0
!     0 |   1 |   2 |          1
!     0 |   1 |   2 |          1
!     0 |   2 |   4 |          2
!     1 |   0 |   0 |          0
!     1 |   0 |   0 |          0
!     1 |   1 |   2 |          1
!     1 |   1 |   2 |          1
!     1 |   2 |   4 |          2
!     2 |   0 |   0 |          0
!     2 |   0 |   0 |          0
!     2 |   1 |   2 |          1
!     2 |   1 |   2 |          1
!     2 |   2 |   4 |          2
!     3 |   0 |   0 |          0
!     3 |   0 |   0 |          0
!     3 |   1 |   2 |          1
!     3 |   1 |   2 |          1
!     3 |   2 |   4 |          2
! (20 rows)
! 
! SELECT four, ten/4 as two,
! 	sum(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row),
! 	last_value(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row)
! FROM (select distinct ten, four from tenk1) ss;
!  four | two | sum | last_value 
! ------+-----+-----+------------
!     0 |   0 |   0 |          0
!     0 |   0 |   0 |          0
!     0 |   1 |   1 |          1
!     0 |   1 |   2 |          1
!     0 |   2 |   4 |          2
!     1 |   0 |   0 |          0
!     1 |   0 |   0 |          0
!     1 |   1 |   1 |          1
!     1 |   1 |   2 |          1
!     1 |   2 |   4 |          2
!     2 |   0 |   0 |          0
!     2 |   0 |   0 |          0
!     2 |   1 |   1 |          1
!     2 |   1 |   2 |          1
!     2 |   2 |   4 |          2
!     3 |   0 |   0 |          0
!     3 |   0 |   0 |          0
!     3 |   1 |   1 |          1
!     3 |   1 |   2 |          1
!     3 |   2 |   4 |          2
! (20 rows)
! 
! SELECT sum(unique1) over (order by four range between current row and unbounded following),
! 	unique1, four
! FROM tenk1 WHERE unique1 < 10;
!  sum | unique1 | four 
! -----+---------+------
!   45 |       0 |    0
!   45 |       8 |    0
!   45 |       4 |    0
!   33 |       5 |    1
!   33 |       9 |    1
!   33 |       1 |    1
!   18 |       6 |    2
!   18 |       2 |    2
!   10 |       3 |    3
!   10 |       7 |    3
! (10 rows)
! 
! SELECT sum(unique1) over (rows between current row and unbounded following),
! 	unique1, four
! FROM tenk1 WHERE unique1 < 10;
!  sum | unique1 | four 
! -----+---------+------
!   45 |       4 |    0
!   41 |       2 |    2
!   39 |       1 |    1
!   38 |       6 |    2
!   32 |       9 |    1
!   23 |       8 |    0
!   15 |       5 |    1
!   10 |       3 |    3
!    7 |       7 |    3
!    0 |       0 |    0
! (10 rows)
! 
! SELECT sum(unique1) over (rows between 2 preceding and 2 following),
! 	unique1, four
! FROM tenk1 WHERE unique1 < 10;
!  sum | unique1 | four 
! -----+---------+------
!    7 |       4 |    0
!   13 |       2 |    2
!   22 |       1 |    1
!   26 |       6 |    2
!   29 |       9 |    1
!   31 |       8 |    0
!   32 |       5 |    1
!   23 |       3 |    3
!   15 |       7 |    3
!   10 |       0 |    0
! (10 rows)
! 
! SELECT sum(unique1) over (rows between 2 preceding and 1 preceding),
! 	unique1, four
! FROM tenk1 WHERE unique1 < 10;
!  sum | unique1 | four 
! -----+---------+------
!      |       4 |    0
!    4 |       2 |    2
!    6 |       1 |    1
!    3 |       6 |    2
!    7 |       9 |    1
!   15 |       8 |    0
!   17 |       5 |    1
!   13 |       3 |    3
!    8 |       7 |    3
!   10 |       0 |    0
! (10 rows)
! 
! SELECT sum(unique1) over (rows between 1 following and 3 following),
! 	unique1, four
! FROM tenk1 WHERE unique1 < 10;
!  sum | unique1 | four 
! -----+---------+------
!    9 |       4 |    0
!   16 |       2 |    2
!   23 |       1 |    1
!   22 |       6 |    2
!   16 |       9 |    1
!   15 |       8 |    0
!   10 |       5 |    1
!    7 |       3 |    3
!    0 |       7 |    3
!      |       0 |    0
! (10 rows)
! 
! SELECT sum(unique1) over (rows between unbounded preceding and 1 following),
! 	unique1, four
! FROM tenk1 WHERE unique1 < 10;
!  sum | unique1 | four 
! -----+---------+------
!    6 |       4 |    0
!    7 |       2 |    2
!   13 |       1 |    1
!   22 |       6 |    2
!   30 |       9 |    1
!   35 |       8 |    0
!   38 |       5 |    1
!   45 |       3 |    3
!   45 |       7 |    3
!   45 |       0 |    0
! (10 rows)
! 
! SELECT sum(unique1) over (w range between current row and unbounded following),
! 	unique1, four
! FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
!  sum | unique1 | four 
! -----+---------+------
!   45 |       0 |    0
!   45 |       8 |    0
!   45 |       4 |    0
!   33 |       5 |    1
!   33 |       9 |    1
!   33 |       1 |    1
!   18 |       6 |    2
!   18 |       2 |    2
!   10 |       3 |    3
!   10 |       7 |    3
! (10 rows)
! 
! -- fail: not implemented yet
! SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
! 	unique1, four
! FROM tenk1 WHERE unique1 < 10;
! ERROR:  RANGE PRECEDING is only supported with UNBOUNDED
! LINE 1: SELECT sum(unique1) over (order by four range between 2::int...
!                                                 ^
! SELECT first_value(unique1) over w,
! 	nth_value(unique1, 2) over w AS nth_2,
! 	last_value(unique1) over w, unique1, four
! FROM tenk1 WHERE unique1 < 10
! WINDOW w AS (order by four range between current row and unbounded following);
!  first_value | nth_2 | last_value | unique1 | four 
! -------------+-------+------------+---------+------
!            0 |     8 |          7 |       0 |    0
!            0 |     8 |          7 |       8 |    0
!            0 |     8 |          7 |       4 |    0
!            5 |     9 |          7 |       5 |    1
!            5 |     9 |          7 |       9 |    1
!            5 |     9 |          7 |       1 |    1
!            6 |     2 |          7 |       6 |    2
!            6 |     2 |          7 |       2 |    2
!            3 |     7 |          7 |       3 |    3
!            3 |     7 |          7 |       7 |    3
! (10 rows)
! 
! SELECT sum(unique1) over
! 	(order by unique1
! 	 rows (SELECT unique1 FROM tenk1 ORDER BY unique1 LIMIT 1) + 1 PRECEDING),
! 	unique1
! FROM tenk1 WHERE unique1 < 10;
!  sum | unique1 
! -----+---------
!    0 |       0
!    1 |       1
!    3 |       2
!    5 |       3
!    7 |       4
!    9 |       5
!   11 |       6
!   13 |       7
!   15 |       8
!   17 |       9
! (10 rows)
! 
! CREATE TEMP VIEW v_window AS
! 	SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following) as sum_rows
! 	FROM generate_series(1, 10) i;
! SELECT * FROM v_window;
!  i  | sum_rows 
! ----+----------
!   1 |        3
!   2 |        6
!   3 |        9
!   4 |       12
!   5 |       15
!   6 |       18
!   7 |       21
!   8 |       24
!   9 |       27
!  10 |       19
! (10 rows)
! 
! SELECT pg_get_viewdef('v_window');
!                                     pg_get_viewdef                                     
! ---------------------------------------------------------------------------------------
!   SELECT i.i,                                                                         +
!      sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
!     FROM generate_series(1, 10) i(i);
! (1 row)
! 
! -- with UNION
! SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
!  count 
! -------
! (0 rows)
! 
! -- ordering by a non-integer constant is allowed
! SELECT rank() OVER (ORDER BY length('abc'));
!  rank 
! ------
!     1
! (1 row)
! 
! -- can't order by another window function
! SELECT rank() OVER (ORDER BY rank() OVER (ORDER BY random()));
! ERROR:  window functions are not allowed in window definitions
! LINE 1: SELECT rank() OVER (ORDER BY rank() OVER (ORDER BY random())...
!                                      ^
! -- some other errors
! SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY salary) < 10;
! ERROR:  window functions are not allowed in WHERE
! LINE 1: SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY sa...
!                                       ^
! SELECT * FROM empsalary INNER JOIN tenk1 ON row_number() OVER (ORDER BY salary) < 10;
! ERROR:  window functions are not allowed in JOIN conditions
! LINE 1: SELECT * FROM empsalary INNER JOIN tenk1 ON row_number() OVE...
!                                                     ^
! SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GROUP BY 1;
! ERROR:  window functions are not allowed in GROUP BY
! LINE 1: SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GRO...
!                ^
! SELECT * FROM rank() OVER (ORDER BY random());
! ERROR:  syntax error at or near "ORDER"
! LINE 1: SELECT * FROM rank() OVER (ORDER BY random());
!                                    ^
! DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())) > 10;
! ERROR:  window functions are not allowed in WHERE
! LINE 1: DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())...
!                                      ^
! DELETE FROM empsalary RETURNING rank() OVER (ORDER BY random());
! ERROR:  window functions are not allowed in RETURNING
! LINE 1: DELETE FROM empsalary RETURNING rank() OVER (ORDER BY random...
!                                         ^
! SELECT count(*) OVER w FROM tenk1 WINDOW w AS (ORDER BY unique1), w AS (ORDER BY unique1);
! ERROR:  window "w" is already defined
! LINE 1: ...w FROM tenk1 WINDOW w AS (ORDER BY unique1), w AS (ORDER BY ...
!                                                              ^
! SELECT rank() OVER (PARTITION BY four, ORDER BY ten) FROM tenk1;
! ERROR:  syntax error at or near "ORDER"
! LINE 1: SELECT rank() OVER (PARTITION BY four, ORDER BY ten) FROM te...
!                                                ^
! SELECT count() OVER () FROM tenk1;
! ERROR:  count(*) must be used to call a parameterless aggregate function
! LINE 1: SELECT count() OVER () FROM tenk1;
!                ^
! SELECT generate_series(1, 100) OVER () FROM empsalary;
! ERROR:  OVER specified, but generate_series is not a window function nor an aggregate function
! LINE 1: SELECT generate_series(1, 100) OVER () FROM empsalary;
!                ^
! SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
! ERROR:  argument of ntile must be greater than zero
! SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
! ERROR:  argument of nth_value must be greater than zero
! -- filter
! SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
!     sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
! ) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
!     depname
! FROM empsalary GROUP BY depname;
!   sum  | row_number | filtered_sum |  depname  
! -------+------------+--------------+-----------
!  14600 |          3 |              | sales
!   7400 |          2 |         3500 | personnel
!  25100 |          1 |        22600 | develop
! (3 rows)
! 
! -- cleanup
! DROP TABLE empsalary;
! -- test user-defined window function with named args and default args
! CREATE FUNCTION nth_value_def(val anyelement, n integer = 1) RETURNS anyelement
!   LANGUAGE internal WINDOW IMMUTABLE STRICT AS 'window_nth_value';
! SELECT nth_value_def(n := 2, val := ten) OVER (PARTITION BY four), ten, four
!   FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
!  nth_value_def | ten | four 
! ---------------+-----+------
!              0 |   0 |    0
!              0 |   0 |    0
!              0 |   4 |    0
!              1 |   1 |    1
!              1 |   1 |    1
!              1 |   7 |    1
!              1 |   9 |    1
!                |   0 |    2
!              3 |   1 |    3
!              3 |   3 |    3
! (10 rows)
! 
! SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
!   FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
!  nth_value_def | ten | four 
! ---------------+-----+------
!              0 |   0 |    0
!              0 |   0 |    0
!              0 |   4 |    0
!              1 |   1 |    1
!              1 |   1 |    1
!              1 |   7 |    1
!              1 |   9 |    1
!              0 |   0 |    2
!              1 |   1 |    3
!              1 |   3 |    3
! (10 rows)
! 
! -- test inverse transition funtions handle NULLs properly
! SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg         
! ---+--------------------
!  1 | 1.5000000000000000
!  2 | 2.0000000000000000
!  3 |                   
!  4 |                   
! (4 rows)
! 
! SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg         
! ---+--------------------
!  1 | 1.5000000000000000
!  2 | 2.0000000000000000
!  3 |                   
!  4 |                   
! (4 rows)
! 
! SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg         
! ---+--------------------
!  1 | 1.5000000000000000
!  2 | 2.0000000000000000
!  3 |                   
!  4 |                   
! (4 rows)
! 
! SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
!  i |        avg         
! ---+--------------------
!  1 | 2.0000000000000000
!  2 | 2.5000000000000000
!  3 |                   
!  4 |                   
! (4 rows)
! 
! SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
!  i |    avg     
! ---+------------
!  1 | @ 1.5 secs
!  2 | @ 2 secs
!  3 | 
!  4 | 
! (4 rows)
! 
! SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
! ---+-----
!  1 |   3
!  2 |   2
!  3 |    
!  4 |    
! (4 rows)
! 
! SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
! ---+-----
!  1 |   3
!  2 |   2
!  3 |    
!  4 |    
! (4 rows)
! 
! SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
! ---+-----
!  1 |   3
!  2 |   2
!  3 |    
!  4 |    
! (4 rows)
! 
! SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
!  i |  sum  
! ---+-------
!  1 | $3.30
!  2 | $2.20
!  3 |      
!  4 |      
! (4 rows)
! 
! SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
!  i |   sum    
! ---+----------
!  1 | @ 3 secs
!  2 | @ 2 secs
!  3 | 
!  4 | 
! (4 rows)
! 
! SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
! ---+-----
!  1 | 3.3
!  2 | 2.2
!  3 |    
!  4 |    
! (4 rows)
! 
! -- This test currently fails due extra trailing 0 digits.
! SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
!  sum  
! ------
!  6.01
!     5
!     3
! (3 rows)
! 
! SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | count 
! ---+-------
!  1 |     2
!  2 |     1
!  3 |     0
!  4 |     0
! (4 rows)
! 
! SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | count 
! ---+-------
!  1 |     4
!  2 |     3
!  3 |     2
!  4 |     1
! (4 rows)
! 
! SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!         var_pop        
! -----------------------
!     21704.000000000000
!     13868.750000000000
!     11266.666666666667
!  4225.0000000000000000
!                      0
! (5 rows)
! 
! SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!         var_pop        
! -----------------------
!     21704.000000000000
!     13868.750000000000
!     11266.666666666667
!  4225.0000000000000000
!                      0
! (5 rows)
! 
! SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!         var_pop        
! -----------------------
!     21704.000000000000
!     13868.750000000000
!     11266.666666666667
!  4225.0000000000000000
!                      0
! (5 rows)
! 
! SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!         var_pop        
! -----------------------
!     21704.000000000000
!     13868.750000000000
!     11266.666666666667
!  4225.0000000000000000
!                      0
! (5 rows)
! 
! SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        var_samp        
! -----------------------
!     27130.000000000000
!     18491.666666666667
!     16900.000000000000
!  8450.0000000000000000
!                       
! (5 rows)
! 
! SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        var_samp        
! -----------------------
!     27130.000000000000
!     18491.666666666667
!     16900.000000000000
!  8450.0000000000000000
!                       
! (5 rows)
! 
! SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        var_samp        
! -----------------------
!     27130.000000000000
!     18491.666666666667
!     16900.000000000000
!  8450.0000000000000000
!                       
! (5 rows)
! 
! SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        var_samp        
! -----------------------
!     27130.000000000000
!     18491.666666666667
!     16900.000000000000
!  8450.0000000000000000
!                       
! (5 rows)
! 
! SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        variance        
! -----------------------
!     27130.000000000000
!     18491.666666666667
!     16900.000000000000
!  8450.0000000000000000
!                       
! (5 rows)
! 
! SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        variance        
! -----------------------
!     27130.000000000000
!     18491.666666666667
!     16900.000000000000
!  8450.0000000000000000
!                       
! (5 rows)
! 
! SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        variance        
! -----------------------
!     27130.000000000000
!     18491.666666666667
!     16900.000000000000
!  8450.0000000000000000
!                       
! (5 rows)
! 
! SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        variance        
! -----------------------
!     27130.000000000000
!     18491.666666666667
!     16900.000000000000
!  8450.0000000000000000
!                       
! (5 rows)
! 
! SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!      stddev_pop      
! ---------------------
!     147.322774885623
!     117.765657133139
!     106.144555520604
!  65.0000000000000000
!                    0
! (5 rows)
! 
! SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!      stddev_pop      
! ---------------------
!     147.322774885623
!     117.765657133139
!     106.144555520604
!  65.0000000000000000
!                    0
! (5 rows)
! 
! SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!      stddev_pop      
! ---------------------
!     147.322774885623
!     117.765657133139
!     106.144555520604
!  65.0000000000000000
!                    0
! (5 rows)
! 
! SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!      stddev_pop      
! ---------------------
!     147.322774885623
!     117.765657133139
!     106.144555520604
!  65.0000000000000000
!                    0
! (5 rows)
! 
! SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!      stddev_samp     
! ---------------------
!     164.711869639076
!     135.984067694222
!     130.000000000000
!  91.9238815542511782
!                     
! (5 rows)
! 
! SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!      stddev_samp     
! ---------------------
!     164.711869639076
!     135.984067694222
!     130.000000000000
!  91.9238815542511782
!                     
! (5 rows)
! 
! SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!      stddev_samp     
! ---------------------
!     164.711869639076
!     135.984067694222
!     130.000000000000
!  91.9238815542511782
!                     
! (5 rows)
! 
! SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!      stddev_samp     
! ---------------------
!     164.711869639076
!     135.984067694222
!     130.000000000000
!  91.9238815542511782
!                     
! (5 rows)
! 
! SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        stddev        
! ---------------------
!     164.711869639076
!     135.984067694222
!     130.000000000000
!  91.9238815542511782
!                     
! (5 rows)
! 
! SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        stddev        
! ---------------------
!     164.711869639076
!     135.984067694222
!     130.000000000000
!  91.9238815542511782
!                     
! (5 rows)
! 
! SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        stddev        
! ---------------------
!     164.711869639076
!     135.984067694222
!     130.000000000000
!  91.9238815542511782
!                     
! (5 rows)
! 
! SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
!        stddev        
! ---------------------
!     164.711869639076
!     135.984067694222
!     130.000000000000
!  91.9238815542511782
!                     
! (5 rows)
! 
! -- test that inverse transition functions work with various frame options
! SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
! ---+-----
!  1 |   1
!  2 |   2
!  3 |    
!  4 |    
! (4 rows)
! 
! SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
!  i | sum 
! ---+-----
!  1 |   3
!  2 |   2
!  3 |    
!  4 |    
! (4 rows)
! 
! SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
!  i | sum 
! ---+-----
!  1 |   3
!  2 |   6
!  3 |   9
!  4 |   7
! (4 rows)
! 
! -- it might be tempting for someone to add an inverse trans function for
! -- float and double precision. This should not be done as it  can give incorrect
! -- results. This test should fail if anyone ever does this without thinking too
! -- hard about it.
! SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
!   FROM (VALUES(1,1e20),(2,1)) n(i,n);
!          to_char          
! --------------------------
!   100000000000000000000
!                       1.0
! (2 rows)
! 
! -- inverse transition function with filter
! SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
!   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
!  i | sum 
! ---+-----
!  1 |   3
!  2 |   2
!  3 |    
!  4 |    
! (4 rows)
! 
--- 19,22 ----
  ('develop', 8, 6000, '2006-10-01'),
  ('develop', 11, 5200, '2007-08-15');
  SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
! connection to server was lost

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/plancache.out	2014-01-02 13:19:06.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/plancache.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,258 ****
! --
! -- Tests to exercise the plan caching/invalidation mechanism
! --
! CREATE TEMP TABLE pcachetest AS SELECT * FROM int8_tbl;
! -- create and use a cached plan
! PREPARE prepstmt AS SELECT * FROM pcachetest;
! EXECUTE prepstmt;
!         q1        |        q2         
! ------------------+-------------------
!               123 |               456
!               123 |  4567890123456789
!  4567890123456789 |               123
!  4567890123456789 |  4567890123456789
!  4567890123456789 | -4567890123456789
! (5 rows)
! 
! -- and one with parameters
! PREPARE prepstmt2(bigint) AS SELECT * FROM pcachetest WHERE q1 = $1;
! EXECUTE prepstmt2(123);
!  q1  |        q2        
! -----+------------------
!  123 |              456
!  123 | 4567890123456789
! (2 rows)
! 
! -- invalidate the plans and see what happens
! DROP TABLE pcachetest;
! EXECUTE prepstmt;
! ERROR:  relation "pcachetest" does not exist
! EXECUTE prepstmt2(123);
! ERROR:  relation "pcachetest" does not exist
! -- recreate the temp table (this demonstrates that the raw plan is
! -- purely textual and doesn't depend on OIDs, for instance)
! CREATE TEMP TABLE pcachetest AS SELECT * FROM int8_tbl ORDER BY 2;
! EXECUTE prepstmt;
!         q1        |        q2         
! ------------------+-------------------
!  4567890123456789 | -4567890123456789
!  4567890123456789 |               123
!               123 |               456
!               123 |  4567890123456789
!  4567890123456789 |  4567890123456789
! (5 rows)
! 
! EXECUTE prepstmt2(123);
!  q1  |        q2        
! -----+------------------
!  123 |              456
!  123 | 4567890123456789
! (2 rows)
! 
! -- prepared statements should prevent change in output tupdesc,
! -- since clients probably aren't expecting that to change on the fly
! ALTER TABLE pcachetest ADD COLUMN q3 bigint;
! EXECUTE prepstmt;
! ERROR:  cached plan must not change result type
! EXECUTE prepstmt2(123);
! ERROR:  cached plan must not change result type
! -- but we're nice guys and will let you undo your mistake
! ALTER TABLE pcachetest DROP COLUMN q3;
! EXECUTE prepstmt;
!         q1        |        q2         
! ------------------+-------------------
!  4567890123456789 | -4567890123456789
!  4567890123456789 |               123
!               123 |               456
!               123 |  4567890123456789
!  4567890123456789 |  4567890123456789
! (5 rows)
! 
! EXECUTE prepstmt2(123);
!  q1  |        q2        
! -----+------------------
!  123 |              456
!  123 | 4567890123456789
! (2 rows)
! 
! -- Try it with a view, which isn't directly used in the resulting plan
! -- but should trigger invalidation anyway
! CREATE TEMP VIEW pcacheview AS
!   SELECT * FROM pcachetest;
! PREPARE vprep AS SELECT * FROM pcacheview;
! EXECUTE vprep;
!         q1        |        q2         
! ------------------+-------------------
!  4567890123456789 | -4567890123456789
!  4567890123456789 |               123
!               123 |               456
!               123 |  4567890123456789
!  4567890123456789 |  4567890123456789
! (5 rows)
! 
! CREATE OR REPLACE TEMP VIEW pcacheview AS
!   SELECT q1, q2/2 AS q2 FROM pcachetest;
! EXECUTE vprep;
!         q1        |        q2         
! ------------------+-------------------
!  4567890123456789 | -2283945061728394
!  4567890123456789 |                61
!               123 |               228
!               123 |  2283945061728394
!  4567890123456789 |  2283945061728394
! (5 rows)
! 
! -- Check basic SPI plan invalidation
! create function cache_test(int) returns int as $$
! declare total int;
! begin
! 	create temp table t1(f1 int);
! 	insert into t1 values($1);
! 	insert into t1 values(11);
! 	insert into t1 values(12);
! 	insert into t1 values(13);
! 	select sum(f1) into total from t1;
! 	drop table t1;
! 	return total;
! end
! $$ language plpgsql;
! select cache_test(1);
!  cache_test 
! ------------
!          37
! (1 row)
! 
! select cache_test(2);
!  cache_test 
! ------------
!          38
! (1 row)
! 
! select cache_test(3);
!  cache_test 
! ------------
!          39
! (1 row)
! 
! -- Check invalidation of plpgsql "simple expression"
! create temp view v1 as
!   select 2+2 as f1;
! create function cache_test_2() returns int as $$
! begin
! 	return f1 from v1;
! end$$ language plpgsql;
! select cache_test_2();
!  cache_test_2 
! --------------
!             4
! (1 row)
! 
! create or replace temp view v1 as
!   select 2+2+4 as f1;
! select cache_test_2();
!  cache_test_2 
! --------------
!             8
! (1 row)
! 
! create or replace temp view v1 as
!   select 2+2+4+(select max(unique1) from tenk1) as f1;
! select cache_test_2();
!  cache_test_2 
! --------------
!         10007
! (1 row)
! 
! --- Check that change of search_path is honored when re-using cached plan
! create schema s1
!   create table abc (f1 int);
! create schema s2
!   create table abc (f1 int);
! insert into s1.abc values(123);
! insert into s2.abc values(456);
! set search_path = s1;
! prepare p1 as select f1 from abc;
! execute p1;
!  f1  
! -----
!  123
! (1 row)
! 
! set search_path = s2;
! select f1 from abc;
!  f1  
! -----
!  456
! (1 row)
! 
! execute p1;
!  f1  
! -----
!  456
! (1 row)
! 
! alter table s1.abc add column f2 float8;   -- force replan
! execute p1;
!  f1  
! -----
!  456
! (1 row)
! 
! drop schema s1 cascade;
! NOTICE:  drop cascades to table s1.abc
! drop schema s2 cascade;
! NOTICE:  drop cascades to table abc
! reset search_path;
! -- Check that invalidation deals with regclass constants
! create temp sequence seq;
! prepare p2 as select nextval('seq');
! execute p2;
!  nextval 
! ---------
!        1
! (1 row)
! 
! drop sequence seq;
! create temp sequence seq;
! execute p2;
!  nextval 
! ---------
!        1
! (1 row)
! 
! -- Check DDL via SPI, immediately followed by SPI plan re-use
! -- (bug in original coding)
! create function cachebug() returns void as $$
! declare r int;
! begin
!   drop table if exists temptable cascade;
!   create temp table temptable as select * from generate_series(1,3) as f1;
!   create temp view vv as select * from temptable;
!   for r in select * from vv loop
!     raise notice '%', r;
!   end loop;
! end$$ language plpgsql;
! select cachebug();
! NOTICE:  table "temptable" does not exist, skipping
! CONTEXT:  SQL statement "drop table if exists temptable cascade"
! PL/pgSQL function cachebug() line 4 at SQL statement
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
!  cachebug 
! ----------
!  
! (1 row)
! 
! select cachebug();
! NOTICE:  drop cascades to view vv
! CONTEXT:  SQL statement "drop table if exists temptable cascade"
! PL/pgSQL function cachebug() line 4 at SQL statement
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
!  cachebug 
! ----------
!  
! (1 row)
! 
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/limit.out	2014-01-02 13:19:06.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/limit.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,131 ****
! --
! -- LIMIT
! -- Check the LIMIT/OFFSET feature of SELECT
! --
! SELECT ''::text AS two, unique1, unique2, stringu1
! 		FROM onek WHERE unique1 > 50
! 		ORDER BY unique1 LIMIT 2;
!  two | unique1 | unique2 | stringu1 
! -----+---------+---------+----------
!      |      51 |      76 | ZBAAAA
!      |      52 |     985 | ACAAAA
! (2 rows)
! 
! SELECT ''::text AS five, unique1, unique2, stringu1
! 		FROM onek WHERE unique1 > 60
! 		ORDER BY unique1 LIMIT 5;
!  five | unique1 | unique2 | stringu1 
! ------+---------+---------+----------
!       |      61 |     560 | JCAAAA
!       |      62 |     633 | KCAAAA
!       |      63 |     296 | LCAAAA
!       |      64 |     479 | MCAAAA
!       |      65 |      64 | NCAAAA
! (5 rows)
! 
! SELECT ''::text AS two, unique1, unique2, stringu1
! 		FROM onek WHERE unique1 > 60 AND unique1 < 63
! 		ORDER BY unique1 LIMIT 5;
!  two | unique1 | unique2 | stringu1 
! -----+---------+---------+----------
!      |      61 |     560 | JCAAAA
!      |      62 |     633 | KCAAAA
! (2 rows)
! 
! SELECT ''::text AS three, unique1, unique2, stringu1
! 		FROM onek WHERE unique1 > 100
! 		ORDER BY unique1 LIMIT 3 OFFSET 20;
!  three | unique1 | unique2 | stringu1 
! -------+---------+---------+----------
!        |     121 |     700 | REAAAA
!        |     122 |     519 | SEAAAA
!        |     123 |     777 | TEAAAA
! (3 rows)
! 
! SELECT ''::text AS zero, unique1, unique2, stringu1
! 		FROM onek WHERE unique1 < 50
! 		ORDER BY unique1 DESC LIMIT 8 OFFSET 99;
!  zero | unique1 | unique2 | stringu1 
! ------+---------+---------+----------
! (0 rows)
! 
! SELECT ''::text AS eleven, unique1, unique2, stringu1
! 		FROM onek WHERE unique1 < 50
! 		ORDER BY unique1 DESC LIMIT 20 OFFSET 39;
!  eleven | unique1 | unique2 | stringu1 
! --------+---------+---------+----------
!         |      10 |     520 | KAAAAA
!         |       9 |      49 | JAAAAA
!         |       8 |     653 | IAAAAA
!         |       7 |     647 | HAAAAA
!         |       6 |     978 | GAAAAA
!         |       5 |     541 | FAAAAA
!         |       4 |     833 | EAAAAA
!         |       3 |     431 | DAAAAA
!         |       2 |     326 | CAAAAA
!         |       1 |     214 | BAAAAA
!         |       0 |     998 | AAAAAA
! (11 rows)
! 
! SELECT ''::text AS ten, unique1, unique2, stringu1
! 		FROM onek
! 		ORDER BY unique1 OFFSET 990;
!  ten | unique1 | unique2 | stringu1 
! -----+---------+---------+----------
!      |     990 |     369 | CMAAAA
!      |     991 |     426 | DMAAAA
!      |     992 |     363 | EMAAAA
!      |     993 |     661 | FMAAAA
!      |     994 |     695 | GMAAAA
!      |     995 |     144 | HMAAAA
!      |     996 |     258 | IMAAAA
!      |     997 |      21 | JMAAAA
!      |     998 |     549 | KMAAAA
!      |     999 |     152 | LMAAAA
! (10 rows)
! 
! SELECT ''::text AS five, unique1, unique2, stringu1
! 		FROM onek
! 		ORDER BY unique1 OFFSET 990 LIMIT 5;
!  five | unique1 | unique2 | stringu1 
! ------+---------+---------+----------
!       |     990 |     369 | CMAAAA
!       |     991 |     426 | DMAAAA
!       |     992 |     363 | EMAAAA
!       |     993 |     661 | FMAAAA
!       |     994 |     695 | GMAAAA
! (5 rows)
! 
! SELECT ''::text AS five, unique1, unique2, stringu1
! 		FROM onek
! 		ORDER BY unique1 LIMIT 5 OFFSET 900;
!  five | unique1 | unique2 | stringu1 
! ------+---------+---------+----------
!       |     900 |     913 | QIAAAA
!       |     901 |     931 | RIAAAA
!       |     902 |     702 | SIAAAA
!       |     903 |     641 | TIAAAA
!       |     904 |     793 | UIAAAA
! (5 rows)
! 
! -- Stress test for variable LIMIT in conjunction with bounded-heap sorting
! SELECT
!   (SELECT n
!      FROM (VALUES (1)) AS x,
!           (SELECT n FROM generate_series(1,10) AS n
!              ORDER BY n LIMIT 1 OFFSET s-1) AS y) AS z
!   FROM generate_series(1,10) AS s;
!  z  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
! (10 rows)
! 
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/plpgsql.out	2014-01-02 13:19:06.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/plpgsql.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,5220 ****
! --
! -- PLPGSQL
! --
! -- Scenario:
! --
! --     A building with a modern TP cable installation where any
! --     of the wall connectors can be used to plug in phones,
! --     ethernet interfaces or local office hubs. The backside
! --     of the wall connectors is wired to one of several patch-
! --     fields in the building.
! --
! --     In the patchfields, there are hubs and all the slots
! --     representing the wall connectors. In addition there are
! --     slots that can represent a phone line from the central
! --     phone system.
! --
! --     Triggers ensure consistency of the patching information.
! --
! --     Functions are used to build up powerful views that let
! --     you look behind the wall when looking at a patchfield
! --     or into a room.
! --
! create table Room (
!     roomno	char(8),
!     comment	text
! );
! create unique index Room_rno on Room using btree (roomno bpchar_ops);
! create table WSlot (
!     slotname	char(20),
!     roomno	char(8),
!     slotlink	char(20),
!     backlink	char(20)
! );
! create unique index WSlot_name on WSlot using btree (slotname bpchar_ops);
! create table PField (
!     name	text,
!     comment	text
! );
! create unique index PField_name on PField using btree (name text_ops);
! create table PSlot (
!     slotname	char(20),
!     pfname	text,
!     slotlink	char(20),
!     backlink	char(20)
! );
! create unique index PSlot_name on PSlot using btree (slotname bpchar_ops);
! create table PLine (
!     slotname	char(20),
!     phonenumber	char(20),
!     comment	text,
!     backlink	char(20)
! );
! create unique index PLine_name on PLine using btree (slotname bpchar_ops);
! create table Hub (
!     name	char(14),
!     comment	text,
!     nslots	integer
! );
! create unique index Hub_name on Hub using btree (name bpchar_ops);
! create table HSlot (
!     slotname	char(20),
!     hubname	char(14),
!     slotno	integer,
!     slotlink	char(20)
! );
! create unique index HSlot_name on HSlot using btree (slotname bpchar_ops);
! create index HSlot_hubname on HSlot using btree (hubname bpchar_ops);
! create table System (
!     name	text,
!     comment	text
! );
! create unique index System_name on System using btree (name text_ops);
! create table IFace (
!     slotname	char(20),
!     sysname	text,
!     ifname	text,
!     slotlink	char(20)
! );
! create unique index IFace_name on IFace using btree (slotname bpchar_ops);
! create table PHone (
!     slotname	char(20),
!     comment	text,
!     slotlink	char(20)
! );
! create unique index PHone_name on PHone using btree (slotname bpchar_ops);
! -- ************************************************************
! -- *
! -- * Trigger procedures and functions for the patchfield
! -- * test of PL/pgSQL
! -- *
! -- ************************************************************
! -- ************************************************************
! -- * AFTER UPDATE on Room
! -- *	- If room no changes let wall slots follow
! -- ************************************************************
! create function tg_room_au() returns trigger as '
! begin
!     if new.roomno != old.roomno then
!         update WSlot set roomno = new.roomno where roomno = old.roomno;
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_room_au after update
!     on Room for each row execute procedure tg_room_au();
! -- ************************************************************
! -- * AFTER DELETE on Room
! -- *	- delete wall slots in this room
! -- ************************************************************
! create function tg_room_ad() returns trigger as '
! begin
!     delete from WSlot where roomno = old.roomno;
!     return old;
! end;
! ' language plpgsql;
! create trigger tg_room_ad after delete
!     on Room for each row execute procedure tg_room_ad();
! -- ************************************************************
! -- * BEFORE INSERT or UPDATE on WSlot
! -- *	- Check that room exists
! -- ************************************************************
! create function tg_wslot_biu() returns trigger as $$
! begin
!     if count(*) = 0 from Room where roomno = new.roomno then
!         raise exception 'Room % does not exist', new.roomno;
!     end if;
!     return new;
! end;
! $$ language plpgsql;
! create trigger tg_wslot_biu before insert or update
!     on WSlot for each row execute procedure tg_wslot_biu();
! -- ************************************************************
! -- * AFTER UPDATE on PField
! -- *	- Let PSlots of this field follow
! -- ************************************************************
! create function tg_pfield_au() returns trigger as '
! begin
!     if new.name != old.name then
!         update PSlot set pfname = new.name where pfname = old.name;
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_pfield_au after update
!     on PField for each row execute procedure tg_pfield_au();
! -- ************************************************************
! -- * AFTER DELETE on PField
! -- *	- Remove all slots of this patchfield
! -- ************************************************************
! create function tg_pfield_ad() returns trigger as '
! begin
!     delete from PSlot where pfname = old.name;
!     return old;
! end;
! ' language plpgsql;
! create trigger tg_pfield_ad after delete
!     on PField for each row execute procedure tg_pfield_ad();
! -- ************************************************************
! -- * BEFORE INSERT or UPDATE on PSlot
! -- *	- Ensure that our patchfield does exist
! -- ************************************************************
! create function tg_pslot_biu() returns trigger as $proc$
! declare
!     pfrec	record;
!     ps          alias for new;
! begin
!     select into pfrec * from PField where name = ps.pfname;
!     if not found then
!         raise exception $$Patchfield "%" does not exist$$, ps.pfname;
!     end if;
!     return ps;
! end;
! $proc$ language plpgsql;
! create trigger tg_pslot_biu before insert or update
!     on PSlot for each row execute procedure tg_pslot_biu();
! -- ************************************************************
! -- * AFTER UPDATE on System
! -- *	- If system name changes let interfaces follow
! -- ************************************************************
! create function tg_system_au() returns trigger as '
! begin
!     if new.name != old.name then
!         update IFace set sysname = new.name where sysname = old.name;
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_system_au after update
!     on System for each row execute procedure tg_system_au();
! -- ************************************************************
! -- * BEFORE INSERT or UPDATE on IFace
! -- *	- set the slotname to IF.sysname.ifname
! -- ************************************************************
! create function tg_iface_biu() returns trigger as $$
! declare
!     sname	text;
!     sysrec	record;
! begin
!     select into sysrec * from system where name = new.sysname;
!     if not found then
!         raise exception $q$system "%" does not exist$q$, new.sysname;
!     end if;
!     sname := 'IF.' || new.sysname;
!     sname := sname || '.';
!     sname := sname || new.ifname;
!     if length(sname) > 20 then
!         raise exception 'IFace slotname "%" too long (20 char max)', sname;
!     end if;
!     new.slotname := sname;
!     return new;
! end;
! $$ language plpgsql;
! create trigger tg_iface_biu before insert or update
!     on IFace for each row execute procedure tg_iface_biu();
! -- ************************************************************
! -- * AFTER INSERT or UPDATE or DELETE on Hub
! -- *	- insert/delete/rename slots as required
! -- ************************************************************
! create function tg_hub_a() returns trigger as '
! declare
!     hname	text;
!     dummy	integer;
! begin
!     if tg_op = ''INSERT'' then
! 	dummy := tg_hub_adjustslots(new.name, 0, new.nslots);
! 	return new;
!     end if;
!     if tg_op = ''UPDATE'' then
! 	if new.name != old.name then
! 	    update HSlot set hubname = new.name where hubname = old.name;
! 	end if;
! 	dummy := tg_hub_adjustslots(new.name, old.nslots, new.nslots);
! 	return new;
!     end if;
!     if tg_op = ''DELETE'' then
! 	dummy := tg_hub_adjustslots(old.name, old.nslots, 0);
! 	return old;
!     end if;
! end;
! ' language plpgsql;
! create trigger tg_hub_a after insert or update or delete
!     on Hub for each row execute procedure tg_hub_a();
! -- ************************************************************
! -- * Support function to add/remove slots of Hub
! -- ************************************************************
! create function tg_hub_adjustslots(hname bpchar,
!                                    oldnslots integer,
!                                    newnslots integer)
! returns integer as '
! begin
!     if newnslots = oldnslots then
!         return 0;
!     end if;
!     if newnslots < oldnslots then
!         delete from HSlot where hubname = hname and slotno > newnslots;
! 	return 0;
!     end if;
!     for i in oldnslots + 1 .. newnslots loop
!         insert into HSlot (slotname, hubname, slotno, slotlink)
! 		values (''HS.dummy'', hname, i, '''');
!     end loop;
!     return 0;
! end
! ' language plpgsql;
! -- Test comments
! COMMENT ON FUNCTION tg_hub_adjustslots_wrong(bpchar, integer, integer) IS 'function with args';
! ERROR:  function tg_hub_adjustslots_wrong(character, integer, integer) does not exist
! COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS 'function with args';
! COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS NULL;
! -- ************************************************************
! -- * BEFORE INSERT or UPDATE on HSlot
! -- *	- prevent from manual manipulation
! -- *	- set the slotname to HS.hubname.slotno
! -- ************************************************************
! create function tg_hslot_biu() returns trigger as '
! declare
!     sname	text;
!     xname	HSlot.slotname%TYPE;
!     hubrec	record;
! begin
!     select into hubrec * from Hub where name = new.hubname;
!     if not found then
!         raise exception ''no manual manipulation of HSlot'';
!     end if;
!     if new.slotno < 1 or new.slotno > hubrec.nslots then
!         raise exception ''no manual manipulation of HSlot'';
!     end if;
!     if tg_op = ''UPDATE'' and new.hubname != old.hubname then
! 	if count(*) > 0 from Hub where name = old.hubname then
! 	    raise exception ''no manual manipulation of HSlot'';
! 	end if;
!     end if;
!     sname := ''HS.'' || trim(new.hubname);
!     sname := sname || ''.'';
!     sname := sname || new.slotno::text;
!     if length(sname) > 20 then
!         raise exception ''HSlot slotname "%" too long (20 char max)'', sname;
!     end if;
!     new.slotname := sname;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_hslot_biu before insert or update
!     on HSlot for each row execute procedure tg_hslot_biu();
! -- ************************************************************
! -- * BEFORE DELETE on HSlot
! -- *	- prevent from manual manipulation
! -- ************************************************************
! create function tg_hslot_bd() returns trigger as '
! declare
!     hubrec	record;
! begin
!     select into hubrec * from Hub where name = old.hubname;
!     if not found then
!         return old;
!     end if;
!     if old.slotno > hubrec.nslots then
!         return old;
!     end if;
!     raise exception ''no manual manipulation of HSlot'';
! end;
! ' language plpgsql;
! create trigger tg_hslot_bd before delete
!     on HSlot for each row execute procedure tg_hslot_bd();
! -- ************************************************************
! -- * BEFORE INSERT on all slots
! -- *	- Check name prefix
! -- ************************************************************
! create function tg_chkslotname() returns trigger as '
! begin
!     if substr(new.slotname, 1, 2) != tg_argv[0] then
!         raise exception ''slotname must begin with %'', tg_argv[0];
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_chkslotname before insert
!     on PSlot for each row execute procedure tg_chkslotname('PS');
! create trigger tg_chkslotname before insert
!     on WSlot for each row execute procedure tg_chkslotname('WS');
! create trigger tg_chkslotname before insert
!     on PLine for each row execute procedure tg_chkslotname('PL');
! create trigger tg_chkslotname before insert
!     on IFace for each row execute procedure tg_chkslotname('IF');
! create trigger tg_chkslotname before insert
!     on PHone for each row execute procedure tg_chkslotname('PH');
! -- ************************************************************
! -- * BEFORE INSERT or UPDATE on all slots with slotlink
! -- *	- Set slotlink to empty string if NULL value given
! -- ************************************************************
! create function tg_chkslotlink() returns trigger as '
! begin
!     if new.slotlink isnull then
!         new.slotlink := '''';
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_chkslotlink before insert or update
!     on PSlot for each row execute procedure tg_chkslotlink();
! create trigger tg_chkslotlink before insert or update
!     on WSlot for each row execute procedure tg_chkslotlink();
! create trigger tg_chkslotlink before insert or update
!     on IFace for each row execute procedure tg_chkslotlink();
! create trigger tg_chkslotlink before insert or update
!     on HSlot for each row execute procedure tg_chkslotlink();
! create trigger tg_chkslotlink before insert or update
!     on PHone for each row execute procedure tg_chkslotlink();
! -- ************************************************************
! -- * BEFORE INSERT or UPDATE on all slots with backlink
! -- *	- Set backlink to empty string if NULL value given
! -- ************************************************************
! create function tg_chkbacklink() returns trigger as '
! begin
!     if new.backlink isnull then
!         new.backlink := '''';
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_chkbacklink before insert or update
!     on PSlot for each row execute procedure tg_chkbacklink();
! create trigger tg_chkbacklink before insert or update
!     on WSlot for each row execute procedure tg_chkbacklink();
! create trigger tg_chkbacklink before insert or update
!     on PLine for each row execute procedure tg_chkbacklink();
! -- ************************************************************
! -- * BEFORE UPDATE on PSlot
! -- *	- do delete/insert instead of update if name changes
! -- ************************************************************
! create function tg_pslot_bu() returns trigger as '
! begin
!     if new.slotname != old.slotname then
!         delete from PSlot where slotname = old.slotname;
! 	insert into PSlot (
! 		    slotname,
! 		    pfname,
! 		    slotlink,
! 		    backlink
! 		) values (
! 		    new.slotname,
! 		    new.pfname,
! 		    new.slotlink,
! 		    new.backlink
! 		);
!         return null;
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_pslot_bu before update
!     on PSlot for each row execute procedure tg_pslot_bu();
! -- ************************************************************
! -- * BEFORE UPDATE on WSlot
! -- *	- do delete/insert instead of update if name changes
! -- ************************************************************
! create function tg_wslot_bu() returns trigger as '
! begin
!     if new.slotname != old.slotname then
!         delete from WSlot where slotname = old.slotname;
! 	insert into WSlot (
! 		    slotname,
! 		    roomno,
! 		    slotlink,
! 		    backlink
! 		) values (
! 		    new.slotname,
! 		    new.roomno,
! 		    new.slotlink,
! 		    new.backlink
! 		);
!         return null;
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_wslot_bu before update
!     on WSlot for each row execute procedure tg_Wslot_bu();
! -- ************************************************************
! -- * BEFORE UPDATE on PLine
! -- *	- do delete/insert instead of update if name changes
! -- ************************************************************
! create function tg_pline_bu() returns trigger as '
! begin
!     if new.slotname != old.slotname then
!         delete from PLine where slotname = old.slotname;
! 	insert into PLine (
! 		    slotname,
! 		    phonenumber,
! 		    comment,
! 		    backlink
! 		) values (
! 		    new.slotname,
! 		    new.phonenumber,
! 		    new.comment,
! 		    new.backlink
! 		);
!         return null;
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_pline_bu before update
!     on PLine for each row execute procedure tg_pline_bu();
! -- ************************************************************
! -- * BEFORE UPDATE on IFace
! -- *	- do delete/insert instead of update if name changes
! -- ************************************************************
! create function tg_iface_bu() returns trigger as '
! begin
!     if new.slotname != old.slotname then
!         delete from IFace where slotname = old.slotname;
! 	insert into IFace (
! 		    slotname,
! 		    sysname,
! 		    ifname,
! 		    slotlink
! 		) values (
! 		    new.slotname,
! 		    new.sysname,
! 		    new.ifname,
! 		    new.slotlink
! 		);
!         return null;
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_iface_bu before update
!     on IFace for each row execute procedure tg_iface_bu();
! -- ************************************************************
! -- * BEFORE UPDATE on HSlot
! -- *	- do delete/insert instead of update if name changes
! -- ************************************************************
! create function tg_hslot_bu() returns trigger as '
! begin
!     if new.slotname != old.slotname or new.hubname != old.hubname then
!         delete from HSlot where slotname = old.slotname;
! 	insert into HSlot (
! 		    slotname,
! 		    hubname,
! 		    slotno,
! 		    slotlink
! 		) values (
! 		    new.slotname,
! 		    new.hubname,
! 		    new.slotno,
! 		    new.slotlink
! 		);
!         return null;
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_hslot_bu before update
!     on HSlot for each row execute procedure tg_hslot_bu();
! -- ************************************************************
! -- * BEFORE UPDATE on PHone
! -- *	- do delete/insert instead of update if name changes
! -- ************************************************************
! create function tg_phone_bu() returns trigger as '
! begin
!     if new.slotname != old.slotname then
!         delete from PHone where slotname = old.slotname;
! 	insert into PHone (
! 		    slotname,
! 		    comment,
! 		    slotlink
! 		) values (
! 		    new.slotname,
! 		    new.comment,
! 		    new.slotlink
! 		);
!         return null;
!     end if;
!     return new;
! end;
! ' language plpgsql;
! create trigger tg_phone_bu before update
!     on PHone for each row execute procedure tg_phone_bu();
! -- ************************************************************
! -- * AFTER INSERT or UPDATE or DELETE on slot with backlink
! -- *	- Ensure that the opponent correctly points back to us
! -- ************************************************************
! create function tg_backlink_a() returns trigger as '
! declare
!     dummy	integer;
! begin
!     if tg_op = ''INSERT'' then
!         if new.backlink != '''' then
! 	    dummy := tg_backlink_set(new.backlink, new.slotname);
! 	end if;
! 	return new;
!     end if;
!     if tg_op = ''UPDATE'' then
!         if new.backlink != old.backlink then
! 	    if old.backlink != '''' then
! 	        dummy := tg_backlink_unset(old.backlink, old.slotname);
! 	    end if;
! 	    if new.backlink != '''' then
! 	        dummy := tg_backlink_set(new.backlink, new.slotname);
! 	    end if;
! 	else
! 	    if new.slotname != old.slotname and new.backlink != '''' then
! 	        dummy := tg_slotlink_set(new.backlink, new.slotname);
! 	    end if;
! 	end if;
! 	return new;
!     end if;
!     if tg_op = ''DELETE'' then
!         if old.backlink != '''' then
! 	    dummy := tg_backlink_unset(old.backlink, old.slotname);
! 	end if;
! 	return old;
!     end if;
! end;
! ' language plpgsql;
! create trigger tg_backlink_a after insert or update or delete
!     on PSlot for each row execute procedure tg_backlink_a('PS');
! create trigger tg_backlink_a after insert or update or delete
!     on WSlot for each row execute procedure tg_backlink_a('WS');
! create trigger tg_backlink_a after insert or update or delete
!     on PLine for each row execute procedure tg_backlink_a('PL');
! -- ************************************************************
! -- * Support function to set the opponents backlink field
! -- * if it does not already point to the requested slot
! -- ************************************************************
! create function tg_backlink_set(myname bpchar, blname bpchar)
! returns integer as '
! declare
!     mytype	char(2);
!     link	char(4);
!     rec		record;
! begin
!     mytype := substr(myname, 1, 2);
!     link := mytype || substr(blname, 1, 2);
!     if link = ''PLPL'' then
!         raise exception
! 		''backlink between two phone lines does not make sense'';
!     end if;
!     if link in (''PLWS'', ''WSPL'') then
!         raise exception
! 		''direct link of phone line to wall slot not permitted'';
!     end if;
!     if mytype = ''PS'' then
!         select into rec * from PSlot where slotname = myname;
! 	if not found then
! 	    raise exception ''% does not exist'', myname;
! 	end if;
! 	if rec.backlink != blname then
! 	    update PSlot set backlink = blname where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''WS'' then
!         select into rec * from WSlot where slotname = myname;
! 	if not found then
! 	    raise exception ''% does not exist'', myname;
! 	end if;
! 	if rec.backlink != blname then
! 	    update WSlot set backlink = blname where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''PL'' then
!         select into rec * from PLine where slotname = myname;
! 	if not found then
! 	    raise exception ''% does not exist'', myname;
! 	end if;
! 	if rec.backlink != blname then
! 	    update PLine set backlink = blname where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     raise exception ''illegal backlink beginning with %'', mytype;
! end;
! ' language plpgsql;
! -- ************************************************************
! -- * Support function to clear out the backlink field if
! -- * it still points to specific slot
! -- ************************************************************
! create function tg_backlink_unset(bpchar, bpchar)
! returns integer as '
! declare
!     myname	alias for $1;
!     blname	alias for $2;
!     mytype	char(2);
!     rec		record;
! begin
!     mytype := substr(myname, 1, 2);
!     if mytype = ''PS'' then
!         select into rec * from PSlot where slotname = myname;
! 	if not found then
! 	    return 0;
! 	end if;
! 	if rec.backlink = blname then
! 	    update PSlot set backlink = '''' where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''WS'' then
!         select into rec * from WSlot where slotname = myname;
! 	if not found then
! 	    return 0;
! 	end if;
! 	if rec.backlink = blname then
! 	    update WSlot set backlink = '''' where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''PL'' then
!         select into rec * from PLine where slotname = myname;
! 	if not found then
! 	    return 0;
! 	end if;
! 	if rec.backlink = blname then
! 	    update PLine set backlink = '''' where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
! end
! ' language plpgsql;
! -- ************************************************************
! -- * AFTER INSERT or UPDATE or DELETE on slot with slotlink
! -- *	- Ensure that the opponent correctly points back to us
! -- ************************************************************
! create function tg_slotlink_a() returns trigger as '
! declare
!     dummy	integer;
! begin
!     if tg_op = ''INSERT'' then
!         if new.slotlink != '''' then
! 	    dummy := tg_slotlink_set(new.slotlink, new.slotname);
! 	end if;
! 	return new;
!     end if;
!     if tg_op = ''UPDATE'' then
!         if new.slotlink != old.slotlink then
! 	    if old.slotlink != '''' then
! 	        dummy := tg_slotlink_unset(old.slotlink, old.slotname);
! 	    end if;
! 	    if new.slotlink != '''' then
! 	        dummy := tg_slotlink_set(new.slotlink, new.slotname);
! 	    end if;
! 	else
! 	    if new.slotname != old.slotname and new.slotlink != '''' then
! 	        dummy := tg_slotlink_set(new.slotlink, new.slotname);
! 	    end if;
! 	end if;
! 	return new;
!     end if;
!     if tg_op = ''DELETE'' then
!         if old.slotlink != '''' then
! 	    dummy := tg_slotlink_unset(old.slotlink, old.slotname);
! 	end if;
! 	return old;
!     end if;
! end;
! ' language plpgsql;
! create trigger tg_slotlink_a after insert or update or delete
!     on PSlot for each row execute procedure tg_slotlink_a('PS');
! create trigger tg_slotlink_a after insert or update or delete
!     on WSlot for each row execute procedure tg_slotlink_a('WS');
! create trigger tg_slotlink_a after insert or update or delete
!     on IFace for each row execute procedure tg_slotlink_a('IF');
! create trigger tg_slotlink_a after insert or update or delete
!     on HSlot for each row execute procedure tg_slotlink_a('HS');
! create trigger tg_slotlink_a after insert or update or delete
!     on PHone for each row execute procedure tg_slotlink_a('PH');
! -- ************************************************************
! -- * Support function to set the opponents slotlink field
! -- * if it does not already point to the requested slot
! -- ************************************************************
! create function tg_slotlink_set(bpchar, bpchar)
! returns integer as '
! declare
!     myname	alias for $1;
!     blname	alias for $2;
!     mytype	char(2);
!     link	char(4);
!     rec		record;
! begin
!     mytype := substr(myname, 1, 2);
!     link := mytype || substr(blname, 1, 2);
!     if link = ''PHPH'' then
!         raise exception
! 		''slotlink between two phones does not make sense'';
!     end if;
!     if link in (''PHHS'', ''HSPH'') then
!         raise exception
! 		''link of phone to hub does not make sense'';
!     end if;
!     if link in (''PHIF'', ''IFPH'') then
!         raise exception
! 		''link of phone to hub does not make sense'';
!     end if;
!     if link in (''PSWS'', ''WSPS'') then
!         raise exception
! 		''slotlink from patchslot to wallslot not permitted'';
!     end if;
!     if mytype = ''PS'' then
!         select into rec * from PSlot where slotname = myname;
! 	if not found then
! 	    raise exception ''% does not exist'', myname;
! 	end if;
! 	if rec.slotlink != blname then
! 	    update PSlot set slotlink = blname where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''WS'' then
!         select into rec * from WSlot where slotname = myname;
! 	if not found then
! 	    raise exception ''% does not exist'', myname;
! 	end if;
! 	if rec.slotlink != blname then
! 	    update WSlot set slotlink = blname where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''IF'' then
!         select into rec * from IFace where slotname = myname;
! 	if not found then
! 	    raise exception ''% does not exist'', myname;
! 	end if;
! 	if rec.slotlink != blname then
! 	    update IFace set slotlink = blname where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''HS'' then
!         select into rec * from HSlot where slotname = myname;
! 	if not found then
! 	    raise exception ''% does not exist'', myname;
! 	end if;
! 	if rec.slotlink != blname then
! 	    update HSlot set slotlink = blname where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''PH'' then
!         select into rec * from PHone where slotname = myname;
! 	if not found then
! 	    raise exception ''% does not exist'', myname;
! 	end if;
! 	if rec.slotlink != blname then
! 	    update PHone set slotlink = blname where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     raise exception ''illegal slotlink beginning with %'', mytype;
! end;
! ' language plpgsql;
! -- ************************************************************
! -- * Support function to clear out the slotlink field if
! -- * it still points to specific slot
! -- ************************************************************
! create function tg_slotlink_unset(bpchar, bpchar)
! returns integer as '
! declare
!     myname	alias for $1;
!     blname	alias for $2;
!     mytype	char(2);
!     rec		record;
! begin
!     mytype := substr(myname, 1, 2);
!     if mytype = ''PS'' then
!         select into rec * from PSlot where slotname = myname;
! 	if not found then
! 	    return 0;
! 	end if;
! 	if rec.slotlink = blname then
! 	    update PSlot set slotlink = '''' where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''WS'' then
!         select into rec * from WSlot where slotname = myname;
! 	if not found then
! 	    return 0;
! 	end if;
! 	if rec.slotlink = blname then
! 	    update WSlot set slotlink = '''' where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''IF'' then
!         select into rec * from IFace where slotname = myname;
! 	if not found then
! 	    return 0;
! 	end if;
! 	if rec.slotlink = blname then
! 	    update IFace set slotlink = '''' where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''HS'' then
!         select into rec * from HSlot where slotname = myname;
! 	if not found then
! 	    return 0;
! 	end if;
! 	if rec.slotlink = blname then
! 	    update HSlot set slotlink = '''' where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
!     if mytype = ''PH'' then
!         select into rec * from PHone where slotname = myname;
! 	if not found then
! 	    return 0;
! 	end if;
! 	if rec.slotlink = blname then
! 	    update PHone set slotlink = '''' where slotname = myname;
! 	end if;
! 	return 0;
!     end if;
! end;
! ' language plpgsql;
! -- ************************************************************
! -- * Describe the backside of a patchfield slot
! -- ************************************************************
! create function pslot_backlink_view(bpchar)
! returns text as '
! <<outer>>
! declare
!     rec		record;
!     bltype	char(2);
!     retval	text;
! begin
!     select into rec * from PSlot where slotname = $1;
!     if not found then
!         return '''';
!     end if;
!     if rec.backlink = '''' then
!         return ''-'';
!     end if;
!     bltype := substr(rec.backlink, 1, 2);
!     if bltype = ''PL'' then
!         declare
! 	    rec		record;
! 	begin
! 	    select into rec * from PLine where slotname = "outer".rec.backlink;
! 	    retval := ''Phone line '' || trim(rec.phonenumber);
! 	    if rec.comment != '''' then
! 	        retval := retval || '' ('';
! 		retval := retval || rec.comment;
! 		retval := retval || '')'';
! 	    end if;
! 	    return retval;
! 	end;
!     end if;
!     if bltype = ''WS'' then
!         select into rec * from WSlot where slotname = rec.backlink;
! 	retval := trim(rec.slotname) || '' in room '';
! 	retval := retval || trim(rec.roomno);
! 	retval := retval || '' -> '';
! 	return retval || wslot_slotlink_view(rec.slotname);
!     end if;
!     return rec.backlink;
! end;
! ' language plpgsql;
! -- ************************************************************
! -- * Describe the front of a patchfield slot
! -- ************************************************************
! create function pslot_slotlink_view(bpchar)
! returns text as '
! declare
!     psrec	record;
!     sltype	char(2);
!     retval	text;
! begin
!     select into psrec * from PSlot where slotname = $1;
!     if not found then
!         return '''';
!     end if;
!     if psrec.slotlink = '''' then
!         return ''-'';
!     end if;
!     sltype := substr(psrec.slotlink, 1, 2);
!     if sltype = ''PS'' then
! 	retval := trim(psrec.slotlink) || '' -> '';
! 	return retval || pslot_backlink_view(psrec.slotlink);
!     end if;
!     if sltype = ''HS'' then
!         retval := comment from Hub H, HSlot HS
! 			where HS.slotname = psrec.slotlink
! 			  and H.name = HS.hubname;
!         retval := retval || '' slot '';
! 	retval := retval || slotno::text from HSlot
! 			where slotname = psrec.slotlink;
! 	return retval;
!     end if;
!     return psrec.slotlink;
! end;
! ' language plpgsql;
! -- ************************************************************
! -- * Describe the front of a wall connector slot
! -- ************************************************************
! create function wslot_slotlink_view(bpchar)
! returns text as '
! declare
!     rec		record;
!     sltype	char(2);
!     retval	text;
! begin
!     select into rec * from WSlot where slotname = $1;
!     if not found then
!         return '''';
!     end if;
!     if rec.slotlink = '''' then
!         return ''-'';
!     end if;
!     sltype := substr(rec.slotlink, 1, 2);
!     if sltype = ''PH'' then
!         select into rec * from PHone where slotname = rec.slotlink;
! 	retval := ''Phone '' || trim(rec.slotname);
! 	if rec.comment != '''' then
! 	    retval := retval || '' ('';
! 	    retval := retval || rec.comment;
! 	    retval := retval || '')'';
! 	end if;
! 	return retval;
!     end if;
!     if sltype = ''IF'' then
! 	declare
! 	    syrow	System%RowType;
! 	    ifrow	IFace%ROWTYPE;
!         begin
! 	    select into ifrow * from IFace where slotname = rec.slotlink;
! 	    select into syrow * from System where name = ifrow.sysname;
! 	    retval := syrow.name || '' IF '';
! 	    retval := retval || ifrow.ifname;
! 	    if syrow.comment != '''' then
! 	        retval := retval || '' ('';
! 		retval := retval || syrow.comment;
! 		retval := retval || '')'';
! 	    end if;
! 	    return retval;
! 	end;
!     end if;
!     return rec.slotlink;
! end;
! ' language plpgsql;
! -- ************************************************************
! -- * View of a patchfield describing backside and patches
! -- ************************************************************
! create view Pfield_v1 as select PF.pfname, PF.slotname,
! 	pslot_backlink_view(PF.slotname) as backside,
! 	pslot_slotlink_view(PF.slotname) as patch
!     from PSlot PF;
! --
! -- First we build the house - so we create the rooms
! --
! insert into Room values ('001', 'Entrance');
! insert into Room values ('002', 'Office');
! insert into Room values ('003', 'Office');
! insert into Room values ('004', 'Technical');
! insert into Room values ('101', 'Office');
! insert into Room values ('102', 'Conference');
! insert into Room values ('103', 'Restroom');
! insert into Room values ('104', 'Technical');
! insert into Room values ('105', 'Office');
! insert into Room values ('106', 'Office');
! --
! -- Second we install the wall connectors
! --
! insert into WSlot values ('WS.001.1a', '001', '', '');
! insert into WSlot values ('WS.001.1b', '001', '', '');
! insert into WSlot values ('WS.001.2a', '001', '', '');
! insert into WSlot values ('WS.001.2b', '001', '', '');
! insert into WSlot values ('WS.001.3a', '001', '', '');
! insert into WSlot values ('WS.001.3b', '001', '', '');
! insert into WSlot values ('WS.002.1a', '002', '', '');
! insert into WSlot values ('WS.002.1b', '002', '', '');
! insert into WSlot values ('WS.002.2a', '002', '', '');
! insert into WSlot values ('WS.002.2b', '002', '', '');
! insert into WSlot values ('WS.002.3a', '002', '', '');
! insert into WSlot values ('WS.002.3b', '002', '', '');
! insert into WSlot values ('WS.003.1a', '003', '', '');
! insert into WSlot values ('WS.003.1b', '003', '', '');
! insert into WSlot values ('WS.003.2a', '003', '', '');
! insert into WSlot values ('WS.003.2b', '003', '', '');
! insert into WSlot values ('WS.003.3a', '003', '', '');
! insert into WSlot values ('WS.003.3b', '003', '', '');
! insert into WSlot values ('WS.101.1a', '101', '', '');
! insert into WSlot values ('WS.101.1b', '101', '', '');
! insert into WSlot values ('WS.101.2a', '101', '', '');
! insert into WSlot values ('WS.101.2b', '101', '', '');
! insert into WSlot values ('WS.101.3a', '101', '', '');
! insert into WSlot values ('WS.101.3b', '101', '', '');
! insert into WSlot values ('WS.102.1a', '102', '', '');
! insert into WSlot values ('WS.102.1b', '102', '', '');
! insert into WSlot values ('WS.102.2a', '102', '', '');
! insert into WSlot values ('WS.102.2b', '102', '', '');
! insert into WSlot values ('WS.102.3a', '102', '', '');
! insert into WSlot values ('WS.102.3b', '102', '', '');
! insert into WSlot values ('WS.105.1a', '105', '', '');
! insert into WSlot values ('WS.105.1b', '105', '', '');
! insert into WSlot values ('WS.105.2a', '105', '', '');
! insert into WSlot values ('WS.105.2b', '105', '', '');
! insert into WSlot values ('WS.105.3a', '105', '', '');
! insert into WSlot values ('WS.105.3b', '105', '', '');
! insert into WSlot values ('WS.106.1a', '106', '', '');
! insert into WSlot values ('WS.106.1b', '106', '', '');
! insert into WSlot values ('WS.106.2a', '106', '', '');
! insert into WSlot values ('WS.106.2b', '106', '', '');
! insert into WSlot values ('WS.106.3a', '106', '', '');
! insert into WSlot values ('WS.106.3b', '106', '', '');
! --
! -- Now create the patch fields and their slots
! --
! insert into PField values ('PF0_1', 'Wallslots basement');
! --
! -- The cables for these will be made later, so they are unconnected for now
! --
! insert into PSlot values ('PS.base.a1', 'PF0_1', '', '');
! insert into PSlot values ('PS.base.a2', 'PF0_1', '', '');
! insert into PSlot values ('PS.base.a3', 'PF0_1', '', '');
! insert into PSlot values ('PS.base.a4', 'PF0_1', '', '');
! insert into PSlot values ('PS.base.a5', 'PF0_1', '', '');
! insert into PSlot values ('PS.base.a6', 'PF0_1', '', '');
! --
! -- These are already wired to the wall connectors
! --
! insert into PSlot values ('PS.base.b1', 'PF0_1', '', 'WS.002.1a');
! insert into PSlot values ('PS.base.b2', 'PF0_1', '', 'WS.002.1b');
! insert into PSlot values ('PS.base.b3', 'PF0_1', '', 'WS.002.2a');
! insert into PSlot values ('PS.base.b4', 'PF0_1', '', 'WS.002.2b');
! insert into PSlot values ('PS.base.b5', 'PF0_1', '', 'WS.002.3a');
! insert into PSlot values ('PS.base.b6', 'PF0_1', '', 'WS.002.3b');
! insert into PSlot values ('PS.base.c1', 'PF0_1', '', 'WS.003.1a');
! insert into PSlot values ('PS.base.c2', 'PF0_1', '', 'WS.003.1b');
! insert into PSlot values ('PS.base.c3', 'PF0_1', '', 'WS.003.2a');
! insert into PSlot values ('PS.base.c4', 'PF0_1', '', 'WS.003.2b');
! insert into PSlot values ('PS.base.c5', 'PF0_1', '', 'WS.003.3a');
! insert into PSlot values ('PS.base.c6', 'PF0_1', '', 'WS.003.3b');
! --
! -- This patchfield will be renamed later into PF0_2 - so its
! -- slots references in pfname should follow
! --
! insert into PField values ('PF0_X', 'Phonelines basement');
! insert into PSlot values ('PS.base.ta1', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.ta2', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.ta3', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.ta4', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.ta5', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.ta6', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.tb1', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.tb2', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.tb3', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.tb4', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.tb5', 'PF0_X', '', '');
! insert into PSlot values ('PS.base.tb6', 'PF0_X', '', '');
! insert into PField values ('PF1_1', 'Wallslots first floor');
! insert into PSlot values ('PS.first.a1', 'PF1_1', '', 'WS.101.1a');
! insert into PSlot values ('PS.first.a2', 'PF1_1', '', 'WS.101.1b');
! insert into PSlot values ('PS.first.a3', 'PF1_1', '', 'WS.101.2a');
! insert into PSlot values ('PS.first.a4', 'PF1_1', '', 'WS.101.2b');
! insert into PSlot values ('PS.first.a5', 'PF1_1', '', 'WS.101.3a');
! insert into PSlot values ('PS.first.a6', 'PF1_1', '', 'WS.101.3b');
! insert into PSlot values ('PS.first.b1', 'PF1_1', '', 'WS.102.1a');
! insert into PSlot values ('PS.first.b2', 'PF1_1', '', 'WS.102.1b');
! insert into PSlot values ('PS.first.b3', 'PF1_1', '', 'WS.102.2a');
! insert into PSlot values ('PS.first.b4', 'PF1_1', '', 'WS.102.2b');
! insert into PSlot values ('PS.first.b5', 'PF1_1', '', 'WS.102.3a');
! insert into PSlot values ('PS.first.b6', 'PF1_1', '', 'WS.102.3b');
! insert into PSlot values ('PS.first.c1', 'PF1_1', '', 'WS.105.1a');
! insert into PSlot values ('PS.first.c2', 'PF1_1', '', 'WS.105.1b');
! insert into PSlot values ('PS.first.c3', 'PF1_1', '', 'WS.105.2a');
! insert into PSlot values ('PS.first.c4', 'PF1_1', '', 'WS.105.2b');
! insert into PSlot values ('PS.first.c5', 'PF1_1', '', 'WS.105.3a');
! insert into PSlot values ('PS.first.c6', 'PF1_1', '', 'WS.105.3b');
! insert into PSlot values ('PS.first.d1', 'PF1_1', '', 'WS.106.1a');
! insert into PSlot values ('PS.first.d2', 'PF1_1', '', 'WS.106.1b');
! insert into PSlot values ('PS.first.d3', 'PF1_1', '', 'WS.106.2a');
! insert into PSlot values ('PS.first.d4', 'PF1_1', '', 'WS.106.2b');
! insert into PSlot values ('PS.first.d5', 'PF1_1', '', 'WS.106.3a');
! insert into PSlot values ('PS.first.d6', 'PF1_1', '', 'WS.106.3b');
! --
! -- Now we wire the wall connectors 1a-2a in room 001 to the
! -- patchfield. In the second update we make an error, and
! -- correct it after
! --
! update PSlot set backlink = 'WS.001.1a' where slotname = 'PS.base.a1';
! update PSlot set backlink = 'WS.001.1b' where slotname = 'PS.base.a3';
! select * from WSlot where roomno = '001' order by slotname;
!        slotname       |  roomno  |       slotlink       |       backlink       
! ----------------------+----------+----------------------+----------------------
!  WS.001.1a            | 001      |                      | PS.base.a1          
!  WS.001.1b            | 001      |                      | PS.base.a3          
!  WS.001.2a            | 001      |                      |                     
!  WS.001.2b            | 001      |                      |                     
!  WS.001.3a            | 001      |                      |                     
!  WS.001.3b            | 001      |                      |                     
! (6 rows)
! 
! select * from PSlot where slotname ~ 'PS.base.a' order by slotname;
!        slotname       | pfname |       slotlink       |       backlink       
! ----------------------+--------+----------------------+----------------------
!  PS.base.a1           | PF0_1  |                      | WS.001.1a           
!  PS.base.a2           | PF0_1  |                      |                     
!  PS.base.a3           | PF0_1  |                      | WS.001.1b           
!  PS.base.a4           | PF0_1  |                      |                     
!  PS.base.a5           | PF0_1  |                      |                     
!  PS.base.a6           | PF0_1  |                      |                     
! (6 rows)
! 
! update PSlot set backlink = 'WS.001.2a' where slotname = 'PS.base.a3';
! select * from WSlot where roomno = '001' order by slotname;
!        slotname       |  roomno  |       slotlink       |       backlink       
! ----------------------+----------+----------------------+----------------------
!  WS.001.1a            | 001      |                      | PS.base.a1          
!  WS.001.1b            | 001      |                      |                     
!  WS.001.2a            | 001      |                      | PS.base.a3          
!  WS.001.2b            | 001      |                      |                     
!  WS.001.3a            | 001      |                      |                     
!  WS.001.3b            | 001      |                      |                     
! (6 rows)
! 
! select * from PSlot where slotname ~ 'PS.base.a' order by slotname;
!        slotname       | pfname |       slotlink       |       backlink       
! ----------------------+--------+----------------------+----------------------
!  PS.base.a1           | PF0_1  |                      | WS.001.1a           
!  PS.base.a2           | PF0_1  |                      |                     
!  PS.base.a3           | PF0_1  |                      | WS.001.2a           
!  PS.base.a4           | PF0_1  |                      |                     
!  PS.base.a5           | PF0_1  |                      |                     
!  PS.base.a6           | PF0_1  |                      |                     
! (6 rows)
! 
! update PSlot set backlink = 'WS.001.1b' where slotname = 'PS.base.a2';
! select * from WSlot where roomno = '001' order by slotname;
!        slotname       |  roomno  |       slotlink       |       backlink       
! ----------------------+----------+----------------------+----------------------
!  WS.001.1a            | 001      |                      | PS.base.a1          
!  WS.001.1b            | 001      |                      | PS.base.a2          
!  WS.001.2a            | 001      |                      | PS.base.a3          
!  WS.001.2b            | 001      |                      |                     
!  WS.001.3a            | 001      |                      |                     
!  WS.001.3b            | 001      |                      |                     
! (6 rows)
! 
! select * from PSlot where slotname ~ 'PS.base.a' order by slotname;
!        slotname       | pfname |       slotlink       |       backlink       
! ----------------------+--------+----------------------+----------------------
!  PS.base.a1           | PF0_1  |                      | WS.001.1a           
!  PS.base.a2           | PF0_1  |                      | WS.001.1b           
!  PS.base.a3           | PF0_1  |                      | WS.001.2a           
!  PS.base.a4           | PF0_1  |                      |                     
!  PS.base.a5           | PF0_1  |                      |                     
!  PS.base.a6           | PF0_1  |                      |                     
! (6 rows)
! 
! --
! -- Same procedure for 2b-3b but this time updating the WSlot instead
! -- of the PSlot. Due to the triggers the result is the same:
! -- WSlot and corresponding PSlot point to each other.
! --
! update WSlot set backlink = 'PS.base.a4' where slotname = 'WS.001.2b';
! update WSlot set backlink = 'PS.base.a6' where slotname = 'WS.001.3a';
! select * from WSlot where roomno = '001' order by slotname;
!        slotname       |  roomno  |       slotlink       |       backlink       
! ----------------------+----------+----------------------+----------------------
!  WS.001.1a            | 001      |                      | PS.base.a1          
!  WS.001.1b            | 001      |                      | PS.base.a2          
!  WS.001.2a            | 001      |                      | PS.base.a3          
!  WS.001.2b            | 001      |                      | PS.base.a4          
!  WS.001.3a            | 001      |                      | PS.base.a6          
!  WS.001.3b            | 001      |                      |                     
! (6 rows)
! 
! select * from PSlot where slotname ~ 'PS.base.a' order by slotname;
!        slotname       | pfname |       slotlink       |       backlink       
! ----------------------+--------+----------------------+----------------------
!  PS.base.a1           | PF0_1  |                      | WS.001.1a           
!  PS.base.a2           | PF0_1  |                      | WS.001.1b           
!  PS.base.a3           | PF0_1  |                      | WS.001.2a           
!  PS.base.a4           | PF0_1  |                      | WS.001.2b           
!  PS.base.a5           | PF0_1  |                      |                     
!  PS.base.a6           | PF0_1  |                      | WS.001.3a           
! (6 rows)
! 
! update WSlot set backlink = 'PS.base.a6' where slotname = 'WS.001.3b';
! select * from WSlot where roomno = '001' order by slotname;
!        slotname       |  roomno  |       slotlink       |       backlink       
! ----------------------+----------+----------------------+----------------------
!  WS.001.1a            | 001      |                      | PS.base.a1          
!  WS.001.1b            | 001      |                      | PS.base.a2          
!  WS.001.2a            | 001      |                      | PS.base.a3          
!  WS.001.2b            | 001      |                      | PS.base.a4          
!  WS.001.3a            | 001      |                      |                     
!  WS.001.3b            | 001      |                      | PS.base.a6          
! (6 rows)
! 
! select * from PSlot where slotname ~ 'PS.base.a' order by slotname;
!        slotname       | pfname |       slotlink       |       backlink       
! ----------------------+--------+----------------------+----------------------
!  PS.base.a1           | PF0_1  |                      | WS.001.1a           
!  PS.base.a2           | PF0_1  |                      | WS.001.1b           
!  PS.base.a3           | PF0_1  |                      | WS.001.2a           
!  PS.base.a4           | PF0_1  |                      | WS.001.2b           
!  PS.base.a5           | PF0_1  |                      |                     
!  PS.base.a6           | PF0_1  |                      | WS.001.3b           
! (6 rows)
! 
! update WSlot set backlink = 'PS.base.a5' where slotname = 'WS.001.3a';
! select * from WSlot where roomno = '001' order by slotname;
!        slotname       |  roomno  |       slotlink       |       backlink       
! ----------------------+----------+----------------------+----------------------
!  WS.001.1a            | 001      |                      | PS.base.a1          
!  WS.001.1b            | 001      |                      | PS.base.a2          
!  WS.001.2a            | 001      |                      | PS.base.a3          
!  WS.001.2b            | 001      |                      | PS.base.a4          
!  WS.001.3a            | 001      |                      | PS.base.a5          
!  WS.001.3b            | 001      |                      | PS.base.a6          
! (6 rows)
! 
! select * from PSlot where slotname ~ 'PS.base.a' order by slotname;
!        slotname       | pfname |       slotlink       |       backlink       
! ----------------------+--------+----------------------+----------------------
!  PS.base.a1           | PF0_1  |                      | WS.001.1a           
!  PS.base.a2           | PF0_1  |                      | WS.001.1b           
!  PS.base.a3           | PF0_1  |                      | WS.001.2a           
!  PS.base.a4           | PF0_1  |                      | WS.001.2b           
!  PS.base.a5           | PF0_1  |                      | WS.001.3a           
!  PS.base.a6           | PF0_1  |                      | WS.001.3b           
! (6 rows)
! 
! insert into PField values ('PF1_2', 'Phonelines first floor');
! insert into PSlot values ('PS.first.ta1', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.ta2', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.ta3', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.ta4', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.ta5', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.ta6', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.tb1', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.tb2', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.tb3', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.tb4', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.tb5', 'PF1_2', '', '');
! insert into PSlot values ('PS.first.tb6', 'PF1_2', '', '');
! --
! -- Fix the wrong name for patchfield PF0_2
! --
! update PField set name = 'PF0_2' where name = 'PF0_X';
! select * from PSlot order by slotname;
!        slotname       | pfname |       slotlink       |       backlink       
! ----------------------+--------+----------------------+----------------------
!  PS.base.a1           | PF0_1  |                      | WS.001.1a           
!  PS.base.a2           | PF0_1  |                      | WS.001.1b           
!  PS.base.a3           | PF0_1  |                      | WS.001.2a           
!  PS.base.a4           | PF0_1  |                      | WS.001.2b           
!  PS.base.a5           | PF0_1  |                      | WS.001.3a           
!  PS.base.a6           | PF0_1  |                      | WS.001.3b           
!  PS.base.b1           | PF0_1  |                      | WS.002.1a           
!  PS.base.b2           | PF0_1  |                      | WS.002.1b           
!  PS.base.b3           | PF0_1  |                      | WS.002.2a           
!  PS.base.b4           | PF0_1  |                      | WS.002.2b           
!  PS.base.b5           | PF0_1  |                      | WS.002.3a           
!  PS.base.b6           | PF0_1  |                      | WS.002.3b           
!  PS.base.c1           | PF0_1  |                      | WS.003.1a           
!  PS.base.c2           | PF0_1  |                      | WS.003.1b           
!  PS.base.c3           | PF0_1  |                      | WS.003.2a           
!  PS.base.c4           | PF0_1  |                      | WS.003.2b           
!  PS.base.c5           | PF0_1  |                      | WS.003.3a           
!  PS.base.c6           | PF0_1  |                      | WS.003.3b           
!  PS.base.ta1          | PF0_2  |                      |                     
!  PS.base.ta2          | PF0_2  |                      |                     
!  PS.base.ta3          | PF0_2  |                      |                     
!  PS.base.ta4          | PF0_2  |                      |                     
!  PS.base.ta5          | PF0_2  |                      |                     
!  PS.base.ta6          | PF0_2  |                      |                     
!  PS.base.tb1          | PF0_2  |                      |                     
!  PS.base.tb2          | PF0_2  |                      |                     
!  PS.base.tb3          | PF0_2  |                      |                     
!  PS.base.tb4          | PF0_2  |                      |                     
!  PS.base.tb5          | PF0_2  |                      |                     
!  PS.base.tb6          | PF0_2  |                      |                     
!  PS.first.a1          | PF1_1  |                      | WS.101.1a           
!  PS.first.a2          | PF1_1  |                      | WS.101.1b           
!  PS.first.a3          | PF1_1  |                      | WS.101.2a           
!  PS.first.a4          | PF1_1  |                      | WS.101.2b           
!  PS.first.a5          | PF1_1  |                      | WS.101.3a           
!  PS.first.a6          | PF1_1  |                      | WS.101.3b           
!  PS.first.b1          | PF1_1  |                      | WS.102.1a           
!  PS.first.b2          | PF1_1  |                      | WS.102.1b           
!  PS.first.b3          | PF1_1  |                      | WS.102.2a           
!  PS.first.b4          | PF1_1  |                      | WS.102.2b           
!  PS.first.b5          | PF1_1  |                      | WS.102.3a           
!  PS.first.b6          | PF1_1  |                      | WS.102.3b           
!  PS.first.c1          | PF1_1  |                      | WS.105.1a           
!  PS.first.c2          | PF1_1  |                      | WS.105.1b           
!  PS.first.c3          | PF1_1  |                      | WS.105.2a           
!  PS.first.c4          | PF1_1  |                      | WS.105.2b           
!  PS.first.c5          | PF1_1  |                      | WS.105.3a           
!  PS.first.c6          | PF1_1  |                      | WS.105.3b           
!  PS.first.d1          | PF1_1  |                      | WS.106.1a           
!  PS.first.d2          | PF1_1  |                      | WS.106.1b           
!  PS.first.d3          | PF1_1  |                      | WS.106.2a           
!  PS.first.d4          | PF1_1  |                      | WS.106.2b           
!  PS.first.d5          | PF1_1  |                      | WS.106.3a           
!  PS.first.d6          | PF1_1  |                      | WS.106.3b           
!  PS.first.ta1         | PF1_2  |                      |                     
!  PS.first.ta2         | PF1_2  |                      |                     
!  PS.first.ta3         | PF1_2  |                      |                     
!  PS.first.ta4         | PF1_2  |                      |                     
!  PS.first.ta5         | PF1_2  |                      |                     
!  PS.first.ta6         | PF1_2  |                      |                     
!  PS.first.tb1         | PF1_2  |                      |                     
!  PS.first.tb2         | PF1_2  |                      |                     
!  PS.first.tb3         | PF1_2  |                      |                     
!  PS.first.tb4         | PF1_2  |                      |                     
!  PS.first.tb5         | PF1_2  |                      |                     
!  PS.first.tb6         | PF1_2  |                      |                     
! (66 rows)
! 
! select * from WSlot order by slotname;
!        slotname       |  roomno  |       slotlink       |       backlink       
! ----------------------+----------+----------------------+----------------------
!  WS.001.1a            | 001      |                      | PS.base.a1          
!  WS.001.1b            | 001      |                      | PS.base.a2          
!  WS.001.2a            | 001      |                      | PS.base.a3          
!  WS.001.2b            | 001      |                      | PS.base.a4          
!  WS.001.3a            | 001      |                      | PS.base.a5          
!  WS.001.3b            | 001      |                      | PS.base.a6          
!  WS.002.1a            | 002      |                      | PS.base.b1          
!  WS.002.1b            | 002      |                      | PS.base.b2          
!  WS.002.2a            | 002      |                      | PS.base.b3          
!  WS.002.2b            | 002      |                      | PS.base.b4          
!  WS.002.3a            | 002      |                      | PS.base.b5          
!  WS.002.3b            | 002      |                      | PS.base.b6          
!  WS.003.1a            | 003      |                      | PS.base.c1          
!  WS.003.1b            | 003      |                      | PS.base.c2          
!  WS.003.2a            | 003      |                      | PS.base.c3          
!  WS.003.2b            | 003      |                      | PS.base.c4          
!  WS.003.3a            | 003      |                      | PS.base.c5          
!  WS.003.3b            | 003      |                      | PS.base.c6          
!  WS.101.1a            | 101      |                      | PS.first.a1         
!  WS.101.1b            | 101      |                      | PS.first.a2         
!  WS.101.2a            | 101      |                      | PS.first.a3         
!  WS.101.2b            | 101      |                      | PS.first.a4         
!  WS.101.3a            | 101      |                      | PS.first.a5         
!  WS.101.3b            | 101      |                      | PS.first.a6         
!  WS.102.1a            | 102      |                      | PS.first.b1         
!  WS.102.1b            | 102      |                      | PS.first.b2         
!  WS.102.2a            | 102      |                      | PS.first.b3         
!  WS.102.2b            | 102      |                      | PS.first.b4         
!  WS.102.3a            | 102      |                      | PS.first.b5         
!  WS.102.3b            | 102      |                      | PS.first.b6         
!  WS.105.1a            | 105      |                      | PS.first.c1         
!  WS.105.1b            | 105      |                      | PS.first.c2         
!  WS.105.2a            | 105      |                      | PS.first.c3         
!  WS.105.2b            | 105      |                      | PS.first.c4         
!  WS.105.3a            | 105      |                      | PS.first.c5         
!  WS.105.3b            | 105      |                      | PS.first.c6         
!  WS.106.1a            | 106      |                      | PS.first.d1         
!  WS.106.1b            | 106      |                      | PS.first.d2         
!  WS.106.2a            | 106      |                      | PS.first.d3         
!  WS.106.2b            | 106      |                      | PS.first.d4         
!  WS.106.3a            | 106      |                      | PS.first.d5         
!  WS.106.3b            | 106      |                      | PS.first.d6         
! (42 rows)
! 
! --
! -- Install the central phone system and create the phone numbers.
! -- They are weired on insert to the patchfields. Again the
! -- triggers automatically tell the PSlots to update their
! -- backlink field.
! --
! insert into PLine values ('PL.001', '-0', 'Central call', 'PS.base.ta1');
! insert into PLine values ('PL.002', '-101', '', 'PS.base.ta2');
! insert into PLine values ('PL.003', '-102', '', 'PS.base.ta3');
! insert into PLine values ('PL.004', '-103', '', 'PS.base.ta5');
! insert into PLine values ('PL.005', '-104', '', 'PS.base.ta6');
! insert into PLine values ('PL.006', '-106', '', 'PS.base.tb2');
! insert into PLine values ('PL.007', '-108', '', 'PS.base.tb3');
! insert into PLine values ('PL.008', '-109', '', 'PS.base.tb4');
! insert into PLine values ('PL.009', '-121', '', 'PS.base.tb5');
! insert into PLine values ('PL.010', '-122', '', 'PS.base.tb6');
! insert into PLine values ('PL.015', '-134', '', 'PS.first.ta1');
! insert into PLine values ('PL.016', '-137', '', 'PS.first.ta3');
! insert into PLine values ('PL.017', '-139', '', 'PS.first.ta4');
! insert into PLine values ('PL.018', '-362', '', 'PS.first.tb1');
! insert into PLine values ('PL.019', '-363', '', 'PS.first.tb2');
! insert into PLine values ('PL.020', '-364', '', 'PS.first.tb3');
! insert into PLine values ('PL.021', '-365', '', 'PS.first.tb5');
! insert into PLine values ('PL.022', '-367', '', 'PS.first.tb6');
! insert into PLine values ('PL.028', '-501', 'Fax entrance', 'PS.base.ta2');
! insert into PLine values ('PL.029', '-502', 'Fax first floor', 'PS.first.ta1');
! --
! -- Buy some phones, plug them into the wall and patch the
! -- phone lines to the corresponding patchfield slots.
! --
! insert into PHone values ('PH.hc001', 'Hicom standard', 'WS.001.1a');
! update PSlot set slotlink = 'PS.base.ta1' where slotname = 'PS.base.a1';
! insert into PHone values ('PH.hc002', 'Hicom standard', 'WS.002.1a');
! update PSlot set slotlink = 'PS.base.ta5' where slotname = 'PS.base.b1';
! insert into PHone values ('PH.hc003', 'Hicom standard', 'WS.002.2a');
! update PSlot set slotlink = 'PS.base.tb2' where slotname = 'PS.base.b3';
! insert into PHone values ('PH.fax001', 'Canon fax', 'WS.001.2a');
! update PSlot set slotlink = 'PS.base.ta2' where slotname = 'PS.base.a3';
! --
! -- Install a hub at one of the patchfields, plug a computers
! -- ethernet interface into the wall and patch it to the hub.
! --
! insert into Hub values ('base.hub1', 'Patchfield PF0_1 hub', 16);
! insert into System values ('orion', 'PC');
! insert into IFace values ('IF', 'orion', 'eth0', 'WS.002.1b');
! update PSlot set slotlink = 'HS.base.hub1.1' where slotname = 'PS.base.b2';
! --
! -- Now we take a look at the patchfield
! --
! select * from PField_v1 where pfname = 'PF0_1' order by slotname;
!  pfname |       slotname       |                         backside                         |                     patch                     
! --------+----------------------+----------------------------------------------------------+-----------------------------------------------
!  PF0_1  | PS.base.a1           | WS.001.1a in room 001 -> Phone PH.hc001 (Hicom standard) | PS.base.ta1 -> Phone line -0 (Central call)
!  PF0_1  | PS.base.a2           | WS.001.1b in room 001 -> -                               | -
!  PF0_1  | PS.base.a3           | WS.001.2a in room 001 -> Phone PH.fax001 (Canon fax)     | PS.base.ta2 -> Phone line -501 (Fax entrance)
!  PF0_1  | PS.base.a4           | WS.001.2b in room 001 -> -                               | -
!  PF0_1  | PS.base.a5           | WS.001.3a in room 001 -> -                               | -
!  PF0_1  | PS.base.a6           | WS.001.3b in room 001 -> -                               | -
!  PF0_1  | PS.base.b1           | WS.002.1a in room 002 -> Phone PH.hc002 (Hicom standard) | PS.base.ta5 -> Phone line -103
!  PF0_1  | PS.base.b2           | WS.002.1b in room 002 -> orion IF eth0 (PC)              | Patchfield PF0_1 hub slot 1
!  PF0_1  | PS.base.b3           | WS.002.2a in room 002 -> Phone PH.hc003 (Hicom standard) | PS.base.tb2 -> Phone line -106
!  PF0_1  | PS.base.b4           | WS.002.2b in room 002 -> -                               | -
!  PF0_1  | PS.base.b5           | WS.002.3a in room 002 -> -                               | -
!  PF0_1  | PS.base.b6           | WS.002.3b in room 002 -> -                               | -
!  PF0_1  | PS.base.c1           | WS.003.1a in room 003 -> -                               | -
!  PF0_1  | PS.base.c2           | WS.003.1b in room 003 -> -                               | -
!  PF0_1  | PS.base.c3           | WS.003.2a in room 003 -> -                               | -
!  PF0_1  | PS.base.c4           | WS.003.2b in room 003 -> -                               | -
!  PF0_1  | PS.base.c5           | WS.003.3a in room 003 -> -                               | -
!  PF0_1  | PS.base.c6           | WS.003.3b in room 003 -> -                               | -
! (18 rows)
! 
! select * from PField_v1 where pfname = 'PF0_2' order by slotname;
!  pfname |       slotname       |            backside            |                                 patch                                  
! --------+----------------------+--------------------------------+------------------------------------------------------------------------
!  PF0_2  | PS.base.ta1          | Phone line -0 (Central call)   | PS.base.a1 -> WS.001.1a in room 001 -> Phone PH.hc001 (Hicom standard)
!  PF0_2  | PS.base.ta2          | Phone line -501 (Fax entrance) | PS.base.a3 -> WS.001.2a in room 001 -> Phone PH.fax001 (Canon fax)
!  PF0_2  | PS.base.ta3          | Phone line -102                | -
!  PF0_2  | PS.base.ta4          | -                              | -
!  PF0_2  | PS.base.ta5          | Phone line -103                | PS.base.b1 -> WS.002.1a in room 002 -> Phone PH.hc002 (Hicom standard)
!  PF0_2  | PS.base.ta6          | Phone line -104                | -
!  PF0_2  | PS.base.tb1          | -                              | -
!  PF0_2  | PS.base.tb2          | Phone line -106                | PS.base.b3 -> WS.002.2a in room 002 -> Phone PH.hc003 (Hicom standard)
!  PF0_2  | PS.base.tb3          | Phone line -108                | -
!  PF0_2  | PS.base.tb4          | Phone line -109                | -
!  PF0_2  | PS.base.tb5          | Phone line -121                | -
!  PF0_2  | PS.base.tb6          | Phone line -122                | -
! (12 rows)
! 
! --
! -- Finally we want errors
! --
! insert into PField values ('PF1_1', 'should fail due to unique index');
! ERROR:  duplicate key value violates unique constraint "pfield_name"
! DETAIL:  Key (name)=(PF1_1) already exists.
! update PSlot set backlink = 'WS.not.there' where slotname = 'PS.base.a1';
! ERROR:  WS.not.there         does not exist
! CONTEXT:  PL/pgSQL function tg_backlink_a() line 17 at assignment
! update PSlot set backlink = 'XX.illegal' where slotname = 'PS.base.a1';
! ERROR:  illegal backlink beginning with XX
! CONTEXT:  PL/pgSQL function tg_backlink_a() line 17 at assignment
! update PSlot set slotlink = 'PS.not.there' where slotname = 'PS.base.a1';
! ERROR:  PS.not.there         does not exist
! CONTEXT:  PL/pgSQL function tg_slotlink_a() line 17 at assignment
! update PSlot set slotlink = 'XX.illegal' where slotname = 'PS.base.a1';
! ERROR:  illegal slotlink beginning with XX
! CONTEXT:  PL/pgSQL function tg_slotlink_a() line 17 at assignment
! insert into HSlot values ('HS', 'base.hub1', 1, '');
! ERROR:  duplicate key value violates unique constraint "hslot_name"
! DETAIL:  Key (slotname)=(HS.base.hub1.1      ) already exists.
! insert into HSlot values ('HS', 'base.hub1', 20, '');
! ERROR:  no manual manipulation of HSlot
! delete from HSlot;
! ERROR:  no manual manipulation of HSlot
! insert into IFace values ('IF', 'notthere', 'eth0', '');
! ERROR:  system "notthere" does not exist
! insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
! ERROR:  IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max)
! --
! -- The following tests are unrelated to the scenario outlined above;
! -- they merely exercise specific parts of PL/pgSQL
! --
! --
! -- Test recursion, per bug report 7-Sep-01
! --
! CREATE FUNCTION recursion_test(int,int) RETURNS text AS '
! DECLARE rslt text;
! BEGIN
!     IF $1 <= 0 THEN
!         rslt = CAST($2 AS TEXT);
!     ELSE
!         rslt = CAST($1 AS TEXT) || '','' || recursion_test($1 - 1, $2);
!     END IF;
!     RETURN rslt;
! END;' LANGUAGE plpgsql;
! SELECT recursion_test(4,3);
!  recursion_test 
! ----------------
!  4,3,2,1,3
! (1 row)
! 
! --
! -- Test the FOUND magic variable
! --
! CREATE TABLE found_test_tbl (a int);
! create function test_found()
!   returns boolean as '
!   declare
!   begin
!   insert into found_test_tbl values (1);
!   if FOUND then
!      insert into found_test_tbl values (2);
!   end if;
! 
!   update found_test_tbl set a = 100 where a = 1;
!   if FOUND then
!     insert into found_test_tbl values (3);
!   end if;
! 
!   delete from found_test_tbl where a = 9999; -- matches no rows
!   if not FOUND then
!     insert into found_test_tbl values (4);
!   end if;
! 
!   for i in 1 .. 10 loop
!     -- no need to do anything
!   end loop;
!   if FOUND then
!     insert into found_test_tbl values (5);
!   end if;
! 
!   -- never executes the loop
!   for i in 2 .. 1 loop
!     -- no need to do anything
!   end loop;
!   if not FOUND then
!     insert into found_test_tbl values (6);
!   end if;
!   return true;
!   end;' language plpgsql;
! select test_found();
!  test_found 
! ------------
!  t
! (1 row)
! 
! select * from found_test_tbl;
!   a  
! -----
!    2
!  100
!    3
!    4
!    5
!    6
! (6 rows)
! 
! --
! -- Test set-returning functions for PL/pgSQL
! --
! create function test_table_func_rec() returns setof found_test_tbl as '
! DECLARE
! 	rec RECORD;
! BEGIN
! 	FOR rec IN select * from found_test_tbl LOOP
! 		RETURN NEXT rec;
! 	END LOOP;
! 	RETURN;
! END;' language plpgsql;
! select * from test_table_func_rec();
!   a  
! -----
!    2
!  100
!    3
!    4
!    5
!    6
! (6 rows)
! 
! create function test_table_func_row() returns setof found_test_tbl as '
! DECLARE
! 	row found_test_tbl%ROWTYPE;
! BEGIN
! 	FOR row IN select * from found_test_tbl LOOP
! 		RETURN NEXT row;
! 	END LOOP;
! 	RETURN;
! END;' language plpgsql;
! select * from test_table_func_row();
!   a  
! -----
!    2
!  100
!    3
!    4
!    5
!    6
! (6 rows)
! 
! create function test_ret_set_scalar(int,int) returns setof int as '
! DECLARE
! 	i int;
! BEGIN
! 	FOR i IN $1 .. $2 LOOP
! 		RETURN NEXT i + 1;
! 	END LOOP;
! 	RETURN;
! END;' language plpgsql;
! select * from test_ret_set_scalar(1,10);
!  test_ret_set_scalar 
! ---------------------
!                    2
!                    3
!                    4
!                    5
!                    6
!                    7
!                    8
!                    9
!                   10
!                   11
! (10 rows)
! 
! create function test_ret_set_rec_dyn(int) returns setof record as '
! DECLARE
! 	retval RECORD;
! BEGIN
! 	IF $1 > 10 THEN
! 		SELECT INTO retval 5, 10, 15;
! 		RETURN NEXT retval;
! 		RETURN NEXT retval;
! 	ELSE
! 		SELECT INTO retval 50, 5::numeric, ''xxx''::text;
! 		RETURN NEXT retval;
! 		RETURN NEXT retval;
! 	END IF;
! 	RETURN;
! END;' language plpgsql;
! SELECT * FROM test_ret_set_rec_dyn(1500) AS (a int, b int, c int);
!  a | b  | c  
! ---+----+----
!  5 | 10 | 15
!  5 | 10 | 15
! (2 rows)
! 
! SELECT * FROM test_ret_set_rec_dyn(5) AS (a int, b numeric, c text);
!  a  | b |  c  
! ----+---+-----
!  50 | 5 | xxx
!  50 | 5 | xxx
! (2 rows)
! 
! create function test_ret_rec_dyn(int) returns record as '
! DECLARE
! 	retval RECORD;
! BEGIN
! 	IF $1 > 10 THEN
! 		SELECT INTO retval 5, 10, 15;
! 		RETURN retval;
! 	ELSE
! 		SELECT INTO retval 50, 5::numeric, ''xxx''::text;
! 		RETURN retval;
! 	END IF;
! END;' language plpgsql;
! SELECT * FROM test_ret_rec_dyn(1500) AS (a int, b int, c int);
!  a | b  | c  
! ---+----+----
!  5 | 10 | 15
! (1 row)
! 
! SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text);
!  a  | b |  c  
! ----+---+-----
!  50 | 5 | xxx
! (1 row)
! 
! --
! -- Test handling of OUT parameters, including polymorphic cases.
! -- Note that RETURN is optional with OUT params; we try both ways.
! --
! -- wrong way to do it:
! create function f1(in i int, out j int) returns int as $$
! begin
!   return i+1;
! end$$ language plpgsql;
! ERROR:  RETURN cannot have a parameter in function with OUT parameters
! LINE 3:   return i+1;
!                  ^
! create function f1(in i int, out j int) as $$
! begin
!   j := i+1;
!   return;
! end$$ language plpgsql;
! select f1(42);
!  f1 
! ----
!  43
! (1 row)
! 
! select * from f1(42);
!  j  
! ----
!  43
! (1 row)
! 
! create or replace function f1(inout i int) as $$
! begin
!   i := i+1;
! end$$ language plpgsql;
! select f1(42);
!  f1 
! ----
!  43
! (1 row)
! 
! select * from f1(42);
!  i  
! ----
!  43
! (1 row)
! 
! drop function f1(int);
! create function f1(in i int, out j int) returns setof int as $$
! begin
!   j := i+1;
!   return next;
!   j := i+2;
!   return next;
!   return;
! end$$ language plpgsql;
! select * from f1(42);
!  j  
! ----
!  43
!  44
! (2 rows)
! 
! drop function f1(int);
! create function f1(in i int, out j int, out k text) as $$
! begin
!   j := i;
!   j := j+1;
!   k := 'foo';
! end$$ language plpgsql;
! select f1(42);
!     f1    
! ----------
!  (43,foo)
! (1 row)
! 
! select * from f1(42);
!  j  |  k  
! ----+-----
!  43 | foo
! (1 row)
! 
! drop function f1(int);
! create function f1(in i int, out j int, out k text) returns setof record as $$
! begin
!   j := i+1;
!   k := 'foo';
!   return next;
!   j := j+1;
!   k := 'foot';
!   return next;
! end$$ language plpgsql;
! select * from f1(42);
!  j  |  k   
! ----+------
!  43 | foo
!  44 | foot
! (2 rows)
! 
! drop function f1(int);
! create function duplic(in i anyelement, out j anyelement, out k anyarray) as $$
! begin
!   j := i;
!   k := array[j,j];
!   return;
! end$$ language plpgsql;
! select * from duplic(42);
!  j  |    k    
! ----+---------
!  42 | {42,42}
! (1 row)
! 
! select * from duplic('foo'::text);
!   j  |     k     
! -----+-----------
!  foo | {foo,foo}
! (1 row)
! 
! drop function duplic(anyelement);
! --
! -- test PERFORM
! --
! create table perform_test (
! 	a	INT,
! 	b	INT
! );
! create function simple_func(int) returns boolean as '
! BEGIN
! 	IF $1 < 20 THEN
! 		INSERT INTO perform_test VALUES ($1, $1 + 10);
! 		RETURN TRUE;
! 	ELSE
! 		RETURN FALSE;
! 	END IF;
! END;' language plpgsql;
! create function perform_test_func() returns void as '
! BEGIN
! 	IF FOUND then
! 		INSERT INTO perform_test VALUES (100, 100);
! 	END IF;
! 
! 	PERFORM simple_func(5);
! 
! 	IF FOUND then
! 		INSERT INTO perform_test VALUES (100, 100);
! 	END IF;
! 
! 	PERFORM simple_func(50);
! 
! 	IF FOUND then
! 		INSERT INTO perform_test VALUES (100, 100);
! 	END IF;
! 
! 	RETURN;
! END;' language plpgsql;
! SELECT perform_test_func();
!  perform_test_func 
! -------------------
!  
! (1 row)
! 
! SELECT * FROM perform_test;
!   a  |  b  
! -----+-----
!    5 |  15
!  100 | 100
!  100 | 100
! (3 rows)
! 
! drop table perform_test;
! --
! -- Test error trapping
! --
! create function trap_zero_divide(int) returns int as $$
! declare x int;
! 	sx smallint;
! begin
! 	begin	-- start a subtransaction
! 		raise notice 'should see this';
! 		x := 100 / $1;
! 		raise notice 'should see this only if % <> 0', $1;
! 		sx := $1;
! 		raise notice 'should see this only if % fits in smallint', $1;
! 		if $1 < 0 then
! 			raise exception '% is less than zero', $1;
! 		end if;
! 	exception
! 		when division_by_zero then
! 			raise notice 'caught division_by_zero';
! 			x := -1;
! 		when NUMERIC_VALUE_OUT_OF_RANGE then
! 			raise notice 'caught numeric_value_out_of_range';
! 			x := -2;
! 	end;
! 	return x;
! end$$ language plpgsql;
! select trap_zero_divide(50);
! NOTICE:  should see this
! NOTICE:  should see this only if 50 <> 0
! NOTICE:  should see this only if 50 fits in smallint
!  trap_zero_divide 
! ------------------
!                 2
! (1 row)
! 
! select trap_zero_divide(0);
! NOTICE:  should see this
! NOTICE:  caught division_by_zero
!  trap_zero_divide 
! ------------------
!                -1
! (1 row)
! 
! select trap_zero_divide(100000);
! NOTICE:  should see this
! NOTICE:  should see this only if 100000 <> 0
! NOTICE:  caught numeric_value_out_of_range
!  trap_zero_divide 
! ------------------
!                -2
! (1 row)
! 
! select trap_zero_divide(-100);
! NOTICE:  should see this
! NOTICE:  should see this only if -100 <> 0
! NOTICE:  should see this only if -100 fits in smallint
! ERROR:  -100 is less than zero
! create function trap_matching_test(int) returns int as $$
! declare x int;
! 	sx smallint;
! 	y int;
! begin
! 	begin	-- start a subtransaction
! 		x := 100 / $1;
! 		sx := $1;
! 		select into y unique1 from tenk1 where unique2 =
! 			(select unique2 from tenk1 b where ten = $1);
! 	exception
! 		when data_exception then  -- category match
! 			raise notice 'caught data_exception';
! 			x := -1;
! 		when NUMERIC_VALUE_OUT_OF_RANGE OR CARDINALITY_VIOLATION then
! 			raise notice 'caught numeric_value_out_of_range or cardinality_violation';
! 			x := -2;
! 	end;
! 	return x;
! end$$ language plpgsql;
! select trap_matching_test(50);
!  trap_matching_test 
! --------------------
!                   2
! (1 row)
! 
! select trap_matching_test(0);
! NOTICE:  caught data_exception
!  trap_matching_test 
! --------------------
!                  -1
! (1 row)
! 
! select trap_matching_test(100000);
! NOTICE:  caught data_exception
!  trap_matching_test 
! --------------------
!                  -1
! (1 row)
! 
! select trap_matching_test(1);
! NOTICE:  caught numeric_value_out_of_range or cardinality_violation
!  trap_matching_test 
! --------------------
!                  -2
! (1 row)
! 
! create temp table foo (f1 int);
! create function blockme() returns int as $$
! declare x int;
! begin
!   x := 1;
!   insert into foo values(x);
!   begin
!     x := x + 1;
!     insert into foo values(x);
!     -- we assume this will take longer than 2 seconds:
!     select count(*) into x from tenk1 a, tenk1 b, tenk1 c;
!   exception
!     when others then
!       raise notice 'caught others?';
!       return -1;
!     when query_canceled then
!       raise notice 'nyeah nyeah, can''t stop me';
!       x := x * 10;
!   end;
!   insert into foo values(x);
!   return x;
! end$$ language plpgsql;
! set statement_timeout to 2000;
! select blockme();
! NOTICE:  nyeah nyeah, can't stop me
!  blockme 
! ---------
!       20
! (1 row)
! 
! reset statement_timeout;
! select * from foo;
!  f1 
! ----
!   1
!  20
! (2 rows)
! 
! drop table foo;
! -- Test for pass-by-ref values being stored in proper context
! create function test_variable_storage() returns text as $$
! declare x text;
! begin
!   x := '1234';
!   begin
!     x := x || '5678';
!     -- force error inside subtransaction SPI context
!     perform trap_zero_divide(-100);
!   exception
!     when others then
!       x := x || '9012';
!   end;
!   return x;
! end$$ language plpgsql;
! select test_variable_storage();
! NOTICE:  should see this
! CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
! PL/pgSQL function test_variable_storage() line 8 at PERFORM
! NOTICE:  should see this only if -100 <> 0
! CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
! PL/pgSQL function test_variable_storage() line 8 at PERFORM
! NOTICE:  should see this only if -100 fits in smallint
! CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
! PL/pgSQL function test_variable_storage() line 8 at PERFORM
!  test_variable_storage 
! -----------------------
!  123456789012
! (1 row)
! 
! --
! -- test foreign key error trapping
! --
! create temp table master(f1 int primary key);
! create temp table slave(f1 int references master deferrable);
! insert into master values(1);
! insert into slave values(1);
! insert into slave values(2);	-- fails
! ERROR:  insert or update on table "slave" violates foreign key constraint "slave_f1_fkey"
! DETAIL:  Key (f1)=(2) is not present in table "master".
! create function trap_foreign_key(int) returns int as $$
! begin
! 	begin	-- start a subtransaction
! 		insert into slave values($1);
! 	exception
! 		when foreign_key_violation then
! 			raise notice 'caught foreign_key_violation';
! 			return 0;
! 	end;
! 	return 1;
! end$$ language plpgsql;
! create function trap_foreign_key_2() returns int as $$
! begin
! 	begin	-- start a subtransaction
! 		set constraints all immediate;
! 	exception
! 		when foreign_key_violation then
! 			raise notice 'caught foreign_key_violation';
! 			return 0;
! 	end;
! 	return 1;
! end$$ language plpgsql;
! select trap_foreign_key(1);
!  trap_foreign_key 
! ------------------
!                 1
! (1 row)
! 
! select trap_foreign_key(2);	-- detects FK violation
! NOTICE:  caught foreign_key_violation
!  trap_foreign_key 
! ------------------
!                 0
! (1 row)
! 
! begin;
!   set constraints all deferred;
!   select trap_foreign_key(2);	-- should not detect FK violation
!  trap_foreign_key 
! ------------------
!                 1
! (1 row)
! 
!   savepoint x;
!     set constraints all immediate; -- fails
! ERROR:  insert or update on table "slave" violates foreign key constraint "slave_f1_fkey"
! DETAIL:  Key (f1)=(2) is not present in table "master".
!   rollback to x;
!   select trap_foreign_key_2();  -- detects FK violation
! NOTICE:  caught foreign_key_violation
!  trap_foreign_key_2 
! --------------------
!                   0
! (1 row)
! 
! commit;				-- still fails
! ERROR:  insert or update on table "slave" violates foreign key constraint "slave_f1_fkey"
! DETAIL:  Key (f1)=(2) is not present in table "master".
! drop function trap_foreign_key(int);
! drop function trap_foreign_key_2();
! --
! -- Test proper snapshot handling in simple expressions
! --
! create temp table users(login text, id serial);
! create function sp_id_user(a_login text) returns int as $$
! declare x int;
! begin
!   select into x id from users where login = a_login;
!   if found then return x; end if;
!   return 0;
! end$$ language plpgsql stable;
! insert into users values('user1');
! select sp_id_user('user1');
!  sp_id_user 
! ------------
!           1
! (1 row)
! 
! select sp_id_user('userx');
!  sp_id_user 
! ------------
!           0
! (1 row)
! 
! create function sp_add_user(a_login text) returns int as $$
! declare my_id_user int;
! begin
!   my_id_user = sp_id_user( a_login );
!   IF  my_id_user > 0 THEN
!     RETURN -1;  -- error code for existing user
!   END IF;
!   INSERT INTO users ( login ) VALUES ( a_login );
!   my_id_user = sp_id_user( a_login );
!   IF  my_id_user = 0 THEN
!     RETURN -2;  -- error code for insertion failure
!   END IF;
!   RETURN my_id_user;
! end$$ language plpgsql;
! select sp_add_user('user1');
!  sp_add_user 
! -------------
!           -1
! (1 row)
! 
! select sp_add_user('user2');
!  sp_add_user 
! -------------
!            2
! (1 row)
! 
! select sp_add_user('user2');
!  sp_add_user 
! -------------
!           -1
! (1 row)
! 
! select sp_add_user('user3');
!  sp_add_user 
! -------------
!            3
! (1 row)
! 
! select sp_add_user('user3');
!  sp_add_user 
! -------------
!           -1
! (1 row)
! 
! drop function sp_add_user(text);
! drop function sp_id_user(text);
! --
! -- tests for refcursors
! --
! create table rc_test (a int, b int);
! copy rc_test from stdin;
! create function return_refcursor(rc refcursor) returns refcursor as $$
! begin
!     open rc for select a from rc_test;
!     return rc;
! end
! $$ language plpgsql;
! create function refcursor_test1(refcursor) returns refcursor as $$
! begin
!     perform return_refcursor($1);
!     return $1;
! end
! $$ language plpgsql;
! begin;
! select refcursor_test1('test1');
!  refcursor_test1 
! -----------------
!  test1
! (1 row)
! 
! fetch next in test1;
!  a 
! ---
!  5
! (1 row)
! 
! select refcursor_test1('test2');
!  refcursor_test1 
! -----------------
!  test2
! (1 row)
! 
! fetch all from test2;
!   a  
! -----
!    5
!   50
!  500
! (3 rows)
! 
! commit;
! -- should fail
! fetch next from test1;
! ERROR:  cursor "test1" does not exist
! create function refcursor_test2(int, int) returns boolean as $$
! declare
!     c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2;
!     nonsense record;
! begin
!     open c1($1, $2);
!     fetch c1 into nonsense;
!     close c1;
!     if found then
!         return true;
!     else
!         return false;
!     end if;
! end
! $$ language plpgsql;
! select refcursor_test2(20000, 20000) as "Should be false",
!        refcursor_test2(20, 20) as "Should be true";
!  Should be false | Should be true 
! -----------------+----------------
!  f               | t
! (1 row)
! 
! --
! -- tests for cursors with named parameter arguments
! --
! create function namedparmcursor_test1(int, int) returns boolean as $$
! declare
!     c1 cursor (param1 int, param12 int) for select * from rc_test where a > param1 and b > param12;
!     nonsense record;
! begin
!     open c1(param12 := $2, param1 := $1);
!     fetch c1 into nonsense;
!     close c1;
!     if found then
!         return true;
!     else
!         return false;
!     end if;
! end
! $$ language plpgsql;
! select namedparmcursor_test1(20000, 20000) as "Should be false",
!        namedparmcursor_test1(20, 20) as "Should be true";
!  Should be false | Should be true 
! -----------------+----------------
!  f               | t
! (1 row)
! 
! -- mixing named and positional argument notations
! create function namedparmcursor_test2(int, int) returns boolean as $$
! declare
!     c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2;
!     nonsense record;
! begin
!     open c1(param1 := $1, $2);
!     fetch c1 into nonsense;
!     close c1;
!     if found then
!         return true;
!     else
!         return false;
!     end if;
! end
! $$ language plpgsql;
! select namedparmcursor_test2(20, 20);
!  namedparmcursor_test2 
! -----------------------
!  t
! (1 row)
! 
! -- mixing named and positional: param2 is given twice, once in named notation
! -- and second time in positional notation. Should throw an error at parse time
! create function namedparmcursor_test3() returns void as $$
! declare
!     c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2;
! begin
!     open c1(param2 := 20, 21);
! end
! $$ language plpgsql;
! ERROR:  value for parameter "param2" of cursor "c1" specified more than once
! LINE 5:     open c1(param2 := 20, 21);
!                                   ^
! -- mixing named and positional: same as previous test, but param1 is duplicated
! create function namedparmcursor_test4() returns void as $$
! declare
!     c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2;
! begin
!     open c1(20, param1 := 21);
! end
! $$ language plpgsql;
! ERROR:  value for parameter "param1" of cursor "c1" specified more than once
! LINE 5:     open c1(20, param1 := 21);
!                         ^
! -- duplicate named parameter, should throw an error at parse time
! create function namedparmcursor_test5() returns void as $$
! declare
!   c1 cursor (p1 int, p2 int) for
!     select * from tenk1 where thousand = p1 and tenthous = p2;
! begin
!   open c1 (p2 := 77, p2 := 42);
! end
! $$ language plpgsql;
! ERROR:  value for parameter "p2" of cursor "c1" specified more than once
! LINE 6:   open c1 (p2 := 77, p2 := 42);
!                              ^
! -- not enough parameters, should throw an error at parse time
! create function namedparmcursor_test6() returns void as $$
! declare
!   c1 cursor (p1 int, p2 int) for
!     select * from tenk1 where thousand = p1 and tenthous = p2;
! begin
!   open c1 (p2 := 77);
! end
! $$ language plpgsql;
! ERROR:  not enough arguments for cursor "c1"
! LINE 6:   open c1 (p2 := 77);
!                            ^
! -- division by zero runtime error, the context given in the error message
! -- should be sensible
! create function namedparmcursor_test7() returns void as $$
! declare
!   c1 cursor (p1 int, p2 int) for
!     select * from tenk1 where thousand = p1 and tenthous = p2;
! begin
!   open c1 (p2 := 77, p1 := 42/0);
! end $$ language plpgsql;
! select namedparmcursor_test7();
! ERROR:  division by zero
! CONTEXT:  SQL statement "SELECT 42/0 AS p1, 77 AS p2;"
! PL/pgSQL function namedparmcursor_test7() line 6 at OPEN
! -- check that line comments work correctly within the argument list (there
! -- is some special handling of this case in the code: the newline after the
! -- comment must be preserved when the argument-evaluating query is
! -- constructed, otherwise the comment effectively comments out the next
! -- argument, too)
! create function namedparmcursor_test8() returns int4 as $$
! declare
!   c1 cursor (p1 int, p2 int) for
!     select count(*) from tenk1 where thousand = p1 and tenthous = p2;
!   n int4;
! begin
!   open c1 (77 -- test
!   , 42);
!   fetch c1 into n;
!   return n;
! end $$ language plpgsql;
! select namedparmcursor_test8();
!  namedparmcursor_test8 
! -----------------------
!                      0
! (1 row)
! 
! -- cursor parameter name can match plpgsql variable or unreserved keyword
! create function namedparmcursor_test9(p1 int) returns int4 as $$
! declare
!   c1 cursor (p1 int, p2 int, debug int) for
!     select count(*) from tenk1 where thousand = p1 and tenthous = p2
!       and four = debug;
!   p2 int4 := 1006;
!   n int4;
! begin
!   open c1 (p1 := p1, p2 := p2, debug := 2);
!   fetch c1 into n;
!   return n;
! end $$ language plpgsql;
! select namedparmcursor_test9(6);
!  namedparmcursor_test9 
! -----------------------
!                      1
! (1 row)
! 
! --
! -- tests for "raise" processing
! --
! create function raise_test1(int) returns int as $$
! begin
!     raise notice 'This message has too many parameters!', $1;
!     return $1;
! end;
! $$ language plpgsql;
! select raise_test1(5);
! ERROR:  too many parameters specified for RAISE
! CONTEXT:  PL/pgSQL function raise_test1(integer) line 3 at RAISE
! create function raise_test2(int) returns int as $$
! begin
!     raise notice 'This message has too few parameters: %, %, %', $1, $1;
!     return $1;
! end;
! $$ language plpgsql;
! select raise_test2(10);
! ERROR:  too few parameters specified for RAISE
! CONTEXT:  PL/pgSQL function raise_test2(integer) line 3 at RAISE
! -- Test re-RAISE inside a nested exception block.  This case is allowed
! -- by Oracle's PL/SQL but was handled differently by PG before 9.1.
! CREATE FUNCTION reraise_test() RETURNS void AS $$
! BEGIN
!    BEGIN
!        RAISE syntax_error;
!    EXCEPTION
!        WHEN syntax_error THEN
!            BEGIN
!                raise notice 'exception % thrown in inner block, reraising', sqlerrm;
!                RAISE;
!            EXCEPTION
!                WHEN OTHERS THEN
!                    raise notice 'RIGHT - exception % caught in inner block', sqlerrm;
!            END;
!    END;
! EXCEPTION
!    WHEN OTHERS THEN
!        raise notice 'WRONG - exception % caught in outer block', sqlerrm;
! END;
! $$ LANGUAGE plpgsql;
! SELECT reraise_test();
! NOTICE:  exception syntax_error thrown in inner block, reraising
! NOTICE:  RIGHT - exception syntax_error caught in inner block
!  reraise_test 
! --------------
!  
! (1 row)
! 
! --
! -- reject function definitions that contain malformed SQL queries at
! -- compile-time, where possible
! --
! create function bad_sql1() returns int as $$
! declare a int;
! begin
!     a := 5;
!     Johnny Yuma;
!     a := 10;
!     return a;
! end$$ language plpgsql;
! ERROR:  syntax error at or near "Johnny"
! LINE 5:     Johnny Yuma;
!             ^
! create function bad_sql2() returns int as $$
! declare r record;
! begin
!     for r in select I fought the law, the law won LOOP
!         raise notice 'in loop';
!     end loop;
!     return 5;
! end;$$ language plpgsql;
! ERROR:  syntax error at or near "the"
! LINE 4:     for r in select I fought the law, the law won LOOP
!                                      ^
! -- a RETURN expression is mandatory, except for void-returning
! -- functions, where it is not allowed
! create function missing_return_expr() returns int as $$
! begin
!     return ;
! end;$$ language plpgsql;
! ERROR:  missing expression at or near ";"
! LINE 3:     return ;
!                    ^
! create function void_return_expr() returns void as $$
! begin
!     return 5;
! end;$$ language plpgsql;
! ERROR:  RETURN cannot have a parameter in function returning void
! LINE 3:     return 5;
!                    ^
! -- VOID functions are allowed to omit RETURN
! create function void_return_expr() returns void as $$
! begin
!     perform 2+2;
! end;$$ language plpgsql;
! select void_return_expr();
!  void_return_expr 
! ------------------
!  
! (1 row)
! 
! -- but ordinary functions are not
! create function missing_return_expr() returns int as $$
! begin
!     perform 2+2;
! end;$$ language plpgsql;
! select missing_return_expr();
! ERROR:  control reached end of function without RETURN
! CONTEXT:  PL/pgSQL function missing_return_expr()
! drop function void_return_expr();
! drop function missing_return_expr();
! --
! -- EXECUTE ... INTO test
! --
! create table eifoo (i integer, y integer);
! create type eitype as (i integer, y integer);
! create or replace function execute_into_test(varchar) returns record as $$
! declare
!     _r record;
!     _rt eifoo%rowtype;
!     _v eitype;
!     i int;
!     j int;
!     k int;
! begin
!     execute 'insert into '||$1||' values(10,15)';
!     execute 'select (row).* from (select row(10,1)::eifoo) s' into _r;
!     raise notice '% %', _r.i, _r.y;
!     execute 'select * from '||$1||' limit 1' into _rt;
!     raise notice '% %', _rt.i, _rt.y;
!     execute 'select *, 20 from '||$1||' limit 1' into i, j, k;
!     raise notice '% % %', i, j, k;
!     execute 'select 1,2' into _v;
!     return _v;
! end; $$ language plpgsql;
! select execute_into_test('eifoo');
! NOTICE:  10 1
! NOTICE:  10 15
! NOTICE:  10 15 20
!  execute_into_test 
! -------------------
!  (1,2)
! (1 row)
! 
! drop table eifoo cascade;
! drop type eitype cascade;
! --
! -- SQLSTATE and SQLERRM test
! --
! create function excpt_test1() returns void as $$
! begin
!     raise notice '% %', sqlstate, sqlerrm;
! end; $$ language plpgsql;
! -- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION
! -- blocks
! select excpt_test1();
! ERROR:  column "sqlstate" does not exist
! LINE 1: SELECT sqlstate
!                ^
! QUERY:  SELECT sqlstate
! CONTEXT:  PL/pgSQL function excpt_test1() line 3 at RAISE
! create function excpt_test2() returns void as $$
! begin
!     begin
!         begin
!             raise notice '% %', sqlstate, sqlerrm;
!         end;
!     end;
! end; $$ language plpgsql;
! -- should fail
! select excpt_test2();
! ERROR:  column "sqlstate" does not exist
! LINE 1: SELECT sqlstate
!                ^
! QUERY:  SELECT sqlstate
! CONTEXT:  PL/pgSQL function excpt_test2() line 5 at RAISE
! create function excpt_test3() returns void as $$
! begin
!     begin
!         raise exception 'user exception';
!     exception when others then
! 	    raise notice 'caught exception % %', sqlstate, sqlerrm;
! 	    begin
! 	        raise notice '% %', sqlstate, sqlerrm;
! 	        perform 10/0;
!         exception
!             when substring_error then
!                 -- this exception handler shouldn't be invoked
!                 raise notice 'unexpected exception: % %', sqlstate, sqlerrm;
! 	        when division_by_zero then
! 	            raise notice 'caught exception % %', sqlstate, sqlerrm;
! 	    end;
! 	    raise notice '% %', sqlstate, sqlerrm;
!     end;
! end; $$ language plpgsql;
! select excpt_test3();
! NOTICE:  caught exception P0001 user exception
! NOTICE:  P0001 user exception
! NOTICE:  caught exception 22012 division by zero
! NOTICE:  P0001 user exception
!  excpt_test3 
! -------------
!  
! (1 row)
! 
! drop function excpt_test1();
! drop function excpt_test2();
! drop function excpt_test3();
! -- parameters of raise stmt can be expressions
! create function raise_exprs() returns void as $$
! declare
!     a integer[] = '{10,20,30}';
!     c varchar = 'xyz';
!     i integer;
! begin
!     i := 2;
!     raise notice '%; %; %; %; %; %', a, a[i], c, (select c || 'abc'), row(10,'aaa',NULL,30), NULL;
! end;$$ language plpgsql;
! select raise_exprs();
! NOTICE:  {10,20,30}; 20; xyz; xyzabc; (10,aaa,,30); <NULL>
!  raise_exprs 
! -------------
!  
! (1 row)
! 
! drop function raise_exprs();
! -- continue statement
! create table conttesttbl(idx serial, v integer);
! insert into conttesttbl(v) values(10);
! insert into conttesttbl(v) values(20);
! insert into conttesttbl(v) values(30);
! insert into conttesttbl(v) values(40);
! create function continue_test1() returns void as $$
! declare _i integer = 0; _r record;
! begin
!   raise notice '---1---';
!   loop
!     _i := _i + 1;
!     raise notice '%', _i;
!     continue when _i < 10;
!     exit;
!   end loop;
! 
!   raise notice '---2---';
!   <<lbl>>
!   loop
!     _i := _i - 1;
!     loop
!       raise notice '%', _i;
!       continue lbl when _i > 0;
!       exit lbl;
!     end loop;
!   end loop;
! 
!   raise notice '---3---';
!   <<the_loop>>
!   while _i < 10 loop
!     _i := _i + 1;
!     continue the_loop when _i % 2 = 0;
!     raise notice '%', _i;
!   end loop;
! 
!   raise notice '---4---';
!   for _i in 1..10 loop
!     begin
!       -- applies to outer loop, not the nested begin block
!       continue when _i < 5;
!       raise notice '%', _i;
!     end;
!   end loop;
! 
!   raise notice '---5---';
!   for _r in select * from conttesttbl loop
!     continue when _r.v <= 20;
!     raise notice '%', _r.v;
!   end loop;
! 
!   raise notice '---6---';
!   for _r in execute 'select * from conttesttbl' loop
!     continue when _r.v <= 20;
!     raise notice '%', _r.v;
!   end loop;
! 
!   raise notice '---7---';
!   for _i in 1..3 loop
!     raise notice '%', _i;
!     continue when _i = 3;
!   end loop;
! 
!   raise notice '---8---';
!   _i := 1;
!   while _i <= 3 loop
!     raise notice '%', _i;
!     _i := _i + 1;
!     continue when _i = 3;
!   end loop;
! 
!   raise notice '---9---';
!   for _r in select * from conttesttbl order by v limit 1 loop
!     raise notice '%', _r.v;
!     continue;
!   end loop;
! 
!   raise notice '---10---';
!   for _r in execute 'select * from conttesttbl order by v limit 1' loop
!     raise notice '%', _r.v;
!     continue;
!   end loop;
! end; $$ language plpgsql;
! select continue_test1();
! NOTICE:  ---1---
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  4
! NOTICE:  5
! NOTICE:  6
! NOTICE:  7
! NOTICE:  8
! NOTICE:  9
! NOTICE:  10
! NOTICE:  ---2---
! NOTICE:  9
! NOTICE:  8
! NOTICE:  7
! NOTICE:  6
! NOTICE:  5
! NOTICE:  4
! NOTICE:  3
! NOTICE:  2
! NOTICE:  1
! NOTICE:  0
! NOTICE:  ---3---
! NOTICE:  1
! NOTICE:  3
! NOTICE:  5
! NOTICE:  7
! NOTICE:  9
! NOTICE:  ---4---
! NOTICE:  5
! NOTICE:  6
! NOTICE:  7
! NOTICE:  8
! NOTICE:  9
! NOTICE:  10
! NOTICE:  ---5---
! NOTICE:  30
! NOTICE:  40
! NOTICE:  ---6---
! NOTICE:  30
! NOTICE:  40
! NOTICE:  ---7---
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  ---8---
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  ---9---
! NOTICE:  10
! NOTICE:  ---10---
! NOTICE:  10
!  continue_test1 
! ----------------
!  
! (1 row)
! 
! -- CONTINUE is only legal inside a loop
! create function continue_test2() returns void as $$
! begin
!     begin
!         continue;
!     end;
!     return;
! end;
! $$ language plpgsql;
! -- should fail
! select continue_test2();
! ERROR:  CONTINUE cannot be used outside a loop
! CONTEXT:  PL/pgSQL function continue_test2()
! -- CONTINUE can't reference the label of a named block
! create function continue_test3() returns void as $$
! begin
!     <<begin_block1>>
!     begin
!         loop
!             continue begin_block1;
!         end loop;
!     end;
! end;
! $$ language plpgsql;
! -- should fail
! select continue_test3();
! ERROR:  CONTINUE cannot be used outside a loop
! CONTEXT:  PL/pgSQL function continue_test3()
! drop function continue_test1();
! drop function continue_test2();
! drop function continue_test3();
! drop table conttesttbl;
! -- verbose end block and end loop
! create function end_label1() returns void as $$
! <<blbl>>
! begin
!   <<flbl1>>
!   for _i in 1 .. 10 loop
!     exit flbl1;
!   end loop flbl1;
!   <<flbl2>>
!   for _i in 1 .. 10 loop
!     exit flbl2;
!   end loop;
! end blbl;
! $$ language plpgsql;
! select end_label1();
!  end_label1 
! ------------
!  
! (1 row)
! 
! drop function end_label1();
! -- should fail: undefined end label
! create function end_label2() returns void as $$
! begin
!   for _i in 1 .. 10 loop
!     exit;
!   end loop flbl1;
! end;
! $$ language plpgsql;
! ERROR:  label does not exist at or near "flbl1"
! LINE 5:   end loop flbl1;
!                    ^
! -- should fail: end label does not match start label
! create function end_label3() returns void as $$
! <<outer_label>>
! begin
!   <<inner_label>>
!   for _i in 1 .. 10 loop
!     exit;
!   end loop outer_label;
! end;
! $$ language plpgsql;
! ERROR:  end label "outer_label" differs from block's label "inner_label"
! LINE 7:   end loop outer_label;
!                    ^
! -- should fail: end label on a block without a start label
! create function end_label4() returns void as $$
! <<outer_label>>
! begin
!   for _i in 1 .. 10 loop
!     exit;
!   end loop outer_label;
! end;
! $$ language plpgsql;
! ERROR:  end label "outer_label" specified for unlabelled block
! LINE 6:   end loop outer_label;
!                    ^
! -- using list of scalars in fori and fore stmts
! create function for_vect() returns void as $proc$
! <<lbl>>declare a integer; b varchar; c varchar; r record;
! begin
!   -- fori
!   for i in 1 .. 3 loop
!     raise notice '%', i;
!   end loop;
!   -- fore with record var
!   for r in select gs as aa, 'BB' as bb, 'CC' as cc from generate_series(1,4) gs loop
!     raise notice '% % %', r.aa, r.bb, r.cc;
!   end loop;
!   -- fore with single scalar
!   for a in select gs from generate_series(1,4) gs loop
!     raise notice '%', a;
!   end loop;
!   -- fore with multiple scalars
!   for a,b,c in select gs, 'BB','CC' from generate_series(1,4) gs loop
!     raise notice '% % %', a, b, c;
!   end loop;
!   -- using qualified names in fors, fore is enabled, disabled only for fori
!   for lbl.a, lbl.b, lbl.c in execute $$select gs, 'bb','cc' from generate_series(1,4) gs$$ loop
!     raise notice '% % %', a, b, c;
!   end loop;
! end;
! $proc$ language plpgsql;
! select for_vect();
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  1 BB CC
! NOTICE:  2 BB CC
! NOTICE:  3 BB CC
! NOTICE:  4 BB CC
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  4
! NOTICE:  1 BB CC
! NOTICE:  2 BB CC
! NOTICE:  3 BB CC
! NOTICE:  4 BB CC
! NOTICE:  1 bb cc
! NOTICE:  2 bb cc
! NOTICE:  3 bb cc
! NOTICE:  4 bb cc
!  for_vect 
! ----------
!  
! (1 row)
! 
! -- regression test: verify that multiple uses of same plpgsql datum within
! -- a SQL command all get mapped to the same $n parameter.  The return value
! -- of the SELECT is not important, we only care that it doesn't fail with
! -- a complaint about an ungrouped column reference.
! create function multi_datum_use(p1 int) returns bool as $$
! declare
!   x int;
!   y int;
! begin
!   select into x,y unique1/p1, unique1/$1 from tenk1 group by unique1/p1;
!   return x = y;
! end$$ language plpgsql;
! select multi_datum_use(42);
!  multi_datum_use 
! -----------------
!  t
! (1 row)
! 
! --
! -- Test STRICT limiter in both planned and EXECUTE invocations.
! -- Note that a data-modifying query is quasi strict (disallow multi rows)
! -- by default in the planned case, but not in EXECUTE.
! --
! create temp table foo (f1 int, f2 int);
! insert into foo values (1,2), (3,4);
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- should work
!   insert into foo values(5,6) returning * into x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! NOTICE:  x.f1 = 5, x.f2 = 6
!  footest 
! ---------
!  
! (1 row)
! 
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- should fail due to implicit strict
!   insert into foo values(7,8),(9,10) returning * into x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function footest() line 5 at SQL statement
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- should work
!   execute 'insert into foo values(5,6) returning *' into x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! NOTICE:  x.f1 = 5, x.f2 = 6
!  footest 
! ---------
!  
! (1 row)
! 
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- this should work since EXECUTE isn't as picky
!   execute 'insert into foo values(7,8),(9,10) returning *' into x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! NOTICE:  x.f1 = 7, x.f2 = 8
!  footest 
! ---------
!  
! (1 row)
! 
! select * from foo;
!  f1 | f2 
! ----+----
!   1 |  2
!   3 |  4
!   5 |  6
!   5 |  6
!   7 |  8
!   9 | 10
! (6 rows)
! 
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- should work
!   select * from foo where f1 = 3 into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! NOTICE:  x.f1 = 3, x.f2 = 4
!  footest 
! ---------
!  
! (1 row)
! 
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- should fail, no rows
!   select * from foo where f1 = 0 into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned no rows
! CONTEXT:  PL/pgSQL function footest() line 5 at SQL statement
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- should fail, too many rows
!   select * from foo where f1 > 3 into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function footest() line 5 at SQL statement
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- should work
!   execute 'select * from foo where f1 = 3' into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! NOTICE:  x.f1 = 3, x.f2 = 4
!  footest 
! ---------
!  
! (1 row)
! 
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- should fail, no rows
!   execute 'select * from foo where f1 = 0' into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned no rows
! CONTEXT:  PL/pgSQL function footest() line 5 at EXECUTE statement
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- should fail, too many rows
!   execute 'select * from foo where f1 > 3' into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function footest() line 5 at EXECUTE statement
! drop function footest();
! -- test printing parameters after failure due to STRICT
! set plpgsql.print_strict_params to true;
! create or replace function footest() returns void as $$
! declare
! x record;
! p1 int := 2;
! p3 text := 'foo';
! begin
!   -- no rows
!   select * from foo where f1 = p1 and f1::text = p3 into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned no rows
! DETAIL:  parameters: p1 = '2', p3 = 'foo'
! CONTEXT:  PL/pgSQL function footest() line 8 at SQL statement
! create or replace function footest() returns void as $$
! declare
! x record;
! p1 int := 2;
! p3 text := 'foo';
! begin
!   -- too many rows
!   select * from foo where f1 > p1 or f1::text = p3  into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned more than one row
! DETAIL:  parameters: p1 = '2', p3 = 'foo'
! CONTEXT:  PL/pgSQL function footest() line 8 at SQL statement
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- too many rows, no params
!   select * from foo where f1 > 3 into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function footest() line 5 at SQL statement
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- no rows
!   execute 'select * from foo where f1 = $1 or f1::text = $2' using 0, 'foo' into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned no rows
! DETAIL:  parameters: $1 = '0', $2 = 'foo'
! CONTEXT:  PL/pgSQL function footest() line 5 at EXECUTE statement
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- too many rows
!   execute 'select * from foo where f1 > $1' using 1 into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned more than one row
! DETAIL:  parameters: $1 = '1'
! CONTEXT:  PL/pgSQL function footest() line 5 at EXECUTE statement
! create or replace function footest() returns void as $$
! declare x record;
! begin
!   -- too many rows, no parameters
!   execute 'select * from foo where f1 > 3' into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function footest() line 5 at EXECUTE statement
! create or replace function footest() returns void as $$
! -- override the global
! #print_strict_params off
! declare
! x record;
! p1 int := 2;
! p3 text := 'foo';
! begin
!   -- too many rows
!   select * from foo where f1 > p1 or f1::text = p3  into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function footest() line 10 at SQL statement
! reset plpgsql.print_strict_params;
! create or replace function footest() returns void as $$
! -- override the global
! #print_strict_params on
! declare
! x record;
! p1 int := 2;
! p3 text := 'foo';
! begin
!   -- too many rows
!   select * from foo where f1 > p1 or f1::text = p3  into strict x;
!   raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
! end$$ language plpgsql;
! select footest();
! ERROR:  query returned more than one row
! DETAIL:  parameters: p1 = '2', p3 = 'foo'
! CONTEXT:  PL/pgSQL function footest() line 10 at SQL statement
! -- test scrollable cursor support
! create function sc_test() returns setof integer as $$
! declare
!   c scroll cursor for select f1 from int4_tbl;
!   x integer;
! begin
!   open c;
!   fetch last from c into x;
!   while found loop
!     return next x;
!     fetch prior from c into x;
!   end loop;
!   close c;
! end;
! $$ language plpgsql;
! select * from sc_test();
!    sc_test   
! -------------
!  -2147483647
!   2147483647
!      -123456
!       123456
!            0
! (5 rows)
! 
! create or replace function sc_test() returns setof integer as $$
! declare
!   c no scroll cursor for select f1 from int4_tbl;
!   x integer;
! begin
!   open c;
!   fetch last from c into x;
!   while found loop
!     return next x;
!     fetch prior from c into x;
!   end loop;
!   close c;
! end;
! $$ language plpgsql;
! select * from sc_test();  -- fails because of NO SCROLL specification
! ERROR:  cursor can only scan forward
! HINT:  Declare it with SCROLL option to enable backward scan.
! CONTEXT:  PL/pgSQL function sc_test() line 7 at FETCH
! create or replace function sc_test() returns setof integer as $$
! declare
!   c refcursor;
!   x integer;
! begin
!   open c scroll for select f1 from int4_tbl;
!   fetch last from c into x;
!   while found loop
!     return next x;
!     fetch prior from c into x;
!   end loop;
!   close c;
! end;
! $$ language plpgsql;
! select * from sc_test();
!    sc_test   
! -------------
!  -2147483647
!   2147483647
!      -123456
!       123456
!            0
! (5 rows)
! 
! create or replace function sc_test() returns setof integer as $$
! declare
!   c refcursor;
!   x integer;
! begin
!   open c scroll for execute 'select f1 from int4_tbl';
!   fetch last from c into x;
!   while found loop
!     return next x;
!     fetch relative -2 from c into x;
!   end loop;
!   close c;
! end;
! $$ language plpgsql;
! select * from sc_test();
!    sc_test   
! -------------
!  -2147483647
!      -123456
!            0
! (3 rows)
! 
! create or replace function sc_test() returns setof integer as $$
! declare
!   c refcursor;
!   x integer;
! begin
!   open c scroll for execute 'select f1 from int4_tbl';
!   fetch last from c into x;
!   while found loop
!     return next x;
!     move backward 2 from c;
!     fetch relative -1 from c into x;
!   end loop;
!   close c;
! end;
! $$ language plpgsql;
! select * from sc_test();
!    sc_test   
! -------------
!  -2147483647
!       123456
! (2 rows)
! 
! create or replace function sc_test() returns setof integer as $$
! declare
!   c cursor for select * from generate_series(1, 10);
!   x integer;
! begin
!   open c;
!   loop
!       move relative 2 in c;
!       if not found then
!           exit;
!       end if;
!       fetch next from c into x;
!       if found then
!           return next x;
!       end if;
!   end loop;
!   close c;
! end;
! $$ language plpgsql;
! select * from sc_test();
!  sc_test 
! ---------
!        3
!        6
!        9
! (3 rows)
! 
! create or replace function sc_test() returns setof integer as $$
! declare
!   c cursor for select * from generate_series(1, 10);
!   x integer;
! begin
!   open c;
!   move forward all in c;
!   fetch backward from c into x;
!   if found then
!     return next x;
!   end if;
!   close c;
! end;
! $$ language plpgsql;
! select * from sc_test();
!  sc_test 
! ---------
!       10
! (1 row)
! 
! drop function sc_test();
! -- test qualified variable names
! create function pl_qual_names (param1 int) returns void as $$
! <<outerblock>>
! declare
!   param1 int := 1;
! begin
!   <<innerblock>>
!   declare
!     param1 int := 2;
!   begin
!     raise notice 'param1 = %', param1;
!     raise notice 'pl_qual_names.param1 = %', pl_qual_names.param1;
!     raise notice 'outerblock.param1 = %', outerblock.param1;
!     raise notice 'innerblock.param1 = %', innerblock.param1;
!   end;
! end;
! $$ language plpgsql;
! select pl_qual_names(42);
! NOTICE:  param1 = 2
! NOTICE:  pl_qual_names.param1 = 42
! NOTICE:  outerblock.param1 = 1
! NOTICE:  innerblock.param1 = 2
!  pl_qual_names 
! ---------------
!  
! (1 row)
! 
! drop function pl_qual_names(int);
! -- tests for RETURN QUERY
! create function ret_query1(out int, out int) returns setof record as $$
! begin
!     $1 := -1;
!     $2 := -2;
!     return next;
!     return query select x + 1, x * 10 from generate_series(0, 10) s (x);
!     return next;
! end;
! $$ language plpgsql;
! select * from ret_query1();
!  column1 | column2 
! ---------+---------
!       -1 |      -2
!        1 |       0
!        2 |      10
!        3 |      20
!        4 |      30
!        5 |      40
!        6 |      50
!        7 |      60
!        8 |      70
!        9 |      80
!       10 |      90
!       11 |     100
!       -1 |      -2
! (13 rows)
! 
! create type record_type as (x text, y int, z boolean);
! create or replace function ret_query2(lim int) returns setof record_type as $$
! begin
!     return query select md5(s.x::text), s.x, s.x > 0
!                  from generate_series(-8, lim) s (x) where s.x % 2 = 0;
! end;
! $$ language plpgsql;
! select * from ret_query2(8);
!                 x                 | y  | z 
! ----------------------------------+----+---
!  a8d2ec85eaf98407310b72eb73dda247 | -8 | f
!  596a3d04481816330f07e4f97510c28f | -6 | f
!  0267aaf632e87a63288a08331f22c7c3 | -4 | f
!  5d7b9adcbe1c629ec722529dd12e5129 | -2 | f
!  cfcd208495d565ef66e7dff9f98764da |  0 | f
!  c81e728d9d4c2f636f067f89cc14862c |  2 | t
!  a87ff679a2f3e71d9181a67b7542122c |  4 | t
!  1679091c5a880faf6fb5e6087eb1b2dc |  6 | t
!  c9f0f895fb98ab9159f51fd0297e236d |  8 | t
! (9 rows)
! 
! -- test EXECUTE USING
! create function exc_using(int, text) returns int as $$
! declare i int;
! begin
!   for i in execute 'select * from generate_series(1,$1)' using $1+1 loop
!     raise notice '%', i;
!   end loop;
!   execute 'select $2 + $2*3 + length($1)' into i using $2,$1;
!   return i;
! end
! $$ language plpgsql;
! select exc_using(5, 'foobar');
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  4
! NOTICE:  5
! NOTICE:  6
!  exc_using 
! -----------
!         26
! (1 row)
! 
! drop function exc_using(int, text);
! create or replace function exc_using(int) returns void as $$
! declare
!   c refcursor;
!   i int;
! begin
!   open c for execute 'select * from generate_series(1,$1)' using $1+1;
!   loop
!     fetch c into i;
!     exit when not found;
!     raise notice '%', i;
!   end loop;
!   close c;
!   return;
! end;
! $$ language plpgsql;
! select exc_using(5);
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  4
! NOTICE:  5
! NOTICE:  6
!  exc_using 
! -----------
!  
! (1 row)
! 
! drop function exc_using(int);
! -- test FOR-over-cursor
! create or replace function forc01() returns void as $$
! declare
!   c cursor(r1 integer, r2 integer)
!        for select * from generate_series(r1,r2) i;
!   c2 cursor
!        for select * from generate_series(41,43) i;
! begin
!   for r in c(5,7) loop
!     raise notice '% from %', r.i, c;
!   end loop;
!   -- again, to test if cursor was closed properly
!   for r in c(9,10) loop
!     raise notice '% from %', r.i, c;
!   end loop;
!   -- and test a parameterless cursor
!   for r in c2 loop
!     raise notice '% from %', r.i, c2;
!   end loop;
!   -- and try it with a hand-assigned name
!   raise notice 'after loop, c2 = %', c2;
!   c2 := 'special_name';
!   for r in c2 loop
!     raise notice '% from %', r.i, c2;
!   end loop;
!   raise notice 'after loop, c2 = %', c2;
!   -- and try it with a generated name
!   -- (which we can't show in the output because it's variable)
!   c2 := null;
!   for r in c2 loop
!     raise notice '%', r.i;
!   end loop;
!   raise notice 'after loop, c2 = %', c2;
!   return;
! end;
! $$ language plpgsql;
! select forc01();
! NOTICE:  5 from c
! NOTICE:  6 from c
! NOTICE:  7 from c
! NOTICE:  9 from c
! NOTICE:  10 from c
! NOTICE:  41 from c2
! NOTICE:  42 from c2
! NOTICE:  43 from c2
! NOTICE:  after loop, c2 = c2
! NOTICE:  41 from special_name
! NOTICE:  42 from special_name
! NOTICE:  43 from special_name
! NOTICE:  after loop, c2 = special_name
! NOTICE:  41
! NOTICE:  42
! NOTICE:  43
! NOTICE:  after loop, c2 = <NULL>
!  forc01 
! --------
!  
! (1 row)
! 
! -- try updating the cursor's current row
! create temp table forc_test as
!   select n as i, n as j from generate_series(1,10) n;
! create or replace function forc01() returns void as $$
! declare
!   c cursor for select * from forc_test;
! begin
!   for r in c loop
!     raise notice '%, %', r.i, r.j;
!     update forc_test set i = i * 100, j = r.j * 2 where current of c;
!   end loop;
! end;
! $$ language plpgsql;
! select forc01();
! NOTICE:  1, 1
! NOTICE:  2, 2
! NOTICE:  3, 3
! NOTICE:  4, 4
! NOTICE:  5, 5
! NOTICE:  6, 6
! NOTICE:  7, 7
! NOTICE:  8, 8
! NOTICE:  9, 9
! NOTICE:  10, 10
!  forc01 
! --------
!  
! (1 row)
! 
! select * from forc_test;
!   i   | j  
! ------+----
!   100 |  2
!   200 |  4
!   300 |  6
!   400 |  8
!   500 | 10
!   600 | 12
!   700 | 14
!   800 | 16
!   900 | 18
!  1000 | 20
! (10 rows)
! 
! -- same, with a cursor whose portal name doesn't match variable name
! create or replace function forc01() returns void as $$
! declare
!   c refcursor := 'fooled_ya';
!   r record;
! begin
!   open c for select * from forc_test;
!   loop
!     fetch c into r;
!     exit when not found;
!     raise notice '%, %', r.i, r.j;
!     update forc_test set i = i * 100, j = r.j * 2 where current of c;
!   end loop;
! end;
! $$ language plpgsql;
! select forc01();
! NOTICE:  100, 2
! NOTICE:  200, 4
! NOTICE:  300, 6
! NOTICE:  400, 8
! NOTICE:  500, 10
! NOTICE:  600, 12
! NOTICE:  700, 14
! NOTICE:  800, 16
! NOTICE:  900, 18
! NOTICE:  1000, 20
!  forc01 
! --------
!  
! (1 row)
! 
! select * from forc_test;
!    i    | j  
! --------+----
!   10000 |  4
!   20000 |  8
!   30000 | 12
!   40000 | 16
!   50000 | 20
!   60000 | 24
!   70000 | 28
!   80000 | 32
!   90000 | 36
!  100000 | 40
! (10 rows)
! 
! drop function forc01();
! -- fail because cursor has no query bound to it
! create or replace function forc_bad() returns void as $$
! declare
!   c refcursor;
! begin
!   for r in c loop
!     raise notice '%', r.i;
!   end loop;
! end;
! $$ language plpgsql;
! ERROR:  cursor FOR loop must use a bound cursor variable
! LINE 5:   for r in c loop
!                    ^
! -- test RETURN QUERY EXECUTE
! create or replace function return_dquery()
! returns setof int as $$
! begin
!   return query execute 'select * from (values(10),(20)) f';
!   return query execute 'select * from (values($1),($2)) f' using 40,50;
! end;
! $$ language plpgsql;
! select * from return_dquery();
!  return_dquery 
! ---------------
!             10
!             20
!             40
!             50
! (4 rows)
! 
! drop function return_dquery();
! -- test RETURN QUERY with dropped columns
! create table tabwithcols(a int, b int, c int, d int);
! insert into tabwithcols values(10,20,30,40),(50,60,70,80);
! create or replace function returnqueryf()
! returns setof tabwithcols as $$
! begin
!   return query select * from tabwithcols;
!   return query execute 'select * from tabwithcols';
! end;
! $$ language plpgsql;
! select * from returnqueryf();
!  a  | b  | c  | d  
! ----+----+----+----
!  10 | 20 | 30 | 40
!  50 | 60 | 70 | 80
!  10 | 20 | 30 | 40
!  50 | 60 | 70 | 80
! (4 rows)
! 
! alter table tabwithcols drop column b;
! select * from returnqueryf();
!  a  | c  | d  
! ----+----+----
!  10 | 30 | 40
!  50 | 70 | 80
!  10 | 30 | 40
!  50 | 70 | 80
! (4 rows)
! 
! alter table tabwithcols drop column d;
! select * from returnqueryf();
!  a  | c  
! ----+----
!  10 | 30
!  50 | 70
!  10 | 30
!  50 | 70
! (4 rows)
! 
! alter table tabwithcols add column d int;
! select * from returnqueryf();
!  a  | c  | d 
! ----+----+---
!  10 | 30 |  
!  50 | 70 |  
!  10 | 30 |  
!  50 | 70 |  
! (4 rows)
! 
! drop function returnqueryf();
! drop table tabwithcols;
! --
! -- Tests for composite-type results
! --
! create type compostype as (x int, y varchar);
! -- test: use of variable of composite type in return statement
! create or replace function compos() returns compostype as $$
! declare
!   v compostype;
! begin
!   v := (1, 'hello');
!   return v;
! end;
! $$ language plpgsql;
! select compos();
!   compos   
! -----------
!  (1,hello)
! (1 row)
! 
! -- test: use of variable of record type in return statement
! create or replace function compos() returns compostype as $$
! declare
!   v record;
! begin
!   v := (1, 'hello'::varchar);
!   return v;
! end;
! $$ language plpgsql;
! select compos();
!   compos   
! -----------
!  (1,hello)
! (1 row)
! 
! -- test: use of row expr in return statement
! create or replace function compos() returns compostype as $$
! begin
!   return (1, 'hello'::varchar);
! end;
! $$ language plpgsql;
! select compos();
!   compos   
! -----------
!  (1,hello)
! (1 row)
! 
! -- this does not work currently (no implicit casting)
! create or replace function compos() returns compostype as $$
! begin
!   return (1, 'hello');
! end;
! $$ language plpgsql;
! select compos();
! ERROR:  returned record type does not match expected record type
! DETAIL:  Returned type unknown does not match expected type character varying in column 2.
! CONTEXT:  PL/pgSQL function compos() while casting return value to function's return type
! -- ... but this does
! create or replace function compos() returns compostype as $$
! begin
!   return (1, 'hello')::compostype;
! end;
! $$ language plpgsql;
! select compos();
!   compos   
! -----------
!  (1,hello)
! (1 row)
! 
! drop function compos();
! -- test: return a row expr as record.
! create or replace function composrec() returns record as $$
! declare
!   v record;
! begin
!   v := (1, 'hello');
!   return v;
! end;
! $$ language plpgsql;
! select composrec();
!  composrec 
! -----------
!  (1,hello)
! (1 row)
! 
! -- test: return row expr in return statement.
! create or replace function composrec() returns record as $$
! begin
!   return (1, 'hello');
! end;
! $$ language plpgsql;
! select composrec();
!  composrec 
! -----------
!  (1,hello)
! (1 row)
! 
! drop function composrec();
! -- test: row expr in RETURN NEXT statement.
! create or replace function compos() returns setof compostype as $$
! begin
!   for i in 1..3
!   loop
!     return next (1, 'hello'::varchar);
!   end loop;
!   return next null::compostype;
!   return next (2, 'goodbye')::compostype;
! end;
! $$ language plpgsql;
! select * from compos();
!  x |    y    
! ---+---------
!  1 | hello
!  1 | hello
!  1 | hello
!    | 
!  2 | goodbye
! (5 rows)
! 
! drop function compos();
! -- test: use invalid expr in return statement.
! create or replace function compos() returns compostype as $$
! begin
!   return 1 + 1;
! end;
! $$ language plpgsql;
! select compos();
! ERROR:  cannot return non-composite value from function returning composite type
! CONTEXT:  PL/pgSQL function compos() line 3 at RETURN
! drop function compos();
! drop type compostype;
! --
! -- Tests for 8.4's new RAISE features
! --
! create or replace function raise_test() returns void as $$
! begin
!   raise notice '% % %', 1, 2, 3
!      using errcode = '55001', detail = 'some detail info', hint = 'some hint';
!   raise '% % %', 1, 2, 3
!      using errcode = 'division_by_zero', detail = 'some detail info';
! end;
! $$ language plpgsql;
! select raise_test();
! NOTICE:  1 2 3
! DETAIL:  some detail info
! HINT:  some hint
! ERROR:  1 2 3
! DETAIL:  some detail info
! -- Since we can't actually see the thrown SQLSTATE in default psql output,
! -- test it like this; this also tests re-RAISE
! create or replace function raise_test() returns void as $$
! begin
!   raise 'check me'
!      using errcode = 'division_by_zero', detail = 'some detail info';
!   exception
!     when others then
!       raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
!       raise;
! end;
! $$ language plpgsql;
! select raise_test();
! NOTICE:  SQLSTATE: 22012 SQLERRM: check me
! ERROR:  check me
! DETAIL:  some detail info
! create or replace function raise_test() returns void as $$
! begin
!   raise 'check me'
!      using errcode = '1234F', detail = 'some detail info';
!   exception
!     when others then
!       raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
!       raise;
! end;
! $$ language plpgsql;
! select raise_test();
! NOTICE:  SQLSTATE: 1234F SQLERRM: check me
! ERROR:  check me
! DETAIL:  some detail info
! -- SQLSTATE specification in WHEN
! create or replace function raise_test() returns void as $$
! begin
!   raise 'check me'
!      using errcode = '1234F', detail = 'some detail info';
!   exception
!     when sqlstate '1234F' then
!       raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
!       raise;
! end;
! $$ language plpgsql;
! select raise_test();
! NOTICE:  SQLSTATE: 1234F SQLERRM: check me
! ERROR:  check me
! DETAIL:  some detail info
! create or replace function raise_test() returns void as $$
! begin
!   raise division_by_zero using detail = 'some detail info';
!   exception
!     when others then
!       raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
!       raise;
! end;
! $$ language plpgsql;
! select raise_test();
! NOTICE:  SQLSTATE: 22012 SQLERRM: division_by_zero
! ERROR:  division_by_zero
! DETAIL:  some detail info
! create or replace function raise_test() returns void as $$
! begin
!   raise division_by_zero;
! end;
! $$ language plpgsql;
! select raise_test();
! ERROR:  division_by_zero
! create or replace function raise_test() returns void as $$
! begin
!   raise sqlstate '1234F';
! end;
! $$ language plpgsql;
! select raise_test();
! ERROR:  1234F
! create or replace function raise_test() returns void as $$
! begin
!   raise division_by_zero using message = 'custom' || ' message';
! end;
! $$ language plpgsql;
! select raise_test();
! ERROR:  custom message
! create or replace function raise_test() returns void as $$
! begin
!   raise using message = 'custom' || ' message', errcode = '22012';
! end;
! $$ language plpgsql;
! select raise_test();
! ERROR:  custom message
! -- conflict on message
! create or replace function raise_test() returns void as $$
! begin
!   raise notice 'some message' using message = 'custom' || ' message', errcode = '22012';
! end;
! $$ language plpgsql;
! select raise_test();
! ERROR:  RAISE option already specified: MESSAGE
! CONTEXT:  PL/pgSQL function raise_test() line 3 at RAISE
! -- conflict on errcode
! create or replace function raise_test() returns void as $$
! begin
!   raise division_by_zero using message = 'custom' || ' message', errcode = '22012';
! end;
! $$ language plpgsql;
! select raise_test();
! ERROR:  RAISE option already specified: ERRCODE
! CONTEXT:  PL/pgSQL function raise_test() line 3 at RAISE
! -- nothing to re-RAISE
! create or replace function raise_test() returns void as $$
! begin
!   raise;
! end;
! $$ language plpgsql;
! select raise_test();
! ERROR:  RAISE without parameters cannot be used outside an exception handler
! CONTEXT:  PL/pgSQL function raise_test() line 3 at RAISE
! -- test access to exception data
! create function zero_divide() returns int as $$
! declare v int := 0;
! begin
!   return 10 / v;
! end;
! $$ language plpgsql;
! create or replace function raise_test() returns void as $$
! begin
!   raise exception 'custom exception'
!      using detail = 'some detail of custom exception',
!            hint = 'some hint related to custom exception';
! end;
! $$ language plpgsql;
! create function stacked_diagnostics_test() returns void as $$
! declare _sqlstate text;
!         _message text;
!         _context text;
! begin
!   perform zero_divide();
! exception when others then
!   get stacked diagnostics
!         _sqlstate = returned_sqlstate,
!         _message = message_text,
!         _context = pg_exception_context;
!   raise notice 'sqlstate: %, message: %, context: [%]',
!     _sqlstate, _message, replace(_context, E'\n', ' <- ');
! end;
! $$ language plpgsql;
! select stacked_diagnostics_test();
! NOTICE:  sqlstate: 22012, message: division by zero, context: [PL/pgSQL function zero_divide() line 4 at RETURN <- SQL statement "SELECT zero_divide()" <- PL/pgSQL function stacked_diagnostics_test() line 6 at PERFORM]
!  stacked_diagnostics_test 
! --------------------------
!  
! (1 row)
! 
! create or replace function stacked_diagnostics_test() returns void as $$
! declare _detail text;
!         _hint text;
!         _message text;
! begin
!   perform raise_test();
! exception when others then
!   get stacked diagnostics
!         _message = message_text,
!         _detail = pg_exception_detail,
!         _hint = pg_exception_hint;
!   raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint;
! end;
! $$ language plpgsql;
! select stacked_diagnostics_test();
! NOTICE:  message: custom exception, detail: some detail of custom exception, hint: some hint related to custom exception
!  stacked_diagnostics_test 
! --------------------------
!  
! (1 row)
! 
! -- fail, cannot use stacked diagnostics statement outside handler
! create or replace function stacked_diagnostics_test() returns void as $$
! declare _detail text;
!         _hint text;
!         _message text;
! begin
!   get stacked diagnostics
!         _message = message_text,
!         _detail = pg_exception_detail,
!         _hint = pg_exception_hint;
!   raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint;
! end;
! $$ language plpgsql;
! select stacked_diagnostics_test();
! ERROR:  GET STACKED DIAGNOSTICS cannot be used outside an exception handler
! CONTEXT:  PL/pgSQL function stacked_diagnostics_test() line 6 at GET DIAGNOSTICS
! drop function zero_divide();
! drop function stacked_diagnostics_test();
! -- check cases where implicit SQLSTATE variable could be confused with
! -- SQLSTATE as a keyword, cf bug #5524
! create or replace function raise_test() returns void as $$
! begin
!   perform 1/0;
! exception
!   when sqlstate '22012' then
!     raise notice using message = sqlstate;
!     raise sqlstate '22012' using message = 'substitute message';
! end;
! $$ language plpgsql;
! select raise_test();
! NOTICE:  22012
! ERROR:  substitute message
! drop function raise_test();
! -- test passing column_name, constraint_name, datatype_name, table_name
! -- and schema_name error fields
! create or replace function stacked_diagnostics_test() returns void as $$
! declare _column_name text;
!         _constraint_name text;
!         _datatype_name text;
!         _table_name text;
!         _schema_name text;
! begin
!   raise exception using
!     column = '>>some column name<<',
!     constraint = '>>some constraint name<<',
!     datatype = '>>some datatype name<<',
!     table = '>>some table name<<',
!     schema = '>>some schema name<<';
! exception when others then
!   get stacked diagnostics
!         _column_name = column_name,
!         _constraint_name = constraint_name,
!         _datatype_name = pg_datatype_name,
!         _table_name = table_name,
!         _schema_name = schema_name;
!   raise notice 'column %, constraint %, type %, table %, schema %',
!     _column_name, _constraint_name, _datatype_name, _table_name, _schema_name;
! end;
! $$ language plpgsql;
! select stacked_diagnostics_test();
! NOTICE:  column >>some column name<<, constraint >>some constraint name<<, type >>some datatype name<<, table >>some table name<<, schema >>some schema name<<
!  stacked_diagnostics_test 
! --------------------------
!  
! (1 row)
! 
! drop function stacked_diagnostics_test();
! -- test CASE statement
! create or replace function case_test(bigint) returns text as $$
! declare a int = 10;
!         b int = 1;
! begin
!   case $1
!     when 1 then
!       return 'one';
!     when 2 then
!       return 'two';
!     when 3,4,3+5 then
!       return 'three, four or eight';
!     when a then
!       return 'ten';
!     when a+b, a+b+1 then
!       return 'eleven, twelve';
!   end case;
! end;
! $$ language plpgsql immutable;
! select case_test(1);
!  case_test 
! -----------
!  one
! (1 row)
! 
! select case_test(2);
!  case_test 
! -----------
!  two
! (1 row)
! 
! select case_test(3);
!       case_test       
! ----------------------
!  three, four or eight
! (1 row)
! 
! select case_test(4);
!       case_test       
! ----------------------
!  three, four or eight
! (1 row)
! 
! select case_test(5); -- fails
! ERROR:  case not found
! HINT:  CASE statement is missing ELSE part.
! CONTEXT:  PL/pgSQL function case_test(bigint) line 5 at CASE
! select case_test(8);
!       case_test       
! ----------------------
!  three, four or eight
! (1 row)
! 
! select case_test(10);
!  case_test 
! -----------
!  ten
! (1 row)
! 
! select case_test(11);
!    case_test    
! ----------------
!  eleven, twelve
! (1 row)
! 
! select case_test(12);
!    case_test    
! ----------------
!  eleven, twelve
! (1 row)
! 
! select case_test(13); -- fails
! ERROR:  case not found
! HINT:  CASE statement is missing ELSE part.
! CONTEXT:  PL/pgSQL function case_test(bigint) line 5 at CASE
! create or replace function catch() returns void as $$
! begin
!   raise notice '%', case_test(6);
! exception
!   when case_not_found then
!     raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
! end
! $$ language plpgsql;
! select catch();
! NOTICE:  caught case_not_found 20000 case not found
!  catch 
! -------
!  
! (1 row)
! 
! -- test the searched variant too, as well as ELSE
! create or replace function case_test(bigint) returns text as $$
! declare a int = 10;
! begin
!   case
!     when $1 = 1 then
!       return 'one';
!     when $1 = a + 2 then
!       return 'twelve';
!     else
!       return 'other';
!   end case;
! end;
! $$ language plpgsql immutable;
! select case_test(1);
!  case_test 
! -----------
!  one
! (1 row)
! 
! select case_test(2);
!  case_test 
! -----------
!  other
! (1 row)
! 
! select case_test(12);
!  case_test 
! -----------
!  twelve
! (1 row)
! 
! select case_test(13);
!  case_test 
! -----------
!  other
! (1 row)
! 
! drop function catch();
! drop function case_test(bigint);
! -- test variadic functions
! create or replace function vari(variadic int[])
! returns void as $$
! begin
!   for i in array_lower($1,1)..array_upper($1,1) loop
!     raise notice '%', $1[i];
!   end loop; end;
! $$ language plpgsql;
! select vari(1,2,3,4,5);
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  4
! NOTICE:  5
!  vari 
! ------
!  
! (1 row)
! 
! select vari(3,4,5);
! NOTICE:  3
! NOTICE:  4
! NOTICE:  5
!  vari 
! ------
!  
! (1 row)
! 
! select vari(variadic array[5,6,7]);
! NOTICE:  5
! NOTICE:  6
! NOTICE:  7
!  vari 
! ------
!  
! (1 row)
! 
! drop function vari(int[]);
! -- coercion test
! create or replace function pleast(variadic numeric[])
! returns numeric as $$
! declare aux numeric = $1[array_lower($1,1)];
! begin
!   for i in array_lower($1,1)+1..array_upper($1,1) loop
!     if $1[i] < aux then aux := $1[i]; end if;
!   end loop;
!   return aux;
! end;
! $$ language plpgsql immutable strict;
! select pleast(10,1,2,3,-16);
!  pleast 
! --------
!     -16
! (1 row)
! 
! select pleast(10.2,2.2,-1.1);
!  pleast 
! --------
!    -1.1
! (1 row)
! 
! select pleast(10.2,10, -20);
!  pleast 
! --------
!     -20
! (1 row)
! 
! select pleast(10,20, -1.0);
!  pleast 
! --------
!    -1.0
! (1 row)
! 
! -- in case of conflict, non-variadic version is preferred
! create or replace function pleast(numeric)
! returns numeric as $$
! begin
!   raise notice 'non-variadic function called';
!   return $1;
! end;
! $$ language plpgsql immutable strict;
! select pleast(10);
! NOTICE:  non-variadic function called
!  pleast 
! --------
!      10
! (1 row)
! 
! drop function pleast(numeric[]);
! drop function pleast(numeric);
! -- test table functions
! create function tftest(int) returns table(a int, b int) as $$
! begin
!   return query select $1, $1+i from generate_series(1,5) g(i);
! end;
! $$ language plpgsql immutable strict;
! select * from tftest(10);
!  a  | b  
! ----+----
!  10 | 11
!  10 | 12
!  10 | 13
!  10 | 14
!  10 | 15
! (5 rows)
! 
! create or replace function tftest(a1 int) returns table(a int, b int) as $$
! begin
!   a := a1; b := a1 + 1;
!   return next;
!   a := a1 * 10; b := a1 * 10 + 1;
!   return next;
! end;
! $$ language plpgsql immutable strict;
! select * from tftest(10);
!   a  |  b  
! -----+-----
!   10 |  11
!  100 | 101
! (2 rows)
! 
! drop function tftest(int);
! create or replace function rttest()
! returns setof int as $$
! declare rc int;
! begin
!   return query values(10),(20);
!   get diagnostics rc = row_count;
!   raise notice '% %', found, rc;
!   return query select * from (values(10),(20)) f(a) where false;
!   get diagnostics rc = row_count;
!   raise notice '% %', found, rc;
!   return query execute 'values(10),(20)';
!   get diagnostics rc = row_count;
!   raise notice '% %', found, rc;
!   return query execute 'select * from (values(10),(20)) f(a) where false';
!   get diagnostics rc = row_count;
!   raise notice '% %', found, rc;
! end;
! $$ language plpgsql;
! select * from rttest();
! NOTICE:  t 2
! NOTICE:  f 0
! NOTICE:  t 2
! NOTICE:  f 0
!  rttest 
! --------
!      10
!      20
!      10
!      20
! (4 rows)
! 
! drop function rttest();
! -- Test for proper cleanup at subtransaction exit.  This example
! -- exposed a bug in PG 8.2.
! CREATE FUNCTION leaker_1(fail BOOL) RETURNS INTEGER AS $$
! DECLARE
!   v_var INTEGER;
! BEGIN
!   BEGIN
!     v_var := (leaker_2(fail)).error_code;
!   EXCEPTION
!     WHEN others THEN RETURN 0;
!   END;
!   RETURN 1;
! END;
! $$ LANGUAGE plpgsql;
! CREATE FUNCTION leaker_2(fail BOOL, OUT error_code INTEGER, OUT new_id INTEGER)
!   RETURNS RECORD AS $$
! BEGIN
!   IF fail THEN
!     RAISE EXCEPTION 'fail ...';
!   END IF;
!   error_code := 1;
!   new_id := 1;
!   RETURN;
! END;
! $$ LANGUAGE plpgsql;
! SELECT * FROM leaker_1(false);
!  leaker_1 
! ----------
!         1
! (1 row)
! 
! SELECT * FROM leaker_1(true);
!  leaker_1 
! ----------
!         0
! (1 row)
! 
! DROP FUNCTION leaker_1(bool);
! DROP FUNCTION leaker_2(bool);
! -- Test for appropriate cleanup of non-simple expression evaluations
! -- (bug in all versions prior to August 2010)
! CREATE FUNCTION nonsimple_expr_test() RETURNS text[] AS $$
! DECLARE
!   arr text[];
!   lr text;
!   i integer;
! BEGIN
!   arr := array[array['foo','bar'], array['baz', 'quux']];
!   lr := 'fool';
!   i := 1;
!   -- use sub-SELECTs to make expressions non-simple
!   arr[(SELECT i)][(SELECT i+1)] := (SELECT lr);
!   RETURN arr;
! END;
! $$ LANGUAGE plpgsql;
! SELECT nonsimple_expr_test();
!    nonsimple_expr_test   
! -------------------------
!  {{foo,fool},{baz,quux}}
! (1 row)
! 
! DROP FUNCTION nonsimple_expr_test();
! CREATE FUNCTION nonsimple_expr_test() RETURNS integer AS $$
! declare
!    i integer NOT NULL := 0;
! begin
!   begin
!     i := (SELECT NULL::integer);  -- should throw error
!   exception
!     WHEN OTHERS THEN
!       i := (SELECT 1::integer);
!   end;
!   return i;
! end;
! $$ LANGUAGE plpgsql;
! SELECT nonsimple_expr_test();
!  nonsimple_expr_test 
! ---------------------
!                    1
! (1 row)
! 
! DROP FUNCTION nonsimple_expr_test();
! --
! -- Test cases involving recursion and error recovery in simple expressions
! -- (bugs in all versions before October 2010).  The problems are most
! -- easily exposed by mutual recursion between plpgsql and sql functions.
! --
! create function recurse(float8) returns float8 as
! $$
! begin
!   if ($1 > 0) then
!     return sql_recurse($1 - 1);
!   else
!     return $1;
!   end if;
! end;
! $$ language plpgsql;
! -- "limit" is to prevent this from being inlined
! create function sql_recurse(float8) returns float8 as
! $$ select recurse($1) limit 1; $$ language sql;
! select recurse(10);
!  recurse 
! ---------
!        0
! (1 row)
! 
! create function error1(text) returns text language sql as
! $$ SELECT relname::text FROM pg_class c WHERE c.oid = $1::regclass $$;
! create function error2(p_name_table text) returns text language plpgsql as $$
! begin
!   return error1(p_name_table);
! end$$;
! BEGIN;
! create table public.stuffs (stuff text);
! SAVEPOINT a;
! select error2('nonexistent.stuffs');
! ERROR:  schema "nonexistent" does not exist
! CONTEXT:  SQL function "error1" statement 1
! PL/pgSQL function error2(text) line 3 at RETURN
! ROLLBACK TO a;
! select error2('public.stuffs');
!  error2 
! --------
!  stuffs
! (1 row)
! 
! rollback;
! drop function error2(p_name_table text);
! drop function error1(text);
! -- Test for consistent reporting of error context
! create function fail() returns int language plpgsql as $$
! begin
!   return 1/0;
! end
! $$;
! select fail();
! ERROR:  division by zero
! CONTEXT:  SQL statement "SELECT 1/0"
! PL/pgSQL function fail() line 3 at RETURN
! select fail();
! ERROR:  division by zero
! CONTEXT:  SQL statement "SELECT 1/0"
! PL/pgSQL function fail() line 3 at RETURN
! drop function fail();
! -- Test handling of string literals.
! set standard_conforming_strings = off;
! create or replace function strtest() returns text as $$
! begin
!   raise notice 'foo\\bar\041baz';
!   return 'foo\\bar\041baz';
! end
! $$ language plpgsql;
! WARNING:  nonstandard use of \\ in a string literal
! LINE 3:   raise notice 'foo\\bar\041baz';
!                        ^
! HINT:  Use the escape string syntax for backslashes, e.g., E'\\'.
! WARNING:  nonstandard use of \\ in a string literal
! LINE 4:   return 'foo\\bar\041baz';
!                  ^
! HINT:  Use the escape string syntax for backslashes, e.g., E'\\'.
! WARNING:  nonstandard use of \\ in a string literal
! LINE 4:   return 'foo\\bar\041baz';
!                  ^
! HINT:  Use the escape string syntax for backslashes, e.g., E'\\'.
! select strtest();
! NOTICE:  foo\bar!baz
! WARNING:  nonstandard use of \\ in a string literal
! LINE 1: SELECT 'foo\\bar\041baz'
!                ^
! HINT:  Use the escape string syntax for backslashes, e.g., E'\\'.
! QUERY:  SELECT 'foo\\bar\041baz'
! CONTEXT:  PL/pgSQL function strtest() line 4 at RETURN
!    strtest   
! -------------
!  foo\bar!baz
! (1 row)
! 
! create or replace function strtest() returns text as $$
! begin
!   raise notice E'foo\\bar\041baz';
!   return E'foo\\bar\041baz';
! end
! $$ language plpgsql;
! select strtest();
! NOTICE:  foo\bar!baz
!    strtest   
! -------------
!  foo\bar!baz
! (1 row)
! 
! set standard_conforming_strings = on;
! create or replace function strtest() returns text as $$
! begin
!   raise notice 'foo\\bar\041baz\';
!   return 'foo\\bar\041baz\';
! end
! $$ language plpgsql;
! select strtest();
! NOTICE:  foo\\bar\041baz\
!      strtest      
! ------------------
!  foo\\bar\041baz\
! (1 row)
! 
! create or replace function strtest() returns text as $$
! begin
!   raise notice E'foo\\bar\041baz';
!   return E'foo\\bar\041baz';
! end
! $$ language plpgsql;
! select strtest();
! NOTICE:  foo\bar!baz
!    strtest   
! -------------
!  foo\bar!baz
! (1 row)
! 
! drop function strtest();
! -- Test anonymous code blocks.
! DO $$
! DECLARE r record;
! BEGIN
!     FOR r IN SELECT rtrim(roomno) AS roomno, comment FROM Room ORDER BY roomno
!     LOOP
!         RAISE NOTICE '%, %', r.roomno, r.comment;
!     END LOOP;
! END$$;
! NOTICE:  001, Entrance
! NOTICE:  002, Office
! NOTICE:  003, Office
! NOTICE:  004, Technical
! NOTICE:  101, Office
! NOTICE:  102, Conference
! NOTICE:  103, Restroom
! NOTICE:  104, Technical
! NOTICE:  105, Office
! NOTICE:  106, Office
! -- these are to check syntax error reporting
! DO LANGUAGE plpgsql $$begin return 1; end$$;
! ERROR:  RETURN cannot have a parameter in function returning void
! LINE 1: DO LANGUAGE plpgsql $$begin return 1; end$$;
!                                            ^
! DO $$
! DECLARE r record;
! BEGIN
!     FOR r IN SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
!     LOOP
!         RAISE NOTICE '%, %', r.roomno, r.comment;
!     END LOOP;
! END$$;
! ERROR:  column "foo" does not exist
! LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn...
!                                         ^
! QUERY:  SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
! CONTEXT:  PL/pgSQL function inline_code_block line 4 at FOR over SELECT rows
! -- Check handling of errors thrown from/into anonymous code blocks.
! do $outer$
! begin
!   for i in 1..10 loop
!    begin
!     execute $ex$
!       do $$
!       declare x int = 0;
!       begin
!         x := 1 / x;
!       end;
!       $$;
!     $ex$;
!   exception when division_by_zero then
!     raise notice 'caught division by zero';
!   end;
!   end loop;
! end;
! $outer$;
! NOTICE:  caught division by zero
! NOTICE:  caught division by zero
! NOTICE:  caught division by zero
! NOTICE:  caught division by zero
! NOTICE:  caught division by zero
! NOTICE:  caught division by zero
! NOTICE:  caught division by zero
! NOTICE:  caught division by zero
! NOTICE:  caught division by zero
! NOTICE:  caught division by zero
! -- Check variable scoping -- a var is not available in its own or prior
! -- default expressions.
! create function scope_test() returns int as $$
! declare x int := 42;
! begin
!   declare y int := x + 1;
!           x int := x + 2;
!   begin
!     return x * 100 + y;
!   end;
! end;
! $$ language plpgsql;
! select scope_test();
!  scope_test 
! ------------
!        4443
! (1 row)
! 
! drop function scope_test();
! -- Check handling of conflicts between plpgsql vars and table columns.
! set plpgsql.variable_conflict = error;
! create function conflict_test() returns setof int8_tbl as $$
! declare r record;
!   q1 bigint := 42;
! begin
!   for r in select q1,q2 from int8_tbl loop
!     return next r;
!   end loop;
! end;
! $$ language plpgsql;
! select * from conflict_test();
! ERROR:  column reference "q1" is ambiguous
! LINE 1: select q1,q2 from int8_tbl
!                ^
! DETAIL:  It could refer to either a PL/pgSQL variable or a table column.
! QUERY:  select q1,q2 from int8_tbl
! CONTEXT:  PL/pgSQL function conflict_test() line 5 at FOR over SELECT rows
! create or replace function conflict_test() returns setof int8_tbl as $$
! #variable_conflict use_variable
! declare r record;
!   q1 bigint := 42;
! begin
!   for r in select q1,q2 from int8_tbl loop
!     return next r;
!   end loop;
! end;
! $$ language plpgsql;
! select * from conflict_test();
!  q1 |        q2         
! ----+-------------------
!  42 |               456
!  42 |  4567890123456789
!  42 |               123
!  42 |  4567890123456789
!  42 | -4567890123456789
! (5 rows)
! 
! create or replace function conflict_test() returns setof int8_tbl as $$
! #variable_conflict use_column
! declare r record;
!   q1 bigint := 42;
! begin
!   for r in select q1,q2 from int8_tbl loop
!     return next r;
!   end loop;
! end;
! $$ language plpgsql;
! select * from conflict_test();
!         q1        |        q2         
! ------------------+-------------------
!               123 |               456
!               123 |  4567890123456789
!  4567890123456789 |               123
!  4567890123456789 |  4567890123456789
!  4567890123456789 | -4567890123456789
! (5 rows)
! 
! drop function conflict_test();
! -- Check that an unreserved keyword can be used as a variable name
! create function unreserved_test() returns int as $$
! declare
!   forward int := 21;
! begin
!   forward := forward * 2;
!   return forward;
! end
! $$ language plpgsql;
! select unreserved_test();
!  unreserved_test 
! -----------------
!               42
! (1 row)
! 
! drop function unreserved_test();
! --
! -- Test FOREACH over arrays
! --
! create function foreach_test(anyarray)
! returns void as $$
! declare x int;
! begin
!   foreach x in array $1
!   loop
!     raise notice '%', x;
!   end loop;
!   end;
! $$ language plpgsql;
! select foreach_test(ARRAY[1,2,3,4]);
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  4
!  foreach_test 
! --------------
!  
! (1 row)
! 
! select foreach_test(ARRAY[[1,2],[3,4]]);
! NOTICE:  1
! NOTICE:  2
! NOTICE:  3
! NOTICE:  4
!  foreach_test 
! --------------
!  
! (1 row)
! 
! create or replace function foreach_test(anyarray)
! returns void as $$
! declare x int;
! begin
!   foreach x slice 1 in array $1
!   loop
!     raise notice '%', x;
!   end loop;
!   end;
! $$ language plpgsql;
! -- should fail
! select foreach_test(ARRAY[1,2,3,4]);
! ERROR:  FOREACH ... SLICE loop variable must be of an array type
! CONTEXT:  PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array
! select foreach_test(ARRAY[[1,2],[3,4]]);
! ERROR:  FOREACH ... SLICE loop variable must be of an array type
! CONTEXT:  PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array
! create or replace function foreach_test(anyarray)
! returns void as $$
! declare x int[];
! begin
!   foreach x slice 1 in array $1
!   loop
!     raise notice '%', x;
!   end loop;
!   end;
! $$ language plpgsql;
! select foreach_test(ARRAY[1,2,3,4]);
! NOTICE:  {1,2,3,4}
!  foreach_test 
! --------------
!  
! (1 row)
! 
! select foreach_test(ARRAY[[1,2],[3,4]]);
! NOTICE:  {1,2}
! NOTICE:  {3,4}
!  foreach_test 
! --------------
!  
! (1 row)
! 
! -- higher level of slicing
! create or replace function foreach_test(anyarray)
! returns void as $$
! declare x int[];
! begin
!   foreach x slice 2 in array $1
!   loop
!     raise notice '%', x;
!   end loop;
!   end;
! $$ language plpgsql;
! -- should fail
! select foreach_test(ARRAY[1,2,3,4]);
! ERROR:  slice dimension (2) is out of the valid range 0..1
! CONTEXT:  PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array
! -- ok
! select foreach_test(ARRAY[[1,2],[3,4]]);
! NOTICE:  {{1,2},{3,4}}
!  foreach_test 
! --------------
!  
! (1 row)
! 
! select foreach_test(ARRAY[[[1,2]],[[3,4]]]);
! NOTICE:  {{1,2}}
! NOTICE:  {{3,4}}
!  foreach_test 
! --------------
!  
! (1 row)
! 
! create type xy_tuple AS (x int, y int);
! -- iteration over array of records
! create or replace function foreach_test(anyarray)
! returns void as $$
! declare r record;
! begin
!   foreach r in array $1
!   loop
!     raise notice '%', r;
!   end loop;
!   end;
! $$ language plpgsql;
! select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]);
! NOTICE:  (10,20)
! NOTICE:  (40,69)
! NOTICE:  (35,78)
!  foreach_test 
! --------------
!  
! (1 row)
! 
! select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
! NOTICE:  (10,20)
! NOTICE:  (40,69)
! NOTICE:  (35,78)
! NOTICE:  (88,76)
!  foreach_test 
! --------------
!  
! (1 row)
! 
! create or replace function foreach_test(anyarray)
! returns void as $$
! declare x int; y int;
! begin
!   foreach x, y in array $1
!   loop
!     raise notice 'x = %, y = %', x, y;
!   end loop;
!   end;
! $$ language plpgsql;
! select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]);
! NOTICE:  x = 10, y = 20
! NOTICE:  x = 40, y = 69
! NOTICE:  x = 35, y = 78
!  foreach_test 
! --------------
!  
! (1 row)
! 
! select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
! NOTICE:  x = 10, y = 20
! NOTICE:  x = 40, y = 69
! NOTICE:  x = 35, y = 78
! NOTICE:  x = 88, y = 76
!  foreach_test 
! --------------
!  
! (1 row)
! 
! -- slicing over array of composite types
! create or replace function foreach_test(anyarray)
! returns void as $$
! declare x xy_tuple[];
! begin
!   foreach x slice 1 in array $1
!   loop
!     raise notice '%', x;
!   end loop;
!   end;
! $$ language plpgsql;
! select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]);
! NOTICE:  {"(10,20)","(40,69)","(35,78)"}
!  foreach_test 
! --------------
!  
! (1 row)
! 
! select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
! NOTICE:  {"(10,20)","(40,69)"}
! NOTICE:  {"(35,78)","(88,76)"}
!  foreach_test 
! --------------
!  
! (1 row)
! 
! drop function foreach_test(anyarray);
! drop type xy_tuple;
! --
! -- Assorted tests for array subscript assignment
! --
! create temp table rtype (id int, ar text[]);
! create function arrayassign1() returns text[] language plpgsql as $$
! declare
!  r record;
! begin
!   r := row(12, '{foo,bar,baz}')::rtype;
!   r.ar[2] := 'replace';
!   return r.ar;
! end$$;
! select arrayassign1();
!    arrayassign1    
! -------------------
!  {foo,replace,baz}
! (1 row)
! 
! select arrayassign1(); -- try again to exercise internal caching
!    arrayassign1    
! -------------------
!  {foo,replace,baz}
! (1 row)
! 
! create domain orderedarray as int[2]
!   constraint sorted check (value[1] < value[2]);
! select '{1,2}'::orderedarray;
!  orderedarray 
! --------------
!  {1,2}
! (1 row)
! 
! select '{2,1}'::orderedarray;  -- fail
! ERROR:  value for domain orderedarray violates check constraint "sorted"
! create function testoa(x1 int, x2 int, x3 int) returns orderedarray
! language plpgsql as $$
! declare res orderedarray;
! begin
!   res := array[x1, x2];
!   res[2] := x3;
!   return res;
! end$$;
! select testoa(1,2,3);
!  testoa 
! --------
!  {1,3}
! (1 row)
! 
! select testoa(1,2,3); -- try again to exercise internal caching
!  testoa 
! --------
!  {1,3}
! (1 row)
! 
! select testoa(2,1,3); -- fail at initial assign
! ERROR:  value for domain orderedarray violates check constraint "sorted"
! CONTEXT:  PL/pgSQL function testoa(integer,integer,integer) line 4 at assignment
! select testoa(1,2,1); -- fail at update
! ERROR:  value for domain orderedarray violates check constraint "sorted"
! CONTEXT:  PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment
! drop function arrayassign1();
! drop function testoa(x1 int, x2 int, x3 int);
! -- access to call stack
! create function inner_func(int)
! returns int as $$
! declare _context text;
! begin
!   get diagnostics _context = pg_context;
!   raise notice '***%***', _context;
!   -- lets do it again, just for fun..
!   get diagnostics _context = pg_context;
!   raise notice '***%***', _context;
!   raise notice 'lets make sure we didnt break anything';
!   return 2 * $1;
! end;
! $$ language plpgsql;
! create or replace function outer_func(int)
! returns int as $$
! declare
!   myresult int;
! begin
!   raise notice 'calling down into inner_func()';
!   myresult := inner_func($1);
!   raise notice 'inner_func() done';
!   return myresult;
! end;
! $$ language plpgsql;
! create or replace function outer_outer_func(int)
! returns int as $$
! declare
!   myresult int;
! begin
!   raise notice 'calling down into outer_func()';
!   myresult := outer_func($1);
!   raise notice 'outer_func() done';
!   return myresult;
! end;
! $$ language plpgsql;
! select outer_outer_func(10);
! NOTICE:  calling down into outer_func()
! NOTICE:  calling down into inner_func()
! CONTEXT:  PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS
! PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  ***PL/pgSQL function inner_func(integer) line 7 at GET DIAGNOSTICS
! PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  lets make sure we didnt break anything
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  inner_func() done
! CONTEXT:  PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  outer_func() done
!  outer_outer_func 
! ------------------
!                20
! (1 row)
! 
! -- repeated call should to work
! select outer_outer_func(20);
! NOTICE:  calling down into outer_func()
! NOTICE:  calling down into inner_func()
! CONTEXT:  PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS
! PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  ***PL/pgSQL function inner_func(integer) line 7 at GET DIAGNOSTICS
! PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  lets make sure we didnt break anything
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  inner_func() done
! CONTEXT:  PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  outer_func() done
!  outer_outer_func 
! ------------------
!                40
! (1 row)
! 
! drop function outer_outer_func(int);
! drop function outer_func(int);
! drop function inner_func(int);
! -- access to call stack from exception
! create function inner_func(int)
! returns int as $$
! declare
!   _context text;
!   sx int := 5;
! begin
!   begin
!     perform sx / 0;
!   exception
!     when division_by_zero then
!       get diagnostics _context = pg_context;
!       raise notice '***%***', _context;
!   end;
! 
!   -- lets do it again, just for fun..
!   get diagnostics _context = pg_context;
!   raise notice '***%***', _context;
!   raise notice 'lets make sure we didnt break anything';
!   return 2 * $1;
! end;
! $$ language plpgsql;
! create or replace function outer_func(int)
! returns int as $$
! declare
!   myresult int;
! begin
!   raise notice 'calling down into inner_func()';
!   myresult := inner_func($1);
!   raise notice 'inner_func() done';
!   return myresult;
! end;
! $$ language plpgsql;
! create or replace function outer_outer_func(int)
! returns int as $$
! declare
!   myresult int;
! begin
!   raise notice 'calling down into outer_func()';
!   myresult := outer_func($1);
!   raise notice 'outer_func() done';
!   return myresult;
! end;
! $$ language plpgsql;
! select outer_outer_func(10);
! NOTICE:  calling down into outer_func()
! NOTICE:  calling down into inner_func()
! CONTEXT:  PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  ***PL/pgSQL function inner_func(integer) line 10 at GET DIAGNOSTICS
! PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  ***PL/pgSQL function inner_func(integer) line 15 at GET DIAGNOSTICS
! PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  lets make sure we didnt break anything
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  inner_func() done
! CONTEXT:  PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  outer_func() done
!  outer_outer_func 
! ------------------
!                20
! (1 row)
! 
! -- repeated call should to work
! select outer_outer_func(20);
! NOTICE:  calling down into outer_func()
! NOTICE:  calling down into inner_func()
! CONTEXT:  PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  ***PL/pgSQL function inner_func(integer) line 10 at GET DIAGNOSTICS
! PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  ***PL/pgSQL function inner_func(integer) line 15 at GET DIAGNOSTICS
! PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  lets make sure we didnt break anything
! CONTEXT:  PL/pgSQL function outer_func(integer) line 6 at assignment
! PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  inner_func() done
! CONTEXT:  PL/pgSQL function outer_outer_func(integer) line 6 at assignment
! NOTICE:  outer_func() done
!  outer_outer_func 
! ------------------
!                40
! (1 row)
! 
! drop function outer_outer_func(int);
! drop function outer_func(int);
! drop function inner_func(int);
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/copy2.out	2014-01-02 13:19:06.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/copy2.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,389 ****
! CREATE TEMP TABLE x (
! 	a serial,
! 	b int,
! 	c text not null default 'stuff',
! 	d text,
! 	e text
! ) WITH OIDS;
! CREATE FUNCTION fn_x_before () RETURNS TRIGGER AS '
!   BEGIN
! 		NEW.e := ''before trigger fired''::text;
! 		return NEW;
! 	END;
! ' LANGUAGE plpgsql;
! CREATE FUNCTION fn_x_after () RETURNS TRIGGER AS '
!   BEGIN
! 		UPDATE x set e=''after trigger fired'' where c=''stuff'';
! 		return NULL;
! 	END;
! ' LANGUAGE plpgsql;
! CREATE TRIGGER trg_x_after AFTER INSERT ON x
! FOR EACH ROW EXECUTE PROCEDURE fn_x_after();
! CREATE TRIGGER trg_x_before BEFORE INSERT ON x
! FOR EACH ROW EXECUTE PROCEDURE fn_x_before();
! COPY x (a, b, c, d, e) from stdin;
! COPY x (b, d) from stdin;
! COPY x (b, d) from stdin;
! COPY x (a, b, c, d, e) from stdin;
! -- non-existent column in column list: should fail
! COPY x (xyz) from stdin;
! ERROR:  column "xyz" of relation "x" does not exist
! -- too many columns in column list: should fail
! COPY x (a, b, c, d, e, d, c) from stdin;
! ERROR:  column "d" specified more than once
! -- missing data: should fail
! COPY x from stdin;
! ERROR:  invalid input syntax for integer: ""
! CONTEXT:  COPY x, line 1, column a: ""
! COPY x from stdin;
! ERROR:  missing data for column "e"
! CONTEXT:  COPY x, line 1: "2000	230	23	23"
! COPY x from stdin;
! ERROR:  missing data for column "e"
! CONTEXT:  COPY x, line 1: "2001	231	\N	\N"
! -- extra data: should fail
! COPY x from stdin;
! ERROR:  extra data after last expected column
! CONTEXT:  COPY x, line 1: "2002	232	40	50	60	70	80"
! -- various COPY options: delimiters, oids, NULL string, encoding
! COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x';
! COPY x from stdin WITH DELIMITER AS ';' NULL AS '';
! COPY x from stdin WITH DELIMITER AS ':' NULL AS E'\\X' ENCODING 'sql_ascii';
! -- check results of copy in
! SELECT * FROM x;
!    a   | b  |     c      |   d    |          e           
! -------+----+------------+--------+----------------------
!   9999 |    | \N         | NN     | before trigger fired
!  10000 | 21 | 31         | 41     | before trigger fired
!  10001 | 22 | 32         | 42     | before trigger fired
!  10002 | 23 | 33         | 43     | before trigger fired
!  10003 | 24 | 34         | 44     | before trigger fired
!  10004 | 25 | 35         | 45     | before trigger fired
!  10005 | 26 | 36         | 46     | before trigger fired
!      6 |    | 45         | 80     | before trigger fired
!      7 |    | x          | \x     | before trigger fired
!      8 |    | ,          | \,     | before trigger fired
!   3000 |    | c          |        | before trigger fired
!   4000 |    | C          |        | before trigger fired
!   4001 |  1 | empty      |        | before trigger fired
!   4002 |  2 | null       |        | before trigger fired
!   4003 |  3 | Backslash  | \      | before trigger fired
!   4004 |  4 | BackslashX | \X     | before trigger fired
!   4005 |  5 | N          | N      | before trigger fired
!   4006 |  6 | BackslashN | \N     | before trigger fired
!   4007 |  7 | XX         | XX     | before trigger fired
!   4008 |  8 | Delimiter  | :      | before trigger fired
!      1 |  1 | stuff      | test_1 | after trigger fired
!      2 |  2 | stuff      | test_2 | after trigger fired
!      3 |  3 | stuff      | test_3 | after trigger fired
!      4 |  4 | stuff      | test_4 | after trigger fired
!      5 |  5 | stuff      | test_5 | after trigger fired
! (25 rows)
! 
! -- COPY w/ oids on a table w/o oids should fail
! CREATE TABLE no_oids (
! 	a	int,
! 	b	int
! ) WITHOUT OIDS;
! INSERT INTO no_oids (a, b) VALUES (5, 10);
! INSERT INTO no_oids (a, b) VALUES (20, 30);
! -- should fail
! COPY no_oids FROM stdin WITH OIDS;
! ERROR:  table "no_oids" does not have OIDs
! COPY no_oids TO stdout WITH OIDS;
! ERROR:  table "no_oids" does not have OIDs
! -- check copy out
! COPY x TO stdout;
! 9999	\N	\\N	NN	before trigger fired
! 10000	21	31	41	before trigger fired
! 10001	22	32	42	before trigger fired
! 10002	23	33	43	before trigger fired
! 10003	24	34	44	before trigger fired
! 10004	25	35	45	before trigger fired
! 10005	26	36	46	before trigger fired
! 6	\N	45	80	before trigger fired
! 7	\N	x	\\x	before trigger fired
! 8	\N	,	\\,	before trigger fired
! 3000	\N	c	\N	before trigger fired
! 4000	\N	C	\N	before trigger fired
! 4001	1	empty		before trigger fired
! 4002	2	null	\N	before trigger fired
! 4003	3	Backslash	\\	before trigger fired
! 4004	4	BackslashX	\\X	before trigger fired
! 4005	5	N	N	before trigger fired
! 4006	6	BackslashN	\\N	before trigger fired
! 4007	7	XX	XX	before trigger fired
! 4008	8	Delimiter	:	before trigger fired
! 1	1	stuff	test_1	after trigger fired
! 2	2	stuff	test_2	after trigger fired
! 3	3	stuff	test_3	after trigger fired
! 4	4	stuff	test_4	after trigger fired
! 5	5	stuff	test_5	after trigger fired
! COPY x (c, e) TO stdout;
! \\N	before trigger fired
! 31	before trigger fired
! 32	before trigger fired
! 33	before trigger fired
! 34	before trigger fired
! 35	before trigger fired
! 36	before trigger fired
! 45	before trigger fired
! x	before trigger fired
! ,	before trigger fired
! c	before trigger fired
! C	before trigger fired
! empty	before trigger fired
! null	before trigger fired
! Backslash	before trigger fired
! BackslashX	before trigger fired
! N	before trigger fired
! BackslashN	before trigger fired
! XX	before trigger fired
! Delimiter	before trigger fired
! stuff	after trigger fired
! stuff	after trigger fired
! stuff	after trigger fired
! stuff	after trigger fired
! stuff	after trigger fired
! COPY x (b, e) TO stdout WITH NULL 'I''m null';
! I'm null	before trigger fired
! 21	before trigger fired
! 22	before trigger fired
! 23	before trigger fired
! 24	before trigger fired
! 25	before trigger fired
! 26	before trigger fired
! I'm null	before trigger fired
! I'm null	before trigger fired
! I'm null	before trigger fired
! I'm null	before trigger fired
! I'm null	before trigger fired
! 1	before trigger fired
! 2	before trigger fired
! 3	before trigger fired
! 4	before trigger fired
! 5	before trigger fired
! 6	before trigger fired
! 7	before trigger fired
! 8	before trigger fired
! 1	after trigger fired
! 2	after trigger fired
! 3	after trigger fired
! 4	after trigger fired
! 5	after trigger fired
! CREATE TEMP TABLE y (
! 	col1 text,
! 	col2 text
! );
! INSERT INTO y VALUES ('Jackson, Sam', E'\\h');
! INSERT INTO y VALUES ('It is "perfect".',E'\t');
! INSERT INTO y VALUES ('', NULL);
! COPY y TO stdout WITH CSV;
! "Jackson, Sam",\h
! "It is ""perfect"".",	
! "",
! COPY y TO stdout WITH CSV QUOTE '''' DELIMITER '|';
! Jackson, Sam|\h
! It is "perfect".|	
! ''|
! COPY y TO stdout WITH CSV FORCE QUOTE col2 ESCAPE E'\\' ENCODING 'sql_ascii';
! "Jackson, Sam","\\h"
! "It is \"perfect\".","	"
! "",
! COPY y TO stdout WITH CSV FORCE QUOTE *;
! "Jackson, Sam","\h"
! "It is ""perfect"".","	"
! "",
! -- Repeat above tests with new 9.0 option syntax
! COPY y TO stdout (FORMAT CSV);
! "Jackson, Sam",\h
! "It is ""perfect"".",	
! "",
! COPY y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|');
! Jackson, Sam|\h
! It is "perfect".|	
! ''|
! COPY y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\');
! "Jackson, Sam","\\h"
! "It is \"perfect\".","	"
! "",
! COPY y TO stdout (FORMAT CSV, FORCE_QUOTE *);
! "Jackson, Sam","\h"
! "It is ""perfect"".","	"
! "",
! \copy y TO stdout (FORMAT CSV)
! "Jackson, Sam",\h
! "It is ""perfect"".",	
! "",
! \copy y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|')
! Jackson, Sam|\h
! It is "perfect".|	
! ''|
! \copy y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\')
! "Jackson, Sam","\\h"
! "It is \"perfect\".","	"
! "",
! \copy y TO stdout (FORMAT CSV, FORCE_QUOTE *)
! "Jackson, Sam","\h"
! "It is ""perfect"".","	"
! "",
! --test that we read consecutive LFs properly
! CREATE TEMP TABLE testnl (a int, b text, c int);
! COPY testnl FROM stdin CSV;
! -- test end of copy marker
! CREATE TEMP TABLE testeoc (a text);
! COPY testeoc FROM stdin CSV;
! COPY testeoc TO stdout CSV;
! a\.
! \.b
! c\.d
! "\."
! -- test handling of nonstandard null marker that violates escaping rules
! CREATE TEMP TABLE testnull(a int, b text);
! INSERT INTO testnull VALUES (1, E'\\0'), (NULL, NULL);
! COPY testnull TO stdout WITH NULL AS E'\\0';
! 1	\\0
! \0	\0
! COPY testnull FROM stdin WITH NULL AS E'\\0';
! SELECT * FROM testnull;
!  a  | b  
! ----+----
!   1 | \0
!     | 
!  42 | \0
!     | 
! (4 rows)
! 
! BEGIN;
! CREATE TABLE vistest (LIKE testeoc);
! COPY vistest FROM stdin CSV;
! COMMIT;
! SELECT * FROM vistest;
!  a  
! ----
!  a0
!  b
! (2 rows)
! 
! BEGIN;
! TRUNCATE vistest;
! COPY vistest FROM stdin CSV;
! SELECT * FROM vistest;
!  a  
! ----
!  a1
!  b
! (2 rows)
! 
! SAVEPOINT s1;
! TRUNCATE vistest;
! COPY vistest FROM stdin CSV;
! SELECT * FROM vistest;
!  a  
! ----
!  d1
!  e
! (2 rows)
! 
! COMMIT;
! SELECT * FROM vistest;
!  a  
! ----
!  d1
!  e
! (2 rows)
! 
! BEGIN;
! TRUNCATE vistest;
! COPY vistest FROM stdin CSV FREEZE;
! SELECT * FROM vistest;
!  a  
! ----
!  a2
!  b
! (2 rows)
! 
! SAVEPOINT s1;
! TRUNCATE vistest;
! COPY vistest FROM stdin CSV FREEZE;
! SELECT * FROM vistest;
!  a  
! ----
!  d2
!  e
! (2 rows)
! 
! COMMIT;
! SELECT * FROM vistest;
!  a  
! ----
!  d2
!  e
! (2 rows)
! 
! BEGIN;
! TRUNCATE vistest;
! COPY vistest FROM stdin CSV FREEZE;
! SELECT * FROM vistest;
!  a 
! ---
!  x
!  y
! (2 rows)
! 
! COMMIT;
! TRUNCATE vistest;
! COPY vistest FROM stdin CSV FREEZE;
! ERROR:  cannot perform FREEZE because the table was not created or truncated in the current subtransaction
! BEGIN;
! TRUNCATE vistest;
! SAVEPOINT s1;
! COPY vistest FROM stdin CSV FREEZE;
! ERROR:  cannot perform FREEZE because the table was not created or truncated in the current subtransaction
! COMMIT;
! BEGIN;
! INSERT INTO vistest VALUES ('z');
! SAVEPOINT s1;
! TRUNCATE vistest;
! ROLLBACK TO SAVEPOINT s1;
! COPY vistest FROM stdin CSV FREEZE;
! ERROR:  cannot perform FREEZE because the table was not created or truncated in the current subtransaction
! COMMIT;
! CREATE FUNCTION truncate_in_subxact() RETURNS VOID AS
! $$
! BEGIN
! 	TRUNCATE vistest;
! EXCEPTION
!   WHEN OTHERS THEN
! 	INSERT INTO vistest VALUES ('subxact failure');
! END;
! $$ language plpgsql;
! BEGIN;
! INSERT INTO vistest VALUES ('z');
! SELECT truncate_in_subxact();
!  truncate_in_subxact 
! ---------------------
!  
! (1 row)
! 
! COPY vistest FROM stdin CSV FREEZE;
! SELECT * FROM vistest;
!  a  
! ----
!  d4
!  e
! (2 rows)
! 
! COMMIT;
! SELECT * FROM vistest;
!  a  
! ----
!  d4
!  e
! (2 rows)
! 
! DROP TABLE vistest;
! DROP FUNCTION truncate_in_subxact();
! DROP TABLE x, y;
! DROP FUNCTION fn_x_before();
! DROP FUNCTION fn_x_after();
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/temp.out	2014-01-02 13:19:15.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/temp.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,201 ****
! --
! -- TEMP
! -- Test temp relations and indexes
! --
! -- test temp table/index masking
! CREATE TABLE temptest(col int);
! CREATE INDEX i_temptest ON temptest(col);
! CREATE TEMP TABLE temptest(tcol int);
! CREATE INDEX i_temptest ON temptest(tcol);
! SELECT * FROM temptest;
!  tcol 
! ------
! (0 rows)
! 
! DROP INDEX i_temptest;
! DROP TABLE temptest;
! SELECT * FROM temptest;
!  col 
! -----
! (0 rows)
! 
! DROP INDEX i_temptest;
! DROP TABLE temptest;
! -- test temp table selects
! CREATE TABLE temptest(col int);
! INSERT INTO temptest VALUES (1);
! CREATE TEMP TABLE temptest(tcol float);
! INSERT INTO temptest VALUES (2.1);
! SELECT * FROM temptest;
!  tcol 
! ------
!   2.1
! (1 row)
! 
! DROP TABLE temptest;
! SELECT * FROM temptest;
!  col 
! -----
!    1
! (1 row)
! 
! DROP TABLE temptest;
! -- test temp table deletion
! CREATE TEMP TABLE temptest(col int);
! \c
! SELECT * FROM temptest;
! ERROR:  relation "temptest" does not exist
! LINE 1: SELECT * FROM temptest;
!                       ^
! -- Test ON COMMIT DELETE ROWS
! CREATE TEMP TABLE temptest(col int) ON COMMIT DELETE ROWS;
! BEGIN;
! INSERT INTO temptest VALUES (1);
! INSERT INTO temptest VALUES (2);
! SELECT * FROM temptest;
!  col 
! -----
!    1
!    2
! (2 rows)
! 
! COMMIT;
! SELECT * FROM temptest;
!  col 
! -----
! (0 rows)
! 
! DROP TABLE temptest;
! BEGIN;
! CREATE TEMP TABLE temptest(col) ON COMMIT DELETE ROWS AS SELECT 1;
! SELECT * FROM temptest;
!  col 
! -----
!    1
! (1 row)
! 
! COMMIT;
! SELECT * FROM temptest;
!  col 
! -----
! (0 rows)
! 
! DROP TABLE temptest;
! -- Test ON COMMIT DROP
! BEGIN;
! CREATE TEMP TABLE temptest(col int) ON COMMIT DROP;
! INSERT INTO temptest VALUES (1);
! INSERT INTO temptest VALUES (2);
! SELECT * FROM temptest;
!  col 
! -----
!    1
!    2
! (2 rows)
! 
! COMMIT;
! SELECT * FROM temptest;
! ERROR:  relation "temptest" does not exist
! LINE 1: SELECT * FROM temptest;
!                       ^
! BEGIN;
! CREATE TEMP TABLE temptest(col) ON COMMIT DROP AS SELECT 1;
! SELECT * FROM temptest;
!  col 
! -----
!    1
! (1 row)
! 
! COMMIT;
! SELECT * FROM temptest;
! ERROR:  relation "temptest" does not exist
! LINE 1: SELECT * FROM temptest;
!                       ^
! -- ON COMMIT is only allowed for TEMP
! CREATE TABLE temptest(col int) ON COMMIT DELETE ROWS;
! ERROR:  ON COMMIT can only be used on temporary tables
! CREATE TABLE temptest(col) ON COMMIT DELETE ROWS AS SELECT 1;
! ERROR:  ON COMMIT can only be used on temporary tables
! -- Test foreign keys
! BEGIN;
! CREATE TEMP TABLE temptest1(col int PRIMARY KEY);
! CREATE TEMP TABLE temptest2(col int REFERENCES temptest1)
!   ON COMMIT DELETE ROWS;
! INSERT INTO temptest1 VALUES (1);
! INSERT INTO temptest2 VALUES (1);
! COMMIT;
! SELECT * FROM temptest1;
!  col 
! -----
!    1
! (1 row)
! 
! SELECT * FROM temptest2;
!  col 
! -----
! (0 rows)
! 
! BEGIN;
! CREATE TEMP TABLE temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
! CREATE TEMP TABLE temptest4(col int REFERENCES temptest3);
! COMMIT;
! ERROR:  unsupported ON COMMIT and foreign key combination
! DETAIL:  Table "temptest4" references "temptest3", but they do not have the same ON COMMIT setting.
! -- Test manipulation of temp schema's placement in search path
! create table public.whereami (f1 text);
! insert into public.whereami values ('public');
! create temp table whereami (f1 text);
! insert into whereami values ('temp');
! create function public.whoami() returns text
!   as $$select 'public'::text$$ language sql;
! create function pg_temp.whoami() returns text
!   as $$select 'temp'::text$$ language sql;
! -- default should have pg_temp implicitly first, but only for tables
! select * from whereami;
!   f1  
! ------
!  temp
! (1 row)
! 
! select whoami();
!  whoami 
! --------
!  public
! (1 row)
! 
! -- can list temp first explicitly, but it still doesn't affect functions
! set search_path = pg_temp, public;
! select * from whereami;
!   f1  
! ------
!  temp
! (1 row)
! 
! select whoami();
!  whoami 
! --------
!  public
! (1 row)
! 
! -- or put it last for security
! set search_path = public, pg_temp;
! select * from whereami;
!    f1   
! --------
!  public
! (1 row)
! 
! select whoami();
!  whoami 
! --------
!  public
! (1 row)
! 
! -- you can invoke a temp function explicitly, though
! select pg_temp.whoami();
!  whoami 
! --------
!  temp
! (1 row)
! 
! drop table public.whereami;
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/domain.out	2014-01-02 13:19:14.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/domain.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,667 ****
! --
! -- Test domains.
! --
! -- Test Comment / Drop
! create domain domaindroptest int4;
! comment on domain domaindroptest is 'About to drop this..';
! create domain dependenttypetest domaindroptest;
! -- fail because of dependent type
! drop domain domaindroptest;
! ERROR:  cannot drop type domaindroptest because other objects depend on it
! DETAIL:  type dependenttypetest depends on type domaindroptest
! HINT:  Use DROP ... CASCADE to drop the dependent objects too.
! drop domain domaindroptest cascade;
! NOTICE:  drop cascades to type dependenttypetest
! -- this should fail because already gone
! drop domain domaindroptest cascade;
! ERROR:  type "domaindroptest" does not exist
! -- Test domain input.
! -- Note: the point of checking both INSERT and COPY FROM is that INSERT
! -- exercises CoerceToDomain while COPY exercises domain_in.
! create domain domainvarchar varchar(5);
! create domain domainnumeric numeric(8,2);
! create domain domainint4 int4;
! create domain domaintext text;
! -- Test explicit coercions --- these should succeed (and truncate)
! SELECT cast('123456' as domainvarchar);
!  domainvarchar 
! ---------------
!  12345
! (1 row)
! 
! SELECT cast('12345' as domainvarchar);
!  domainvarchar 
! ---------------
!  12345
! (1 row)
! 
! -- Test tables using domains
! create table basictest
!            ( testint4 domainint4
!            , testtext domaintext
!            , testvarchar domainvarchar
!            , testnumeric domainnumeric
!            );
! INSERT INTO basictest values ('88', 'haha', 'short', '123.12');      -- Good
! INSERT INTO basictest values ('88', 'haha', 'short text', '123.12'); -- Bad varchar
! ERROR:  value too long for type character varying(5)
! INSERT INTO basictest values ('88', 'haha', 'short', '123.1212');    -- Truncate numeric
! -- Test copy
! COPY basictest (testvarchar) FROM stdin; -- fail
! ERROR:  value too long for type character varying(5)
! CONTEXT:  COPY basictest, line 1, column testvarchar: "notsoshorttext"
! COPY basictest (testvarchar) FROM stdin;
! select * from basictest;
!  testint4 | testtext | testvarchar | testnumeric 
! ----------+----------+-------------+-------------
!        88 | haha     | short       |      123.12
!        88 | haha     | short       |      123.12
!           |          | short       |            
! (3 rows)
! 
! -- check that domains inherit operations from base types
! select testtext || testvarchar as concat, testnumeric + 42 as sum
! from basictest;
!   concat   |  sum   
! -----------+--------
!  hahashort | 165.12
!  hahashort | 165.12
!            |       
! (3 rows)
! 
! -- check that union/case/coalesce type resolution handles domains properly
! select coalesce(4::domainint4, 7) is of (int4) as t;
!  t 
! ---
!  t
! (1 row)
! 
! select coalesce(4::domainint4, 7) is of (domainint4) as f;
!  f 
! ---
!  f
! (1 row)
! 
! select coalesce(4::domainint4, 7::domainint4) is of (domainint4) as t;
!  t 
! ---
!  t
! (1 row)
! 
! drop table basictest;
! drop domain domainvarchar restrict;
! drop domain domainnumeric restrict;
! drop domain domainint4 restrict;
! drop domain domaintext;
! -- Test domains over array types
! create domain domainint4arr int4[1];
! create domain domainchar4arr varchar(4)[2][3];
! create table domarrtest
!            ( testint4arr domainint4arr
!            , testchar4arr domainchar4arr
!             );
! INSERT INTO domarrtest values ('{2,2}', '{{"a","b"},{"c","d"}}');
! INSERT INTO domarrtest values ('{{2,2},{2,2}}', '{{"a","b"}}');
! INSERT INTO domarrtest values ('{2,2}', '{{"a","b"},{"c","d"},{"e","f"}}');
! INSERT INTO domarrtest values ('{2,2}', '{{"a"},{"c"}}');
! INSERT INTO domarrtest values (NULL, '{{"a","b","c"},{"d","e","f"}}');
! INSERT INTO domarrtest values (NULL, '{{"toolong","b","c"},{"d","e","f"}}');
! ERROR:  value too long for type character varying(4)
! select * from domarrtest;
!   testint4arr  |    testchar4arr     
! ---------------+---------------------
!  {2,2}         | {{a,b},{c,d}}
!  {{2,2},{2,2}} | {{a,b}}
!  {2,2}         | {{a,b},{c,d},{e,f}}
!  {2,2}         | {{a},{c}}
!                | {{a,b,c},{d,e,f}}
! (5 rows)
! 
! select testint4arr[1], testchar4arr[2:2] from domarrtest;
!  testint4arr | testchar4arr 
! -------------+--------------
!            2 | {{c,d}}
!              | {}
!            2 | {{c,d}}
!            2 | {{c}}
!              | {{d,e,f}}
! (5 rows)
! 
! select array_dims(testint4arr), array_dims(testchar4arr) from domarrtest;
!  array_dims | array_dims 
! ------------+------------
!  [1:2]      | [1:2][1:2]
!  [1:2][1:2] | [1:1][1:2]
!  [1:2]      | [1:3][1:2]
!  [1:2]      | [1:2][1:1]
!             | [1:2][1:3]
! (5 rows)
! 
! COPY domarrtest FROM stdin;
! COPY domarrtest FROM stdin;	-- fail
! ERROR:  value too long for type character varying(4)
! CONTEXT:  COPY domarrtest, line 1, column testchar4arr: "{qwerty,w,e}"
! select * from domarrtest;
!   testint4arr  |    testchar4arr     
! ---------------+---------------------
!  {2,2}         | {{a,b},{c,d}}
!  {{2,2},{2,2}} | {{a,b}}
!  {2,2}         | {{a,b},{c,d},{e,f}}
!  {2,2}         | {{a},{c}}
!                | {{a,b,c},{d,e,f}}
!  {3,4}         | {q,w,e}
!                | 
! (7 rows)
! 
! drop table domarrtest;
! drop domain domainint4arr restrict;
! drop domain domainchar4arr restrict;
! create domain dia as int[];
! select '{1,2,3}'::dia;
!    dia   
! ---------
!  {1,2,3}
! (1 row)
! 
! select array_dims('{1,2,3}'::dia);
!  array_dims 
! ------------
!  [1:3]
! (1 row)
! 
! select pg_typeof('{1,2,3}'::dia);
!  pg_typeof 
! -----------
!  dia
! (1 row)
! 
! select pg_typeof('{1,2,3}'::dia || 42); -- should be int[] not dia
!  pg_typeof 
! -----------
!  integer[]
! (1 row)
! 
! drop domain dia;
! create domain dnotnull varchar(15) NOT NULL;
! create domain dnull    varchar(15);
! create domain dcheck   varchar(15) NOT NULL CHECK (VALUE = 'a' OR VALUE = 'c' OR VALUE = 'd');
! create table nulltest
!            ( col1 dnotnull
!            , col2 dnotnull NULL  -- NOT NULL in the domain cannot be overridden
!            , col3 dnull    NOT NULL
!            , col4 dnull
!            , col5 dcheck CHECK (col5 IN ('c', 'd'))
!            );
! INSERT INTO nulltest DEFAULT VALUES;
! ERROR:  domain dnotnull does not allow null values
! INSERT INTO nulltest values ('a', 'b', 'c', 'd', 'c');  -- Good
! insert into nulltest values ('a', 'b', 'c', 'd', NULL);
! ERROR:  domain dcheck does not allow null values
! insert into nulltest values ('a', 'b', 'c', 'd', 'a');
! ERROR:  new row for relation "nulltest" violates check constraint "nulltest_col5_check"
! DETAIL:  Failing row contains (a, b, c, d, a).
! INSERT INTO nulltest values (NULL, 'b', 'c', 'd', 'd');
! ERROR:  domain dnotnull does not allow null values
! INSERT INTO nulltest values ('a', NULL, 'c', 'd', 'c');
! ERROR:  domain dnotnull does not allow null values
! INSERT INTO nulltest values ('a', 'b', NULL, 'd', 'c');
! ERROR:  null value in column "col3" violates not-null constraint
! DETAIL:  Failing row contains (a, b, null, d, c).
! INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good
! -- Test copy
! COPY nulltest FROM stdin; --fail
! ERROR:  null value in column "col3" violates not-null constraint
! DETAIL:  Failing row contains (a, b, null, d, d).
! CONTEXT:  COPY nulltest, line 1: "a	b	\N	d	d"
! COPY nulltest FROM stdin; --fail
! ERROR:  domain dcheck does not allow null values
! CONTEXT:  COPY nulltest, line 1, column col5: null input
! -- Last row is bad
! COPY nulltest FROM stdin;
! ERROR:  new row for relation "nulltest" violates check constraint "nulltest_col5_check"
! DETAIL:  Failing row contains (a, b, c, null, a).
! CONTEXT:  COPY nulltest, line 3: "a	b	c	\N	a"
! select * from nulltest;
!  col1 | col2 | col3 | col4 | col5 
! ------+------+------+------+------
!  a    | b    | c    | d    | c
!  a    | b    | c    |      | d
! (2 rows)
! 
! -- Test out coerced (casted) constraints
! SELECT cast('1' as dnotnull);
!  dnotnull 
! ----------
!  1
! (1 row)
! 
! SELECT cast(NULL as dnotnull); -- fail
! ERROR:  domain dnotnull does not allow null values
! SELECT cast(cast(NULL as dnull) as dnotnull); -- fail
! ERROR:  domain dnotnull does not allow null values
! SELECT cast(col4 as dnotnull) from nulltest; -- fail
! ERROR:  domain dnotnull does not allow null values
! -- cleanup
! drop table nulltest;
! drop domain dnotnull restrict;
! drop domain dnull restrict;
! drop domain dcheck restrict;
! create domain ddef1 int4 DEFAULT 3;
! create domain ddef2 oid DEFAULT '12';
! -- Type mixing, function returns int8
! create domain ddef3 text DEFAULT 5;
! create sequence ddef4_seq;
! create domain ddef4 int4 DEFAULT nextval('ddef4_seq');
! create domain ddef5 numeric(8,2) NOT NULL DEFAULT '12.12';
! create table defaulttest
!             ( col1 ddef1
!             , col2 ddef2
!             , col3 ddef3
!             , col4 ddef4 PRIMARY KEY
!             , col5 ddef1 NOT NULL DEFAULT NULL
!             , col6 ddef2 DEFAULT '88'
!             , col7 ddef4 DEFAULT 8000
!             , col8 ddef5
!             );
! insert into defaulttest(col4) values(0); -- fails, col5 defaults to null
! ERROR:  null value in column "col5" violates not-null constraint
! DETAIL:  Failing row contains (3, 12, 5, 0, null, 88, 8000, 12.12).
! alter table defaulttest alter column col5 drop default;
! insert into defaulttest default values; -- succeeds, inserts domain default
! -- We used to treat SET DEFAULT NULL as equivalent to DROP DEFAULT; wrong
! alter table defaulttest alter column col5 set default null;
! insert into defaulttest(col4) values(0); -- fails
! ERROR:  null value in column "col5" violates not-null constraint
! DETAIL:  Failing row contains (3, 12, 5, 0, null, 88, 8000, 12.12).
! alter table defaulttest alter column col5 drop default;
! insert into defaulttest default values;
! insert into defaulttest default values;
! -- Test defaults with copy
! COPY defaulttest(col5) FROM stdin;
! select * from defaulttest;
!  col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8  
! ------+------+------+------+------+------+------+-------
!     3 |   12 | 5    |    1 |    3 |   88 | 8000 | 12.12
!     3 |   12 | 5    |    2 |    3 |   88 | 8000 | 12.12
!     3 |   12 | 5    |    3 |    3 |   88 | 8000 | 12.12
!     3 |   12 | 5    |    4 |   42 |   88 | 8000 | 12.12
! (4 rows)
! 
! drop table defaulttest cascade;
! -- Test ALTER DOMAIN .. NOT NULL
! create domain dnotnulltest integer;
! create table domnotnull
! ( col1 dnotnulltest
! , col2 dnotnulltest
! );
! insert into domnotnull default values;
! alter domain dnotnulltest set not null; -- fails
! ERROR:  column "col1" of table "domnotnull" contains null values
! update domnotnull set col1 = 5;
! alter domain dnotnulltest set not null; -- fails
! ERROR:  column "col2" of table "domnotnull" contains null values
! update domnotnull set col2 = 6;
! alter domain dnotnulltest set not null;
! update domnotnull set col1 = null; -- fails
! ERROR:  domain dnotnulltest does not allow null values
! alter domain dnotnulltest drop not null;
! update domnotnull set col1 = null;
! drop domain dnotnulltest cascade;
! NOTICE:  drop cascades to 2 other objects
! DETAIL:  drop cascades to table domnotnull column col1
! drop cascades to table domnotnull column col2
! -- Test ALTER DOMAIN .. DEFAULT ..
! create table domdeftest (col1 ddef1);
! insert into domdeftest default values;
! select * from domdeftest;
!  col1 
! ------
!     3
! (1 row)
! 
! alter domain ddef1 set default '42';
! insert into domdeftest default values;
! select * from domdeftest;
!  col1 
! ------
!     3
!    42
! (2 rows)
! 
! alter domain ddef1 drop default;
! insert into domdeftest default values;
! select * from domdeftest;
!  col1 
! ------
!     3
!    42
!      
! (3 rows)
! 
! drop table domdeftest;
! -- Test ALTER DOMAIN .. CONSTRAINT ..
! create domain con as integer;
! create table domcontest (col1 con);
! insert into domcontest values (1);
! insert into domcontest values (2);
! alter domain con add constraint t check (VALUE < 1); -- fails
! ERROR:  column "col1" of table "domcontest" contains values that violate the new constraint
! alter domain con add constraint t check (VALUE < 34);
! alter domain con add check (VALUE > 0);
! insert into domcontest values (-5); -- fails
! ERROR:  value for domain con violates check constraint "con_check"
! insert into domcontest values (42); -- fails
! ERROR:  value for domain con violates check constraint "t"
! insert into domcontest values (5);
! alter domain con drop constraint t;
! insert into domcontest values (-5); --fails
! ERROR:  value for domain con violates check constraint "con_check"
! insert into domcontest values (42);
! alter domain con drop constraint nonexistent;
! ERROR:  constraint "nonexistent" of domain "con" does not exist
! alter domain con drop constraint if exists nonexistent;
! NOTICE:  constraint "nonexistent" of domain "con" does not exist, skipping
! -- Test ALTER DOMAIN .. CONSTRAINT .. NOT VALID
! create domain things AS INT;
! CREATE TABLE thethings (stuff things);
! INSERT INTO thethings (stuff) VALUES (55);
! ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11);
! ERROR:  column "stuff" of table "thethings" contains values that violate the new constraint
! ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11) NOT VALID;
! ALTER DOMAIN things VALIDATE CONSTRAINT meow;
! ERROR:  column "stuff" of table "thethings" contains values that violate the new constraint
! UPDATE thethings SET stuff = 10;
! ALTER DOMAIN things VALIDATE CONSTRAINT meow;
! -- Confirm ALTER DOMAIN with RULES.
! create table domtab (col1 integer);
! create domain dom as integer;
! create view domview as select cast(col1 as dom) from domtab;
! insert into domtab (col1) values (null);
! insert into domtab (col1) values (5);
! select * from domview;
!  col1 
! ------
!      
!     5
! (2 rows)
! 
! alter domain dom set not null;
! select * from domview; -- fail
! ERROR:  domain dom does not allow null values
! alter domain dom drop not null;
! select * from domview;
!  col1 
! ------
!      
!     5
! (2 rows)
! 
! alter domain dom add constraint domchkgt6 check(value > 6);
! select * from domview; --fail
! ERROR:  value for domain dom violates check constraint "domchkgt6"
! alter domain dom drop constraint domchkgt6 restrict;
! select * from domview;
!  col1 
! ------
!      
!     5
! (2 rows)
! 
! -- cleanup
! drop domain ddef1 restrict;
! drop domain ddef2 restrict;
! drop domain ddef3 restrict;
! drop domain ddef4 restrict;
! drop domain ddef5 restrict;
! drop sequence ddef4_seq;
! -- Test domains over domains
! create domain vchar4 varchar(4);
! create domain dinter vchar4 check (substring(VALUE, 1, 1) = 'x');
! create domain dtop dinter check (substring(VALUE, 2, 1) = '1');
! select 'x123'::dtop;
!  dtop 
! ------
!  x123
! (1 row)
! 
! select 'x1234'::dtop; -- explicit coercion should truncate
!  dtop 
! ------
!  x123
! (1 row)
! 
! select 'y1234'::dtop; -- fail
! ERROR:  value for domain dtop violates check constraint "dinter_check"
! select 'y123'::dtop; -- fail
! ERROR:  value for domain dtop violates check constraint "dinter_check"
! select 'yz23'::dtop; -- fail
! ERROR:  value for domain dtop violates check constraint "dinter_check"
! select 'xz23'::dtop; -- fail
! ERROR:  value for domain dtop violates check constraint "dtop_check"
! create temp table dtest(f1 dtop);
! insert into dtest values('x123');
! insert into dtest values('x1234'); -- fail, implicit coercion
! ERROR:  value too long for type character varying(4)
! insert into dtest values('y1234'); -- fail, implicit coercion
! ERROR:  value too long for type character varying(4)
! insert into dtest values('y123'); -- fail
! ERROR:  value for domain dtop violates check constraint "dinter_check"
! insert into dtest values('yz23'); -- fail
! ERROR:  value for domain dtop violates check constraint "dinter_check"
! insert into dtest values('xz23'); -- fail
! ERROR:  value for domain dtop violates check constraint "dtop_check"
! drop table dtest;
! drop domain vchar4 cascade;
! NOTICE:  drop cascades to 2 other objects
! DETAIL:  drop cascades to type dinter
! drop cascades to type dtop
! -- Make sure that constraints of newly-added domain columns are
! -- enforced correctly, even if there's no default value for the new
! -- column. Per bug #1433
! create domain str_domain as text not null;
! create table domain_test (a int, b int);
! insert into domain_test values (1, 2);
! insert into domain_test values (1, 2);
! -- should fail
! alter table domain_test add column c str_domain;
! ERROR:  domain str_domain does not allow null values
! create domain str_domain2 as text check (value <> 'foo') default 'foo';
! -- should fail
! alter table domain_test add column d str_domain2;
! ERROR:  value for domain str_domain2 violates check constraint "str_domain2_check"
! -- Check that domain constraints on prepared statement parameters of
! -- unknown type are enforced correctly.
! create domain pos_int as int4 check (value > 0) not null;
! prepare s1 as select $1::pos_int = 10 as "is_ten";
! execute s1(10);
!  is_ten 
! --------
!  t
! (1 row)
! 
! execute s1(0); -- should fail
! ERROR:  value for domain pos_int violates check constraint "pos_int_check"
! execute s1(NULL); -- should fail
! ERROR:  domain pos_int does not allow null values
! -- Check that domain constraints on plpgsql function parameters, results,
! -- and local variables are enforced correctly.
! create function doubledecrement(p1 pos_int) returns pos_int as $$
! declare v pos_int;
! begin
!     return p1;
! end$$ language plpgsql;
! select doubledecrement(3); -- fail because of implicit null assignment
! ERROR:  domain pos_int does not allow null values
! CONTEXT:  PL/pgSQL function doubledecrement(pos_int) line 3 during statement block local variable initialization
! create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
! declare v pos_int := 0;
! begin
!     return p1;
! end$$ language plpgsql;
! select doubledecrement(3); -- fail at initialization assignment
! ERROR:  value for domain pos_int violates check constraint "pos_int_check"
! CONTEXT:  PL/pgSQL function doubledecrement(pos_int) line 3 during statement block local variable initialization
! create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
! declare v pos_int := 1;
! begin
!     v := p1 - 1;
!     return v - 1;
! end$$ language plpgsql;
! select doubledecrement(null); -- fail before call
! ERROR:  domain pos_int does not allow null values
! select doubledecrement(0); -- fail before call
! ERROR:  value for domain pos_int violates check constraint "pos_int_check"
! select doubledecrement(1); -- fail at assignment to v
! ERROR:  value for domain pos_int violates check constraint "pos_int_check"
! CONTEXT:  PL/pgSQL function doubledecrement(pos_int) line 4 at assignment
! select doubledecrement(2); -- fail at return
! ERROR:  value for domain pos_int violates check constraint "pos_int_check"
! CONTEXT:  PL/pgSQL function doubledecrement(pos_int) while casting return value to function's return type
! select doubledecrement(3); -- good
!  doubledecrement 
! -----------------
!                1
! (1 row)
! 
! -- Check that ALTER DOMAIN tests columns of derived types
! create domain posint as int4;
! -- Currently, this doesn't work for composite types, but verify it complains
! create type ddtest1 as (f1 posint);
! create table ddtest2(f1 ddtest1);
! insert into ddtest2 values(row(-1));
! alter domain posint add constraint c1 check(value >= 0);
! ERROR:  cannot alter type "posint" because column "ddtest2.f1" uses it
! drop table ddtest2;
! create table ddtest2(f1 ddtest1[]);
! insert into ddtest2 values('{(-1)}');
! alter domain posint add constraint c1 check(value >= 0);
! ERROR:  cannot alter type "posint" because column "ddtest2.f1" uses it
! drop table ddtest2;
! alter domain posint add constraint c1 check(value >= 0);
! create domain posint2 as posint check (value % 2 = 0);
! create table ddtest2(f1 posint2);
! insert into ddtest2 values(11); -- fail
! ERROR:  value for domain posint2 violates check constraint "posint2_check"
! insert into ddtest2 values(-2); -- fail
! ERROR:  value for domain posint2 violates check constraint "c1"
! insert into ddtest2 values(2);
! alter domain posint add constraint c2 check(value >= 10); -- fail
! ERROR:  column "f1" of table "ddtest2" contains values that violate the new constraint
! alter domain posint add constraint c2 check(value > 0); -- OK
! drop table ddtest2;
! drop type ddtest1;
! drop domain posint cascade;
! NOTICE:  drop cascades to type posint2
! --
! -- Check enforcement of domain-related typmod in plpgsql (bug #5717)
! --
! create or replace function array_elem_check(numeric) returns numeric as $$
! declare
!   x numeric(4,2)[1];
! begin
!   x[1] := $1;
!   return x[1];
! end$$ language plpgsql;
! select array_elem_check(121.00);
! ERROR:  numeric field overflow
! DETAIL:  A field with precision 4, scale 2 must round to an absolute value less than 10^2.
! CONTEXT:  PL/pgSQL function array_elem_check(numeric) line 5 at assignment
! select array_elem_check(1.23456);
!  array_elem_check 
! ------------------
!              1.23
! (1 row)
! 
! create domain mynums as numeric(4,2)[1];
! create or replace function array_elem_check(numeric) returns numeric as $$
! declare
!   x mynums;
! begin
!   x[1] := $1;
!   return x[1];
! end$$ language plpgsql;
! select array_elem_check(121.00);
! ERROR:  numeric field overflow
! DETAIL:  A field with precision 4, scale 2 must round to an absolute value less than 10^2.
! CONTEXT:  PL/pgSQL function array_elem_check(numeric) line 5 at assignment
! select array_elem_check(1.23456);
!  array_elem_check 
! ------------------
!              1.23
! (1 row)
! 
! create domain mynums2 as mynums;
! create or replace function array_elem_check(numeric) returns numeric as $$
! declare
!   x mynums2;
! begin
!   x[1] := $1;
!   return x[1];
! end$$ language plpgsql;
! select array_elem_check(121.00);
! ERROR:  numeric field overflow
! DETAIL:  A field with precision 4, scale 2 must round to an absolute value less than 10^2.
! CONTEXT:  PL/pgSQL function array_elem_check(numeric) line 5 at assignment
! select array_elem_check(1.23456);
!  array_elem_check 
! ------------------
!              1.23
! (1 row)
! 
! drop function array_elem_check(numeric);
! --
! -- Check enforcement of array-level domain constraints
! --
! create domain orderedpair as int[2] check (value[1] < value[2]);
! select array[1,2]::orderedpair;
!  array 
! -------
!  {1,2}
! (1 row)
! 
! select array[2,1]::orderedpair;  -- fail
! ERROR:  value for domain orderedpair violates check constraint "orderedpair_check"
! create temp table op (f1 orderedpair);
! insert into op values (array[1,2]);
! insert into op values (array[2,1]);  -- fail
! ERROR:  value for domain orderedpair violates check constraint "orderedpair_check"
! update op set f1[2] = 3;
! update op set f1[2] = 0;  -- fail
! ERROR:  value for domain orderedpair violates check constraint "orderedpair_check"
! select * from op;
!   f1   
! -------
!  {1,3}
! (1 row)
! 
! create or replace function array_elem_check(int) returns int as $$
! declare
!   x orderedpair := '{1,2}';
! begin
!   x[2] := $1;
!   return x[2];
! end$$ language plpgsql;
! select array_elem_check(3);
!  array_elem_check 
! ------------------
!                 3
! (1 row)
! 
! select array_elem_check(-1);
! ERROR:  value for domain orderedpair violates check constraint "orderedpair_check"
! CONTEXT:  PL/pgSQL function array_elem_check(integer) line 5 at assignment
! drop function array_elem_check(int);
! --
! -- Renaming
! --
! create domain testdomain1 as int;
! alter domain testdomain1 rename to testdomain2;
! alter type testdomain2 rename to testdomain3;  -- alter type also works
! drop domain testdomain3;
! --
! -- Renaming domain constraints
! --
! create domain testdomain1 as int constraint unsigned check (value > 0);
! alter domain testdomain1 rename constraint unsigned to unsigned_foo;
! alter domain testdomain1 drop constraint unsigned_foo;
! drop domain testdomain1;
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/rangefuncs.out	2014-01-02 13:19:15.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/rangefuncs.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,1994 ****
! SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
!          name         | setting 
! ----------------------+---------
!  enable_bitmapscan    | on
!  enable_hashagg       | on
!  enable_hashjoin      | on
!  enable_indexonlyscan | on
!  enable_indexscan     | on
!  enable_material      | on
!  enable_mergejoin     | on
!  enable_nestloop      | on
!  enable_seqscan       | on
!  enable_sort          | on
!  enable_tidscan       | on
! (11 rows)
! 
! CREATE TABLE foo2(fooid int, f2 int);
! INSERT INTO foo2 VALUES(1, 11);
! INSERT INTO foo2 VALUES(2, 22);
! INSERT INTO foo2 VALUES(1, 111);
! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1 ORDER BY f2;' LANGUAGE SQL;
! -- function with ORDINALITY
! select * from foot(1) with ordinality as z(a,b,ord);
!  a |  b  | ord 
! ---+-----+-----
!  1 |  11 |   1
!  1 | 111 |   2
! (2 rows)
! 
! select * from foot(1) with ordinality as z(a,b,ord) where b > 100;   -- ordinal 2, not 1
!  a |  b  | ord 
! ---+-----+-----
!  1 | 111 |   2
! (1 row)
! 
! -- ordinality vs. column names and types
! select a,b,ord from foot(1) with ordinality as z(a,b,ord);
!  a |  b  | ord 
! ---+-----+-----
!  1 |  11 |   1
!  1 | 111 |   2
! (2 rows)
! 
! select a,ord from unnest(array['a','b']) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  a |   1
!  b |   2
! (2 rows)
! 
! select * from unnest(array['a','b']) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  a |   1
!  b |   2
! (2 rows)
! 
! select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  1 |   1
! (1 row)
! 
! select * from unnest(array[1.0::float8]) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  1 |   1
! (1 row)
! 
! select row_to_json(s.*) from generate_series(11,14) with ordinality s;
!        row_to_json       
! -------------------------
!  {"s":11,"ordinality":1}
!  {"s":12,"ordinality":2}
!  {"s":13,"ordinality":3}
!  {"s":14,"ordinality":4}
! (4 rows)
! 
! -- ordinality vs. views
! create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) with ordinality as z(a,b,ord) on (n=ord);
! select * from vw_ord;
!  n | a | b  | ord 
! ---+---+----+-----
!  1 | 1 | 11 |   1
! (1 row)
! 
! select definition from pg_views where viewname='vw_ord';
!                             definition                             
! -------------------------------------------------------------------
!   SELECT v.n,                                                     +
!      z.a,                                                         +
!      z.b,                                                         +
!      z.ord                                                        +
!     FROM (( VALUES (1)) v(n)                                      +
!     JOIN foot(1) WITH ORDINALITY z(a, b, ord) ON ((v.n = z.ord)));
! (1 row)
! 
! drop view vw_ord;
! -- multiple functions
! select * from rows from(foot(1),foot(2)) with ordinality as z(a,b,c,d,ord);
!  a |  b  | c | d  | ord 
! ---+-----+---+----+-----
!  1 |  11 | 2 | 22 |   1
!  1 | 111 |   |    |   2
! (2 rows)
! 
! create temporary view vw_ord as select * from (values (1)) v(n) join rows from(foot(1),foot(2)) with ordinality as z(a,b,c,d,ord) on (n=ord);
! select * from vw_ord;
!  n | a | b  | c | d  | ord 
! ---+---+----+---+----+-----
!  1 | 1 | 11 | 2 | 22 |   1
! (1 row)
! 
! select definition from pg_views where viewname='vw_ord';
!                                          definition                                          
! ---------------------------------------------------------------------------------------------
!   SELECT v.n,                                                                               +
!      z.a,                                                                                   +
!      z.b,                                                                                   +
!      z.c,                                                                                   +
!      z.d,                                                                                   +
!      z.ord                                                                                  +
!     FROM (( VALUES (1)) v(n)                                                                +
!     JOIN ROWS FROM(foot(1), foot(2)) WITH ORDINALITY z(a, b, c, d, ord) ON ((v.n = z.ord)));
! (1 row)
! 
! drop view vw_ord;
! -- expansions of unnest()
! select * from unnest(array[10,20],array['foo','bar'],array[1.0]);
!  unnest | unnest | unnest 
! --------+--------+--------
!      10 | foo    |    1.0
!      20 | bar    |       
! (2 rows)
! 
! select * from unnest(array[10,20],array['foo','bar'],array[1.0]) with ordinality as z(a,b,c,ord);
!  a  |  b  |  c  | ord 
! ----+-----+-----+-----
!  10 | foo | 1.0 |   1
!  20 | bar |     |   2
! (2 rows)
! 
! select * from rows from(unnest(array[10,20],array['foo','bar'],array[1.0])) with ordinality as z(a,b,c,ord);
!  a  |  b  |  c  | ord 
! ----+-----+-----+-----
!  10 | foo | 1.0 |   1
!  20 | bar |     |   2
! (2 rows)
! 
! select * from rows from(unnest(array[10,20],array['foo','bar']), generate_series(101,102)) with ordinality as z(a,b,c,ord);
!  a  |  b  |  c  | ord 
! ----+-----+-----+-----
!  10 | foo | 101 |   1
!  20 | bar | 102 |   2
! (2 rows)
! 
! create temporary view vw_ord as select * from unnest(array[10,20],array['foo','bar'],array[1.0]) as z(a,b,c);
! select * from vw_ord;
!  a  |  b  |  c  
! ----+-----+-----
!  10 | foo | 1.0
!  20 | bar |    
! (2 rows)
! 
! select definition from pg_views where viewname='vw_ord';
!                                        definition                                       
! ----------------------------------------------------------------------------------------
!   SELECT z.a,                                                                          +
!      z.b,                                                                              +
!      z.c                                                                               +
!     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
! (1 row)
! 
! drop view vw_ord;
! create temporary view vw_ord as select * from rows from(unnest(array[10,20],array['foo','bar'],array[1.0])) as z(a,b,c);
! select * from vw_ord;
!  a  |  b  |  c  
! ----+-----+-----
!  10 | foo | 1.0
!  20 | bar |    
! (2 rows)
! 
! select definition from pg_views where viewname='vw_ord';
!                                        definition                                       
! ----------------------------------------------------------------------------------------
!   SELECT z.a,                                                                          +
!      z.b,                                                                              +
!      z.c                                                                               +
!     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
! (1 row)
! 
! drop view vw_ord;
! create temporary view vw_ord as select * from rows from(unnest(array[10,20],array['foo','bar']), generate_series(1,2)) as z(a,b,c);
! select * from vw_ord;
!  a  |  b  | c 
! ----+-----+---
!  10 | foo | 1
!  20 | bar | 2
! (2 rows)
! 
! select definition from pg_views where viewname='vw_ord';
!                                                       definition                                                      
! ----------------------------------------------------------------------------------------------------------------------
!   SELECT z.a,                                                                                                        +
!      z.b,                                                                                                            +
!      z.c                                                                                                             +
!     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
! (1 row)
! 
! drop view vw_ord;
! -- ordinality and multiple functions vs. rewind and reverse scan
! begin;
! declare foo scroll cursor for select * from rows from(generate_series(1,5),generate_series(1,2)) with ordinality as g(i,j,o);
! fetch all from foo;
!  i | j | o 
! ---+---+---
!  1 | 1 | 1
!  2 | 2 | 2
!  3 |   | 3
!  4 |   | 4
!  5 |   | 5
! (5 rows)
! 
! fetch backward all from foo;
!  i | j | o 
! ---+---+---
!  5 |   | 5
!  4 |   | 4
!  3 |   | 3
!  2 | 2 | 2
!  1 | 1 | 1
! (5 rows)
! 
! fetch all from foo;
!  i | j | o 
! ---+---+---
!  1 | 1 | 1
!  2 | 2 | 2
!  3 |   | 3
!  4 |   | 4
!  5 |   | 5
! (5 rows)
! 
! fetch next from foo;
!  i | j | o 
! ---+---+---
! (0 rows)
! 
! fetch next from foo;
!  i | j | o 
! ---+---+---
! (0 rows)
! 
! fetch prior from foo;
!  i | j | o 
! ---+---+---
!  5 |   | 5
! (1 row)
! 
! fetch absolute 1 from foo;
!  i | j | o 
! ---+---+---
!  1 | 1 | 1
! (1 row)
! 
! fetch next from foo;
!  i | j | o 
! ---+---+---
!  2 | 2 | 2
! (1 row)
! 
! fetch next from foo;
!  i | j | o 
! ---+---+---
!  3 |   | 3
! (1 row)
! 
! fetch next from foo;
!  i | j | o 
! ---+---+---
!  4 |   | 4
! (1 row)
! 
! fetch prior from foo;
!  i | j | o 
! ---+---+---
!  3 |   | 3
! (1 row)
! 
! fetch prior from foo;
!  i | j | o 
! ---+---+---
!  2 | 2 | 2
! (1 row)
! 
! fetch prior from foo;
!  i | j | o 
! ---+---+---
!  1 | 1 | 1
! (1 row)
! 
! commit;
! -- function with implicit LATERAL
! select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
!  fooid | f2  | fooid | f2  
! -------+-----+-------+-----
!      1 |  11 |     1 |  11
!      2 |  22 |     2 |  22
!      1 | 111 |     1 | 111
! (3 rows)
! 
! -- function with implicit LATERAL and explicit ORDINALITY
! select * from foo2, foot(foo2.fooid) with ordinality as z(fooid,f2,ord) where foo2.f2 = z.f2;
!  fooid | f2  | fooid | f2  | ord 
! -------+-----+-------+-----+-----
!      1 |  11 |     1 |  11 |   1
!      2 |  22 |     2 |  22 |   1
!      1 | 111 |     1 | 111 |   2
! (3 rows)
! 
! -- function in subselect
! select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2;
!  fooid | f2  
! -------+-----
!      1 |  11
!      1 | 111
!      2 |  22
! (3 rows)
! 
! -- function in subselect
! select * from foo2 where f2 in (select f2 from foot(1) z where z.fooid = foo2.fooid) ORDER BY 1,2;
!  fooid | f2  
! -------+-----
!      1 |  11
!      1 | 111
! (2 rows)
! 
! -- function in subselect
! select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = 1) ORDER BY 1,2;
!  fooid | f2  
! -------+-----
!      1 |  11
!      1 | 111
! (2 rows)
! 
! -- nested functions
! select foot.fooid, foot.f2 from foot(sin(pi()/2)::int) ORDER BY 1,2;
!  fooid | f2  
! -------+-----
!      1 |  11
!      1 | 111
! (2 rows)
! 
! CREATE TABLE foo (fooid int, foosubid int, fooname text, primary key(fooid,foosubid));
! INSERT INTO foo VALUES(1,1,'Joe');
! INSERT INTO foo VALUES(1,2,'Ed');
! INSERT INTO foo VALUES(2,1,'Mary');
! -- sql, proretset = f, prorettype = b
! CREATE FUNCTION getfoo1(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
! SELECT * FROM getfoo1(1) AS t1;
!  t1 
! ----
!   1
! (1 row)
! 
! SELECT * FROM getfoo1(1) WITH ORDINALITY AS t1(v,o);
!  v | o 
! ---+---
!  1 | 1
! (1 row)
! 
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1);
! SELECT * FROM vw_getfoo;
!  getfoo1 
! ---------
!        1
! (1 row)
! 
! DROP VIEW vw_getfoo;
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1) WITH ORDINALITY as t1(v,o);
! SELECT * FROM vw_getfoo;
!  v | o 
! ---+---
!  1 | 1
! (1 row)
! 
! DROP VIEW vw_getfoo;
! -- sql, proretset = t, prorettype = b
! CREATE FUNCTION getfoo2(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
! SELECT * FROM getfoo2(1) AS t1;
!  t1 
! ----
!   1
!   1
! (2 rows)
! 
! SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o);
!  v | o 
! ---+---
!  1 | 1
!  1 | 2
! (2 rows)
! 
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1);
! SELECT * FROM vw_getfoo;
!  getfoo2 
! ---------
!        1
!        1
! (2 rows)
! 
! DROP VIEW vw_getfoo;
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o);
! SELECT * FROM vw_getfoo;
!  v | o 
! ---+---
!  1 | 1
!  1 | 2
! (2 rows)
! 
! DROP VIEW vw_getfoo;
! -- sql, proretset = t, prorettype = b
! CREATE FUNCTION getfoo3(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
! SELECT * FROM getfoo3(1) AS t1;
!  t1  
! -----
!  Joe
!  Ed
! (2 rows)
! 
! SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o);
!   v  | o 
! -----+---
!  Joe | 1
!  Ed  | 2
! (2 rows)
! 
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1);
! SELECT * FROM vw_getfoo;
!  getfoo3 
! ---------
!  Joe
!  Ed
! (2 rows)
! 
! DROP VIEW vw_getfoo;
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o);
! SELECT * FROM vw_getfoo;
!   v  | o 
! -----+---
!  Joe | 1
!  Ed  | 2
! (2 rows)
! 
! DROP VIEW vw_getfoo;
! -- sql, proretset = f, prorettype = c
! CREATE FUNCTION getfoo4(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
! SELECT * FROM getfoo4(1) AS t1;
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
! (1 row)
! 
! SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o);
!  a | b |  c  | o 
! ---+---+-----+---
!  1 | 1 | Joe | 1
! (1 row)
! 
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1);
! SELECT * FROM vw_getfoo;
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
! (1 row)
! 
! DROP VIEW vw_getfoo;
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o);
! SELECT * FROM vw_getfoo;
!  a | b |  c  | o 
! ---+---+-----+---
!  1 | 1 | Joe | 1
! (1 row)
! 
! DROP VIEW vw_getfoo;
! -- sql, proretset = t, prorettype = c
! CREATE FUNCTION getfoo5(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
! SELECT * FROM getfoo5(1) AS t1;
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
!      1 |        2 | Ed
! (2 rows)
! 
! SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o);
!  a | b |  c  | o 
! ---+---+-----+---
!  1 | 1 | Joe | 1
!  1 | 2 | Ed  | 2
! (2 rows)
! 
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1);
! SELECT * FROM vw_getfoo;
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
!      1 |        2 | Ed
! (2 rows)
! 
! DROP VIEW vw_getfoo;
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o);
! SELECT * FROM vw_getfoo;
!  a | b |  c  | o 
! ---+---+-----+---
!  1 | 1 | Joe | 1
!  1 | 2 | Ed  | 2
! (2 rows)
! 
! DROP VIEW vw_getfoo;
! -- sql, proretset = f, prorettype = record
! CREATE FUNCTION getfoo6(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
! SELECT * FROM getfoo6(1) AS t1(fooid int, foosubid int, fooname text);
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
! (1 row)
! 
! SELECT * FROM ROWS FROM( getfoo6(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY;
!  fooid | foosubid | fooname | ordinality 
! -------+----------+---------+------------
!      1 |        1 | Joe     |          1
! (1 row)
! 
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1) AS
! (fooid int, foosubid int, fooname text);
! SELECT * FROM vw_getfoo;
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
! (1 row)
! 
! DROP VIEW vw_getfoo;
! CREATE VIEW vw_getfoo AS
!   SELECT * FROM ROWS FROM( getfoo6(1) AS (fooid int, foosubid int, fooname text) )
!                 WITH ORDINALITY;
! SELECT * FROM vw_getfoo;
!  fooid | foosubid | fooname | ordinality 
! -------+----------+---------+------------
!      1 |        1 | Joe     |          1
! (1 row)
! 
! DROP VIEW vw_getfoo;
! -- sql, proretset = t, prorettype = record
! CREATE FUNCTION getfoo7(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
! SELECT * FROM getfoo7(1) AS t1(fooid int, foosubid int, fooname text);
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
!      1 |        2 | Ed
! (2 rows)
! 
! SELECT * FROM ROWS FROM( getfoo7(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY;
!  fooid | foosubid | fooname | ordinality 
! -------+----------+---------+------------
!      1 |        1 | Joe     |          1
!      1 |        2 | Ed      |          2
! (2 rows)
! 
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo7(1) AS
! (fooid int, foosubid int, fooname text);
! SELECT * FROM vw_getfoo;
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
!      1 |        2 | Ed
! (2 rows)
! 
! DROP VIEW vw_getfoo;
! CREATE VIEW vw_getfoo AS
!   SELECT * FROM ROWS FROM( getfoo7(1) AS (fooid int, foosubid int, fooname text) )
!                 WITH ORDINALITY;
! SELECT * FROM vw_getfoo;
!  fooid | foosubid | fooname | ordinality 
! -------+----------+---------+------------
!      1 |        1 | Joe     |          1
!      1 |        2 | Ed      |          2
! (2 rows)
! 
! DROP VIEW vw_getfoo;
! -- plpgsql, proretset = f, prorettype = b
! CREATE FUNCTION getfoo8(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql;
! SELECT * FROM getfoo8(1) AS t1;
!  t1 
! ----
!   1
! (1 row)
! 
! SELECT * FROM getfoo8(1) WITH ORDINALITY AS t1(v,o);
!  v | o 
! ---+---
!  1 | 1
! (1 row)
! 
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo8(1);
! SELECT * FROM vw_getfoo;
!  getfoo8 
! ---------
!        1
! (1 row)
! 
! DROP VIEW vw_getfoo;
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo8(1) WITH ORDINALITY AS t1(v,o);
! SELECT * FROM vw_getfoo;
!  v | o 
! ---+---
!  1 | 1
! (1 row)
! 
! DROP VIEW vw_getfoo;
! -- plpgsql, proretset = f, prorettype = c
! CREATE FUNCTION getfoo9(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql;
! SELECT * FROM getfoo9(1) AS t1;
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
! (1 row)
! 
! SELECT * FROM getfoo9(1) WITH ORDINALITY AS t1(a,b,c,o);
!  a | b |  c  | o 
! ---+---+-----+---
!  1 | 1 | Joe | 1
! (1 row)
! 
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo9(1);
! SELECT * FROM vw_getfoo;
!  fooid | foosubid | fooname 
! -------+----------+---------
!      1 |        1 | Joe
! (1 row)
! 
! DROP VIEW vw_getfoo;
! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo9(1) WITH ORDINALITY AS t1(a,b,c,o);
! SELECT * FROM vw_getfoo;
!  a | b |  c  | o 
! ---+---+-----+---
!  1 | 1 | Joe | 1
! (1 row)
! 
! DROP VIEW vw_getfoo;
! -- mix 'n match kinds, to exercise expandRTE and related logic
! select * from rows from(getfoo1(1),getfoo2(1),getfoo3(1),getfoo4(1),getfoo5(1),
!                     getfoo6(1) AS (fooid int, foosubid int, fooname text),
!                     getfoo7(1) AS (fooid int, foosubid int, fooname text),
!                     getfoo8(1),getfoo9(1))
!               with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
!  a | b |  c  | d | e |  f  | g | h |  i  | j | k |  l  | m | o |  p  | q | r | s |  t  | u 
! ---+---+-----+---+---+-----+---+---+-----+---+---+-----+---+---+-----+---+---+---+-----+---
!  1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | 1 | Joe | 1
!    | 1 | Ed  |   |   |     | 1 | 2 | Ed  |   |   |     | 1 | 2 | Ed  |   |   |   |     | 2
! (2 rows)
! 
! select * from rows from(getfoo9(1),getfoo8(1),
!                     getfoo7(1) AS (fooid int, foosubid int, fooname text),
!                     getfoo6(1) AS (fooid int, foosubid int, fooname text),
!                     getfoo5(1),getfoo4(1),getfoo3(1),getfoo2(1),getfoo1(1))
!               with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
!  a | b |  c  | d | e | f |  g  | h | i |  j  | k | l |  m  | o | p |  q  |  r  | s | t | u 
! ---+---+-----+---+---+---+-----+---+---+-----+---+---+-----+---+---+-----+-----+---+---+---
!  1 | 1 | Joe | 1 | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | Joe | 1 | 1 | 1
!    |   |     |   | 1 | 2 | Ed  |   |   |     | 1 | 2 | Ed  |   |   |     | Ed  | 1 |   | 2
! (2 rows)
! 
! create temporary view vw_foo as
!   select * from rows from(getfoo9(1),
!                       getfoo7(1) AS (fooid int, foosubid int, fooname text),
!                       getfoo1(1))
!                 with ordinality as t1(a,b,c,d,e,f,g,n);
! select * from vw_foo;
!  a | b |  c  | d | e |  f  | g | n 
! ---+---+-----+---+---+-----+---+---
!  1 | 1 | Joe | 1 | 1 | Joe | 1 | 1
!    |   |     | 1 | 2 | Ed  |   | 2
! (2 rows)
! 
! select pg_get_viewdef('vw_foo');
!                                                                     pg_get_viewdef                                                                    
! ------------------------------------------------------------------------------------------------------------------------------------------------------
!   SELECT t1.a,                                                                                                                                       +
!      t1.b,                                                                                                                                           +
!      t1.c,                                                                                                                                           +
!      t1.d,                                                                                                                                           +
!      t1.e,                                                                                                                                           +
!      t1.f,                                                                                                                                           +
!      t1.g,                                                                                                                                           +
!      t1.n                                                                                                                                            +
!     FROM ROWS FROM(getfoo9(1), getfoo7(1) AS (fooid integer, foosubid integer, fooname text), getfoo1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
! (1 row)
! 
! drop view vw_foo;
! DROP FUNCTION getfoo1(int);
! DROP FUNCTION getfoo2(int);
! DROP FUNCTION getfoo3(int);
! DROP FUNCTION getfoo4(int);
! DROP FUNCTION getfoo5(int);
! DROP FUNCTION getfoo6(int);
! DROP FUNCTION getfoo7(int);
! DROP FUNCTION getfoo8(int);
! DROP FUNCTION getfoo9(int);
! DROP FUNCTION foot(int);
! DROP TABLE foo2;
! DROP TABLE foo;
! -- Rescan tests --
! CREATE TEMPORARY SEQUENCE foo_rescan_seq1;
! CREATE TEMPORARY SEQUENCE foo_rescan_seq2;
! CREATE TYPE foo_rescan_t AS (i integer, s bigint);
! CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq1'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
! -- plpgsql functions use materialize mode
! CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq2'')); end loop; end;' LANGUAGE plpgsql;
! --invokes ExecReScanFunctionScan - all these cases should materialize the function only once
! -- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
! -- is on the inner path of a nestloop join
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100;
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 11 | 1
!  2 | 12 | 2
!  2 | 13 | 3
!  3 | 11 | 1
!  3 | 12 | 2
!  3 | 13 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 11 | 1 | 1
!  2 | 12 | 2 | 2
!  2 | 13 | 3 | 3
!  3 | 11 | 1 | 1
!  3 | 12 | 2 | 2
!  3 | 13 | 3 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100;
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 11 | 1
!  2 | 12 | 2
!  2 | 13 | 3
!  3 | 11 | 1
!  3 | 12 | 2
!  3 | 13 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 11 | 1 | 1
!  2 | 12 | 2 | 2
!  2 | 13 | 3 | 3
!  3 | 11 | 1 | 1
!  3 | 12 | 2 | 2
!  3 | 13 | 3 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN ROWS FROM( foo_sql(11,13), foo_mat(11,13) ) WITH ORDINALITY AS f(i1,s1,i2,s2,o) ON (r+i1+i2)<100;
!  r | i1 | s1 | i2 | s2 | o 
! ---+----+----+----+----+---
!  1 | 11 |  1 | 11 |  1 | 1
!  1 | 12 |  2 | 12 |  2 | 2
!  1 | 13 |  3 | 13 |  3 | 3
!  2 | 11 |  1 | 11 |  1 | 1
!  2 | 12 |  2 | 12 |  2 | 2
!  2 | 13 |  3 | 13 |  3 | 3
!  3 | 11 |  1 | 11 |  1 | 1
!  3 | 12 |  2 | 12 |  2 | 2
!  3 | 13 |  3 | 13 |  3 | 3
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100;
!  r | i  
! ---+----
!  1 | 11
!  1 | 12
!  1 | 13
!  2 | 11
!  2 | 12
!  2 | 13
!  3 | 11
!  3 | 12
!  3 | 13
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
!  r | i  | o 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 11 | 1
!  2 | 12 | 2
!  2 | 13 | 3
!  3 | 11 | 1
!  3 | 12 | 2
!  3 | 13 | 3
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100;
!  r | i  
! ---+----
!  1 | 10
!  1 | 20
!  1 | 30
!  2 | 10
!  2 | 20
!  2 | 30
!  3 | 10
!  3 | 20
!  3 | 30
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
!  r | i  | o 
! ---+----+---
!  1 | 10 | 1
!  1 | 20 | 2
!  1 | 30 | 3
!  2 | 10 | 1
!  2 | 20 | 2
!  2 | 30 | 3
!  3 | 10 | 1
!  3 | 20 | 2
!  3 | 30 | 3
! (9 rows)
! 
! --invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 12 | 4
!  2 | 13 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 12 | 4 | 1
!  2 | 13 | 5 | 2
!  3 | 13 | 6 | 1
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  2 | 11 | 2
!  2 | 12 | 3
!  3 | 11 | 4
!  3 | 12 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  2 | 11 | 2 | 1
!  2 | 12 | 3 | 2
!  3 | 11 | 4 | 1
!  3 | 12 | 5 | 2
!  3 | 13 | 6 | 3
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2);
!  r1 | r2 | i  | s  
! ----+----+----+----
!  11 | 12 | 11 |  1
!  11 | 12 | 12 |  2
!  13 | 15 | 13 |  3
!  13 | 15 | 14 |  4
!  13 | 15 | 15 |  5
!  16 | 20 | 16 |  6
!  16 | 20 | 17 |  7
!  16 | 20 | 18 |  8
!  16 | 20 | 19 |  9
!  16 | 20 | 20 | 10
! (10 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o);
!  r1 | r2 | i  | s  | o 
! ----+----+----+----+---
!  11 | 12 | 11 |  1 | 1
!  11 | 12 | 12 |  2 | 2
!  13 | 15 | 13 |  3 | 1
!  13 | 15 | 14 |  4 | 2
!  13 | 15 | 15 |  5 | 3
!  16 | 20 | 16 |  6 | 1
!  16 | 20 | 17 |  7 | 2
!  16 | 20 | 18 |  8 | 3
!  16 | 20 | 19 |  9 | 4
!  16 | 20 | 20 | 10 | 5
! (10 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 12 | 4
!  2 | 13 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 12 | 4 | 1
!  2 | 13 | 5 | 2
!  3 | 13 | 6 | 1
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  2 | 11 | 2
!  2 | 12 | 3
!  3 | 11 | 4
!  3 | 12 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  2 | 11 | 2 | 1
!  2 | 12 | 3 | 2
!  3 | 11 | 4 | 1
!  3 | 12 | 5 | 2
!  3 | 13 | 6 | 3
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2);
!  r1 | r2 | i  | s  
! ----+----+----+----
!  11 | 12 | 11 |  1
!  11 | 12 | 12 |  2
!  13 | 15 | 13 |  3
!  13 | 15 | 14 |  4
!  13 | 15 | 15 |  5
!  16 | 20 | 16 |  6
!  16 | 20 | 17 |  7
!  16 | 20 | 18 |  8
!  16 | 20 | 19 |  9
!  16 | 20 | 20 | 10
! (10 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o);
!  r1 | r2 | i  | s  | o 
! ----+----+----+----+---
!  11 | 12 | 11 |  1 | 1
!  11 | 12 | 12 |  2 | 2
!  13 | 15 | 13 |  3 | 1
!  13 | 15 | 14 |  4 | 2
!  13 | 15 | 15 |  5 | 3
!  16 | 20 | 16 |  6 | 1
!  16 | 20 | 17 |  7 | 2
!  16 | 20 | 18 |  8 | 3
!  16 | 20 | 19 |  9 | 4
!  16 | 20 | 20 | 10 | 5
! (10 rows)
! 
! -- selective rescan of multiple functions:
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(11,11), foo_mat(10+r,13) );
!  r | i  | s | i  | s 
! ---+----+---+----+---
!  1 | 11 | 1 | 11 | 1
!  1 |    |   | 12 | 2
!  1 |    |   | 13 | 3
!  2 | 11 | 1 | 12 | 4
!  2 |    |   | 13 | 5
!  3 | 11 | 1 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(10+r,13), foo_mat(11,11) );
!  r | i  | s | i  | s 
! ---+----+---+----+---
!  1 | 11 | 1 | 11 | 1
!  1 | 12 | 2 |    |  
!  1 | 13 | 3 |    |  
!  2 | 12 | 4 | 11 | 1
!  2 | 13 | 5 |    |  
!  3 | 13 | 6 | 11 | 1
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(10+r,13), foo_mat(10+r,13) );
!  r | i  | s | i  | s 
! ---+----+---+----+---
!  1 | 11 | 1 | 11 | 1
!  1 | 12 | 2 | 12 | 2
!  1 | 13 | 3 | 13 | 3
!  2 | 12 | 4 | 12 | 4
!  2 | 13 | 5 | 13 | 5
!  3 | 13 | 6 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
!  setval | setval 
! --------+--------
!       1 |      1
! (1 row)
! 
! SELECT * FROM generate_series(1,2) r1, generate_series(r1,3) r2, ROWS FROM( foo_sql(10+r1,13), foo_mat(10+r2,13) );
!  r1 | r2 | i  | s  | i  | s 
! ----+----+----+----+----+---
!   1 |  1 | 11 |  1 | 11 | 1
!   1 |  1 | 12 |  2 | 12 | 2
!   1 |  1 | 13 |  3 | 13 | 3
!   1 |  2 | 11 |  4 | 12 | 4
!   1 |  2 | 12 |  5 | 13 | 5
!   1 |  2 | 13 |  6 |    |  
!   1 |  3 | 11 |  7 | 13 | 6
!   1 |  3 | 12 |  8 |    |  
!   1 |  3 | 13 |  9 |    |  
!   2 |  2 | 12 | 10 | 12 | 7
!   2 |  2 | 13 | 11 | 13 | 8
!   2 |  3 | 12 | 12 | 13 | 9
!   2 |  3 | 13 | 13 |    |  
! (13 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i);
!  r | i  
! ---+----
!  1 | 11
!  1 | 12
!  1 | 13
!  1 | 14
!  1 | 15
!  1 | 16
!  1 | 17
!  1 | 18
!  1 | 19
!  2 | 12
!  2 | 13
!  2 | 14
!  2 | 15
!  2 | 16
!  2 | 17
!  2 | 18
!  3 | 13
!  3 | 14
!  3 | 15
!  3 | 16
!  3 | 17
! (21 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o);
!  r | i  | o 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  1 | 14 | 4
!  1 | 15 | 5
!  1 | 16 | 6
!  1 | 17 | 7
!  1 | 18 | 8
!  1 | 19 | 9
!  2 | 12 | 1
!  2 | 13 | 2
!  2 | 14 | 3
!  2 | 15 | 4
!  2 | 16 | 5
!  2 | 17 | 6
!  2 | 18 | 7
!  3 | 13 | 1
!  3 | 14 | 2
!  3 | 15 | 3
!  3 | 16 | 4
!  3 | 17 | 5
! (21 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i);
!  r | i  
! ---+----
!  1 | 10
!  1 | 20
!  1 | 30
!  2 | 20
!  2 | 40
!  2 | 60
!  3 | 30
!  3 | 60
!  3 | 90
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o);
!  r | i  | o 
! ---+----+---
!  1 | 10 | 1
!  1 | 20 | 2
!  1 | 30 | 3
!  2 | 20 | 1
!  2 | 40 | 2
!  2 | 60 | 3
!  3 | 30 | 1
!  3 | 60 | 2
!  3 | 90 | 3
! (9 rows)
! 
! -- deep nesting
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i  
! ----+----+----+----
!   1 |  1 | 10 | 21
!   1 |  1 | 10 | 22
!   1 |  1 | 10 | 23
!   1 |  1 | 20 | 21
!   1 |  1 | 20 | 22
!   1 |  1 | 20 | 23
!   1 |  1 | 30 | 21
!   1 |  1 | 30 | 22
!   1 |  1 | 30 | 23
!   2 |  2 | 10 | 21
!   2 |  2 | 10 | 22
!   2 |  2 | 10 | 23
!   2 |  2 | 20 | 21
!   2 |  2 | 20 | 22
!   2 |  2 | 20 | 23
!   2 |  2 | 30 | 21
!   2 |  2 | 30 | 22
!   2 |  2 | 30 | 23
!   3 |  3 | 10 | 21
!   3 |  3 | 10 | 22
!   3 |  3 | 10 | 23
!   3 |  3 | 20 | 21
!   3 |  3 | 20 | 22
!   3 |  3 | 20 | 23
!   3 |  3 | 30 | 21
!   3 |  3 | 30 | 22
!   3 |  3 | 30 | 23
! (27 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i  
! ----+----+----+----
!   1 |  1 | 10 | 21
!   1 |  1 | 10 | 22
!   1 |  1 | 10 | 23
!   1 |  1 | 20 | 21
!   1 |  1 | 20 | 22
!   1 |  1 | 20 | 23
!   1 |  1 | 30 | 21
!   1 |  1 | 30 | 22
!   1 |  1 | 30 | 23
!   2 |  2 | 10 | 22
!   2 |  2 | 10 | 23
!   2 |  2 | 20 | 22
!   2 |  2 | 20 | 23
!   2 |  2 | 30 | 22
!   2 |  2 | 30 | 23
!   3 |  3 | 10 | 23
!   3 |  3 | 20 | 23
!   3 |  3 | 30 | 23
! (18 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i  
! ----+----+----+----
!   1 |  1 | 10 | 10
!   1 |  1 | 10 | 11
!   1 |  1 | 10 | 12
!   1 |  1 | 10 | 13
!   1 |  1 | 20 | 20
!   1 |  1 | 20 | 21
!   1 |  1 | 20 | 22
!   1 |  1 | 20 | 23
!   1 |  1 | 30 | 30
!   1 |  1 | 30 | 31
!   1 |  1 | 30 | 32
!   1 |  1 | 30 | 33
!   2 |  2 | 10 | 10
!   2 |  2 | 10 | 11
!   2 |  2 | 10 | 12
!   2 |  2 | 10 | 13
!   2 |  2 | 20 | 20
!   2 |  2 | 20 | 21
!   2 |  2 | 20 | 22
!   2 |  2 | 20 | 23
!   2 |  2 | 30 | 30
!   2 |  2 | 30 | 31
!   2 |  2 | 30 | 32
!   2 |  2 | 30 | 33
!   3 |  3 | 10 | 10
!   3 |  3 | 10 | 11
!   3 |  3 | 10 | 12
!   3 |  3 | 10 | 13
!   3 |  3 | 20 | 20
!   3 |  3 | 20 | 21
!   3 |  3 | 20 | 22
!   3 |  3 | 20 | 23
!   3 |  3 | 30 | 30
!   3 |  3 | 30 | 31
!   3 |  3 | 30 | 32
!   3 |  3 | 30 | 33
! (36 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i 
! ----+----+----+---
!   1 |  1 | 10 | 1
!   1 |  1 | 10 | 2
!   1 |  1 | 10 | 3
!   1 |  1 | 10 | 4
!   1 |  1 | 20 | 1
!   1 |  1 | 20 | 2
!   1 |  1 | 20 | 3
!   1 |  1 | 20 | 4
!   1 |  1 | 20 | 5
!   1 |  1 | 20 | 6
!   1 |  1 | 30 | 1
!   1 |  1 | 30 | 2
!   1 |  1 | 30 | 3
!   1 |  1 | 30 | 4
!   1 |  1 | 30 | 5
!   1 |  1 | 30 | 6
!   1 |  1 | 30 | 7
!   1 |  1 | 30 | 8
!   2 |  2 | 10 | 2
!   2 |  2 | 10 | 3
!   2 |  2 | 10 | 4
!   2 |  2 | 20 | 2
!   2 |  2 | 20 | 3
!   2 |  2 | 20 | 4
!   2 |  2 | 20 | 5
!   2 |  2 | 20 | 6
!   2 |  2 | 30 | 2
!   2 |  2 | 30 | 3
!   2 |  2 | 30 | 4
!   2 |  2 | 30 | 5
!   2 |  2 | 30 | 6
!   2 |  2 | 30 | 7
!   2 |  2 | 30 | 8
!   3 |  3 | 10 | 3
!   3 |  3 | 10 | 4
!   3 |  3 | 20 | 3
!   3 |  3 | 20 | 4
!   3 |  3 | 20 | 5
!   3 |  3 | 20 | 6
!   3 |  3 | 30 | 3
!   3 |  3 | 30 | 4
!   3 |  3 | 30 | 5
!   3 |  3 | 30 | 6
!   3 |  3 | 30 | 7
!   3 |  3 | 30 | 8
! (45 rows)
! 
! DROP FUNCTION foo_sql(int,int);
! DROP FUNCTION foo_mat(int,int);
! DROP SEQUENCE foo_rescan_seq1;
! DROP SEQUENCE foo_rescan_seq2;
! --
! -- Test cases involving OUT parameters
! --
! CREATE FUNCTION foo(in f1 int, out f2 int)
! AS 'select $1+1' LANGUAGE sql;
! SELECT foo(42);
!  foo 
! -----
!   43
! (1 row)
! 
! SELECT * FROM foo(42);
!  f2 
! ----
!  43
! (1 row)
! 
! SELECT * FROM foo(42) AS p(x);
!  x  
! ----
!  43
! (1 row)
! 
! -- explicit spec of return type is OK
! CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS int
! AS 'select $1+1' LANGUAGE sql;
! -- error, wrong result type
! CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS float
! AS 'select $1+1' LANGUAGE sql;
! ERROR:  function result type must be integer because of OUT parameters
! -- with multiple OUT params you must get a RECORD result
! CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text) RETURNS int
! AS 'select $1+1' LANGUAGE sql;
! ERROR:  function result type must be record because of OUT parameters
! CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text)
! RETURNS record
! AS 'select $1+1' LANGUAGE sql;
! ERROR:  cannot change return type of existing function
! HINT:  Use DROP FUNCTION foo(integer) first.
! CREATE OR REPLACE FUNCTION foor(in f1 int, out f2 int, out text)
! AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql;
! SELECT f1, foor(f1) FROM int4_tbl;
!      f1      |            foor            
! -------------+----------------------------
!            0 | (-1,0z)
!       123456 | (123455,123456z)
!      -123456 | (-123457,-123456z)
!   2147483647 | (2147483646,2147483647z)
!  -2147483647 | (-2147483648,-2147483647z)
! (5 rows)
! 
! SELECT * FROM foor(42);
!  f2 | column2 
! ----+---------
!  41 | 42z
! (1 row)
! 
! SELECT * FROM foor(42) AS p(a,b);
!  a  |  b  
! ----+-----
!  41 | 42z
! (1 row)
! 
! CREATE OR REPLACE FUNCTION foob(in f1 int, inout f2 int, out text)
! AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql;
! SELECT f1, foob(f1, f1/2) FROM int4_tbl;
!      f1      |            foob            
! -------------+----------------------------
!            0 | (-1,0z)
!       123456 | (61727,123456z)
!      -123456 | (-61729,-123456z)
!   2147483647 | (1073741822,2147483647z)
!  -2147483647 | (-1073741824,-2147483647z)
! (5 rows)
! 
! SELECT * FROM foob(42, 99);
!  f2 | column2 
! ----+---------
!  98 | 42z
! (1 row)
! 
! SELECT * FROM foob(42, 99) AS p(a,b);
!  a  |  b  
! ----+-----
!  98 | 42z
! (1 row)
! 
! -- Can reference function with or without OUT params for DROP, etc
! DROP FUNCTION foo(int);
! DROP FUNCTION foor(in f2 int, out f1 int, out text);
! DROP FUNCTION foob(in f1 int, inout f2 int);
! --
! -- For my next trick, polymorphic OUT parameters
! --
! CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray)
! AS 'select $1, array[$1,$1]' LANGUAGE sql;
! SELECT dup(22);
!       dup       
! ----------------
!  (22,"{22,22}")
! (1 row)
! 
! SELECT dup('xyz');	-- fails
! ERROR:  could not determine polymorphic type because input has type "unknown"
! SELECT dup('xyz'::text);
!         dup        
! -------------------
!  (xyz,"{xyz,xyz}")
! (1 row)
! 
! SELECT * FROM dup('xyz'::text);
!  f2  |    f3     
! -----+-----------
!  xyz | {xyz,xyz}
! (1 row)
! 
! -- fails, as we are attempting to rename first argument
! CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
! AS 'select $1, array[$1,$1]' LANGUAGE sql;
! ERROR:  cannot change name of input parameter "f1"
! HINT:  Use DROP FUNCTION dup(anyelement) first.
! DROP FUNCTION dup(anyelement);
! -- equivalent behavior, though different name exposed for input arg
! CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
! AS 'select $1, array[$1,$1]' LANGUAGE sql;
! SELECT dup(22);
!       dup       
! ----------------
!  (22,"{22,22}")
! (1 row)
! 
! DROP FUNCTION dup(anyelement);
! -- fails, no way to deduce outputs
! CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
! AS 'select $1, array[$1,$1]' LANGUAGE sql;
! ERROR:  cannot determine result data type
! DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
! --
! -- table functions
! --
! CREATE OR REPLACE FUNCTION foo()
! RETURNS TABLE(a int)
! AS $$ SELECT a FROM generate_series(1,5) a(a) $$ LANGUAGE sql;
! SELECT * FROM foo();
!  a 
! ---
!  1
!  2
!  3
!  4
!  5
! (5 rows)
! 
! DROP FUNCTION foo();
! CREATE OR REPLACE FUNCTION foo(int)
! RETURNS TABLE(a int, b int)
! AS $$ SELECT a, b
!          FROM generate_series(1,$1) a(a),
!               generate_series(1,$1) b(b) $$ LANGUAGE sql;
! SELECT * FROM foo(3);
!  a | b 
! ---+---
!  1 | 1
!  1 | 2
!  1 | 3
!  2 | 1
!  2 | 2
!  2 | 3
!  3 | 1
!  3 | 2
!  3 | 3
! (9 rows)
! 
! DROP FUNCTION foo(int);
! -- case that causes change of typmod knowledge during inlining
! CREATE OR REPLACE FUNCTION foo()
! RETURNS TABLE(a varchar(5))
! AS $$ SELECT 'hello'::varchar(5) $$ LANGUAGE sql STABLE;
! SELECT * FROM foo() GROUP BY 1;
!    a   
! -------
!  hello
! (1 row)
! 
! DROP FUNCTION foo();
! --
! -- some tests on SQL functions with RETURNING
! --
! create temp table tt(f1 serial, data text);
! create function insert_tt(text) returns int as
! $$ insert into tt(data) values($1) returning f1 $$
! language sql;
! select insert_tt('foo');
!  insert_tt 
! -----------
!          1
! (1 row)
! 
! select insert_tt('bar');
!  insert_tt 
! -----------
!          2
! (1 row)
! 
! select * from tt;
!  f1 | data 
! ----+------
!   1 | foo
!   2 | bar
! (2 rows)
! 
! -- insert will execute to completion even if function needs just 1 row
! create or replace function insert_tt(text) returns int as
! $$ insert into tt(data) values($1),($1||$1) returning f1 $$
! language sql;
! select insert_tt('fool');
!  insert_tt 
! -----------
!          3
! (1 row)
! 
! select * from tt;
!  f1 |   data   
! ----+----------
!   1 | foo
!   2 | bar
!   3 | fool
!   4 | foolfool
! (4 rows)
! 
! -- setof does what's expected
! create or replace function insert_tt2(text,text) returns setof int as
! $$ insert into tt(data) values($1),($2) returning f1 $$
! language sql;
! select insert_tt2('foolish','barrish');
!  insert_tt2 
! ------------
!           5
!           6
! (2 rows)
! 
! select * from insert_tt2('baz','quux');
!  insert_tt2 
! ------------
!           7
!           8
! (2 rows)
! 
! select * from tt;
!  f1 |   data   
! ----+----------
!   1 | foo
!   2 | bar
!   3 | fool
!   4 | foolfool
!   5 | foolish
!   6 | barrish
!   7 | baz
!   8 | quux
! (8 rows)
! 
! -- limit doesn't prevent execution to completion
! select insert_tt2('foolish','barrish') limit 1;
!  insert_tt2 
! ------------
!           9
! (1 row)
! 
! select * from tt;
!  f1 |   data   
! ----+----------
!   1 | foo
!   2 | bar
!   3 | fool
!   4 | foolfool
!   5 | foolish
!   6 | barrish
!   7 | baz
!   8 | quux
!   9 | foolish
!  10 | barrish
! (10 rows)
! 
! -- triggers will fire, too
! create function noticetrigger() returns trigger as $$
! begin
!   raise notice 'noticetrigger % %', new.f1, new.data;
!   return null;
! end $$ language plpgsql;
! create trigger tnoticetrigger after insert on tt for each row
! execute procedure noticetrigger();
! select insert_tt2('foolme','barme') limit 1;
! NOTICE:  noticetrigger 11 foolme
! CONTEXT:  SQL function "insert_tt2" statement 1
! NOTICE:  noticetrigger 12 barme
! CONTEXT:  SQL function "insert_tt2" statement 1
!  insert_tt2 
! ------------
!          11
! (1 row)
! 
! select * from tt;
!  f1 |   data   
! ----+----------
!   1 | foo
!   2 | bar
!   3 | fool
!   4 | foolfool
!   5 | foolish
!   6 | barrish
!   7 | baz
!   8 | quux
!   9 | foolish
!  10 | barrish
!  11 | foolme
!  12 | barme
! (12 rows)
! 
! -- and rules work
! create temp table tt_log(f1 int, data text);
! create rule insert_tt_rule as on insert to tt do also
!   insert into tt_log values(new.*);
! select insert_tt2('foollog','barlog') limit 1;
! NOTICE:  noticetrigger 13 foollog
! CONTEXT:  SQL function "insert_tt2" statement 1
! NOTICE:  noticetrigger 14 barlog
! CONTEXT:  SQL function "insert_tt2" statement 1
!  insert_tt2 
! ------------
!          13
! (1 row)
! 
! select * from tt;
!  f1 |   data   
! ----+----------
!   1 | foo
!   2 | bar
!   3 | fool
!   4 | foolfool
!   5 | foolish
!   6 | barrish
!   7 | baz
!   8 | quux
!   9 | foolish
!  10 | barrish
!  11 | foolme
!  12 | barme
!  13 | foollog
!  14 | barlog
! (14 rows)
! 
! -- note that nextval() gets executed a second time in the rule expansion,
! -- which is expected.
! select * from tt_log;
!  f1 |  data   
! ----+---------
!  15 | foollog
!  16 | barlog
! (2 rows)
! 
! -- test case for a whole-row-variable bug
! create function foo1(n integer, out a text, out b text)
!   returns setof record
!   language sql
!   as $$ select 'foo ' || i, 'bar ' || i from generate_series(1,$1) i $$;
! set work_mem='64kB';
! select t.a, t, t.a from foo1(10000) t limit 1;
!    a   |         t         |   a   
! -------+-------------------+-------
!  foo 1 | ("foo 1","bar 1") | foo 1
! (1 row)
! 
! reset work_mem;
! select t.a, t, t.a from foo1(10000) t limit 1;
!    a   |         t         |   a   
! -------+-------------------+-------
!  foo 1 | ("foo 1","bar 1") | foo 1
! (1 row)
! 
! drop function foo1(n integer);
! -- test use of SQL functions returning record
! -- this is supported in some cases where the query doesn't specify
! -- the actual record type ...
! 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)
! 
! select * from array_to_set(array['one', 'two']) as t(f1 int,f2 text);
!  f1 | f2  
! ----+-----
!   1 | one
!   2 | two
! (2 rows)
! 
! select * from array_to_set(array['one', 'two']); -- fail
! ERROR:  a column definition list is required for functions returning "record"
! LINE 1: select * from array_to_set(array['one', 'two']);
!                       ^
! create temp table foo(f1 int8, f2 int8);
! create function testfoo() returns record as $$
!   insert into foo values (1,2) returning *;
! $$ language sql;
! select testfoo();
!  testfoo 
! ---------
!  (1,2)
! (1 row)
! 
! select * from testfoo() as t(f1 int8,f2 int8);
!  f1 | f2 
! ----+----
!   1 |  2
! (1 row)
! 
! select * from testfoo(); -- fail
! ERROR:  a column definition list is required for functions returning "record"
! LINE 1: select * from testfoo();
!                       ^
! drop function testfoo();
! create function testfoo() returns setof record as $$
!   insert into foo values (1,2), (3,4) returning *;
! $$ language sql;
! select testfoo();
!  testfoo 
! ---------
!  (1,2)
!  (3,4)
! (2 rows)
! 
! select * from testfoo() as t(f1 int8,f2 int8);
!  f1 | f2 
! ----+----
!   1 |  2
!   3 |  4
! (2 rows)
! 
! select * from testfoo(); -- fail
! ERROR:  a column definition list is required for functions returning "record"
! LINE 1: select * from testfoo();
!                       ^
! drop function testfoo();
! --
! -- Check some cases involving added/dropped columns in a rowtype result
! --
! create temp table users (userid text, seq int, email text, todrop bool, moredrop int, enabled bool);
! insert into users values ('id',1,'email',true,11,true);
! insert into users values ('id2',2,'email2',true,12,true);
! alter table users drop column todrop;
! create or replace function get_first_user() returns users as
! $$ SELECT * FROM users ORDER BY userid LIMIT 1; $$
! language sql stable;
! SELECT get_first_user();
!   get_first_user   
! -------------------
!  (id,1,email,11,t)
! (1 row)
! 
! SELECT * FROM get_first_user();
!  userid | seq | email | moredrop | enabled 
! --------+-----+-------+----------+---------
!  id     |   1 | email |       11 | t
! (1 row)
! 
! create or replace function get_users() returns setof users as
! $$ SELECT * FROM users ORDER BY userid; $$
! language sql stable;
! SELECT get_users();
!       get_users      
! ---------------------
!  (id,1,email,11,t)
!  (id2,2,email2,12,t)
! (2 rows)
! 
! SELECT * FROM get_users();
!  userid | seq | email  | moredrop | enabled 
! --------+-----+--------+----------+---------
!  id     |   1 | email  |       11 | t
!  id2    |   2 | email2 |       12 | t
! (2 rows)
! 
! SELECT * FROM get_users() WITH ORDINALITY;   -- make sure ordinality copes
!  userid | seq | email  | moredrop | enabled | ordinality 
! --------+-----+--------+----------+---------+------------
!  id     |   1 | email  |       11 | t       |          1
!  id2    |   2 | email2 |       12 | t       |          2
! (2 rows)
! 
! -- multiple functions vs. dropped columns
! SELECT * FROM ROWS FROM(generate_series(10,11), get_users()) WITH ORDINALITY;
!  generate_series | userid | seq | email  | moredrop | enabled | ordinality 
! -----------------+--------+-----+--------+----------+---------+------------
!               10 | id     |   1 | email  |       11 | t       |          1
!               11 | id2    |   2 | email2 |       12 | t       |          2
! (2 rows)
! 
! SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
!  userid | seq | email  | moredrop | enabled | generate_series | ordinality 
! --------+-----+--------+----------+---------+-----------------+------------
!  id     |   1 | email  |       11 | t       |              10 |          1
!  id2    |   2 | email2 |       12 | t       |              11 |          2
! (2 rows)
! 
! -- check that we can cope with post-parsing changes in rowtypes
! create temp view usersview as
! SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
! select * from usersview;
!  userid | seq | email  | moredrop | enabled | generate_series | ordinality 
! --------+-----+--------+----------+---------+-----------------+------------
!  id     |   1 | email  |       11 | t       |              10 |          1
!  id2    |   2 | email2 |       12 | t       |              11 |          2
! (2 rows)
! 
! alter table users drop column moredrop;
! select * from usersview;
!  userid | seq | email  | moredrop | enabled | generate_series | ordinality 
! --------+-----+--------+----------+---------+-----------------+------------
!  id     |   1 | email  |          | t       |              10 |          1
!  id2    |   2 | email2 |          | t       |              11 |          2
! (2 rows)
! 
! alter table users add column junk text;
! select * from usersview;
!  userid | seq | email  | moredrop | enabled | generate_series | ordinality 
! --------+-----+--------+----------+---------+-----------------+------------
!  id     |   1 | email  |          | t       |              10 |          1
!  id2    |   2 | email2 |          | t       |              11 |          2
! (2 rows)
! 
! alter table users alter column seq type numeric;
! select * from usersview;  -- expect clean failure
! ERROR:  attribute 2 has wrong type
! DETAIL:  Table has type numeric, but query expects integer.
! drop view usersview;
! drop function get_first_user();
! drop function get_users();
! drop table users;
! -- this won't get inlined because of type coercion, but it shouldn't fail
! create or replace function foobar() returns setof text as
! $$ select 'foo'::varchar union all select 'bar'::varchar ; $$
! language sql stable;
! select foobar();
!  foobar 
! --------
!  foo
!  bar
! (2 rows)
! 
! select * from foobar();
!  foobar 
! --------
!  foo
!  bar
! (2 rows)
! 
! drop function foobar();
! -- check handling of a SQL function with multiple OUT params (bug #5777)
! create or replace function foobar(out integer, out numeric) as
! $$ select (1, 2.1) $$ language sql;
! select * from foobar();
!  column1 | column2 
! ---------+---------
!        1 |     2.1
! (1 row)
! 
! create or replace function foobar(out integer, out numeric) as
! $$ select (1, 2) $$ language sql;
! select * from foobar();  -- fail
! ERROR:  function return row and query-specified return row do not match
! DETAIL:  Returned type integer at ordinal position 2, but query expects numeric.
! create or replace function foobar(out integer, out numeric) as
! $$ select (1, 2.1, 3) $$ language sql;
! select * from foobar();  -- fail
! ERROR:  function return row and query-specified return row do not match
! DETAIL:  Returned row contains 3 attributes, but query expects 2.
! drop function foobar();
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/prepare.out	2014-01-02 13:19:14.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/prepare.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,180 ****
! -- Regression tests for prepareable statements. We query the content
! -- of the pg_prepared_statements view as prepared statements are
! -- created and removed.
! SELECT name, statement, parameter_types FROM pg_prepared_statements;
!  name | statement | parameter_types 
! ------+-----------+-----------------
! (0 rows)
! 
! PREPARE q1 AS SELECT 1 AS a;
! EXECUTE q1;
!  a 
! ---
!  1
! (1 row)
! 
! SELECT name, statement, parameter_types FROM pg_prepared_statements;
!  name |          statement           | parameter_types 
! ------+------------------------------+-----------------
!  q1   | PREPARE q1 AS SELECT 1 AS a; | {}
! (1 row)
! 
! -- should fail
! PREPARE q1 AS SELECT 2;
! ERROR:  prepared statement "q1" already exists
! -- should succeed
! DEALLOCATE q1;
! PREPARE q1 AS SELECT 2;
! EXECUTE q1;
!  ?column? 
! ----------
!         2
! (1 row)
! 
! PREPARE q2 AS SELECT 2 AS b;
! SELECT name, statement, parameter_types FROM pg_prepared_statements;
!  name |          statement           | parameter_types 
! ------+------------------------------+-----------------
!  q1   | PREPARE q1 AS SELECT 2;      | {}
!  q2   | PREPARE q2 AS SELECT 2 AS b; | {}
! (2 rows)
! 
! -- sql92 syntax
! DEALLOCATE PREPARE q1;
! SELECT name, statement, parameter_types FROM pg_prepared_statements;
!  name |          statement           | parameter_types 
! ------+------------------------------+-----------------
!  q2   | PREPARE q2 AS SELECT 2 AS b; | {}
! (1 row)
! 
! DEALLOCATE PREPARE q2;
! -- the view should return the empty set again
! SELECT name, statement, parameter_types FROM pg_prepared_statements;
!  name | statement | parameter_types 
! ------+-----------+-----------------
! (0 rows)
! 
! -- parameterized queries
! PREPARE q2(text) AS
! 	SELECT datname, datistemplate, datallowconn
! 	FROM pg_database WHERE datname = $1;
! EXECUTE q2('postgres');
!  datname  | datistemplate | datallowconn 
! ----------+---------------+--------------
!  postgres | f             | t
! (1 row)
! 
! PREPARE q3(text, int, float, boolean, oid, smallint) AS
! 	SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR
! 	ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int)
! 	ORDER BY unique1;
! EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 500::oid, 4::bigint);
!  unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 
! ---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
!        2 |    2716 |   0 |    2 |   2 |      2 |       2 |        2 |           2 |         2 |        2 |   4 |    5 | CAAAAA   | MAEAAA   | AAAAxx
!      102 |     612 |   0 |    2 |   2 |      2 |       2 |      102 |         102 |       102 |      102 |   4 |    5 | YDAAAA   | OXAAAA   | AAAAxx
!      802 |    2908 |   0 |    2 |   2 |      2 |       2 |      802 |         802 |       802 |      802 |   4 |    5 | WEAAAA   | WHEAAA   | AAAAxx
!      902 |    1104 |   0 |    2 |   2 |      2 |       2 |      902 |         902 |       902 |      902 |   4 |    5 | SIAAAA   | MQBAAA   | AAAAxx
!     1002 |    2580 |   0 |    2 |   2 |      2 |       2 |        2 |        1002 |      1002 |     1002 |   4 |    5 | OMAAAA   | GVDAAA   | AAAAxx
!     1602 |    8148 |   0 |    2 |   2 |      2 |       2 |      602 |        1602 |      1602 |     1602 |   4 |    5 | QJAAAA   | KBMAAA   | AAAAxx
!     1702 |    7940 |   0 |    2 |   2 |      2 |       2 |      702 |        1702 |      1702 |     1702 |   4 |    5 | MNAAAA   | KTLAAA   | AAAAxx
!     2102 |    6184 |   0 |    2 |   2 |      2 |       2 |      102 |         102 |      2102 |     2102 |   4 |    5 | WCAAAA   | WDJAAA   | AAAAxx
!     2202 |    8028 |   0 |    2 |   2 |      2 |       2 |      202 |         202 |      2202 |     2202 |   4 |    5 | SGAAAA   | UWLAAA   | AAAAxx
!     2302 |    7112 |   0 |    2 |   2 |      2 |       2 |      302 |         302 |      2302 |     2302 |   4 |    5 | OKAAAA   | ONKAAA   | AAAAxx
!     2902 |    6816 |   0 |    2 |   2 |      2 |       2 |      902 |         902 |      2902 |     2902 |   4 |    5 | QHAAAA   | ECKAAA   | AAAAxx
!     3202 |    7128 |   0 |    2 |   2 |      2 |       2 |      202 |        1202 |      3202 |     3202 |   4 |    5 | ETAAAA   | EOKAAA   | AAAAxx
!     3902 |    9224 |   0 |    2 |   2 |      2 |       2 |      902 |        1902 |      3902 |     3902 |   4 |    5 | CUAAAA   | UQNAAA   | AAAAxx
!     4102 |    7676 |   0 |    2 |   2 |      2 |       2 |      102 |         102 |      4102 |     4102 |   4 |    5 | UBAAAA   | GJLAAA   | AAAAxx
!     4202 |    6628 |   0 |    2 |   2 |      2 |       2 |      202 |         202 |      4202 |     4202 |   4 |    5 | QFAAAA   | YUJAAA   | AAAAxx
!     4502 |     412 |   0 |    2 |   2 |      2 |       2 |      502 |         502 |      4502 |     4502 |   4 |    5 | ERAAAA   | WPAAAA   | AAAAxx
!     4702 |    2520 |   0 |    2 |   2 |      2 |       2 |      702 |         702 |      4702 |     4702 |   4 |    5 | WYAAAA   | YSDAAA   | AAAAxx
!     4902 |    1600 |   0 |    2 |   2 |      2 |       2 |      902 |         902 |      4902 |     4902 |   4 |    5 | OGAAAA   | OJCAAA   | AAAAxx
!     5602 |    8796 |   0 |    2 |   2 |      2 |       2 |      602 |        1602 |       602 |     5602 |   4 |    5 | MHAAAA   | IANAAA   | AAAAxx
!     6002 |    8932 |   0 |    2 |   2 |      2 |       2 |        2 |           2 |      1002 |     6002 |   4 |    5 | WWAAAA   | OFNAAA   | AAAAxx
!     6402 |    3808 |   0 |    2 |   2 |      2 |       2 |      402 |         402 |      1402 |     6402 |   4 |    5 | GMAAAA   | MQFAAA   | AAAAxx
!     7602 |    1040 |   0 |    2 |   2 |      2 |       2 |      602 |        1602 |      2602 |     7602 |   4 |    5 | KGAAAA   | AOBAAA   | AAAAxx
!     7802 |    7508 |   0 |    2 |   2 |      2 |       2 |      802 |        1802 |      2802 |     7802 |   4 |    5 | COAAAA   | UCLAAA   | AAAAxx
!     8002 |    9980 |   0 |    2 |   2 |      2 |       2 |        2 |           2 |      3002 |     8002 |   4 |    5 | UVAAAA   | WTOAAA   | AAAAxx
!     8302 |    7800 |   0 |    2 |   2 |      2 |       2 |      302 |         302 |      3302 |     8302 |   4 |    5 | IHAAAA   | AOLAAA   | AAAAxx
!     8402 |    5708 |   0 |    2 |   2 |      2 |       2 |      402 |         402 |      3402 |     8402 |   4 |    5 | ELAAAA   | OLIAAA   | AAAAxx
!     8602 |    5440 |   0 |    2 |   2 |      2 |       2 |      602 |         602 |      3602 |     8602 |   4 |    5 | WSAAAA   | GBIAAA   | AAAAxx
!     9502 |    1812 |   0 |    2 |   2 |      2 |       2 |      502 |        1502 |      4502 |     9502 |   4 |    5 | MBAAAA   | SRCAAA   | AAAAxx
!     9602 |    9972 |   0 |    2 |   2 |      2 |       2 |      602 |        1602 |      4602 |     9602 |   4 |    5 | IFAAAA   | OTOAAA   | AAAAxx
! (29 rows)
! 
! -- too few params
! EXECUTE q3('bool');
! ERROR:  wrong number of parameters for prepared statement "q3"
! DETAIL:  Expected 6 parameters but got 1.
! -- too many params
! EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 500::oid, 4::bigint, true);
! ERROR:  wrong number of parameters for prepared statement "q3"
! DETAIL:  Expected 6 parameters but got 7.
! -- wrong param types
! EXECUTE q3(5::smallint, 10.5::float, false, 500::oid, 4::bigint, 'bytea');
! ERROR:  parameter $3 of type boolean cannot be coerced to the expected type double precision
! HINT:  You will need to rewrite or cast the expression.
! -- invalid type
! PREPARE q4(nonexistenttype) AS SELECT $1;
! ERROR:  type "nonexistenttype" does not exist
! LINE 1: PREPARE q4(nonexistenttype) AS SELECT $1;
!                    ^
! -- create table as execute
! PREPARE q5(int, text) AS
! 	SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2
! 	ORDER BY unique1;
! CREATE TEMPORARY TABLE q5_prep_results AS EXECUTE q5(200, 'DTAAAA');
! SELECT * FROM q5_prep_results;
!  unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 
! ---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
!      200 |    9441 |   0 |    0 |   0 |      0 |       0 |      200 |         200 |       200 |      200 |   0 |    1 | SHAAAA   | DZNAAA   | HHHHxx
!      497 |    9092 |   1 |    1 |   7 |     17 |      97 |      497 |         497 |       497 |      497 | 194 |  195 | DTAAAA   | SLNAAA   | AAAAxx
!     1173 |    6699 |   1 |    1 |   3 |     13 |      73 |      173 |        1173 |      1173 |     1173 | 146 |  147 | DTAAAA   | RXJAAA   | VVVVxx
!     1849 |    8143 |   1 |    1 |   9 |      9 |      49 |      849 |        1849 |      1849 |     1849 |  98 |   99 | DTAAAA   | FBMAAA   | VVVVxx
!     2525 |      64 |   1 |    1 |   5 |      5 |      25 |      525 |         525 |      2525 |     2525 |  50 |   51 | DTAAAA   | MCAAAA   | AAAAxx
!     3201 |    7309 |   1 |    1 |   1 |      1 |       1 |      201 |        1201 |      3201 |     3201 |   2 |    3 | DTAAAA   | DVKAAA   | HHHHxx
!     3877 |    4060 |   1 |    1 |   7 |     17 |      77 |      877 |        1877 |      3877 |     3877 | 154 |  155 | DTAAAA   | EAGAAA   | AAAAxx
!     4553 |    4113 |   1 |    1 |   3 |     13 |      53 |      553 |         553 |      4553 |     4553 | 106 |  107 | DTAAAA   | FCGAAA   | HHHHxx
!     5229 |    6407 |   1 |    1 |   9 |      9 |      29 |      229 |        1229 |       229 |     5229 |  58 |   59 | DTAAAA   | LMJAAA   | VVVVxx
!     5905 |    9537 |   1 |    1 |   5 |      5 |       5 |      905 |        1905 |       905 |     5905 |  10 |   11 | DTAAAA   | VCOAAA   | HHHHxx
!     6581 |    4686 |   1 |    1 |   1 |      1 |      81 |      581 |         581 |      1581 |     6581 | 162 |  163 | DTAAAA   | GYGAAA   | OOOOxx
!     7257 |    1895 |   1 |    1 |   7 |     17 |      57 |      257 |        1257 |      2257 |     7257 | 114 |  115 | DTAAAA   | XUCAAA   | VVVVxx
!     7933 |    4514 |   1 |    1 |   3 |     13 |      33 |      933 |        1933 |      2933 |     7933 |  66 |   67 | DTAAAA   | QRGAAA   | OOOOxx
!     8609 |    5918 |   1 |    1 |   9 |      9 |       9 |      609 |         609 |      3609 |     8609 |  18 |   19 | DTAAAA   | QTIAAA   | OOOOxx
!     9285 |    8469 |   1 |    1 |   5 |      5 |      85 |      285 |        1285 |      4285 |     9285 | 170 |  171 | DTAAAA   | TNMAAA   | HHHHxx
!     9961 |    2058 |   1 |    1 |   1 |      1 |      61 |      961 |        1961 |      4961 |     9961 | 122 |  123 | DTAAAA   | EBDAAA   | OOOOxx
! (16 rows)
! 
! -- unknown or unspecified parameter types: should succeed
! PREPARE q6 AS
!     SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2;
! PREPARE q7(unknown) AS
!     SELECT * FROM road WHERE thepath = $1;
! SELECT name, statement, parameter_types FROM pg_prepared_statements
!     ORDER BY name;
!  name |                              statement                              |                    parameter_types                     
! ------+---------------------------------------------------------------------+--------------------------------------------------------
!  q2   | PREPARE q2(text) AS                                                +| {text}
!       |         SELECT datname, datistemplate, datallowconn                +| 
!       |         FROM pg_database WHERE datname = $1;                        | 
!  q3   | PREPARE q3(text, int, float, boolean, oid, smallint) AS            +| {text,integer,"double precision",boolean,oid,smallint}
!       |         SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR   +| 
!       |         ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int)+| 
!       |         ORDER BY unique1;                                           | 
!  q5   | PREPARE q5(int, text) AS                                           +| {integer,text}
!       |         SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2    +| 
!       |         ORDER BY unique1;                                           | 
!  q6   | PREPARE q6 AS                                                      +| {integer,name}
!       |     SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2;       | 
!  q7   | PREPARE q7(unknown) AS                                             +| {path}
!       |     SELECT * FROM road WHERE thepath = $1;                          | 
! (5 rows)
! 
! -- test DEALLOCATE ALL;
! DEALLOCATE ALL;
! SELECT name, statement, parameter_types FROM pg_prepared_statements
!     ORDER BY name;
!  name | statement | parameter_types 
! ------+-----------+-----------------
! (0 rows)
! 
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/without_oid.out	2014-01-02 13:19:14.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/without_oid.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,103 ****
! --
! -- WITHOUT OID
! --
! --
! -- This test tries to verify that WITHOUT OIDS actually saves space.
! -- On machines where MAXALIGN is 8, WITHOUT OIDS may or may not save any
! -- space, depending on the size of the tuple header + null bitmap.
! -- As of 8.3 we need a null bitmap of 8 or less bits for the difference
! -- to appear.
! --
! CREATE TABLE wi (i INT,
!                  n1 int, n2 int, n3 int, n4 int,
!                  n5 int, n6 int, n7 int) WITH OIDS;
! CREATE TABLE wo (i INT,
!                  n1 int, n2 int, n3 int, n4 int,
!                  n5 int, n6 int, n7 int) WITHOUT OIDS;
! INSERT INTO wi VALUES (1);  -- 1
! INSERT INTO wo SELECT i FROM wi;  -- 1
! INSERT INTO wo SELECT i+1 FROM wi;  -- 1+1=2
! INSERT INTO wi SELECT i+1 FROM wo;  -- 1+2=3
! INSERT INTO wi SELECT i+3 FROM wi;  -- 3+3=6
! INSERT INTO wo SELECT i+2 FROM wi;  -- 2+6=8
! INSERT INTO wo SELECT i+8 FROM wo;  -- 8+8=16
! INSERT INTO wi SELECT i+6 FROM wo;  -- 6+16=22
! INSERT INTO wi SELECT i+22 FROM wi;  -- 22+22=44
! INSERT INTO wo SELECT i+16 FROM wi;  -- 16+44=60
! INSERT INTO wo SELECT i+60 FROM wo;  -- 60+60=120
! INSERT INTO wi SELECT i+44 FROM wo;  -- 44+120=164
! INSERT INTO wi SELECT i+164 FROM wi;  -- 164+164=328
! INSERT INTO wo SELECT i+120 FROM wi;  -- 120+328=448
! INSERT INTO wo SELECT i+448 FROM wo;  -- 448+448=896
! INSERT INTO wi SELECT i+328 FROM wo;  -- 328+896=1224
! INSERT INTO wi SELECT i+1224 FROM wi;  -- 1224+1224=2448
! INSERT INTO wo SELECT i+896 FROM wi;  -- 896+2448=3344
! INSERT INTO wo SELECT i+3344 FROM wo;  -- 3344+3344=6688
! INSERT INTO wi SELECT i+2448 FROM wo;  -- 2448+6688=9136
! INSERT INTO wo SELECT i+6688 FROM wi WHERE i<=2448;  -- 6688+2448=9136
! SELECT count(oid) FROM wi;
!  count 
! -------
!   9136
! (1 row)
! 
! -- should fail
! SELECT count(oid) FROM wo;
! ERROR:  column "oid" does not exist
! LINE 1: SELECT count(oid) FROM wo;
!                      ^
! VACUUM ANALYZE wi;
! VACUUM ANALYZE wo;
! SELECT min(relpages) < max(relpages), min(reltuples) - max(reltuples)
!   FROM pg_class
!  WHERE relname IN ('wi', 'wo');
!  ?column? | ?column? 
! ----------+----------
!  t        |        0
! (1 row)
! 
! DROP TABLE wi;
! DROP TABLE wo;
! --
! -- WITH / WITHOUT OIDS in CREATE TABLE AS
! --
! CREATE TABLE create_table_test (
!     a int,
!     b int
! );
! COPY create_table_test FROM stdin;
! CREATE TABLE create_table_test2 WITH OIDS AS
!     SELECT a + b AS c1, a - b AS c2 FROM create_table_test;
! CREATE TABLE create_table_test3 WITHOUT OIDS AS
!     SELECT a + b AS c1, a - b AS c2 FROM create_table_test;
! SELECT count(oid) FROM create_table_test2;
!  count 
! -------
!      2
! (1 row)
! 
! -- should fail
! SELECT count(oid) FROM create_table_test3;
! ERROR:  column "oid" does not exist
! LINE 1: SELECT count(oid) FROM create_table_test3;
!                      ^
! PREPARE table_source(int) AS
!     SELECT a + b AS c1, a - b AS c2, $1 AS c3 FROM create_table_test;
! CREATE TABLE execute_with WITH OIDS AS EXECUTE table_source(1);
! CREATE TABLE execute_without WITHOUT OIDS AS EXECUTE table_source(2);
! SELECT count(oid) FROM execute_with;
!  count 
! -------
!      2
! (1 row)
! 
! -- should fail
! SELECT count(oid) FROM execute_without;
! ERROR:  column "oid" does not exist
! LINE 1: SELECT count(oid) FROM execute_without;
!                      ^
! DROP TABLE create_table_test;
! DROP TABLE create_table_test2;
! DROP TABLE create_table_test3;
! DROP TABLE execute_with;
! DROP TABLE execute_without;
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/conversion.out	2014-01-02 13:19:15.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/conversion.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,949 ****
! -- ensure consistent test output regardless of the default bytea format
! SET bytea_output TO escape;
! --
! -- create user defined conversion
! --
! CREATE USER conversion_test_user WITH NOCREATEDB NOCREATEUSER;
! SET SESSION AUTHORIZATION conversion_test_user;
! CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
! --
! -- cannot make same name conversion in same schema
! --
! CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
! ERROR:  conversion "myconv" already exists
! --
! -- create default conversion with qualified name
! --
! CREATE DEFAULT CONVERSION public.mydef FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
! --
! -- cannot make default conversion with same shcema/for_encoding/to_encoding
! --
! CREATE DEFAULT CONVERSION public.mydef2 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
! ERROR:  default conversion for LATIN1 to UTF8 already exists
! -- test comments
! COMMENT ON CONVERSION myconv_bad IS 'foo';
! ERROR:  conversion "myconv_bad" does not exist
! COMMENT ON CONVERSION myconv IS 'bar';
! COMMENT ON CONVERSION myconv IS NULL;
! --
! -- drop user defined conversion
! --
! DROP CONVERSION myconv;
! DROP CONVERSION mydef;
! --
! -- make sure all pre-defined conversions are fine.
! -- SQL_ASCII --> MULE_INTERNAL
! SELECT CONVERT('foo', 'SQL_ASCII', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> SQL_ASCII
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'SQL_ASCII');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- KOI8R --> MULE_INTERNAL
! SELECT CONVERT('foo', 'KOI8R', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> KOI8R
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'KOI8R');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- ISO-8859-5 --> MULE_INTERNAL
! SELECT CONVERT('foo', 'ISO-8859-5', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> ISO-8859-5
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'ISO-8859-5');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1251 --> MULE_INTERNAL
! SELECT CONVERT('foo', 'WIN1251', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> WIN1251
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'WIN1251');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN866 --> MULE_INTERNAL
! SELECT CONVERT('foo', 'WIN866', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> WIN866
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'WIN866');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- KOI8R --> WIN1251
! SELECT CONVERT('foo', 'KOI8R', 'WIN1251');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1251 --> KOI8R
! SELECT CONVERT('foo', 'WIN1251', 'KOI8R');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- KOI8R --> WIN866
! SELECT CONVERT('foo', 'KOI8R', 'WIN866');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN866 --> KOI8R
! SELECT CONVERT('foo', 'WIN866', 'KOI8R');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN866 --> WIN1251
! SELECT CONVERT('foo', 'WIN866', 'WIN1251');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1251 --> WIN866
! SELECT CONVERT('foo', 'WIN1251', 'WIN866');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- ISO-8859-5 --> KOI8R
! SELECT CONVERT('foo', 'ISO-8859-5', 'KOI8R');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- KOI8R --> ISO-8859-5
! SELECT CONVERT('foo', 'KOI8R', 'ISO-8859-5');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- ISO-8859-5 --> WIN1251
! SELECT CONVERT('foo', 'ISO-8859-5', 'WIN1251');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1251 --> ISO-8859-5
! SELECT CONVERT('foo', 'WIN1251', 'ISO-8859-5');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- ISO-8859-5 --> WIN866
! SELECT CONVERT('foo', 'ISO-8859-5', 'WIN866');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN866 --> ISO-8859-5
! SELECT CONVERT('foo', 'WIN866', 'ISO-8859-5');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_CN --> MULE_INTERNAL
! SELECT CONVERT('foo', 'EUC_CN', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> EUC_CN
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'EUC_CN');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_JP --> SJIS
! SELECT CONVERT('foo', 'EUC_JP', 'SJIS');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- SJIS --> EUC_JP
! SELECT CONVERT('foo', 'SJIS', 'EUC_JP');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_JP --> MULE_INTERNAL
! SELECT CONVERT('foo', 'EUC_JP', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- SJIS --> MULE_INTERNAL
! SELECT CONVERT('foo', 'SJIS', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> EUC_JP
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'EUC_JP');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> SJIS
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'SJIS');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_KR --> MULE_INTERNAL
! SELECT CONVERT('foo', 'EUC_KR', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> EUC_KR
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'EUC_KR');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_TW --> BIG5
! SELECT CONVERT('foo', 'EUC_TW', 'BIG5');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- BIG5 --> EUC_TW
! SELECT CONVERT('foo', 'BIG5', 'EUC_TW');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_TW --> MULE_INTERNAL
! SELECT CONVERT('foo', 'EUC_TW', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- BIG5 --> MULE_INTERNAL
! SELECT CONVERT('foo', 'BIG5', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> EUC_TW
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'EUC_TW');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> BIG5
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'BIG5');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN2 --> MULE_INTERNAL
! SELECT CONVERT('foo', 'LATIN2', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> LATIN2
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'LATIN2');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1250 --> MULE_INTERNAL
! SELECT CONVERT('foo', 'WIN1250', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> WIN1250
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'WIN1250');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN2 --> WIN1250
! SELECT CONVERT('foo', 'LATIN2', 'WIN1250');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1250 --> LATIN2
! SELECT CONVERT('foo', 'WIN1250', 'LATIN2');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN1 --> MULE_INTERNAL
! SELECT CONVERT('foo', 'LATIN1', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> LATIN1
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'LATIN1');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN3 --> MULE_INTERNAL
! SELECT CONVERT('foo', 'LATIN3', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> LATIN3
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'LATIN3');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN4 --> MULE_INTERNAL
! SELECT CONVERT('foo', 'LATIN4', 'MULE_INTERNAL');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- MULE_INTERNAL --> LATIN4
! SELECT CONVERT('foo', 'MULE_INTERNAL', 'LATIN4');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- SQL_ASCII --> UTF8
! SELECT CONVERT('foo', 'SQL_ASCII', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> SQL_ASCII
! SELECT CONVERT('foo', 'UTF8', 'SQL_ASCII');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- BIG5 --> UTF8
! SELECT CONVERT('foo', 'BIG5', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> BIG5
! SELECT CONVERT('foo', 'UTF8', 'BIG5');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> KOI8R
! SELECT CONVERT('foo', 'UTF8', 'KOI8R');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- KOI8R --> UTF8
! SELECT CONVERT('foo', 'KOI8R', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN1251
! SELECT CONVERT('foo', 'UTF8', 'WIN1251');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1251 --> UTF8
! SELECT CONVERT('foo', 'WIN1251', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN1252
! SELECT CONVERT('foo', 'UTF8', 'WIN1252');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1252 --> UTF8
! SELECT CONVERT('foo', 'WIN1252', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN866
! SELECT CONVERT('foo', 'UTF8', 'WIN866');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN866 --> UTF8
! SELECT CONVERT('foo', 'WIN866', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_CN --> UTF8
! SELECT CONVERT('foo', 'EUC_CN', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> EUC_CN
! SELECT CONVERT('foo', 'UTF8', 'EUC_CN');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_JP --> UTF8
! SELECT CONVERT('foo', 'EUC_JP', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> EUC_JP
! SELECT CONVERT('foo', 'UTF8', 'EUC_JP');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_KR --> UTF8
! SELECT CONVERT('foo', 'EUC_KR', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> EUC_KR
! SELECT CONVERT('foo', 'UTF8', 'EUC_KR');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_TW --> UTF8
! SELECT CONVERT('foo', 'EUC_TW', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> EUC_TW
! SELECT CONVERT('foo', 'UTF8', 'EUC_TW');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- GB18030 --> UTF8
! SELECT CONVERT('foo', 'GB18030', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> GB18030
! SELECT CONVERT('foo', 'UTF8', 'GB18030');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- GBK --> UTF8
! SELECT CONVERT('foo', 'GBK', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> GBK
! SELECT CONVERT('foo', 'UTF8', 'GBK');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN2
! SELECT CONVERT('foo', 'UTF8', 'LATIN2');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN2 --> UTF8
! SELECT CONVERT('foo', 'LATIN2', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN3
! SELECT CONVERT('foo', 'UTF8', 'LATIN3');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN3 --> UTF8
! SELECT CONVERT('foo', 'LATIN3', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN4
! SELECT CONVERT('foo', 'UTF8', 'LATIN4');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN4 --> UTF8
! SELECT CONVERT('foo', 'LATIN4', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN5
! SELECT CONVERT('foo', 'UTF8', 'LATIN5');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN5 --> UTF8
! SELECT CONVERT('foo', 'LATIN5', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN6
! SELECT CONVERT('foo', 'UTF8', 'LATIN6');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN6 --> UTF8
! SELECT CONVERT('foo', 'LATIN6', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN7
! SELECT CONVERT('foo', 'UTF8', 'LATIN7');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN7 --> UTF8
! SELECT CONVERT('foo', 'LATIN7', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN8
! SELECT CONVERT('foo', 'UTF8', 'LATIN8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN8 --> UTF8
! SELECT CONVERT('foo', 'LATIN8', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN9
! SELECT CONVERT('foo', 'UTF8', 'LATIN9');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN9 --> UTF8
! SELECT CONVERT('foo', 'LATIN9', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN10
! SELECT CONVERT('foo', 'UTF8', 'LATIN10');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN10 --> UTF8
! SELECT CONVERT('foo', 'LATIN10', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> ISO-8859-5
! SELECT CONVERT('foo', 'UTF8', 'ISO-8859-5');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- ISO-8859-5 --> UTF8
! SELECT CONVERT('foo', 'ISO-8859-5', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> ISO-8859-6
! SELECT CONVERT('foo', 'UTF8', 'ISO-8859-6');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- ISO-8859-6 --> UTF8
! SELECT CONVERT('foo', 'ISO-8859-6', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> ISO-8859-7
! SELECT CONVERT('foo', 'UTF8', 'ISO-8859-7');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- ISO-8859-7 --> UTF8
! SELECT CONVERT('foo', 'ISO-8859-7', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> ISO-8859-8
! SELECT CONVERT('foo', 'UTF8', 'ISO-8859-8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- ISO-8859-8 --> UTF8
! SELECT CONVERT('foo', 'ISO-8859-8', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- LATIN1 --> UTF8
! SELECT CONVERT('foo', 'LATIN1', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> LATIN1
! SELECT CONVERT('foo', 'UTF8', 'LATIN1');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- JOHAB --> UTF8
! SELECT CONVERT('foo', 'JOHAB', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> JOHAB
! SELECT CONVERT('foo', 'UTF8', 'JOHAB');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- SJIS --> UTF8
! SELECT CONVERT('foo', 'SJIS', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> SJIS
! SELECT CONVERT('foo', 'UTF8', 'SJIS');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1258 --> UTF8
! SELECT CONVERT('foo', 'WIN1258', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN1258
! SELECT CONVERT('foo', 'UTF8', 'WIN1258');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UHC --> UTF8
! SELECT CONVERT('foo', 'UHC', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> UHC
! SELECT CONVERT('foo', 'UTF8', 'UHC');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN1250
! SELECT CONVERT('foo', 'UTF8', 'WIN1250');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1250 --> UTF8
! SELECT CONVERT('foo', 'WIN1250', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN1256
! SELECT CONVERT('foo', 'UTF8', 'WIN1256');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1256 --> UTF8
! SELECT CONVERT('foo', 'WIN1256', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN874
! SELECT CONVERT('foo', 'UTF8', 'WIN874');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN874 --> UTF8
! SELECT CONVERT('foo', 'WIN874', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN1253
! SELECT CONVERT('foo', 'UTF8', 'WIN1253');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1253 --> UTF8
! SELECT CONVERT('foo', 'WIN1253', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN1254
! SELECT CONVERT('foo', 'UTF8', 'WIN1254');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1254 --> UTF8
! SELECT CONVERT('foo', 'WIN1254', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN1255
! SELECT CONVERT('foo', 'UTF8', 'WIN1255');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1255 --> UTF8
! SELECT CONVERT('foo', 'WIN1255', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> WIN1257
! SELECT CONVERT('foo', 'UTF8', 'WIN1257');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- WIN1257 --> UTF8
! SELECT CONVERT('foo', 'WIN1257', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> EUC_JIS_2004
! SELECT CONVERT('foo', 'UTF8', 'EUC_JIS_2004');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_JIS_2004 --> UTF8
! SELECT CONVERT('foo', 'EUC_JIS_2004', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- UTF8 --> SHIFT_JIS_2004
! SELECT CONVERT('foo', 'UTF8', 'SHIFT_JIS_2004');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- SHIFT_JIS_2004 --> UTF8
! SELECT CONVERT('foo', 'SHIFT_JIS_2004', 'UTF8');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- EUC_JIS_2004 --> SHIFT_JIS_2004
! SELECT CONVERT('foo', 'EUC_JIS_2004', 'SHIFT_JIS_2004');
!  convert 
! ---------
!  foo
! (1 row)
! 
! -- SHIFT_JIS_2004 --> EUC_JIS_2004
! SELECT CONVERT('foo', 'SHIFT_JIS_2004', 'EUC_JIS_2004');
!  convert 
! ---------
!  foo
! (1 row)
! 
! --
! -- return to the super user
! --
! RESET SESSION AUTHORIZATION;
! DROP USER conversion_test_user;
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/truncate.out	2014-01-02 13:19:15.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/truncate.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,422 ****
! -- Test basic TRUNCATE functionality.
! CREATE TABLE truncate_a (col1 integer primary key);
! INSERT INTO truncate_a VALUES (1);
! INSERT INTO truncate_a VALUES (2);
! SELECT * FROM truncate_a;
!  col1 
! ------
!     1
!     2
! (2 rows)
! 
! -- Roll truncate back
! BEGIN;
! TRUNCATE truncate_a;
! ROLLBACK;
! SELECT * FROM truncate_a;
!  col1 
! ------
!     1
!     2
! (2 rows)
! 
! -- Commit the truncate this time
! BEGIN;
! TRUNCATE truncate_a;
! COMMIT;
! SELECT * FROM truncate_a;
!  col1 
! ------
! (0 rows)
! 
! -- Test foreign-key checks
! CREATE TABLE trunc_b (a int REFERENCES truncate_a);
! CREATE TABLE trunc_c (a serial PRIMARY KEY);
! CREATE TABLE trunc_d (a int REFERENCES trunc_c);
! CREATE TABLE trunc_e (a int REFERENCES truncate_a, b int REFERENCES trunc_c);
! TRUNCATE TABLE truncate_a;		-- fail
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_b" references "truncate_a".
! HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE truncate_a,trunc_b;		-- fail
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_e" references "truncate_a".
! HINT:  Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE truncate_a,trunc_b,trunc_e;	-- ok
! TRUNCATE TABLE truncate_a,trunc_e;		-- fail
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_b" references "truncate_a".
! HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE trunc_c;		-- fail
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_d" references "trunc_c".
! HINT:  Truncate table "trunc_d" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE trunc_c,trunc_d;		-- fail
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_e" references "trunc_c".
! HINT:  Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE trunc_c,trunc_d,trunc_e;	-- ok
! TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a;	-- fail
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_b" references "truncate_a".
! HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a,trunc_b;	-- ok
! TRUNCATE TABLE truncate_a RESTRICT; -- fail
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_b" references "truncate_a".
! HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE truncate_a CASCADE;  -- ok
! NOTICE:  truncate cascades to table "trunc_b"
! NOTICE:  truncate cascades to table "trunc_e"
! -- circular references
! ALTER TABLE truncate_a ADD FOREIGN KEY (col1) REFERENCES trunc_c;
! -- Add some data to verify that truncating actually works ...
! INSERT INTO trunc_c VALUES (1);
! INSERT INTO truncate_a VALUES (1);
! INSERT INTO trunc_b VALUES (1);
! INSERT INTO trunc_d VALUES (1);
! INSERT INTO trunc_e VALUES (1,1);
! TRUNCATE TABLE trunc_c;
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "truncate_a" references "trunc_c".
! HINT:  Truncate table "truncate_a" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE trunc_c,truncate_a;
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_d" references "trunc_c".
! HINT:  Truncate table "trunc_d" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE trunc_c,truncate_a,trunc_d;
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_e" references "trunc_c".
! HINT:  Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE trunc_c,truncate_a,trunc_d,trunc_e;
! ERROR:  cannot truncate a table referenced in a foreign key constraint
! DETAIL:  Table "trunc_b" references "truncate_a".
! HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
! TRUNCATE TABLE trunc_c,truncate_a,trunc_d,trunc_e,trunc_b;
! -- Verify that truncating did actually work
! SELECT * FROM truncate_a
!    UNION ALL
!  SELECT * FROM trunc_c
!    UNION ALL
!  SELECT * FROM trunc_b
!    UNION ALL
!  SELECT * FROM trunc_d;
!  col1 
! ------
! (0 rows)
! 
! SELECT * FROM trunc_e;
!  a | b 
! ---+---
! (0 rows)
! 
! -- Add data again to test TRUNCATE ... CASCADE
! INSERT INTO trunc_c VALUES (1);
! INSERT INTO truncate_a VALUES (1);
! INSERT INTO trunc_b VALUES (1);
! INSERT INTO trunc_d VALUES (1);
! INSERT INTO trunc_e VALUES (1,1);
! TRUNCATE TABLE trunc_c CASCADE;  -- ok
! NOTICE:  truncate cascades to table "truncate_a"
! NOTICE:  truncate cascades to table "trunc_d"
! NOTICE:  truncate cascades to table "trunc_e"
! NOTICE:  truncate cascades to table "trunc_b"
! SELECT * FROM truncate_a
!    UNION ALL
!  SELECT * FROM trunc_c
!    UNION ALL
!  SELECT * FROM trunc_b
!    UNION ALL
!  SELECT * FROM trunc_d;
!  col1 
! ------
! (0 rows)
! 
! SELECT * FROM trunc_e;
!  a | b 
! ---+---
! (0 rows)
! 
! DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
! -- Test TRUNCATE with inheritance
! CREATE TABLE trunc_f (col1 integer primary key);
! INSERT INTO trunc_f VALUES (1);
! INSERT INTO trunc_f VALUES (2);
! CREATE TABLE trunc_fa (col2a text) INHERITS (trunc_f);
! INSERT INTO trunc_fa VALUES (3, 'three');
! CREATE TABLE trunc_fb (col2b int) INHERITS (trunc_f);
! INSERT INTO trunc_fb VALUES (4, 444);
! CREATE TABLE trunc_faa (col3 text) INHERITS (trunc_fa);
! INSERT INTO trunc_faa VALUES (5, 'five', 'FIVE');
! BEGIN;
! SELECT * FROM trunc_f;
!  col1 
! ------
!     1
!     2
!     3
!     4
!     5
! (5 rows)
! 
! TRUNCATE trunc_f;
! SELECT * FROM trunc_f;
!  col1 
! ------
! (0 rows)
! 
! ROLLBACK;
! BEGIN;
! SELECT * FROM trunc_f;
!  col1 
! ------
!     1
!     2
!     3
!     4
!     5
! (5 rows)
! 
! TRUNCATE ONLY trunc_f;
! SELECT * FROM trunc_f;
!  col1 
! ------
!     3
!     4
!     5
! (3 rows)
! 
! ROLLBACK;
! BEGIN;
! SELECT * FROM trunc_f;
!  col1 
! ------
!     1
!     2
!     3
!     4
!     5
! (5 rows)
! 
! SELECT * FROM trunc_fa;
!  col1 | col2a 
! ------+-------
!     3 | three
!     5 | five
! (2 rows)
! 
! SELECT * FROM trunc_faa;
!  col1 | col2a | col3 
! ------+-------+------
!     5 | five  | FIVE
! (1 row)
! 
! TRUNCATE ONLY trunc_fb, ONLY trunc_fa;
! SELECT * FROM trunc_f;
!  col1 
! ------
!     1
!     2
!     5
! (3 rows)
! 
! SELECT * FROM trunc_fa;
!  col1 | col2a 
! ------+-------
!     5 | five
! (1 row)
! 
! SELECT * FROM trunc_faa;
!  col1 | col2a | col3 
! ------+-------+------
!     5 | five  | FIVE
! (1 row)
! 
! ROLLBACK;
! BEGIN;
! SELECT * FROM trunc_f;
!  col1 
! ------
!     1
!     2
!     3
!     4
!     5
! (5 rows)
! 
! SELECT * FROM trunc_fa;
!  col1 | col2a 
! ------+-------
!     3 | three
!     5 | five
! (2 rows)
! 
! SELECT * FROM trunc_faa;
!  col1 | col2a | col3 
! ------+-------+------
!     5 | five  | FIVE
! (1 row)
! 
! TRUNCATE ONLY trunc_fb, trunc_fa;
! SELECT * FROM trunc_f;
!  col1 
! ------
!     1
!     2
! (2 rows)
! 
! SELECT * FROM trunc_fa;
!  col1 | col2a 
! ------+-------
! (0 rows)
! 
! SELECT * FROM trunc_faa;
!  col1 | col2a | col3 
! ------+-------+------
! (0 rows)
! 
! ROLLBACK;
! DROP TABLE trunc_f CASCADE;
! NOTICE:  drop cascades to 3 other objects
! DETAIL:  drop cascades to table trunc_fa
! drop cascades to table trunc_faa
! drop cascades to table trunc_fb
! -- Test ON TRUNCATE triggers
! CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);
! CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text,
!         tgargv text, tgtable name, rowcount bigint);
! CREATE FUNCTION trunctrigger() RETURNS trigger as $$
! declare c bigint;
! begin
!     execute 'select count(*) from ' || quote_ident(tg_table_name) into c;
!     insert into trunc_trigger_log values
!       (TG_OP, TG_LEVEL, TG_WHEN, TG_ARGV[0], tg_table_name, c);
!     return null;
! end;
! $$ LANGUAGE plpgsql;
! -- basic before trigger
! INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux');
! CREATE TRIGGER t
! BEFORE TRUNCATE ON trunc_trigger_test
! FOR EACH STATEMENT
! EXECUTE PROCEDURE trunctrigger('before trigger truncate');
! SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
!  Row count in test table 
! -------------------------
!                        2
! (1 row)
! 
! SELECT * FROM trunc_trigger_log;
!  tgop | tglevel | tgwhen | tgargv | tgtable | rowcount 
! ------+---------+--------+--------+---------+----------
! (0 rows)
! 
! TRUNCATE trunc_trigger_test;
! SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
!  Row count in test table 
! -------------------------
!                        0
! (1 row)
! 
! SELECT * FROM trunc_trigger_log;
!    tgop   |  tglevel  | tgwhen |         tgargv          |      tgtable       | rowcount 
! ----------+-----------+--------+-------------------------+--------------------+----------
!  TRUNCATE | STATEMENT | BEFORE | before trigger truncate | trunc_trigger_test |        2
! (1 row)
! 
! DROP TRIGGER t ON trunc_trigger_test;
! truncate trunc_trigger_log;
! -- same test with an after trigger
! INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux');
! CREATE TRIGGER tt
! AFTER TRUNCATE ON trunc_trigger_test
! FOR EACH STATEMENT
! EXECUTE PROCEDURE trunctrigger('after trigger truncate');
! SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
!  Row count in test table 
! -------------------------
!                        2
! (1 row)
! 
! SELECT * FROM trunc_trigger_log;
!  tgop | tglevel | tgwhen | tgargv | tgtable | rowcount 
! ------+---------+--------+--------+---------+----------
! (0 rows)
! 
! TRUNCATE trunc_trigger_test;
! SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
!  Row count in test table 
! -------------------------
!                        0
! (1 row)
! 
! SELECT * FROM trunc_trigger_log;
!    tgop   |  tglevel  | tgwhen |         tgargv         |      tgtable       | rowcount 
! ----------+-----------+--------+------------------------+--------------------+----------
!  TRUNCATE | STATEMENT | AFTER  | after trigger truncate | trunc_trigger_test |        0
! (1 row)
! 
! DROP TABLE trunc_trigger_test;
! DROP TABLE trunc_trigger_log;
! DROP FUNCTION trunctrigger();
! -- test TRUNCATE ... RESTART IDENTITY
! CREATE SEQUENCE truncate_a_id1 START WITH 33;
! CREATE TABLE truncate_a (id serial,
!                          id1 integer default nextval('truncate_a_id1'));
! ALTER SEQUENCE truncate_a_id1 OWNED BY truncate_a.id1;
! INSERT INTO truncate_a DEFAULT VALUES;
! INSERT INTO truncate_a DEFAULT VALUES;
! SELECT * FROM truncate_a;
!  id | id1 
! ----+-----
!   1 |  33
!   2 |  34
! (2 rows)
! 
! TRUNCATE truncate_a;
! INSERT INTO truncate_a DEFAULT VALUES;
! INSERT INTO truncate_a DEFAULT VALUES;
! SELECT * FROM truncate_a;
!  id | id1 
! ----+-----
!   3 |  35
!   4 |  36
! (2 rows)
! 
! TRUNCATE truncate_a RESTART IDENTITY;
! INSERT INTO truncate_a DEFAULT VALUES;
! INSERT INTO truncate_a DEFAULT VALUES;
! SELECT * FROM truncate_a;
!  id | id1 
! ----+-----
!   1 |  33
!   2 |  34
! (2 rows)
! 
! -- check rollback of a RESTART IDENTITY operation
! BEGIN;
! TRUNCATE truncate_a RESTART IDENTITY;
! INSERT INTO truncate_a DEFAULT VALUES;
! SELECT * FROM truncate_a;
!  id | id1 
! ----+-----
!   1 |  33
! (1 row)
! 
! ROLLBACK;
! INSERT INTO truncate_a DEFAULT VALUES;
! INSERT INTO truncate_a DEFAULT VALUES;
! SELECT * FROM truncate_a;
!  id | id1 
! ----+-----
!   1 |  33
!   2 |  34
!   3 |  35
!   4 |  36
! (4 rows)
! 
! DROP TABLE truncate_a;
! SELECT nextval('truncate_a_id1'); -- fail, seq should have been dropped
! ERROR:  relation "truncate_a_id1" does not exist
! LINE 1: SELECT nextval('truncate_a_id1');
!                        ^
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/alter_table.out	2014-01-02 13:19:06.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/alter_table.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,2372 ****
! --
! -- ALTER_TABLE
! -- add attribute
! --
! CREATE TABLE tmp (initial int4);
! COMMENT ON TABLE tmp_wrong IS 'table comment';
! ERROR:  relation "tmp_wrong" does not exist
! COMMENT ON TABLE tmp IS 'table comment';
! COMMENT ON TABLE tmp IS NULL;
! ALTER TABLE tmp ADD COLUMN xmin integer; -- fails
! ERROR:  column name "xmin" conflicts with a system column name
! ALTER TABLE tmp ADD COLUMN a int4 default 3;
! ALTER TABLE tmp ADD COLUMN b name;
! ALTER TABLE tmp ADD COLUMN c text;
! ALTER TABLE tmp ADD COLUMN d float8;
! ALTER TABLE tmp ADD COLUMN e float4;
! ALTER TABLE tmp ADD COLUMN f int2;
! ALTER TABLE tmp ADD COLUMN g polygon;
! ALTER TABLE tmp ADD COLUMN h abstime;
! ALTER TABLE tmp ADD COLUMN i char;
! ALTER TABLE tmp ADD COLUMN j abstime[];
! ALTER TABLE tmp ADD COLUMN k int4;
! ALTER TABLE tmp ADD COLUMN l tid;
! ALTER TABLE tmp ADD COLUMN m xid;
! ALTER TABLE tmp ADD COLUMN n oidvector;
! --ALTER TABLE tmp ADD COLUMN o lock;
! ALTER TABLE tmp ADD COLUMN p smgr;
! ALTER TABLE tmp ADD COLUMN q point;
! ALTER TABLE tmp ADD COLUMN r lseg;
! ALTER TABLE tmp ADD COLUMN s path;
! ALTER TABLE tmp ADD COLUMN t box;
! ALTER TABLE tmp ADD COLUMN u tinterval;
! ALTER TABLE tmp ADD COLUMN v timestamp;
! ALTER TABLE tmp ADD COLUMN w interval;
! ALTER TABLE tmp ADD COLUMN x float8[];
! ALTER TABLE tmp ADD COLUMN y float4[];
! ALTER TABLE tmp ADD COLUMN z int2[];
! INSERT INTO tmp (a, b, c, d, e, f, g, h, i, j, k, l, m, n, p, q, r, s, t, u,
! 	v, w, x, y, z)
!    VALUES (4, 'name', 'text', 4.1, 4.1, 2, '(4.1,4.1,3.1,3.1)',
!         'Mon May  1 00:30:30 1995', 'c', '{Mon May  1 00:30:30 1995, Monday Aug 24 14:43:07 1992, epoch}',
! 	314159, '(1,1)', '512',
! 	'1 2 3 4 5 6 7 8', 'magnetic disk', '(1.1,1.1)', '(4.1,4.1,3.1,3.1)',
! 	'(0,2,4.1,4.1,3.1,3.1)', '(4.1,4.1,3.1,3.1)', '["epoch" "infinity"]',
! 	'epoch', '01:00:10', '{1.0,2.0,3.0,4.0}', '{1.0,2.0,3.0,4.0}', '{1,2,3,4}');
! SELECT * FROM tmp;
!  initial | a |  b   |  c   |  d  |  e  | f |           g           |              h               | i |                                               j                                                |   k    |   l   |  m  |        n        |       p       |     q     |           r           |              s              |          t          |                      u                      |            v             |        w         |     x     |     y     |     z     
! ---------+---+------+------+-----+-----+---+-----------------------+------------------------------+---+------------------------------------------------------------------------------------------------+--------+-------+-----+-----------------+---------------+-----------+-----------------------+-----------------------------+---------------------+---------------------------------------------+--------------------------+------------------+-----------+-----------+-----------
!          | 4 | name | text | 4.1 | 4.1 | 2 | ((4.1,4.1),(3.1,3.1)) | Mon May 01 00:30:30 1995 PDT | c | {"Mon May 01 00:30:30 1995 PDT","Mon Aug 24 14:43:07 1992 PDT","Wed Dec 31 16:00:00 1969 PST"} | 314159 | (1,1) | 512 | 1 2 3 4 5 6 7 8 | magnetic disk | (1.1,1.1) | [(4.1,4.1),(3.1,3.1)] | ((0,2),(4.1,4.1),(3.1,3.1)) | (4.1,4.1),(3.1,3.1) | ["Wed Dec 31 16:00:00 1969 PST" "infinity"] | Thu Jan 01 00:00:00 1970 | @ 1 hour 10 secs | {1,2,3,4} | {1,2,3,4} | {1,2,3,4}
! (1 row)
! 
! DROP TABLE tmp;
! -- the wolf bug - schema mods caused inconsistent row descriptors
! CREATE TABLE tmp (
! 	initial 	int4
! );
! ALTER TABLE tmp ADD COLUMN a int4;
! ALTER TABLE tmp ADD COLUMN b name;
! ALTER TABLE tmp ADD COLUMN c text;
! ALTER TABLE tmp ADD COLUMN d float8;
! ALTER TABLE tmp ADD COLUMN e float4;
! ALTER TABLE tmp ADD COLUMN f int2;
! ALTER TABLE tmp ADD COLUMN g polygon;
! ALTER TABLE tmp ADD COLUMN h abstime;
! ALTER TABLE tmp ADD COLUMN i char;
! ALTER TABLE tmp ADD COLUMN j abstime[];
! ALTER TABLE tmp ADD COLUMN k int4;
! ALTER TABLE tmp ADD COLUMN l tid;
! ALTER TABLE tmp ADD COLUMN m xid;
! ALTER TABLE tmp ADD COLUMN n oidvector;
! --ALTER TABLE tmp ADD COLUMN o lock;
! ALTER TABLE tmp ADD COLUMN p smgr;
! ALTER TABLE tmp ADD COLUMN q point;
! ALTER TABLE tmp ADD COLUMN r lseg;
! ALTER TABLE tmp ADD COLUMN s path;
! ALTER TABLE tmp ADD COLUMN t box;
! ALTER TABLE tmp ADD COLUMN u tinterval;
! ALTER TABLE tmp ADD COLUMN v timestamp;
! ALTER TABLE tmp ADD COLUMN w interval;
! ALTER TABLE tmp ADD COLUMN x float8[];
! ALTER TABLE tmp ADD COLUMN y float4[];
! ALTER TABLE tmp ADD COLUMN z int2[];
! INSERT INTO tmp (a, b, c, d, e, f, g, h, i, j, k, l, m, n, p, q, r, s, t, u,
! 	v, w, x, y, z)
!    VALUES (4, 'name', 'text', 4.1, 4.1, 2, '(4.1,4.1,3.1,3.1)',
!         'Mon May  1 00:30:30 1995', 'c', '{Mon May  1 00:30:30 1995, Monday Aug 24 14:43:07 1992, epoch}',
! 	314159, '(1,1)', '512',
! 	'1 2 3 4 5 6 7 8', 'magnetic disk', '(1.1,1.1)', '(4.1,4.1,3.1,3.1)',
! 	'(0,2,4.1,4.1,3.1,3.1)', '(4.1,4.1,3.1,3.1)', '["epoch" "infinity"]',
! 	'epoch', '01:00:10', '{1.0,2.0,3.0,4.0}', '{1.0,2.0,3.0,4.0}', '{1,2,3,4}');
! SELECT * FROM tmp;
!  initial | a |  b   |  c   |  d  |  e  | f |           g           |              h               | i |                                               j                                                |   k    |   l   |  m  |        n        |       p       |     q     |           r           |              s              |          t          |                      u                      |            v             |        w         |     x     |     y     |     z     
! ---------+---+------+------+-----+-----+---+-----------------------+------------------------------+---+------------------------------------------------------------------------------------------------+--------+-------+-----+-----------------+---------------+-----------+-----------------------+-----------------------------+---------------------+---------------------------------------------+--------------------------+------------------+-----------+-----------+-----------
!          | 4 | name | text | 4.1 | 4.1 | 2 | ((4.1,4.1),(3.1,3.1)) | Mon May 01 00:30:30 1995 PDT | c | {"Mon May 01 00:30:30 1995 PDT","Mon Aug 24 14:43:07 1992 PDT","Wed Dec 31 16:00:00 1969 PST"} | 314159 | (1,1) | 512 | 1 2 3 4 5 6 7 8 | magnetic disk | (1.1,1.1) | [(4.1,4.1),(3.1,3.1)] | ((0,2),(4.1,4.1),(3.1,3.1)) | (4.1,4.1),(3.1,3.1) | ["Wed Dec 31 16:00:00 1969 PST" "infinity"] | Thu Jan 01 00:00:00 1970 | @ 1 hour 10 secs | {1,2,3,4} | {1,2,3,4} | {1,2,3,4}
! (1 row)
! 
! DROP TABLE tmp;
! --
! -- rename - check on both non-temp and temp tables
! --
! CREATE TABLE tmp (regtable int);
! CREATE TEMP TABLE tmp (tmptable int);
! ALTER TABLE tmp RENAME TO tmp_new;
! SELECT * FROM tmp;
!  regtable 
! ----------
! (0 rows)
! 
! SELECT * FROM tmp_new;
!  tmptable 
! ----------
! (0 rows)
! 
! ALTER TABLE tmp RENAME TO tmp_new2;
! SELECT * FROM tmp;		-- should fail
! ERROR:  relation "tmp" does not exist
! LINE 1: SELECT * FROM tmp;
!                       ^
! SELECT * FROM tmp_new;
!  tmptable 
! ----------
! (0 rows)
! 
! SELECT * FROM tmp_new2;
!  regtable 
! ----------
! (0 rows)
! 
! DROP TABLE tmp_new;
! DROP TABLE tmp_new2;
! -- ALTER TABLE ... RENAME on non-table relations
! -- renaming indexes (FIXME: this should probably test the index's functionality)
! ALTER INDEX IF EXISTS __onek_unique1 RENAME TO tmp_onek_unique1;
! NOTICE:  relation "__onek_unique1" does not exist, skipping
! ALTER INDEX IF EXISTS __tmp_onek_unique1 RENAME TO onek_unique1;
! NOTICE:  relation "__tmp_onek_unique1" does not exist, skipping
! ALTER INDEX onek_unique1 RENAME TO tmp_onek_unique1;
! ALTER INDEX tmp_onek_unique1 RENAME TO onek_unique1;
! -- renaming views
! CREATE VIEW tmp_view (unique1) AS SELECT unique1 FROM tenk1;
! ALTER TABLE tmp_view RENAME TO tmp_view_new;
! -- hack to ensure we get an indexscan here
! set enable_seqscan to off;
! set enable_bitmapscan to off;
! -- 5 values, sorted
! SELECT unique1 FROM tenk1 WHERE unique1 < 5;
!  unique1 
! ---------
!        0
!        1
!        2
!        3
!        4
! (5 rows)
! 
! reset enable_seqscan;
! reset enable_bitmapscan;
! DROP VIEW tmp_view_new;
! -- toast-like relation name
! alter table stud_emp rename to pg_toast_stud_emp;
! alter table pg_toast_stud_emp rename to stud_emp;
! -- renaming index should rename constraint as well
! ALTER TABLE onek ADD CONSTRAINT onek_unique1_constraint UNIQUE (unique1);
! ALTER INDEX onek_unique1_constraint RENAME TO onek_unique1_constraint_foo;
! ALTER TABLE onek DROP CONSTRAINT onek_unique1_constraint_foo;
! -- renaming constraint
! ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0);
! ALTER TABLE onek RENAME CONSTRAINT onek_check_constraint TO onek_check_constraint_foo;
! ALTER TABLE onek DROP CONSTRAINT onek_check_constraint_foo;
! -- renaming constraint should rename index as well
! ALTER TABLE onek ADD CONSTRAINT onek_unique1_constraint UNIQUE (unique1);
! DROP INDEX onek_unique1_constraint;  -- to see whether it's there
! ERROR:  cannot drop index onek_unique1_constraint because constraint onek_unique1_constraint on table onek requires it
! HINT:  You can drop constraint onek_unique1_constraint on table onek instead.
! ALTER TABLE onek RENAME CONSTRAINT onek_unique1_constraint TO onek_unique1_constraint_foo;
! DROP INDEX onek_unique1_constraint_foo;  -- to see whether it's there
! ERROR:  cannot drop index onek_unique1_constraint_foo because constraint onek_unique1_constraint_foo on table onek requires it
! HINT:  You can drop constraint onek_unique1_constraint_foo on table onek instead.
! ALTER TABLE onek DROP CONSTRAINT onek_unique1_constraint_foo;
! -- renaming constraints vs. inheritance
! CREATE TABLE constraint_rename_test (a int CONSTRAINT con1 CHECK (a > 0), b int, c int);
! \d constraint_rename_test
! Table "public.constraint_rename_test"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | integer | 
!  c      | integer | 
! Check constraints:
!     "con1" CHECK (a > 0)
! 
! CREATE TABLE constraint_rename_test2 (a int CONSTRAINT con1 CHECK (a > 0), d int) INHERITS (constraint_rename_test);
! NOTICE:  merging column "a" with inherited definition
! NOTICE:  merging constraint "con1" with inherited definition
! \d constraint_rename_test2
! Table "public.constraint_rename_test2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | integer | 
!  c      | integer | 
!  d      | integer | 
! Check constraints:
!     "con1" CHECK (a > 0)
! Inherits: constraint_rename_test
! 
! ALTER TABLE constraint_rename_test2 RENAME CONSTRAINT con1 TO con1foo; -- fail
! ERROR:  cannot rename inherited constraint "con1"
! ALTER TABLE ONLY constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- fail
! ERROR:  inherited constraint "con1" must be renamed in child tables too
! ALTER TABLE constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- ok
! \d constraint_rename_test
! Table "public.constraint_rename_test"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | integer | 
!  c      | integer | 
! Check constraints:
!     "con1foo" CHECK (a > 0)
! Number of child tables: 1 (Use \d+ to list them.)
! 
! \d constraint_rename_test2
! Table "public.constraint_rename_test2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | integer | 
!  c      | integer | 
!  d      | integer | 
! Check constraints:
!     "con1foo" CHECK (a > 0)
! Inherits: constraint_rename_test
! 
! ALTER TABLE constraint_rename_test ADD CONSTRAINT con2 CHECK (b > 0) NO INHERIT;
! ALTER TABLE ONLY constraint_rename_test RENAME CONSTRAINT con2 TO con2foo; -- ok
! ALTER TABLE constraint_rename_test RENAME CONSTRAINT con2foo TO con2bar; -- ok
! \d constraint_rename_test
! Table "public.constraint_rename_test"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | integer | 
!  c      | integer | 
! Check constraints:
!     "con1foo" CHECK (a > 0)
!     "con2bar" CHECK (b > 0) NO INHERIT
! Number of child tables: 1 (Use \d+ to list them.)
! 
! \d constraint_rename_test2
! Table "public.constraint_rename_test2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | integer | 
!  c      | integer | 
!  d      | integer | 
! Check constraints:
!     "con1foo" CHECK (a > 0)
! Inherits: constraint_rename_test
! 
! ALTER TABLE constraint_rename_test ADD CONSTRAINT con3 PRIMARY KEY (a);
! ALTER TABLE constraint_rename_test RENAME CONSTRAINT con3 TO con3foo; -- ok
! \d constraint_rename_test
! Table "public.constraint_rename_test"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | not null
!  b      | integer | 
!  c      | integer | 
! Indexes:
!     "con3foo" PRIMARY KEY, btree (a)
! Check constraints:
!     "con1foo" CHECK (a > 0)
!     "con2bar" CHECK (b > 0) NO INHERIT
! Number of child tables: 1 (Use \d+ to list them.)
! 
! \d constraint_rename_test2
! Table "public.constraint_rename_test2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | integer | 
!  c      | integer | 
!  d      | integer | 
! Check constraints:
!     "con1foo" CHECK (a > 0)
! Inherits: constraint_rename_test
! 
! DROP TABLE constraint_rename_test2;
! DROP TABLE constraint_rename_test;
! ALTER TABLE IF EXISTS constraint_rename_test ADD CONSTRAINT con4 UNIQUE (a);
! NOTICE:  relation "constraint_rename_test" does not exist, skipping
! -- FOREIGN KEY CONSTRAINT adding TEST
! CREATE TABLE tmp2 (a int primary key);
! CREATE TABLE tmp3 (a int, b int);
! CREATE TABLE tmp4 (a int, b int, unique(a,b));
! CREATE TABLE tmp5 (a int, b int);
! -- Insert rows into tmp2 (pktable)
! INSERT INTO tmp2 values (1);
! INSERT INTO tmp2 values (2);
! INSERT INTO tmp2 values (3);
! INSERT INTO tmp2 values (4);
! -- Insert rows into tmp3
! INSERT INTO tmp3 values (1,10);
! INSERT INTO tmp3 values (1,20);
! INSERT INTO tmp3 values (5,50);
! -- Try (and fail) to add constraint due to invalid source columns
! ALTER TABLE tmp3 add constraint tmpconstr foreign key(c) references tmp2 match full;
! ERROR:  column "c" referenced in foreign key constraint does not exist
! -- Try (and fail) to add constraint due to invalide destination columns explicitly given
! ALTER TABLE tmp3 add constraint tmpconstr foreign key(a) references tmp2(b) match full;
! ERROR:  column "b" referenced in foreign key constraint does not exist
! -- Try (and fail) to add constraint due to invalid data
! ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
! ERROR:  insert or update on table "tmp3" violates foreign key constraint "tmpconstr"
! DETAIL:  Key (a)=(5) is not present in table "tmp2".
! -- Delete failing row
! DELETE FROM tmp3 where a=5;
! -- Try (and succeed)
! ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
! ALTER TABLE tmp3 drop constraint tmpconstr;
! INSERT INTO tmp3 values (5,50);
! -- Try NOT VALID and then VALIDATE CONSTRAINT, but fails. Delete failure then re-validate
! ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full NOT VALID;
! ALTER TABLE tmp3 validate constraint tmpconstr;
! ERROR:  insert or update on table "tmp3" violates foreign key constraint "tmpconstr"
! DETAIL:  Key (a)=(5) is not present in table "tmp2".
! -- Delete failing row
! DELETE FROM tmp3 where a=5;
! -- Try (and succeed) and repeat to show it works on already valid constraint
! ALTER TABLE tmp3 validate constraint tmpconstr;
! ALTER TABLE tmp3 validate constraint tmpconstr;
! -- Try a non-verified CHECK constraint
! ALTER TABLE tmp3 ADD CONSTRAINT b_greater_than_ten CHECK (b > 10); -- fail
! ERROR:  check constraint "b_greater_than_ten" is violated by some row
! ALTER TABLE tmp3 ADD CONSTRAINT b_greater_than_ten CHECK (b > 10) NOT VALID; -- succeeds
! ALTER TABLE tmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- fails
! ERROR:  check constraint "b_greater_than_ten" is violated by some row
! DELETE FROM tmp3 WHERE NOT b > 10;
! ALTER TABLE tmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- succeeds
! ALTER TABLE tmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- succeeds
! -- Test inherited NOT VALID CHECK constraints
! select * from tmp3;
!  a | b  
! ---+----
!  1 | 20
! (1 row)
! 
! CREATE TABLE tmp6 () INHERITS (tmp3);
! CREATE TABLE tmp7 () INHERITS (tmp3);
! INSERT INTO tmp6 VALUES (6, 30), (7, 16);
! ALTER TABLE tmp3 ADD CONSTRAINT b_le_20 CHECK (b <= 20) NOT VALID;
! ALTER TABLE tmp3 VALIDATE CONSTRAINT b_le_20;	-- fails
! ERROR:  check constraint "b_le_20" is violated by some row
! DELETE FROM tmp6 WHERE b > 20;
! ALTER TABLE tmp3 VALIDATE CONSTRAINT b_le_20;	-- succeeds
! -- An already validated constraint must not be revalidated
! CREATE FUNCTION boo(int) RETURNS int IMMUTABLE STRICT LANGUAGE plpgsql AS $$ BEGIN RAISE NOTICE 'boo: %', $1; RETURN $1; END; $$;
! INSERT INTO tmp7 VALUES (8, 18);
! ALTER TABLE tmp7 ADD CONSTRAINT identity CHECK (b = boo(b));
! NOTICE:  boo: 18
! ALTER TABLE tmp3 ADD CONSTRAINT IDENTITY check (b = boo(b)) NOT VALID;
! NOTICE:  merging constraint "identity" with inherited definition
! ALTER TABLE tmp3 VALIDATE CONSTRAINT identity;
! NOTICE:  boo: 16
! NOTICE:  boo: 20
! -- Try (and fail) to create constraint from tmp5(a) to tmp4(a) - unique constraint on
! -- tmp4 is a,b
! ALTER TABLE tmp5 add constraint tmpconstr foreign key(a) references tmp4(a) match full;
! ERROR:  there is no unique constraint matching given keys for referenced table "tmp4"
! DROP TABLE tmp7;
! DROP TABLE tmp6;
! DROP TABLE tmp5;
! DROP TABLE tmp4;
! DROP TABLE tmp3;
! DROP TABLE tmp2;
! -- NOT VALID with plan invalidation -- ensure we don't use a constraint for
! -- exclusion until validated
! set constraint_exclusion TO 'partition';
! create table nv_parent (d date);
! create table nv_child_2010 () inherits (nv_parent);
! create table nv_child_2011 () inherits (nv_parent);
! alter table nv_child_2010 add check (d between '2010-01-01'::date and '2010-12-31'::date) not valid;
! alter table nv_child_2011 add check (d between '2011-01-01'::date and '2011-12-31'::date) not valid;
! explain (costs off) select * from nv_parent where d between '2011-08-01' and '2011-08-31';
!                                 QUERY PLAN                                 
! ---------------------------------------------------------------------------
!  Append
!    ->  Seq Scan on nv_parent
!          Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!    ->  Seq Scan on nv_child_2010
!          Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!    ->  Seq Scan on nv_child_2011
!          Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
! (7 rows)
! 
! create table nv_child_2009 (check (d between '2009-01-01'::date and '2009-12-31'::date)) inherits (nv_parent);
! explain (costs off) select * from nv_parent where d between '2011-08-01'::date and '2011-08-31'::date;
!                                 QUERY PLAN                                 
! ---------------------------------------------------------------------------
!  Append
!    ->  Seq Scan on nv_parent
!          Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!    ->  Seq Scan on nv_child_2010
!          Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!    ->  Seq Scan on nv_child_2011
!          Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
! (7 rows)
! 
! explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date;
!                                 QUERY PLAN                                 
! ---------------------------------------------------------------------------
!  Append
!    ->  Seq Scan on nv_parent
!          Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!    ->  Seq Scan on nv_child_2010
!          Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!    ->  Seq Scan on nv_child_2011
!          Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!    ->  Seq Scan on nv_child_2009
!          Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
! (9 rows)
! 
! -- after validation, the constraint should be used
! alter table nv_child_2011 VALIDATE CONSTRAINT nv_child_2011_d_check;
! explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date;
!                                 QUERY PLAN                                 
! ---------------------------------------------------------------------------
!  Append
!    ->  Seq Scan on nv_parent
!          Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!    ->  Seq Scan on nv_child_2010
!          Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!    ->  Seq Scan on nv_child_2009
!          Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
! (7 rows)
! 
! -- Foreign key adding test with mixed types
! -- Note: these tables are TEMP to avoid name conflicts when this test
! -- is run in parallel with foreign_key.sql.
! CREATE TEMP TABLE PKTABLE (ptest1 int PRIMARY KEY);
! INSERT INTO PKTABLE VALUES(42);
! CREATE TEMP TABLE FKTABLE (ftest1 inet);
! -- This next should fail, because int=inet does not exist
! ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
! ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
! DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
! -- This should also fail for the same reason, but here we
! -- give the column name
! ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1);
! ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
! DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
! DROP TABLE FKTABLE;
! -- This should succeed, even though they are different types,
! -- because int=int8 exists and is a member of the integer opfamily
! CREATE TEMP TABLE FKTABLE (ftest1 int8);
! ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
! -- Check it actually works
! INSERT INTO FKTABLE VALUES(42);		-- should succeed
! INSERT INTO FKTABLE VALUES(43);		-- should fail
! ERROR:  insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
! DETAIL:  Key (ftest1)=(43) is not present in table "pktable".
! DROP TABLE FKTABLE;
! -- This should fail, because we'd have to cast numeric to int which is
! -- not an implicit coercion (or use numeric=numeric, but that's not part
! -- of the integer opfamily)
! CREATE TEMP TABLE FKTABLE (ftest1 numeric);
! ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
! ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
! DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: numeric and integer.
! DROP TABLE FKTABLE;
! DROP TABLE PKTABLE;
! -- On the other hand, this should work because int implicitly promotes to
! -- numeric, and we allow promotion on the FK side
! CREATE TEMP TABLE PKTABLE (ptest1 numeric PRIMARY KEY);
! INSERT INTO PKTABLE VALUES(42);
! CREATE TEMP TABLE FKTABLE (ftest1 int);
! ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
! -- Check it actually works
! INSERT INTO FKTABLE VALUES(42);		-- should succeed
! INSERT INTO FKTABLE VALUES(43);		-- should fail
! ERROR:  insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
! DETAIL:  Key (ftest1)=(43) is not present in table "pktable".
! DROP TABLE FKTABLE;
! DROP TABLE PKTABLE;
! CREATE TEMP TABLE PKTABLE (ptest1 int, ptest2 inet,
!                            PRIMARY KEY(ptest1, ptest2));
! -- This should fail, because we just chose really odd types
! CREATE TEMP TABLE FKTABLE (ftest1 cidr, ftest2 timestamp);
! ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2) references pktable;
! ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
! DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: cidr and integer.
! DROP TABLE FKTABLE;
! -- Again, so should this...
! CREATE TEMP TABLE FKTABLE (ftest1 cidr, ftest2 timestamp);
! ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2)
!      references pktable(ptest1, ptest2);
! ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
! DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: cidr and integer.
! DROP TABLE FKTABLE;
! -- This fails because we mixed up the column ordering
! CREATE TEMP TABLE FKTABLE (ftest1 int, ftest2 inet);
! ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2)
!      references pktable(ptest2, ptest1);
! ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
! DETAIL:  Key columns "ftest1" and "ptest2" are of incompatible types: integer and inet.
! -- As does this...
! ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest2, ftest1)
!      references pktable(ptest1, ptest2);
! ERROR:  foreign key constraint "fktable_ftest2_fkey" cannot be implemented
! DETAIL:  Key columns "ftest2" and "ptest1" are of incompatible types: inet and integer.
! -- temp tables should go away by themselves, need not drop them.
! -- test check constraint adding
! create table atacc1 ( test int );
! -- add a check constraint
! alter table atacc1 add constraint atacc_test1 check (test>3);
! -- should fail
! insert into atacc1 (test) values (2);
! ERROR:  new row for relation "atacc1" violates check constraint "atacc_test1"
! DETAIL:  Failing row contains (2).
! -- should succeed
! insert into atacc1 (test) values (4);
! drop table atacc1;
! -- let's do one where the check fails when added
! create table atacc1 ( test int );
! -- insert a soon to be failing row
! insert into atacc1 (test) values (2);
! -- add a check constraint (fails)
! alter table atacc1 add constraint atacc_test1 check (test>3);
! ERROR:  check constraint "atacc_test1" is violated by some row
! insert into atacc1 (test) values (4);
! drop table atacc1;
! -- let's do one where the check fails because the column doesn't exist
! create table atacc1 ( test int );
! -- add a check constraint (fails)
! alter table atacc1 add constraint atacc_test1 check (test1>3);
! ERROR:  column "test1" does not exist
! drop table atacc1;
! -- something a little more complicated
! create table atacc1 ( test int, test2 int, test3 int);
! -- add a check constraint (fails)
! alter table atacc1 add constraint atacc_test1 check (test+test2<test3*4);
! -- should fail
! insert into atacc1 (test,test2,test3) values (4,4,2);
! ERROR:  new row for relation "atacc1" violates check constraint "atacc_test1"
! DETAIL:  Failing row contains (4, 4, 2).
! -- should succeed
! insert into atacc1 (test,test2,test3) values (4,4,5);
! drop table atacc1;
! -- lets do some naming tests
! create table atacc1 (test int check (test>3), test2 int);
! alter table atacc1 add check (test2>test);
! -- should fail for $2
! insert into atacc1 (test2, test) values (3, 4);
! ERROR:  new row for relation "atacc1" violates check constraint "atacc1_check"
! DETAIL:  Failing row contains (4, 3).
! drop table atacc1;
! -- inheritance related tests
! create table atacc1 (test int);
! create table atacc2 (test2 int);
! create table atacc3 (test3 int) inherits (atacc1, atacc2);
! alter table atacc2 add constraint foo check (test2>0);
! -- fail and then succeed on atacc2
! insert into atacc2 (test2) values (-3);
! ERROR:  new row for relation "atacc2" violates check constraint "foo"
! DETAIL:  Failing row contains (-3).
! insert into atacc2 (test2) values (3);
! -- fail and then succeed on atacc3
! insert into atacc3 (test2) values (-3);
! ERROR:  new row for relation "atacc3" violates check constraint "foo"
! DETAIL:  Failing row contains (null, -3, null).
! insert into atacc3 (test2) values (3);
! drop table atacc3;
! drop table atacc2;
! drop table atacc1;
! -- same things with one created with INHERIT
! create table atacc1 (test int);
! create table atacc2 (test2 int);
! create table atacc3 (test3 int) inherits (atacc1, atacc2);
! alter table atacc3 no inherit atacc2;
! -- fail
! alter table atacc3 no inherit atacc2;
! ERROR:  relation "atacc2" is not a parent of relation "atacc3"
! -- make sure it really isn't a child
! insert into atacc3 (test2) values (3);
! select test2 from atacc2;
!  test2 
! -------
! (0 rows)
! 
! -- fail due to missing constraint
! alter table atacc2 add constraint foo check (test2>0);
! alter table atacc3 inherit atacc2;
! ERROR:  child table is missing constraint "foo"
! -- fail due to missing column
! alter table atacc3 rename test2 to testx;
! alter table atacc3 inherit atacc2;
! ERROR:  child table is missing column "test2"
! -- fail due to mismatched data type
! alter table atacc3 add test2 bool;
! alter table atacc3 inherit atacc2;
! ERROR:  child table "atacc3" has different type for column "test2"
! alter table atacc3 drop test2;
! -- succeed
! alter table atacc3 add test2 int;
! update atacc3 set test2 = 4 where test2 is null;
! alter table atacc3 add constraint foo check (test2>0);
! alter table atacc3 inherit atacc2;
! -- fail due to duplicates and circular inheritance
! alter table atacc3 inherit atacc2;
! ERROR:  relation "atacc2" would be inherited from more than once
! alter table atacc2 inherit atacc3;
! ERROR:  circular inheritance not allowed
! DETAIL:  "atacc3" is already a child of "atacc2".
! alter table atacc2 inherit atacc2;
! ERROR:  circular inheritance not allowed
! DETAIL:  "atacc2" is already a child of "atacc2".
! -- test that we really are a child now (should see 4 not 3 and cascade should go through)
! select test2 from atacc2;
!  test2 
! -------
!      4
! (1 row)
! 
! drop table atacc2 cascade;
! NOTICE:  drop cascades to table atacc3
! drop table atacc1;
! -- adding only to a parent is allowed as of 9.2
! create table atacc1 (test int);
! create table atacc2 (test2 int) inherits (atacc1);
! -- ok:
! alter table atacc1 add constraint foo check (test>0) no inherit;
! -- check constraint is not there on child
! insert into atacc2 (test) values (-3);
! -- check constraint is there on parent
! insert into atacc1 (test) values (-3);
! ERROR:  new row for relation "atacc1" violates check constraint "foo"
! DETAIL:  Failing row contains (-3).
! insert into atacc1 (test) values (3);
! -- fail, violating row:
! alter table atacc2 add constraint foo check (test>0) no inherit;
! ERROR:  check constraint "foo" is violated by some row
! drop table atacc2;
! drop table atacc1;
! -- test unique constraint adding
! create table atacc1 ( test int ) with oids;
! -- add a unique constraint
! alter table atacc1 add constraint atacc_test1 unique (test);
! -- insert first value
! insert into atacc1 (test) values (2);
! -- should fail
! insert into atacc1 (test) values (2);
! ERROR:  duplicate key value violates unique constraint "atacc_test1"
! DETAIL:  Key (test)=(2) already exists.
! -- should succeed
! insert into atacc1 (test) values (4);
! -- try adding a unique oid constraint
! alter table atacc1 add constraint atacc_oid1 unique(oid);
! -- try to create duplicates via alter table using - should fail
! alter table atacc1 alter column test type integer using 0;
! ERROR:  could not create unique index "atacc_test1"
! DETAIL:  Key (test)=(0) is duplicated.
! drop table atacc1;
! -- let's do one where the unique constraint fails when added
! create table atacc1 ( test int );
! -- insert soon to be failing rows
! insert into atacc1 (test) values (2);
! insert into atacc1 (test) values (2);
! -- add a unique constraint (fails)
! alter table atacc1 add constraint atacc_test1 unique (test);
! ERROR:  could not create unique index "atacc_test1"
! DETAIL:  Key (test)=(2) is duplicated.
! insert into atacc1 (test) values (3);
! drop table atacc1;
! -- let's do one where the unique constraint fails
! -- because the column doesn't exist
! create table atacc1 ( test int );
! -- add a unique constraint (fails)
! alter table atacc1 add constraint atacc_test1 unique (test1);
! ERROR:  column "test1" named in key does not exist
! drop table atacc1;
! -- something a little more complicated
! create table atacc1 ( test int, test2 int);
! -- add a unique constraint
! alter table atacc1 add constraint atacc_test1 unique (test, test2);
! -- insert initial value
! insert into atacc1 (test,test2) values (4,4);
! -- should fail
! insert into atacc1 (test,test2) values (4,4);
! ERROR:  duplicate key value violates unique constraint "atacc_test1"
! DETAIL:  Key (test, test2)=(4, 4) already exists.
! -- should all succeed
! insert into atacc1 (test,test2) values (4,5);
! insert into atacc1 (test,test2) values (5,4);
! insert into atacc1 (test,test2) values (5,5);
! drop table atacc1;
! -- lets do some naming tests
! create table atacc1 (test int, test2 int, unique(test));
! alter table atacc1 add unique (test2);
! -- should fail for @@ second one @@
! insert into atacc1 (test2, test) values (3, 3);
! insert into atacc1 (test2, test) values (2, 3);
! ERROR:  duplicate key value violates unique constraint "atacc1_test_key"
! DETAIL:  Key (test)=(3) already exists.
! drop table atacc1;
! -- test primary key constraint adding
! create table atacc1 ( test int ) with oids;
! -- add a primary key constraint
! alter table atacc1 add constraint atacc_test1 primary key (test);
! -- insert first value
! insert into atacc1 (test) values (2);
! -- should fail
! insert into atacc1 (test) values (2);
! ERROR:  duplicate key value violates unique constraint "atacc_test1"
! DETAIL:  Key (test)=(2) already exists.
! -- should succeed
! insert into atacc1 (test) values (4);
! -- inserting NULL should fail
! insert into atacc1 (test) values(NULL);
! ERROR:  null value in column "test" violates not-null constraint
! DETAIL:  Failing row contains (null).
! -- try adding a second primary key (should fail)
! alter table atacc1 add constraint atacc_oid1 primary key(oid);
! ERROR:  multiple primary keys for table "atacc1" are not allowed
! -- drop first primary key constraint
! alter table atacc1 drop constraint atacc_test1 restrict;
! -- try adding a primary key on oid (should succeed)
! alter table atacc1 add constraint atacc_oid1 primary key(oid);
! drop table atacc1;
! -- let's do one where the primary key constraint fails when added
! create table atacc1 ( test int );
! -- insert soon to be failing rows
! insert into atacc1 (test) values (2);
! insert into atacc1 (test) values (2);
! -- add a primary key (fails)
! alter table atacc1 add constraint atacc_test1 primary key (test);
! ERROR:  could not create unique index "atacc_test1"
! DETAIL:  Key (test)=(2) is duplicated.
! insert into atacc1 (test) values (3);
! drop table atacc1;
! -- let's do another one where the primary key constraint fails when added
! create table atacc1 ( test int );
! -- insert soon to be failing row
! insert into atacc1 (test) values (NULL);
! -- add a primary key (fails)
! alter table atacc1 add constraint atacc_test1 primary key (test);
! ERROR:  column "test" contains null values
! insert into atacc1 (test) values (3);
! drop table atacc1;
! -- let's do one where the primary key constraint fails
! -- because the column doesn't exist
! create table atacc1 ( test int );
! -- add a primary key constraint (fails)
! alter table atacc1 add constraint atacc_test1 primary key (test1);
! ERROR:  column "test1" named in key does not exist
! drop table atacc1;
! -- adding a new column as primary key to a non-empty table.
! -- should fail unless the column has a non-null default value.
! create table atacc1 ( test int );
! insert into atacc1 (test) values (0);
! -- add a primary key column without a default (fails).
! alter table atacc1 add column test2 int primary key;
! ERROR:  column "test2" contains null values
! -- now add a primary key column with a default (succeeds).
! alter table atacc1 add column test2 int default 0 primary key;
! drop table atacc1;
! -- something a little more complicated
! create table atacc1 ( test int, test2 int);
! -- add a primary key constraint
! alter table atacc1 add constraint atacc_test1 primary key (test, test2);
! -- try adding a second primary key - should fail
! alter table atacc1 add constraint atacc_test2 primary key (test);
! ERROR:  multiple primary keys for table "atacc1" are not allowed
! -- insert initial value
! insert into atacc1 (test,test2) values (4,4);
! -- should fail
! insert into atacc1 (test,test2) values (4,4);
! ERROR:  duplicate key value violates unique constraint "atacc_test1"
! DETAIL:  Key (test, test2)=(4, 4) already exists.
! insert into atacc1 (test,test2) values (NULL,3);
! ERROR:  null value in column "test" violates not-null constraint
! DETAIL:  Failing row contains (null, 3).
! insert into atacc1 (test,test2) values (3, NULL);
! ERROR:  null value in column "test2" violates not-null constraint
! DETAIL:  Failing row contains (3, null).
! insert into atacc1 (test,test2) values (NULL,NULL);
! ERROR:  null value in column "test" violates not-null constraint
! DETAIL:  Failing row contains (null, null).
! -- should all succeed
! insert into atacc1 (test,test2) values (4,5);
! insert into atacc1 (test,test2) values (5,4);
! insert into atacc1 (test,test2) values (5,5);
! drop table atacc1;
! -- lets do some naming tests
! create table atacc1 (test int, test2 int, primary key(test));
! -- only first should succeed
! insert into atacc1 (test2, test) values (3, 3);
! insert into atacc1 (test2, test) values (2, 3);
! ERROR:  duplicate key value violates unique constraint "atacc1_pkey"
! DETAIL:  Key (test)=(3) already exists.
! insert into atacc1 (test2, test) values (1, NULL);
! ERROR:  null value in column "test" violates not-null constraint
! DETAIL:  Failing row contains (null, 1).
! drop table atacc1;
! -- alter table / alter column [set/drop] not null tests
! -- try altering system catalogs, should fail
! alter table pg_class alter column relname drop not null;
! ERROR:  permission denied: "pg_class" is a system catalog
! alter table pg_class alter relname set not null;
! ERROR:  permission denied: "pg_class" is a system catalog
! -- try altering non-existent table, should fail
! alter table non_existent alter column bar set not null;
! ERROR:  relation "non_existent" does not exist
! alter table non_existent alter column bar drop not null;
! ERROR:  relation "non_existent" does not exist
! -- test setting columns to null and not null and vice versa
! -- test checking for null values and primary key
! create table atacc1 (test int not null) with oids;
! alter table atacc1 add constraint "atacc1_pkey" primary key (test);
! alter table atacc1 alter column test drop not null;
! ERROR:  column "test" is in a primary key
! alter table atacc1 drop constraint "atacc1_pkey";
! alter table atacc1 alter column test drop not null;
! insert into atacc1 values (null);
! alter table atacc1 alter test set not null;
! ERROR:  column "test" contains null values
! delete from atacc1;
! alter table atacc1 alter test set not null;
! -- try altering a non-existent column, should fail
! alter table atacc1 alter bar set not null;
! ERROR:  column "bar" of relation "atacc1" does not exist
! alter table atacc1 alter bar drop not null;
! ERROR:  column "bar" of relation "atacc1" does not exist
! -- try altering the oid column, should fail
! alter table atacc1 alter oid set not null;
! ERROR:  cannot alter system column "oid"
! alter table atacc1 alter oid drop not null;
! ERROR:  cannot alter system column "oid"
! -- try creating a view and altering that, should fail
! create view myview as select * from atacc1;
! alter table myview alter column test drop not null;
! ERROR:  "myview" is not a table or foreign table
! alter table myview alter column test set not null;
! ERROR:  "myview" is not a table or foreign table
! drop view myview;
! drop table atacc1;
! -- test inheritance
! create table parent (a int);
! create table child (b varchar(255)) inherits (parent);
! alter table parent alter a set not null;
! insert into parent values (NULL);
! ERROR:  null value in column "a" violates not-null constraint
! DETAIL:  Failing row contains (null).
! insert into child (a, b) values (NULL, 'foo');
! ERROR:  null value in column "a" violates not-null constraint
! DETAIL:  Failing row contains (null, foo).
! alter table parent alter a drop not null;
! insert into parent values (NULL);
! insert into child (a, b) values (NULL, 'foo');
! alter table only parent alter a set not null;
! ERROR:  column "a" contains null values
! alter table child alter a set not null;
! ERROR:  column "a" contains null values
! delete from parent;
! alter table only parent alter a set not null;
! insert into parent values (NULL);
! ERROR:  null value in column "a" violates not-null constraint
! DETAIL:  Failing row contains (null).
! alter table child alter a set not null;
! insert into child (a, b) values (NULL, 'foo');
! ERROR:  null value in column "a" violates not-null constraint
! DETAIL:  Failing row contains (null, foo).
! delete from child;
! alter table child alter a set not null;
! insert into child (a, b) values (NULL, 'foo');
! ERROR:  null value in column "a" violates not-null constraint
! DETAIL:  Failing row contains (null, foo).
! drop table child;
! drop table parent;
! -- test setting and removing default values
! create table def_test (
! 	c1	int4 default 5,
! 	c2	text default 'initial_default'
! );
! insert into def_test default values;
! alter table def_test alter column c1 drop default;
! insert into def_test default values;
! alter table def_test alter column c2 drop default;
! insert into def_test default values;
! alter table def_test alter column c1 set default 10;
! alter table def_test alter column c2 set default 'new_default';
! insert into def_test default values;
! select * from def_test;
!  c1 |       c2        
! ----+-----------------
!   5 | initial_default
!     | initial_default
!     | 
!  10 | new_default
! (4 rows)
! 
! -- set defaults to an incorrect type: this should fail
! alter table def_test alter column c1 set default 'wrong_datatype';
! ERROR:  invalid input syntax for integer: "wrong_datatype"
! alter table def_test alter column c2 set default 20;
! -- set defaults on a non-existent column: this should fail
! alter table def_test alter column c3 set default 30;
! ERROR:  column "c3" of relation "def_test" does not exist
! -- set defaults on views: we need to create a view, add a rule
! -- to allow insertions into it, and then alter the view to add
! -- a default
! create view def_view_test as select * from def_test;
! create rule def_view_test_ins as
! 	on insert to def_view_test
! 	do instead insert into def_test select new.*;
! insert into def_view_test default values;
! alter table def_view_test alter column c1 set default 45;
! insert into def_view_test default values;
! alter table def_view_test alter column c2 set default 'view_default';
! insert into def_view_test default values;
! select * from def_view_test;
!  c1 |       c2        
! ----+-----------------
!   5 | initial_default
!     | initial_default
!     | 
!  10 | new_default
!     | 
!  45 | 
!  45 | view_default
! (7 rows)
! 
! drop rule def_view_test_ins on def_view_test;
! drop view def_view_test;
! drop table def_test;
! -- alter table / drop column tests
! -- try altering system catalogs, should fail
! alter table pg_class drop column relname;
! ERROR:  permission denied: "pg_class" is a system catalog
! -- try altering non-existent table, should fail
! alter table nosuchtable drop column bar;
! ERROR:  relation "nosuchtable" does not exist
! -- test dropping columns
! create table atacc1 (a int4 not null, b int4, c int4 not null, d int4) with oids;
! insert into atacc1 values (1, 2, 3, 4);
! alter table atacc1 drop a;
! alter table atacc1 drop a;
! ERROR:  column "a" of relation "atacc1" does not exist
! -- SELECTs
! select * from atacc1;
!  b | c | d 
! ---+---+---
!  2 | 3 | 4
! (1 row)
! 
! select * from atacc1 order by a;
! ERROR:  column "a" does not exist
! LINE 1: select * from atacc1 order by a;
!                                       ^
! select * from atacc1 order by "........pg.dropped.1........";
! ERROR:  column "........pg.dropped.1........" does not exist
! LINE 1: select * from atacc1 order by "........pg.dropped.1........"...
!                                       ^
! select * from atacc1 group by a;
! ERROR:  column "a" does not exist
! LINE 1: select * from atacc1 group by a;
!                                       ^
! select * from atacc1 group by "........pg.dropped.1........";
! ERROR:  column "........pg.dropped.1........" does not exist
! LINE 1: select * from atacc1 group by "........pg.dropped.1........"...
!                                       ^
! select atacc1.* from atacc1;
!  b | c | d 
! ---+---+---
!  2 | 3 | 4
! (1 row)
! 
! select a from atacc1;
! ERROR:  column "a" does not exist
! LINE 1: select a from atacc1;
!                ^
! select atacc1.a from atacc1;
! ERROR:  column atacc1.a does not exist
! LINE 1: select atacc1.a from atacc1;
!                ^
! select b,c,d from atacc1;
!  b | c | d 
! ---+---+---
!  2 | 3 | 4
! (1 row)
! 
! select a,b,c,d from atacc1;
! ERROR:  column "a" does not exist
! LINE 1: select a,b,c,d from atacc1;
!                ^
! select * from atacc1 where a = 1;
! ERROR:  column "a" does not exist
! LINE 1: select * from atacc1 where a = 1;
!                                    ^
! select "........pg.dropped.1........" from atacc1;
! ERROR:  column "........pg.dropped.1........" does not exist
! LINE 1: select "........pg.dropped.1........" from atacc1;
!                ^
! select atacc1."........pg.dropped.1........" from atacc1;
! ERROR:  column atacc1.........pg.dropped.1........ does not exist
! LINE 1: select atacc1."........pg.dropped.1........" from atacc1;
!                ^
! select "........pg.dropped.1........",b,c,d from atacc1;
! ERROR:  column "........pg.dropped.1........" does not exist
! LINE 1: select "........pg.dropped.1........",b,c,d from atacc1;
!                ^
! select * from atacc1 where "........pg.dropped.1........" = 1;
! ERROR:  column "........pg.dropped.1........" does not exist
! LINE 1: select * from atacc1 where "........pg.dropped.1........" = ...
!                                    ^
! -- UPDATEs
! update atacc1 set a = 3;
! ERROR:  column "a" of relation "atacc1" does not exist
! LINE 1: update atacc1 set a = 3;
!                           ^
! update atacc1 set b = 2 where a = 3;
! ERROR:  column "a" does not exist
! LINE 1: update atacc1 set b = 2 where a = 3;
!                                       ^
! update atacc1 set "........pg.dropped.1........" = 3;
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! LINE 1: update atacc1 set "........pg.dropped.1........" = 3;
!                           ^
! update atacc1 set b = 2 where "........pg.dropped.1........" = 3;
! ERROR:  column "........pg.dropped.1........" does not exist
! LINE 1: update atacc1 set b = 2 where "........pg.dropped.1........"...
!                                       ^
! -- INSERTs
! insert into atacc1 values (10, 11, 12, 13);
! ERROR:  INSERT has more expressions than target columns
! LINE 1: insert into atacc1 values (10, 11, 12, 13);
!                                                ^
! insert into atacc1 values (default, 11, 12, 13);
! ERROR:  INSERT has more expressions than target columns
! LINE 1: insert into atacc1 values (default, 11, 12, 13);
!                                                     ^
! insert into atacc1 values (11, 12, 13);
! insert into atacc1 (a) values (10);
! ERROR:  column "a" of relation "atacc1" does not exist
! LINE 1: insert into atacc1 (a) values (10);
!                             ^
! insert into atacc1 (a) values (default);
! ERROR:  column "a" of relation "atacc1" does not exist
! LINE 1: insert into atacc1 (a) values (default);
!                             ^
! insert into atacc1 (a,b,c,d) values (10,11,12,13);
! ERROR:  column "a" of relation "atacc1" does not exist
! LINE 1: insert into atacc1 (a,b,c,d) values (10,11,12,13);
!                             ^
! insert into atacc1 (a,b,c,d) values (default,11,12,13);
! ERROR:  column "a" of relation "atacc1" does not exist
! LINE 1: insert into atacc1 (a,b,c,d) values (default,11,12,13);
!                             ^
! insert into atacc1 (b,c,d) values (11,12,13);
! insert into atacc1 ("........pg.dropped.1........") values (10);
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! LINE 1: insert into atacc1 ("........pg.dropped.1........") values (...
!                             ^
! insert into atacc1 ("........pg.dropped.1........") values (default);
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! LINE 1: insert into atacc1 ("........pg.dropped.1........") values (...
!                             ^
! insert into atacc1 ("........pg.dropped.1........",b,c,d) values (10,11,12,13);
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! LINE 1: insert into atacc1 ("........pg.dropped.1........",b,c,d) va...
!                             ^
! insert into atacc1 ("........pg.dropped.1........",b,c,d) values (default,11,12,13);
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! LINE 1: insert into atacc1 ("........pg.dropped.1........",b,c,d) va...
!                             ^
! -- DELETEs
! delete from atacc1 where a = 3;
! ERROR:  column "a" does not exist
! LINE 1: delete from atacc1 where a = 3;
!                                  ^
! delete from atacc1 where "........pg.dropped.1........" = 3;
! ERROR:  column "........pg.dropped.1........" does not exist
! LINE 1: delete from atacc1 where "........pg.dropped.1........" = 3;
!                                  ^
! delete from atacc1;
! -- try dropping a non-existent column, should fail
! alter table atacc1 drop bar;
! ERROR:  column "bar" of relation "atacc1" does not exist
! -- try dropping the oid column, should succeed
! alter table atacc1 drop oid;
! -- try dropping the xmin column, should fail
! alter table atacc1 drop xmin;
! ERROR:  cannot drop system column "xmin"
! -- try creating a view and altering that, should fail
! create view myview as select * from atacc1;
! select * from myview;
!  b | c | d 
! ---+---+---
! (0 rows)
! 
! alter table myview drop d;
! ERROR:  "myview" is not a table, composite type, or foreign table
! drop view myview;
! -- test some commands to make sure they fail on the dropped column
! analyze atacc1(a);
! ERROR:  column "a" of relation "atacc1" does not exist
! analyze atacc1("........pg.dropped.1........");
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! vacuum analyze atacc1(a);
! ERROR:  column "a" of relation "atacc1" does not exist
! vacuum analyze atacc1("........pg.dropped.1........");
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! comment on column atacc1.a is 'testing';
! ERROR:  column "a" of relation "atacc1" does not exist
! comment on column atacc1."........pg.dropped.1........" is 'testing';
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! alter table atacc1 alter a set storage plain;
! ERROR:  column "a" of relation "atacc1" does not exist
! alter table atacc1 alter "........pg.dropped.1........" set storage plain;
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! alter table atacc1 alter a set statistics 0;
! ERROR:  column "a" of relation "atacc1" does not exist
! alter table atacc1 alter "........pg.dropped.1........" set statistics 0;
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! alter table atacc1 alter a set default 3;
! ERROR:  column "a" of relation "atacc1" does not exist
! alter table atacc1 alter "........pg.dropped.1........" set default 3;
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! alter table atacc1 alter a drop default;
! ERROR:  column "a" of relation "atacc1" does not exist
! alter table atacc1 alter "........pg.dropped.1........" drop default;
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! alter table atacc1 alter a set not null;
! ERROR:  column "a" of relation "atacc1" does not exist
! alter table atacc1 alter "........pg.dropped.1........" set not null;
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! alter table atacc1 alter a drop not null;
! ERROR:  column "a" of relation "atacc1" does not exist
! alter table atacc1 alter "........pg.dropped.1........" drop not null;
! ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exist
! alter table atacc1 rename a to x;
! ERROR:  column "a" does not exist
! alter table atacc1 rename "........pg.dropped.1........" to x;
! ERROR:  column "........pg.dropped.1........" does not exist
! alter table atacc1 add primary key(a);
! ERROR:  column "a" named in key does not exist
! alter table atacc1 add primary key("........pg.dropped.1........");
! ERROR:  column "........pg.dropped.1........" named in key does not exist
! alter table atacc1 add unique(a);
! ERROR:  column "a" named in key does not exist
! alter table atacc1 add unique("........pg.dropped.1........");
! ERROR:  column "........pg.dropped.1........" named in key does not exist
! alter table atacc1 add check (a > 3);
! ERROR:  column "a" does not exist
! alter table atacc1 add check ("........pg.dropped.1........" > 3);
! ERROR:  column "........pg.dropped.1........" does not exist
! create table atacc2 (id int4 unique);
! alter table atacc1 add foreign key (a) references atacc2(id);
! ERROR:  column "a" referenced in foreign key constraint does not exist
! alter table atacc1 add foreign key ("........pg.dropped.1........") references atacc2(id);
! ERROR:  column "........pg.dropped.1........" referenced in foreign key constraint does not exist
! alter table atacc2 add foreign key (id) references atacc1(a);
! ERROR:  column "a" referenced in foreign key constraint does not exist
! alter table atacc2 add foreign key (id) references atacc1("........pg.dropped.1........");
! ERROR:  column "........pg.dropped.1........" referenced in foreign key constraint does not exist
! drop table atacc2;
! create index "testing_idx" on atacc1(a);
! ERROR:  column "a" does not exist
! create index "testing_idx" on atacc1("........pg.dropped.1........");
! ERROR:  column "........pg.dropped.1........" does not exist
! -- test create as and select into
! insert into atacc1 values (21, 22, 23);
! create table test1 as select * from atacc1;
! select * from test1;
!  b  | c  | d  
! ----+----+----
!  21 | 22 | 23
! (1 row)
! 
! drop table test1;
! select * into test2 from atacc1;
! select * from test2;
!  b  | c  | d  
! ----+----+----
!  21 | 22 | 23
! (1 row)
! 
! drop table test2;
! -- try dropping all columns
! alter table atacc1 drop c;
! alter table atacc1 drop d;
! alter table atacc1 drop b;
! select * from atacc1;
! --
! (1 row)
! 
! drop table atacc1;
! -- test constraint error reporting in presence of dropped columns
! create table atacc1 (id serial primary key, value int check (value < 10));
! insert into atacc1(value) values (100);
! ERROR:  new row for relation "atacc1" violates check constraint "atacc1_value_check"
! DETAIL:  Failing row contains (1, 100).
! alter table atacc1 drop column value;
! alter table atacc1 add column value int check (value < 10);
! insert into atacc1(value) values (100);
! ERROR:  new row for relation "atacc1" violates check constraint "atacc1_value_check"
! DETAIL:  Failing row contains (2, 100).
! insert into atacc1(id, value) values (null, 0);
! ERROR:  null value in column "id" violates not-null constraint
! DETAIL:  Failing row contains (null, 0).
! drop table atacc1;
! -- test inheritance
! create table parent (a int, b int, c int);
! insert into parent values (1, 2, 3);
! alter table parent drop a;
! create table child (d varchar(255)) inherits (parent);
! insert into child values (12, 13, 'testing');
! select * from parent;
!  b  | c  
! ----+----
!   2 |  3
!  12 | 13
! (2 rows)
! 
! select * from child;
!  b  | c  |    d    
! ----+----+---------
!  12 | 13 | testing
! (1 row)
! 
! alter table parent drop c;
! select * from parent;
!  b  
! ----
!   2
!  12
! (2 rows)
! 
! select * from child;
!  b  |    d    
! ----+---------
!  12 | testing
! (1 row)
! 
! drop table child;
! drop table parent;
! -- test copy in/out
! create table test (a int4, b int4, c int4);
! insert into test values (1,2,3);
! alter table test drop a;
! copy test to stdout;
! 2	3
! copy test(a) to stdout;
! ERROR:  column "a" of relation "test" does not exist
! copy test("........pg.dropped.1........") to stdout;
! ERROR:  column "........pg.dropped.1........" of relation "test" does not exist
! copy test from stdin;
! ERROR:  extra data after last expected column
! CONTEXT:  COPY test, line 1: "10	11	12"
! select * from test;
!  b | c 
! ---+---
!  2 | 3
! (1 row)
! 
! copy test from stdin;
! select * from test;
!  b  | c  
! ----+----
!   2 |  3
!  21 | 22
! (2 rows)
! 
! copy test(a) from stdin;
! ERROR:  column "a" of relation "test" does not exist
! copy test("........pg.dropped.1........") from stdin;
! ERROR:  column "........pg.dropped.1........" of relation "test" does not exist
! copy test(b,c) from stdin;
! select * from test;
!  b  | c  
! ----+----
!   2 |  3
!  21 | 22
!  31 | 32
! (3 rows)
! 
! drop table test;
! -- test inheritance
! create table dropColumn (a int, b int, e int);
! create table dropColumnChild (c int) inherits (dropColumn);
! create table dropColumnAnother (d int) inherits (dropColumnChild);
! -- these two should fail
! alter table dropColumnchild drop column a;
! ERROR:  cannot drop inherited column "a"
! alter table only dropColumnChild drop column b;
! ERROR:  cannot drop inherited column "b"
! -- these three should work
! alter table only dropColumn drop column e;
! alter table dropColumnChild drop column c;
! alter table dropColumn drop column a;
! create table renameColumn (a int);
! create table renameColumnChild (b int) inherits (renameColumn);
! create table renameColumnAnother (c int) inherits (renameColumnChild);
! -- these three should fail
! alter table renameColumnChild rename column a to d;
! ERROR:  cannot rename inherited column "a"
! alter table only renameColumnChild rename column a to d;
! ERROR:  inherited column "a" must be renamed in child tables too
! alter table only renameColumn rename column a to d;
! ERROR:  inherited column "a" must be renamed in child tables too
! -- these should work
! alter table renameColumn rename column a to d;
! alter table renameColumnChild rename column b to a;
! -- these should work
! alter table if exists doesnt_exist_tab rename column a to d;
! NOTICE:  relation "doesnt_exist_tab" does not exist, skipping
! alter table if exists doesnt_exist_tab rename column b to a;
! NOTICE:  relation "doesnt_exist_tab" does not exist, skipping
! -- this should work
! alter table renameColumn add column w int;
! -- this should fail
! alter table only renameColumn add column x int;
! ERROR:  column must be added to child tables too
! -- Test corner cases in dropping of inherited columns
! create table p1 (f1 int, f2 int);
! create table c1 (f1 int not null) inherits(p1);
! NOTICE:  merging column "f1" with inherited definition
! -- should be rejected since c1.f1 is inherited
! alter table c1 drop column f1;
! ERROR:  cannot drop inherited column "f1"
! -- should work
! alter table p1 drop column f1;
! -- c1.f1 is still there, but no longer inherited
! select f1 from c1;
!  f1 
! ----
! (0 rows)
! 
! alter table c1 drop column f1;
! select f1 from c1;
! ERROR:  column "f1" does not exist
! LINE 1: select f1 from c1;
!                ^
! drop table p1 cascade;
! NOTICE:  drop cascades to table c1
! create table p1 (f1 int, f2 int);
! create table c1 () inherits(p1);
! -- should be rejected since c1.f1 is inherited
! alter table c1 drop column f1;
! ERROR:  cannot drop inherited column "f1"
! alter table p1 drop column f1;
! -- c1.f1 is dropped now, since there is no local definition for it
! select f1 from c1;
! ERROR:  column "f1" does not exist
! LINE 1: select f1 from c1;
!                ^
! drop table p1 cascade;
! NOTICE:  drop cascades to table c1
! create table p1 (f1 int, f2 int);
! create table c1 () inherits(p1);
! -- should be rejected since c1.f1 is inherited
! alter table c1 drop column f1;
! ERROR:  cannot drop inherited column "f1"
! alter table only p1 drop column f1;
! -- c1.f1 is NOT dropped, but must now be considered non-inherited
! alter table c1 drop column f1;
! drop table p1 cascade;
! NOTICE:  drop cascades to table c1
! create table p1 (f1 int, f2 int);
! create table c1 (f1 int not null) inherits(p1);
! NOTICE:  merging column "f1" with inherited definition
! -- should be rejected since c1.f1 is inherited
! alter table c1 drop column f1;
! ERROR:  cannot drop inherited column "f1"
! alter table only p1 drop column f1;
! -- c1.f1 is still there, but no longer inherited
! alter table c1 drop column f1;
! drop table p1 cascade;
! NOTICE:  drop cascades to table c1
! create table p1(id int, name text);
! create table p2(id2 int, name text, height int);
! create table c1(age int) inherits(p1,p2);
! NOTICE:  merging multiple inherited definitions of column "name"
! create table gc1() inherits (c1);
! select relname, attname, attinhcount, attislocal
! from pg_class join pg_attribute on (pg_class.oid = pg_attribute.attrelid)
! where relname in ('p1','p2','c1','gc1') and attnum > 0 and not attisdropped
! order by relname, attnum;
!  relname | attname | attinhcount | attislocal 
! ---------+---------+-------------+------------
!  c1      | id      |           1 | f
!  c1      | name    |           2 | f
!  c1      | id2     |           1 | f
!  c1      | height  |           1 | f
!  c1      | age     |           0 | t
!  gc1     | id      |           1 | f
!  gc1     | name    |           1 | f
!  gc1     | id2     |           1 | f
!  gc1     | height  |           1 | f
!  gc1     | age     |           1 | f
!  p1      | id      |           0 | t
!  p1      | name    |           0 | t
!  p2      | id2     |           0 | t
!  p2      | name    |           0 | t
!  p2      | height  |           0 | t
! (15 rows)
! 
! -- should work
! alter table only p1 drop column name;
! -- should work. Now c1.name is local and inhcount is 0.
! alter table p2 drop column name;
! -- should be rejected since its inherited
! alter table gc1 drop column name;
! ERROR:  cannot drop inherited column "name"
! -- should work, and drop gc1.name along
! alter table c1 drop column name;
! -- should fail: column does not exist
! alter table gc1 drop column name;
! ERROR:  column "name" of relation "gc1" does not exist
! -- should work and drop the attribute in all tables
! alter table p2 drop column height;
! -- IF EXISTS test
! create table dropColumnExists ();
! alter table dropColumnExists drop column non_existing; --fail
! ERROR:  column "non_existing" of relation "dropcolumnexists" does not exist
! alter table dropColumnExists drop column if exists non_existing; --succeed
! NOTICE:  column "non_existing" of relation "dropcolumnexists" does not exist, skipping
! select relname, attname, attinhcount, attislocal
! from pg_class join pg_attribute on (pg_class.oid = pg_attribute.attrelid)
! where relname in ('p1','p2','c1','gc1') and attnum > 0 and not attisdropped
! order by relname, attnum;
!  relname | attname | attinhcount | attislocal 
! ---------+---------+-------------+------------
!  c1      | id      |           1 | f
!  c1      | id2     |           1 | f
!  c1      | age     |           0 | t
!  gc1     | id      |           1 | f
!  gc1     | id2     |           1 | f
!  gc1     | age     |           1 | f
!  p1      | id      |           0 | t
!  p2      | id2     |           0 | t
! (8 rows)
! 
! drop table p1, p2 cascade;
! NOTICE:  drop cascades to 2 other objects
! DETAIL:  drop cascades to table c1
! drop cascades to table gc1
! -- test attinhcount tracking with merged columns
! create table depth0();
! create table depth1(c text) inherits (depth0);
! create table depth2() inherits (depth1);
! alter table depth0 add c text;
! NOTICE:  merging definition of column "c" for child "depth1"
! select attrelid::regclass, attname, attinhcount, attislocal
! from pg_attribute
! where attnum > 0 and attrelid::regclass in ('depth0', 'depth1', 'depth2')
! order by attrelid::regclass::text, attnum;
!  attrelid | attname | attinhcount | attislocal 
! ----------+---------+-------------+------------
!  depth0   | c       |           0 | t
!  depth1   | c       |           1 | t
!  depth2   | c       |           1 | f
! (3 rows)
! 
! --
! -- Test the ALTER TABLE SET WITH/WITHOUT OIDS command
! --
! create table altstartwith (col integer) with oids;
! insert into altstartwith values (1);
! select oid > 0, * from altstartwith;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! alter table altstartwith set without oids;
! select oid > 0, * from altstartwith; -- fails
! ERROR:  column "oid" does not exist
! LINE 1: select oid > 0, * from altstartwith;
!                ^
! select * from altstartwith;
!  col 
! -----
!    1
! (1 row)
! 
! alter table altstartwith set with oids;
! select oid > 0, * from altstartwith;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! drop table altstartwith;
! -- Check inheritance cases
! create table altwithoid (col integer) with oids;
! -- Inherits parents oid column anyway
! create table altinhoid () inherits (altwithoid) without oids;
! insert into altinhoid values (1);
! select oid > 0, * from altwithoid;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! select oid > 0, * from altinhoid;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! alter table altwithoid set without oids;
! select oid > 0, * from altwithoid; -- fails
! ERROR:  column "oid" does not exist
! LINE 1: select oid > 0, * from altwithoid;
!                ^
! select oid > 0, * from altinhoid; -- fails
! ERROR:  column "oid" does not exist
! LINE 1: select oid > 0, * from altinhoid;
!                ^
! select * from altwithoid;
!  col 
! -----
!    1
! (1 row)
! 
! select * from altinhoid;
!  col 
! -----
!    1
! (1 row)
! 
! alter table altwithoid set with oids;
! select oid > 0, * from altwithoid;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! select oid > 0, * from altinhoid;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! drop table altwithoid cascade;
! NOTICE:  drop cascades to table altinhoid
! create table altwithoid (col integer) without oids;
! -- child can have local oid column
! create table altinhoid () inherits (altwithoid) with oids;
! insert into altinhoid values (1);
! select oid > 0, * from altwithoid; -- fails
! ERROR:  column "oid" does not exist
! LINE 1: select oid > 0, * from altwithoid;
!                ^
! select oid > 0, * from altinhoid;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! alter table altwithoid set with oids;
! NOTICE:  merging definition of column "oid" for child "altinhoid"
! select oid > 0, * from altwithoid;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! select oid > 0, * from altinhoid;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! -- the child's local definition should remain
! alter table altwithoid set without oids;
! select oid > 0, * from altwithoid; -- fails
! ERROR:  column "oid" does not exist
! LINE 1: select oid > 0, * from altwithoid;
!                ^
! select oid > 0, * from altinhoid;
!  ?column? | col 
! ----------+-----
!  t        |   1
! (1 row)
! 
! drop table altwithoid cascade;
! NOTICE:  drop cascades to table altinhoid
! -- test renumbering of child-table columns in inherited operations
! create table p1 (f1 int);
! create table c1 (f2 text, f3 int) inherits (p1);
! alter table p1 add column a1 int check (a1 > 0);
! alter table p1 add column f2 text;
! NOTICE:  merging definition of column "f2" for child "c1"
! insert into p1 values (1,2,'abc');
! insert into c1 values(11,'xyz',33,0); -- should fail
! ERROR:  new row for relation "c1" violates check constraint "p1_a1_check"
! DETAIL:  Failing row contains (11, xyz, 33, 0).
! insert into c1 values(11,'xyz',33,22);
! select * from p1;
!  f1 | a1 | f2  
! ----+----+-----
!   1 |  2 | abc
!  11 | 22 | xyz
! (2 rows)
! 
! update p1 set a1 = a1 + 1, f2 = upper(f2);
! select * from p1;
!  f1 | a1 | f2  
! ----+----+-----
!   1 |  3 | ABC
!  11 | 23 | XYZ
! (2 rows)
! 
! drop table p1 cascade;
! NOTICE:  drop cascades to table c1
! -- test that operations with a dropped column do not try to reference
! -- its datatype
! create domain mytype as text;
! create temp table foo (f1 text, f2 mytype, f3 text);
! insert into foo values('bb','cc','dd');
! select * from foo;
!  f1 | f2 | f3 
! ----+----+----
!  bb | cc | dd
! (1 row)
! 
! drop domain mytype cascade;
! NOTICE:  drop cascades to table foo column f2
! select * from foo;
!  f1 | f3 
! ----+----
!  bb | dd
! (1 row)
! 
! insert into foo values('qq','rr');
! select * from foo;
!  f1 | f3 
! ----+----
!  bb | dd
!  qq | rr
! (2 rows)
! 
! update foo set f3 = 'zz';
! select * from foo;
!  f1 | f3 
! ----+----
!  bb | zz
!  qq | zz
! (2 rows)
! 
! select f3,max(f1) from foo group by f3;
!  f3 | max 
! ----+-----
!  zz | qq
! (1 row)
! 
! -- Simple tests for alter table column type
! alter table foo alter f1 TYPE integer; -- fails
! ERROR:  column "f1" cannot be cast automatically to type integer
! HINT:  Specify a USING expression to perform the conversion.
! alter table foo alter f1 TYPE varchar(10);
! create table anothertab (atcol1 serial8, atcol2 boolean,
! 	constraint anothertab_chk check (atcol1 <= 3));
! insert into anothertab (atcol1, atcol2) values (default, true);
! insert into anothertab (atcol1, atcol2) values (default, false);
! select * from anothertab;
!  atcol1 | atcol2 
! --------+--------
!       1 | t
!       2 | f
! (2 rows)
! 
! alter table anothertab alter column atcol1 type boolean; -- fails
! ERROR:  column "atcol1" cannot be cast automatically to type boolean
! HINT:  Specify a USING expression to perform the conversion.
! alter table anothertab alter column atcol1 type integer;
! select * from anothertab;
!  atcol1 | atcol2 
! --------+--------
!       1 | t
!       2 | f
! (2 rows)
! 
! insert into anothertab (atcol1, atcol2) values (45, null); -- fails
! ERROR:  new row for relation "anothertab" violates check constraint "anothertab_chk"
! DETAIL:  Failing row contains (45, null).
! insert into anothertab (atcol1, atcol2) values (default, null);
! select * from anothertab;
!  atcol1 | atcol2 
! --------+--------
!       1 | t
!       2 | f
!       3 | 
! (3 rows)
! 
! alter table anothertab alter column atcol2 type text
!       using case when atcol2 is true then 'IT WAS TRUE'
!                  when atcol2 is false then 'IT WAS FALSE'
!                  else 'IT WAS NULL!' end;
! select * from anothertab;
!  atcol1 |    atcol2    
! --------+--------------
!       1 | IT WAS TRUE
!       2 | IT WAS FALSE
!       3 | IT WAS NULL!
! (3 rows)
! 
! alter table anothertab alter column atcol1 type boolean
!         using case when atcol1 % 2 = 0 then true else false end; -- fails
! ERROR:  default for column "atcol1" cannot be cast automatically to type boolean
! alter table anothertab alter column atcol1 drop default;
! alter table anothertab alter column atcol1 type boolean
!         using case when atcol1 % 2 = 0 then true else false end; -- fails
! ERROR:  operator does not exist: boolean <= integer
! HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
! alter table anothertab drop constraint anothertab_chk;
! alter table anothertab drop constraint anothertab_chk; -- fails
! ERROR:  constraint "anothertab_chk" of relation "anothertab" does not exist
! alter table anothertab drop constraint IF EXISTS anothertab_chk; -- succeeds
! NOTICE:  constraint "anothertab_chk" of relation "anothertab" does not exist, skipping
! alter table anothertab alter column atcol1 type boolean
!         using case when atcol1 % 2 = 0 then true else false end;
! select * from anothertab;
!  atcol1 |    atcol2    
! --------+--------------
!  f      | IT WAS TRUE
!  t      | IT WAS FALSE
!  f      | IT WAS NULL!
! (3 rows)
! 
! drop table anothertab;
! create table another (f1 int, f2 text);
! insert into another values(1, 'one');
! insert into another values(2, 'two');
! insert into another values(3, 'three');
! select * from another;
!  f1 |  f2   
! ----+-------
!   1 | one
!   2 | two
!   3 | three
! (3 rows)
! 
! alter table another
!   alter f1 type text using f2 || ' more',
!   alter f2 type bigint using f1 * 10;
! select * from another;
!      f1     | f2 
! ------------+----
!  one more   | 10
!  two more   | 20
!  three more | 30
! (3 rows)
! 
! drop table another;
! -- table's row type
! create table tab1 (a int, b text);
! create table tab2 (x int, y tab1);
! alter table tab1 alter column b type varchar; -- fails
! ERROR:  cannot alter table "tab1" because column "tab2.y" uses its row type
! -- disallow recursive containment of row types
! create temp table recur1 (f1 int);
! alter table recur1 add column f2 recur1; -- fails
! ERROR:  composite type recur1 cannot be made a member of itself
! alter table recur1 add column f2 recur1[]; -- fails
! ERROR:  composite type recur1 cannot be made a member of itself
! create domain array_of_recur1 as recur1[];
! alter table recur1 add column f2 array_of_recur1; -- fails
! ERROR:  composite type recur1 cannot be made a member of itself
! create temp table recur2 (f1 int, f2 recur1);
! alter table recur1 add column f2 recur2; -- fails
! ERROR:  composite type recur1 cannot be made a member of itself
! alter table recur1 add column f2 int;
! alter table recur1 alter column f2 type recur2; -- fails
! ERROR:  composite type recur1 cannot be made a member of itself
! -- SET STORAGE may need to add a TOAST table
! create table test_storage (a text);
! alter table test_storage alter a set storage plain;
! alter table test_storage add b int default 0; -- rewrite table to remove its TOAST table
! alter table test_storage alter a set storage extended; -- re-add TOAST table
! select reltoastrelid <> 0 as has_toast_table
! from pg_class
! where oid = 'test_storage'::regclass;
!  has_toast_table 
! -----------------
!  t
! (1 row)
! 
! -- ALTER TYPE with a check constraint and a child table (bug before Nov 2012)
! CREATE TABLE test_inh_check (a float check (a > 10.2));
! CREATE TABLE test_inh_check_child() INHERITS(test_inh_check);
! ALTER TABLE test_inh_check ALTER COLUMN a TYPE numeric;
! \d test_inh_check
! Table "public.test_inh_check"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | numeric | 
! Check constraints:
!     "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
! Number of child tables: 1 (Use \d+ to list them.)
! 
! \d test_inh_check_child
! Table "public.test_inh_check_child"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | numeric | 
! Check constraints:
!     "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
! Inherits: test_inh_check
! 
! --
! -- lock levels
! --
! drop type lockmodes;
! ERROR:  type "lockmodes" does not exist
! create type lockmodes as enum (
!  'AccessShareLock'
! ,'RowShareLock'
! ,'RowExclusiveLock'
! ,'ShareUpdateExclusiveLock'
! ,'ShareLock'
! ,'ShareRowExclusiveLock'
! ,'ExclusiveLock'
! ,'AccessExclusiveLock'
! );
! drop view my_locks;
! ERROR:  view "my_locks" does not exist
! create or replace view my_locks as
! select case when c.relname like 'pg_toast%' then 'pg_toast' else c.relname end, max(mode::lockmodes) as max_lockmode
! from pg_locks l join pg_class c on l.relation = c.oid
! where virtualtransaction = (
!         select virtualtransaction
!         from pg_locks
!         where transactionid = txid_current()::integer)
! and locktype = 'relation'
! and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
! and c.relname != 'my_locks'
! group by c.relname;
! create table alterlock (f1 int primary key, f2 text);
! begin; alter table alterlock alter column f2 set statistics 150;
! select * from my_locks order by 1;
!   relname  |    max_lockmode     
! -----------+---------------------
!  alterlock | AccessExclusiveLock
! (1 row)
! 
! rollback;
! begin; alter table alterlock cluster on alterlock_pkey;
! select * from my_locks order by 1;
!     relname     |    max_lockmode     
! ----------------+---------------------
!  alterlock      | AccessExclusiveLock
!  alterlock_pkey | AccessExclusiveLock
! (2 rows)
! 
! commit;
! begin; alter table alterlock set without cluster;
! select * from my_locks order by 1;
!   relname  |    max_lockmode     
! -----------+---------------------
!  alterlock | AccessExclusiveLock
! (1 row)
! 
! commit;
! begin; alter table alterlock set (fillfactor = 100);
! select * from my_locks order by 1;
!   relname  |    max_lockmode     
! -----------+---------------------
!  alterlock | AccessExclusiveLock
!  pg_toast  | AccessExclusiveLock
! (2 rows)
! 
! commit;
! begin; alter table alterlock reset (fillfactor);
! select * from my_locks order by 1;
!   relname  |    max_lockmode     
! -----------+---------------------
!  alterlock | AccessExclusiveLock
!  pg_toast  | AccessExclusiveLock
! (2 rows)
! 
! commit;
! begin; alter table alterlock set (toast.autovacuum_enabled = off);
! select * from my_locks order by 1;
!   relname  |    max_lockmode     
! -----------+---------------------
!  alterlock | AccessExclusiveLock
!  pg_toast  | AccessExclusiveLock
! (2 rows)
! 
! commit;
! begin; alter table alterlock set (autovacuum_enabled = off);
! select * from my_locks order by 1;
!   relname  |    max_lockmode     
! -----------+---------------------
!  alterlock | AccessExclusiveLock
!  pg_toast  | AccessExclusiveLock
! (2 rows)
! 
! commit;
! begin; alter table alterlock alter column f2 set (n_distinct = 1);
! select * from my_locks order by 1;
!   relname  |    max_lockmode     
! -----------+---------------------
!  alterlock | AccessExclusiveLock
! (1 row)
! 
! rollback;
! begin; alter table alterlock alter column f2 set storage extended;
! select * from my_locks order by 1;
!   relname  |    max_lockmode     
! -----------+---------------------
!  alterlock | AccessExclusiveLock
! (1 row)
! 
! rollback;
! begin; alter table alterlock alter column f2 set default 'x';
! select * from my_locks order by 1;
!   relname  |    max_lockmode     
! -----------+---------------------
!  alterlock | AccessExclusiveLock
! (1 row)
! 
! rollback;
! -- cleanup
! drop table alterlock;
! drop view my_locks;
! drop type lockmodes;
! --
! -- alter function
! --
! create function test_strict(text) returns text as
!     'select coalesce($1, ''got passed a null'');'
!     language sql returns null on null input;
! select test_strict(NULL);
!  test_strict 
! -------------
!  
! (1 row)
! 
! alter function test_strict(text) called on null input;
! select test_strict(NULL);
!     test_strict    
! -------------------
!  got passed a null
! (1 row)
! 
! create function non_strict(text) returns text as
!     'select coalesce($1, ''got passed a null'');'
!     language sql called on null input;
! select non_strict(NULL);
!     non_strict     
! -------------------
!  got passed a null
! (1 row)
! 
! alter function non_strict(text) returns null on null input;
! select non_strict(NULL);
!  non_strict 
! ------------
!  
! (1 row)
! 
! --
! -- alter object set schema
! --
! create schema alter1;
! create schema alter2;
! create table alter1.t1(f1 serial primary key, f2 int check (f2 > 0));
! create view alter1.v1 as select * from alter1.t1;
! create function alter1.plus1(int) returns int as 'select $1+1' language sql;
! create domain alter1.posint integer check (value > 0);
! create type alter1.ctype as (f1 int, f2 text);
! create function alter1.same(alter1.ctype, alter1.ctype) returns boolean language sql
! as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2';
! create operator alter1.=(procedure = alter1.same, leftarg  = alter1.ctype, rightarg = alter1.ctype);
! create operator class alter1.ctype_hash_ops default for type alter1.ctype using hash as
!   operator 1 alter1.=(alter1.ctype, alter1.ctype);
! create conversion alter1.ascii_to_utf8 for 'sql_ascii' to 'utf8' from ascii_to_utf8;
! create text search parser alter1.prs(start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
! create text search configuration alter1.cfg(parser = alter1.prs);
! create text search template alter1.tmpl(init = dsimple_init, lexize = dsimple_lexize);
! create text search dictionary alter1.dict(template = alter1.tmpl);
! insert into alter1.t1(f2) values(11);
! insert into alter1.t1(f2) values(12);
! alter table alter1.t1 set schema alter2;
! alter table alter1.v1 set schema alter2;
! alter function alter1.plus1(int) set schema alter2;
! alter domain alter1.posint set schema alter2;
! alter operator class alter1.ctype_hash_ops using hash set schema alter2;
! alter operator family alter1.ctype_hash_ops using hash set schema alter2;
! alter operator alter1.=(alter1.ctype, alter1.ctype) set schema alter2;
! alter function alter1.same(alter1.ctype, alter1.ctype) set schema alter2;
! alter type alter1.ctype set schema alter2;
! alter conversion alter1.ascii_to_utf8 set schema alter2;
! alter text search parser alter1.prs set schema alter2;
! alter text search configuration alter1.cfg set schema alter2;
! alter text search template alter1.tmpl set schema alter2;
! alter text search dictionary alter1.dict set schema alter2;
! -- this should succeed because nothing is left in alter1
! drop schema alter1;
! insert into alter2.t1(f2) values(13);
! insert into alter2.t1(f2) values(14);
! select * from alter2.t1;
!  f1 | f2 
! ----+----
!   1 | 11
!   2 | 12
!   3 | 13
!   4 | 14
! (4 rows)
! 
! select * from alter2.v1;
!  f1 | f2 
! ----+----
!   1 | 11
!   2 | 12
!   3 | 13
!   4 | 14
! (4 rows)
! 
! select alter2.plus1(41);
!  plus1 
! -------
!     42
! (1 row)
! 
! -- clean up
! drop schema alter2 cascade;
! NOTICE:  drop cascades to 13 other objects
! DETAIL:  drop cascades to table alter2.t1
! drop cascades to view alter2.v1
! drop cascades to function alter2.plus1(integer)
! drop cascades to type alter2.posint
! drop cascades to operator family alter2.ctype_hash_ops for access method hash
! drop cascades to type alter2.ctype
! drop cascades to function alter2.same(alter2.ctype,alter2.ctype)
! drop cascades to operator alter2.=(alter2.ctype,alter2.ctype)
! drop cascades to conversion ascii_to_utf8
! drop cascades to text search parser prs
! drop cascades to text search configuration cfg
! drop cascades to text search template tmpl
! drop cascades to text search dictionary dict
! --
! -- composite types
! --
! CREATE TYPE test_type AS (a int);
! \d test_type
! Composite type "public.test_type"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
! 
! ALTER TYPE nosuchtype ADD ATTRIBUTE b text; -- fails
! ERROR:  relation "nosuchtype" does not exist
! ALTER TYPE test_type ADD ATTRIBUTE b text;
! \d test_type
! Composite type "public.test_type"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | text    | 
! 
! ALTER TYPE test_type ADD ATTRIBUTE b text; -- fails
! ERROR:  column "b" of relation "test_type" already exists
! ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE varchar;
! \d test_type
!    Composite type "public.test_type"
!  Column |       Type        | Modifiers 
! --------+-------------------+-----------
!  a      | integer           | 
!  b      | character varying | 
! 
! ALTER TYPE test_type ALTER ATTRIBUTE b SET DATA TYPE integer;
! \d test_type
! Composite type "public.test_type"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | integer | 
! 
! ALTER TYPE test_type DROP ATTRIBUTE b;
! \d test_type
! Composite type "public.test_type"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
! 
! ALTER TYPE test_type DROP ATTRIBUTE c; -- fails
! ERROR:  column "c" of relation "test_type" does not exist
! ALTER TYPE test_type DROP ATTRIBUTE IF EXISTS c;
! NOTICE:  column "c" of relation "test_type" does not exist, skipping
! ALTER TYPE test_type DROP ATTRIBUTE a, ADD ATTRIBUTE d boolean;
! \d test_type
! Composite type "public.test_type"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  d      | boolean | 
! 
! ALTER TYPE test_type RENAME ATTRIBUTE a TO aa;
! ERROR:  column "a" does not exist
! ALTER TYPE test_type RENAME ATTRIBUTE d TO dd;
! \d test_type
! Composite type "public.test_type"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  dd     | boolean | 
! 
! DROP TYPE test_type;
! CREATE TYPE test_type1 AS (a int, b text);
! CREATE TABLE test_tbl1 (x int, y test_type1);
! ALTER TYPE test_type1 ALTER ATTRIBUTE b TYPE varchar; -- fails
! ERROR:  cannot alter type "test_type1" because column "test_tbl1.y" uses it
! CREATE TYPE test_type2 AS (a int, b text);
! CREATE TABLE test_tbl2 OF test_type2;
! CREATE TABLE test_tbl2_subclass () INHERITS (test_tbl2);
! \d test_type2
! Composite type "public.test_type2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | text    | 
! 
! \d test_tbl2
!    Table "public.test_tbl2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | text    | 
! Number of child tables: 1 (Use \d+ to list them.)
! Typed table of type: test_type2
! 
! ALTER TYPE test_type2 ADD ATTRIBUTE c text; -- fails
! ERROR:  cannot alter type "test_type2" because it is the type of a typed table
! HINT:  Use ALTER ... CASCADE to alter the typed tables too.
! ALTER TYPE test_type2 ADD ATTRIBUTE c text CASCADE;
! \d test_type2
! Composite type "public.test_type2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | text    | 
!  c      | text    | 
! 
! \d test_tbl2
!    Table "public.test_tbl2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  b      | text    | 
!  c      | text    | 
! Number of child tables: 1 (Use \d+ to list them.)
! Typed table of type: test_type2
! 
! ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar; -- fails
! ERROR:  cannot alter type "test_type2" because it is the type of a typed table
! HINT:  Use ALTER ... CASCADE to alter the typed tables too.
! ALTER TYPE test_type2 ALTER ATTRIBUTE b TYPE varchar CASCADE;
! \d test_type2
!    Composite type "public.test_type2"
!  Column |       Type        | Modifiers 
! --------+-------------------+-----------
!  a      | integer           | 
!  b      | character varying | 
!  c      | text              | 
! 
! \d test_tbl2
!         Table "public.test_tbl2"
!  Column |       Type        | Modifiers 
! --------+-------------------+-----------
!  a      | integer           | 
!  b      | character varying | 
!  c      | text              | 
! Number of child tables: 1 (Use \d+ to list them.)
! Typed table of type: test_type2
! 
! ALTER TYPE test_type2 DROP ATTRIBUTE b; -- fails
! ERROR:  cannot alter type "test_type2" because it is the type of a typed table
! HINT:  Use ALTER ... CASCADE to alter the typed tables too.
! ALTER TYPE test_type2 DROP ATTRIBUTE b CASCADE;
! \d test_type2
! Composite type "public.test_type2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  c      | text    | 
! 
! \d test_tbl2
!    Table "public.test_tbl2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  a      | integer | 
!  c      | text    | 
! Number of child tables: 1 (Use \d+ to list them.)
! Typed table of type: test_type2
! 
! ALTER TYPE test_type2 RENAME ATTRIBUTE a TO aa; -- fails
! ERROR:  cannot alter type "test_type2" because it is the type of a typed table
! HINT:  Use ALTER ... CASCADE to alter the typed tables too.
! ALTER TYPE test_type2 RENAME ATTRIBUTE a TO aa CASCADE;
! \d test_type2
! Composite type "public.test_type2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  aa     | integer | 
!  c      | text    | 
! 
! \d test_tbl2
!    Table "public.test_tbl2"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  aa     | integer | 
!  c      | text    | 
! Number of child tables: 1 (Use \d+ to list them.)
! Typed table of type: test_type2
! 
! \d test_tbl2_subclass
! Table "public.test_tbl2_subclass"
!  Column |  Type   | Modifiers 
! --------+---------+-----------
!  aa     | integer | 
!  c      | text    | 
! Inherits: test_tbl2
! 
! DROP TABLE test_tbl2_subclass;
! -- This test isn't that interesting on its own, but the purpose is to leave
! -- behind a table to test pg_upgrade with. The table has a composite type
! -- column in it, and the composite type has a dropped attribute.
! CREATE TYPE test_type3 AS (a int);
! CREATE TABLE test_tbl3 (c) AS SELECT '(1)'::test_type3;
! ALTER TYPE test_type3 DROP ATTRIBUTE a, ADD ATTRIBUTE b int;
! CREATE TYPE test_type_empty AS ();
! DROP TYPE test_type_empty;
! --
! -- typed tables: OF / NOT OF
! --
! CREATE TYPE tt_t0 AS (z inet, x int, y numeric(8,2));
! ALTER TYPE tt_t0 DROP ATTRIBUTE z;
! CREATE TABLE tt0 (x int NOT NULL, y numeric(8,2));	-- OK
! CREATE TABLE tt1 (x int, y bigint);					-- wrong base type
! CREATE TABLE tt2 (x int, y numeric(9,2));			-- wrong typmod
! CREATE TABLE tt3 (y numeric(8,2), x int);			-- wrong column order
! CREATE TABLE tt4 (x int);							-- too few columns
! CREATE TABLE tt5 (x int, y numeric(8,2), z int);	-- too few columns
! CREATE TABLE tt6 () INHERITS (tt0);					-- can't have a parent
! CREATE TABLE tt7 (x int, q text, y numeric(8,2)) WITH OIDS;
! ALTER TABLE tt7 DROP q;								-- OK
! ALTER TABLE tt0 OF tt_t0;
! ALTER TABLE tt1 OF tt_t0;
! ERROR:  table "tt1" has different type for column "y"
! ALTER TABLE tt2 OF tt_t0;
! ERROR:  table "tt2" has different type for column "y"
! ALTER TABLE tt3 OF tt_t0;
! ERROR:  table has column "y" where type requires "x"
! ALTER TABLE tt4 OF tt_t0;
! ERROR:  table is missing column "y"
! ALTER TABLE tt5 OF tt_t0;
! ERROR:  table has extra column "z"
! ALTER TABLE tt6 OF tt_t0;
! ERROR:  typed tables cannot inherit
! ALTER TABLE tt7 OF tt_t0;
! CREATE TYPE tt_t1 AS (x int, y numeric(8,2));
! ALTER TABLE tt7 OF tt_t1;			-- reassign an already-typed table
! ALTER TABLE tt7 NOT OF;
! \d tt7
!         Table "public.tt7"
!  Column |     Type     | Modifiers 
! --------+--------------+-----------
!  x      | integer      | 
!  y      | numeric(8,2) | 
! 
! -- make sure we can drop a constraint on the parent but it remains on the child
! CREATE TABLE test_drop_constr_parent (c text CHECK (c IS NOT NULL));
! CREATE TABLE test_drop_constr_child () INHERITS (test_drop_constr_parent);
! ALTER TABLE ONLY test_drop_constr_parent DROP CONSTRAINT "test_drop_constr_parent_c_check";
! -- should fail
! INSERT INTO test_drop_constr_child (c) VALUES (NULL);
! ERROR:  new row for relation "test_drop_constr_child" violates check constraint "test_drop_constr_parent_c_check"
! DETAIL:  Failing row contains (null).
! DROP TABLE test_drop_constr_parent CASCADE;
! NOTICE:  drop cascades to table test_drop_constr_child
! --
! -- IF EXISTS test
! --
! ALTER TABLE IF EXISTS tt8 ADD COLUMN f int;
! NOTICE:  relation "tt8" does not exist, skipping
! ALTER TABLE IF EXISTS tt8 ADD CONSTRAINT xxx PRIMARY KEY(f);
! NOTICE:  relation "tt8" does not exist, skipping
! ALTER TABLE IF EXISTS tt8 ADD CHECK (f BETWEEN 0 AND 10);
! NOTICE:  relation "tt8" does not exist, skipping
! ALTER TABLE IF EXISTS tt8 ALTER COLUMN f SET DEFAULT 0;
! NOTICE:  relation "tt8" does not exist, skipping
! ALTER TABLE IF EXISTS tt8 RENAME COLUMN f TO f1;
! NOTICE:  relation "tt8" does not exist, skipping
! ALTER TABLE IF EXISTS tt8 SET SCHEMA alter2;
! NOTICE:  relation "tt8" does not exist, skipping
! CREATE TABLE tt8(a int);
! CREATE SCHEMA alter2;
! ALTER TABLE IF EXISTS tt8 ADD COLUMN f int;
! ALTER TABLE IF EXISTS tt8 ADD CONSTRAINT xxx PRIMARY KEY(f);
! ALTER TABLE IF EXISTS tt8 ADD CHECK (f BETWEEN 0 AND 10);
! ALTER TABLE IF EXISTS tt8 ALTER COLUMN f SET DEFAULT 0;
! ALTER TABLE IF EXISTS tt8 RENAME COLUMN f TO f1;
! ALTER TABLE IF EXISTS tt8 SET SCHEMA alter2;
! \d alter2.tt8
!           Table "alter2.tt8"
!  Column |  Type   |     Modifiers      
! --------+---------+--------------------
!  a      | integer | 
!  f1     | integer | not null default 0
! Indexes:
!     "xxx" PRIMARY KEY, btree (f1)
! Check constraints:
!     "tt8_f_check" CHECK (f1 >= 0 AND f1 <= 10)
! 
! DROP TABLE alter2.tt8;
! DROP SCHEMA alter2;
! -- Check that we map relation oids to filenodes and back correctly.
! -- Don't display all the mappings so the test output doesn't change
! -- all the time, but make sure we actually do test some values.
! SELECT
!     SUM((mapped_oid != oid OR mapped_oid IS NULL)::int) incorrectly_mapped,
!     count(*) > 200 have_mappings
! FROM (
!     SELECT
!         oid, reltablespace, relfilenode, relname,
!         pg_filenode_relation(reltablespace, pg_relation_filenode(oid)) mapped_oid
!     FROM pg_class
!     WHERE relkind IN ('r', 'i', 'S', 't', 'm')
!     ) mapped;
!  incorrectly_mapped | have_mappings 
! --------------------+---------------
!                   0 | t
! (1 row)
! 
! -- Checks on creating and manipulation of user defined relations in
! -- pg_catalog.
! --
! -- XXX: It would be useful to add checks around trying to manipulate
! -- catalog tables, but that might have ugly consequences when run
! -- against an existing server with allow_system_table_mods = on.
! SHOW allow_system_table_mods;
!  allow_system_table_mods 
! -------------------------
!  off
! (1 row)
! 
! -- disallowed because of search_path issues with pg_dump
! CREATE TABLE pg_catalog.new_system_table();
! ERROR:  permission denied to create "pg_catalog.new_system_table"
! DETAIL:  System catalog modifications are currently disallowed.
! -- instead create in public first, move to catalog
! CREATE TABLE new_system_table(id serial primary key, othercol text);
! ALTER TABLE new_system_table SET SCHEMA pg_catalog;
! -- XXX: it's currently impossible to move relations out of pg_catalog
! ALTER TABLE new_system_table SET SCHEMA public;
! ERROR:  cannot remove dependency on schema pg_catalog because it is a system object
! -- move back, will currently error out, already there
! ALTER TABLE new_system_table SET SCHEMA pg_catalog;
! ERROR:  table new_system_table is already in schema "pg_catalog"
! ALTER TABLE new_system_table RENAME TO old_system_table;
! CREATE INDEX old_system_table__othercol ON old_system_table (othercol);
! INSERT INTO old_system_table(othercol) VALUES ('somedata'), ('otherdata');
! UPDATE old_system_table SET id = -id;
! DELETE FROM old_system_table WHERE othercol = 'somedata';
! TRUNCATE old_system_table;
! ALTER TABLE old_system_table DROP CONSTRAINT new_system_table_pkey;
! ALTER TABLE old_system_table DROP COLUMN othercol;
! DROP TABLE old_system_table;
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/sequence_1.out	2014-01-02 13:19:14.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/sequence.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,388 ****
! ---
! --- test creation of SERIAL column
! ---
! CREATE TABLE serialTest (f1 text, f2 serial);
! INSERT INTO serialTest VALUES ('foo');
! INSERT INTO serialTest VALUES ('bar');
! INSERT INTO serialTest VALUES ('force', 100);
! INSERT INTO serialTest VALUES ('wrong', NULL);
! ERROR:  null value in column "f2" violates not-null constraint
! DETAIL:  Failing row contains (wrong, null).
! SELECT * FROM serialTest;
!   f1   | f2  
! -------+-----
!  foo   |   1
!  bar   |   2
!  force | 100
! (3 rows)
! 
! -- test smallserial / bigserial
! CREATE TABLE serialTest2 (f1 text, f2 serial, f3 smallserial, f4 serial2,
!   f5 bigserial, f6 serial8);
! INSERT INTO serialTest2 (f1)
!   VALUES ('test_defaults');
! INSERT INTO serialTest2 (f1, f2, f3, f4, f5, f6)
!   VALUES ('test_max_vals', 2147483647, 32767, 32767, 9223372036854775807,
!           9223372036854775807),
!          ('test_min_vals', -2147483648, -32768, -32768, -9223372036854775808,
!           -9223372036854775808);
! -- All these INSERTs should fail:
! INSERT INTO serialTest2 (f1, f3)
!   VALUES ('bogus', -32769);
! ERROR:  smallint out of range
! INSERT INTO serialTest2 (f1, f4)
!   VALUES ('bogus', -32769);
! ERROR:  smallint out of range
! INSERT INTO serialTest2 (f1, f3)
!   VALUES ('bogus', 32768);
! ERROR:  smallint out of range
! INSERT INTO serialTest2 (f1, f4)
!   VALUES ('bogus', 32768);
! ERROR:  smallint out of range
! INSERT INTO serialTest2 (f1, f5)
!   VALUES ('bogus', -9223372036854775809);
! ERROR:  bigint out of range
! INSERT INTO serialTest2 (f1, f6)
!   VALUES ('bogus', -9223372036854775809);
! ERROR:  bigint out of range
! INSERT INTO serialTest2 (f1, f5)
!   VALUES ('bogus', 9223372036854775808);
! ERROR:  bigint out of range
! INSERT INTO serialTest2 (f1, f6)
!   VALUES ('bogus', 9223372036854775808);
! ERROR:  bigint out of range
! SELECT * FROM serialTest2 ORDER BY f2 ASC;
!       f1       |     f2      |   f3   |   f4   |          f5          |          f6          
! ---------------+-------------+--------+--------+----------------------+----------------------
!  test_min_vals | -2147483648 | -32768 | -32768 | -9223372036854775808 | -9223372036854775808
!  test_defaults |           1 |      1 |      1 |                    1 |                    1
!  test_max_vals |  2147483647 |  32767 |  32767 |  9223372036854775807 |  9223372036854775807
! (3 rows)
! 
! SELECT nextval('serialTest2_f2_seq');
!  nextval 
! ---------
!        2
! (1 row)
! 
! SELECT nextval('serialTest2_f3_seq');
!  nextval 
! ---------
!        2
! (1 row)
! 
! SELECT nextval('serialTest2_f4_seq');
!  nextval 
! ---------
!        2
! (1 row)
! 
! SELECT nextval('serialTest2_f5_seq');
!  nextval 
! ---------
!        2
! (1 row)
! 
! SELECT nextval('serialTest2_f6_seq');
!  nextval 
! ---------
!        2
! (1 row)
! 
! -- basic sequence operations using both text and oid references
! CREATE SEQUENCE sequence_test;
! SELECT nextval('sequence_test'::text);
!  nextval 
! ---------
!        1
! (1 row)
! 
! SELECT nextval('sequence_test'::regclass);
!  nextval 
! ---------
!        2
! (1 row)
! 
! SELECT currval('sequence_test'::text);
!  currval 
! ---------
!        2
! (1 row)
! 
! SELECT currval('sequence_test'::regclass);
!  currval 
! ---------
!        2
! (1 row)
! 
! SELECT setval('sequence_test'::text, 32);
!  setval 
! --------
!      32
! (1 row)
! 
! SELECT nextval('sequence_test'::regclass);
!  nextval 
! ---------
!       33
! (1 row)
! 
! SELECT setval('sequence_test'::text, 99, false);
!  setval 
! --------
!      99
! (1 row)
! 
! SELECT nextval('sequence_test'::regclass);
!  nextval 
! ---------
!       99
! (1 row)
! 
! SELECT setval('sequence_test'::regclass, 32);
!  setval 
! --------
!      32
! (1 row)
! 
! SELECT nextval('sequence_test'::text);
!  nextval 
! ---------
!       33
! (1 row)
! 
! SELECT setval('sequence_test'::regclass, 99, false);
!  setval 
! --------
!      99
! (1 row)
! 
! SELECT nextval('sequence_test'::text);
!  nextval 
! ---------
!       99
! (1 row)
! 
! DROP SEQUENCE sequence_test;
! -- renaming sequences
! CREATE SEQUENCE foo_seq;
! ALTER TABLE foo_seq RENAME TO foo_seq_new;
! SELECT * FROM foo_seq_new;
!  sequence_name | last_value | start_value | increment_by |      max_value      | min_value | cache_value | log_cnt | is_cycled | is_called 
! ---------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+-----------
!  foo_seq       |          1 |           1 |            1 | 9223372036854775807 |         1 |           1 |       0 | f         | f
! (1 row)
! 
! SELECT nextval('foo_seq_new');
!  nextval 
! ---------
!        1
! (1 row)
! 
! SELECT nextval('foo_seq_new');
!  nextval 
! ---------
!        2
! (1 row)
! 
! SELECT * FROM foo_seq_new;
!  sequence_name | last_value | start_value | increment_by |      max_value      | min_value | cache_value | log_cnt | is_cycled | is_called 
! ---------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+-----------
!  foo_seq       |          2 |           1 |            1 | 9223372036854775807 |         1 |           1 |      32 | f         | t
! (1 row)
! 
! DROP SEQUENCE foo_seq_new;
! -- renaming serial sequences
! ALTER TABLE serialtest_f2_seq RENAME TO serialtest_f2_foo;
! INSERT INTO serialTest VALUES ('more');
! SELECT * FROM serialTest;
!   f1   | f2  
! -------+-----
!  foo   |   1
!  bar   |   2
!  force | 100
!  more  |   3
! (4 rows)
! 
! --
! -- Check dependencies of serial and ordinary sequences
! --
! CREATE TEMP SEQUENCE myseq2;
! CREATE TEMP SEQUENCE myseq3;
! CREATE TEMP TABLE t1 (
!   f1 serial,
!   f2 int DEFAULT nextval('myseq2'),
!   f3 int DEFAULT nextval('myseq3'::text)
! );
! -- Both drops should fail, but with different error messages:
! DROP SEQUENCE t1_f1_seq;
! ERROR:  cannot drop sequence t1_f1_seq because other objects depend on it
! DETAIL:  default for table t1 column f1 depends on sequence t1_f1_seq
! HINT:  Use DROP ... CASCADE to drop the dependent objects too.
! DROP SEQUENCE myseq2;
! ERROR:  cannot drop sequence myseq2 because other objects depend on it
! DETAIL:  default for table t1 column f2 depends on sequence myseq2
! HINT:  Use DROP ... CASCADE to drop the dependent objects too.
! -- This however will work:
! DROP SEQUENCE myseq3;
! DROP TABLE t1;
! -- Fails because no longer existent:
! DROP SEQUENCE t1_f1_seq;
! ERROR:  sequence "t1_f1_seq" does not exist
! -- Now OK:
! DROP SEQUENCE myseq2;
! --
! -- Alter sequence
! --
! ALTER SEQUENCE IF EXISTS sequence_test2 RESTART WITH 24
! 	 INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE;
! NOTICE:  relation "sequence_test2" does not exist, skipping
! CREATE SEQUENCE sequence_test2 START WITH 32;
! SELECT nextval('sequence_test2');
!  nextval 
! ---------
!       32
! (1 row)
! 
! ALTER SEQUENCE sequence_test2 RESTART WITH 24
! 	 INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE;
! SELECT nextval('sequence_test2');
!  nextval 
! ---------
!       24
! (1 row)
! 
! SELECT nextval('sequence_test2');
!  nextval 
! ---------
!       28
! (1 row)
! 
! SELECT nextval('sequence_test2');
!  nextval 
! ---------
!       32
! (1 row)
! 
! SELECT nextval('sequence_test2');
!  nextval 
! ---------
!       36
! (1 row)
! 
! SELECT nextval('sequence_test2');
!  nextval 
! ---------
!        5
! (1 row)
! 
! ALTER SEQUENCE sequence_test2 RESTART;
! SELECT nextval('sequence_test2');
!  nextval 
! ---------
!       32
! (1 row)
! 
! SELECT nextval('sequence_test2');
!  nextval 
! ---------
!       36
! (1 row)
! 
! SELECT nextval('sequence_test2');
!  nextval 
! ---------
!        5
! (1 row)
! 
! -- Information schema
! SELECT * FROM information_schema.sequences WHERE sequence_name IN
!   ('sequence_test2', 'serialtest2_f2_seq', 'serialtest2_f3_seq',
!    'serialtest2_f4_seq', 'serialtest2_f5_seq', 'serialtest2_f6_seq')
!   ORDER BY sequence_name ASC;
!  sequence_catalog | sequence_schema |   sequence_name    | data_type | numeric_precision | numeric_precision_radix | numeric_scale | start_value | minimum_value |    maximum_value    | increment | cycle_option 
! ------------------+-----------------+--------------------+-----------+-------------------+-------------------------+---------------+-------------+---------------+---------------------+-----------+--------------
!  regression       | public          | sequence_test2     | bigint    |                64 |                       2 |             0 | 32          | 5             | 36                  | 4         | YES
!  regression       | public          | serialtest2_f2_seq | bigint    |                64 |                       2 |             0 | 1           | 1             | 9223372036854775807 | 1         | NO
!  regression       | public          | serialtest2_f3_seq | bigint    |                64 |                       2 |             0 | 1           | 1             | 9223372036854775807 | 1         | NO
!  regression       | public          | serialtest2_f4_seq | bigint    |                64 |                       2 |             0 | 1           | 1             | 9223372036854775807 | 1         | NO
!  regression       | public          | serialtest2_f5_seq | bigint    |                64 |                       2 |             0 | 1           | 1             | 9223372036854775807 | 1         | NO
!  regression       | public          | serialtest2_f6_seq | bigint    |                64 |                       2 |             0 | 1           | 1             | 9223372036854775807 | 1         | NO
! (6 rows)
! 
! -- Test comments
! COMMENT ON SEQUENCE asdf IS 'won''t work';
! ERROR:  relation "asdf" does not exist
! COMMENT ON SEQUENCE sequence_test2 IS 'will work';
! COMMENT ON SEQUENCE sequence_test2 IS NULL;
! -- Test lastval()
! CREATE SEQUENCE seq;
! SELECT nextval('seq');
!  nextval 
! ---------
!        1
! (1 row)
! 
! SELECT lastval();
!  lastval 
! ---------
!        1
! (1 row)
! 
! SELECT setval('seq', 99);
!  setval 
! --------
!      99
! (1 row)
! 
! SELECT lastval();
!  lastval 
! ---------
!       99
! (1 row)
! 
! CREATE SEQUENCE seq2;
! SELECT nextval('seq2');
!  nextval 
! ---------
!        1
! (1 row)
! 
! SELECT lastval();
!  lastval 
! ---------
!        1
! (1 row)
! 
! DROP SEQUENCE seq2;
! -- should fail
! SELECT lastval();
! ERROR:  lastval is not yet defined in this session
! CREATE USER seq_user;
! BEGIN;
! SET LOCAL SESSION AUTHORIZATION seq_user;
! CREATE SEQUENCE seq3;
! SELECT nextval('seq3');
!  nextval 
! ---------
!        1
! (1 row)
! 
! REVOKE ALL ON seq3 FROM seq_user;
! SELECT lastval();
! ERROR:  permission denied for sequence seq3
! ROLLBACK;
! -- Sequences should get wiped out as well:
! DROP TABLE serialTest, serialTest2;
! -- Make sure sequences are gone:
! SELECT * FROM information_schema.sequences WHERE sequence_name IN
!   ('sequence_test2', 'serialtest2_f2_seq', 'serialtest2_f3_seq',
!    'serialtest2_f4_seq', 'serialtest2_f5_seq', 'serialtest2_f6_seq')
!   ORDER BY sequence_name ASC;
!  sequence_catalog | sequence_schema | sequence_name  | data_type | numeric_precision | numeric_precision_radix | numeric_scale | start_value | minimum_value | maximum_value | increment | cycle_option 
! ------------------+-----------------+----------------+-----------+-------------------+-------------------------+---------------+-------------+---------------+---------------+-----------+--------------
!  regression       | public          | sequence_test2 | bigint    |                64 |                       2 |             0 | 32          | 5             | 36            | 4         | YES
! (1 row)
! 
! DROP USER seq_user;
! DROP SEQUENCE seq;
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/polymorphism.out	2014-01-02 13:19:06.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/polymorphism.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,1391 ****
! -- Currently this tests polymorphic aggregates and indirectly does some
! -- testing of polymorphic SQL functions.  It ought to be extended.
! -- Tests for other features related to function-calling have snuck in, too.
! -- Legend:
! -----------
! -- A = type is ANY
! -- P = type is polymorphic
! -- N = type is non-polymorphic
! -- B = aggregate base type
! -- S = aggregate state type
! -- R = aggregate return type
! -- 1 = arg1 of a function
! -- 2 = arg2 of a function
! -- ag = aggregate
! -- tf = trans (state) function
! -- ff = final function
! -- rt = return type of a function
! -- -> = implies
! -- => = allowed
! -- !> = not allowed
! -- E  = exists
! -- NE = not-exists
! --
! -- Possible states:
! -- ----------------
! -- B = (A || P || N)
! --   when (B = A) -> (tf2 = NE)
! -- S = (P || N)
! -- ff = (E || NE)
! -- tf1 = (P || N)
! -- tf2 = (NE || P || N)
! -- R = (P || N)
! -- create functions for use as tf and ff with the needed combinations of
! -- argument polymorphism, but within the constraints of valid aggregate
! -- functions, i.e. tf arg1 and tf return type must match
! -- polymorphic single arg transfn
! CREATE FUNCTION stfp(anyarray) RETURNS anyarray AS
! 'select $1' LANGUAGE SQL;
! -- non-polymorphic single arg transfn
! CREATE FUNCTION stfnp(int[]) RETURNS int[] AS
! 'select $1' LANGUAGE SQL;
! -- dual polymorphic transfn
! CREATE FUNCTION tfp(anyarray,anyelement) RETURNS anyarray AS
! 'select $1 || $2' LANGUAGE SQL;
! -- dual non-polymorphic transfn
! CREATE FUNCTION tfnp(int[],int) RETURNS int[] AS
! 'select $1 || $2' LANGUAGE SQL;
! -- arg1 only polymorphic transfn
! CREATE FUNCTION tf1p(anyarray,int) RETURNS anyarray AS
! 'select $1' LANGUAGE SQL;
! -- arg2 only polymorphic transfn
! CREATE FUNCTION tf2p(int[],anyelement) RETURNS int[] AS
! 'select $1' LANGUAGE SQL;
! -- multi-arg polymorphic
! CREATE FUNCTION sum3(anyelement,anyelement,anyelement) returns anyelement AS
! 'select $1+$2+$3' language sql strict;
! -- finalfn polymorphic
! CREATE FUNCTION ffp(anyarray) RETURNS anyarray AS
! 'select $1' LANGUAGE SQL;
! -- finalfn non-polymorphic
! CREATE FUNCTION ffnp(int[]) returns int[] as
! 'select $1' LANGUAGE SQL;
! -- Try to cover all the possible states:
! --
! -- Note: in Cases 1 & 2, we are trying to return P. Therefore, if the transfn
! -- is stfnp, tfnp, or tf2p, we must use ffp as finalfn, because stfnp, tfnp,
! -- and tf2p do not return P. Conversely, in Cases 3 & 4, we are trying to
! -- return N. Therefore, if the transfn is stfp, tfp, or tf1p, we must use ffnp
! -- as finalfn, because stfp, tfp, and tf1p do not return N.
! --
! --     Case1 (R = P) && (B = A)
! --     ------------------------
! --     S    tf1
! --     -------
! --     N    N
! -- should CREATE
! CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
!   FINALFUNC = ffp, INITCOND = '{}');
! --     P    N
! -- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
! CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
!   FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --     N    P
! -- should CREATE
! CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
!   FINALFUNC = ffp, INITCOND = '{}');
! CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
!   INITCOND = '{}');
! --     P    P
! -- should ERROR: we have no way to resolve S
! CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
!   FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
!   INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    Case2 (R = P) && ((B = P) || (B = N))
! --    -------------------------------------
! --    S    tf1      B    tf2
! --    -----------------------
! --    N    N        N    N
! -- should CREATE
! CREATE AGGREGATE myaggp05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[],
!   FINALFUNC = ffp, INITCOND = '{}');
! --    N    N        N    P
! -- should CREATE
! CREATE AGGREGATE myaggp06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[],
!   FINALFUNC = ffp, INITCOND = '{}');
! --    N    N        P    N
! -- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int)
! CREATE AGGREGATE myaggp07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[],
!   FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  function tfnp(integer[], anyelement) does not exist
! --    N    N        P    P
! -- should CREATE
! CREATE AGGREGATE myaggp08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[],
!   FINALFUNC = ffp, INITCOND = '{}');
! --    N    P        N    N
! -- should CREATE
! CREATE AGGREGATE myaggp09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[],
!   FINALFUNC = ffp, INITCOND = '{}');
! CREATE AGGREGATE myaggp09b(BASETYPE = int, SFUNC = tf1p, STYPE = int[],
!   INITCOND = '{}');
! --    N    P        N    P
! -- should CREATE
! CREATE AGGREGATE myaggp10a(BASETYPE = int, SFUNC = tfp, STYPE = int[],
!   FINALFUNC = ffp, INITCOND = '{}');
! CREATE AGGREGATE myaggp10b(BASETYPE = int, SFUNC = tfp, STYPE = int[],
!   INITCOND = '{}');
! --    N    P        P    N
! -- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int)
! CREATE AGGREGATE myaggp11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[],
!   FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  function tf1p(integer[], anyelement) does not exist
! CREATE AGGREGATE myaggp11b(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[],
!   INITCOND = '{}');
! ERROR:  function tf1p(integer[], anyelement) does not exist
! --    N    P        P    P
! -- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement)
! CREATE AGGREGATE myaggp12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[],
!   FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  function tfp(integer[], anyelement) does not exist
! CREATE AGGREGATE myaggp12b(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[],
!   INITCOND = '{}');
! ERROR:  function tfp(integer[], anyelement) does not exist
! --    P    N        N    N
! -- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int)
! CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
!   FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    P    N        N    P
! -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
! CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
!   FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    P    N        P    N
! -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
! CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
!   STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  function tfnp(anyarray, anyelement) does not exist
! --    P    N        P    P
! -- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement)
! CREATE AGGREGATE myaggp16a(BASETYPE = anyelement, SFUNC = tf2p,
!   STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  function tf2p(anyarray, anyelement) does not exist
! --    P    P        N    N
! -- should ERROR: we have no way to resolve S
! CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
!   FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
!   INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    P    P        N    P
! -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
! CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
!   FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
!   INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    P    P        P    N
! -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
! CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
!   STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
! ERROR:  function tf1p(anyarray, anyelement) does not exist
! CREATE AGGREGATE myaggp19b(BASETYPE = anyelement, SFUNC = tf1p,
!   STYPE = anyarray, INITCOND = '{}');
! ERROR:  function tf1p(anyarray, anyelement) does not exist
! --    P    P        P    P
! -- should CREATE
! CREATE AGGREGATE myaggp20a(BASETYPE = anyelement, SFUNC = tfp,
!   STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}');
! CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp,
!   STYPE = anyarray, INITCOND = '{}');
! --     Case3 (R = N) && (B = A)
! --     ------------------------
! --     S    tf1
! --     -------
! --     N    N
! -- should CREATE
! CREATE AGGREGATE myaggn01a(*) (SFUNC = stfnp, STYPE = int4[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
!   INITCOND = '{}');
! --     P    N
! -- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
! CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
!   FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
!   INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --     N    P
! -- should CREATE
! CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! --     P    P
! -- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
! CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
!   FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    Case4 (R = N) && ((B = P) || (B = N))
! --    -------------------------------------
! --    S    tf1      B    tf2
! --    -----------------------
! --    N    N        N    N
! -- should CREATE
! CREATE AGGREGATE myaggn05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! CREATE AGGREGATE myaggn05b(BASETYPE = int, SFUNC = tfnp, STYPE = int[],
!   INITCOND = '{}');
! --    N    N        N    P
! -- should CREATE
! CREATE AGGREGATE myaggn06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! CREATE AGGREGATE myaggn06b(BASETYPE = int, SFUNC = tf2p, STYPE = int[],
!   INITCOND = '{}');
! --    N    N        P    N
! -- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int)
! CREATE AGGREGATE myaggn07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  function tfnp(integer[], anyelement) does not exist
! CREATE AGGREGATE myaggn07b(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[],
!   INITCOND = '{}');
! ERROR:  function tfnp(integer[], anyelement) does not exist
! --    N    N        P    P
! -- should CREATE
! CREATE AGGREGATE myaggn08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! CREATE AGGREGATE myaggn08b(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[],
!   INITCOND = '{}');
! --    N    P        N    N
! -- should CREATE
! CREATE AGGREGATE myaggn09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! --    N    P        N    P
! -- should CREATE
! CREATE AGGREGATE myaggn10a(BASETYPE = int, SFUNC = tfp, STYPE = int[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! --    N    P        P    N
! -- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int)
! CREATE AGGREGATE myaggn11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  function tf1p(integer[], anyelement) does not exist
! --    N    P        P    P
! -- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement)
! CREATE AGGREGATE myaggn12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[],
!   FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  function tfp(integer[], anyelement) does not exist
! --    P    N        N    N
! -- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int)
! CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
!   FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
!   INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    P    N        N    P
! -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
! CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
!   FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
!   INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    P    N        P    N
! -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
! CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
!   STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  function tfnp(anyarray, anyelement) does not exist
! CREATE AGGREGATE myaggn15b(BASETYPE = anyelement, SFUNC = tfnp,
!   STYPE = anyarray, INITCOND = '{}');
! ERROR:  function tfnp(anyarray, anyelement) does not exist
! --    P    N        P    P
! -- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement)
! CREATE AGGREGATE myaggn16a(BASETYPE = anyelement, SFUNC = tf2p,
!   STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  function tf2p(anyarray, anyelement) does not exist
! CREATE AGGREGATE myaggn16b(BASETYPE = anyelement, SFUNC = tf2p,
!   STYPE = anyarray, INITCOND = '{}');
! ERROR:  function tf2p(anyarray, anyelement) does not exist
! --    P    P        N    N
! -- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
! CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
!   FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    P    P        N    P
! -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
! CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
!   FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  cannot determine transition data type
! DETAIL:  An aggregate using a polymorphic transition type must have at least one polymorphic argument.
! --    P    P        P    N
! -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
! CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
!   STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  function tf1p(anyarray, anyelement) does not exist
! --    P    P        P    P
! -- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
! CREATE AGGREGATE myaggn20a(BASETYPE = anyelement, SFUNC = tfp,
!   STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
! ERROR:  function ffnp(anyarray) does not exist
! -- multi-arg polymorphic
! CREATE AGGREGATE mysum2(anyelement,anyelement) (SFUNC = sum3,
!   STYPE = anyelement, INITCOND = '0');
! -- create test data for polymorphic aggregates
! create temp table t(f1 int, f2 int[], f3 text);
! insert into t values(1,array[1],'a');
! insert into t values(1,array[11],'b');
! insert into t values(1,array[111],'c');
! insert into t values(2,array[2],'a');
! insert into t values(2,array[22],'b');
! insert into t values(2,array[222],'c');
! insert into t values(3,array[3],'a');
! insert into t values(3,array[3],'b');
! -- test the successfully created polymorphic aggregates
! select f3, myaggp01a(*) from t group by f3 order by f3;
!  f3 | myaggp01a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggp03a(*) from t group by f3 order by f3;
!  f3 | myaggp03a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggp03b(*) from t group by f3 order by f3;
!  f3 | myaggp03b 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggp05a(f1) from t group by f3 order by f3;
!  f3 | myaggp05a 
! ----+-----------
!  a  | {1,2,3}
!  b  | {1,2,3}
!  c  | {1,2}
! (3 rows)
! 
! select f3, myaggp06a(f1) from t group by f3 order by f3;
!  f3 | myaggp06a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggp08a(f1) from t group by f3 order by f3;
!  f3 | myaggp08a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggp09a(f1) from t group by f3 order by f3;
!  f3 | myaggp09a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggp09b(f1) from t group by f3 order by f3;
!  f3 | myaggp09b 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggp10a(f1) from t group by f3 order by f3;
!  f3 | myaggp10a 
! ----+-----------
!  a  | {1,2,3}
!  b  | {1,2,3}
!  c  | {1,2}
! (3 rows)
! 
! select f3, myaggp10b(f1) from t group by f3 order by f3;
!  f3 | myaggp10b 
! ----+-----------
!  a  | {1,2,3}
!  b  | {1,2,3}
!  c  | {1,2}
! (3 rows)
! 
! select f3, myaggp20a(f1) from t group by f3 order by f3;
!  f3 | myaggp20a 
! ----+-----------
!  a  | {1,2,3}
!  b  | {1,2,3}
!  c  | {1,2}
! (3 rows)
! 
! select f3, myaggp20b(f1) from t group by f3 order by f3;
!  f3 | myaggp20b 
! ----+-----------
!  a  | {1,2,3}
!  b  | {1,2,3}
!  c  | {1,2}
! (3 rows)
! 
! select f3, myaggn01a(*) from t group by f3 order by f3;
!  f3 | myaggn01a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggn01b(*) from t group by f3 order by f3;
!  f3 | myaggn01b 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggn03a(*) from t group by f3 order by f3;
!  f3 | myaggn03a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggn05a(f1) from t group by f3 order by f3;
!  f3 | myaggn05a 
! ----+-----------
!  a  | {1,2,3}
!  b  | {1,2,3}
!  c  | {1,2}
! (3 rows)
! 
! select f3, myaggn05b(f1) from t group by f3 order by f3;
!  f3 | myaggn05b 
! ----+-----------
!  a  | {1,2,3}
!  b  | {1,2,3}
!  c  | {1,2}
! (3 rows)
! 
! select f3, myaggn06a(f1) from t group by f3 order by f3;
!  f3 | myaggn06a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggn06b(f1) from t group by f3 order by f3;
!  f3 | myaggn06b 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggn08a(f1) from t group by f3 order by f3;
!  f3 | myaggn08a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggn08b(f1) from t group by f3 order by f3;
!  f3 | myaggn08b 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggn09a(f1) from t group by f3 order by f3;
!  f3 | myaggn09a 
! ----+-----------
!  a  | {}
!  b  | {}
!  c  | {}
! (3 rows)
! 
! select f3, myaggn10a(f1) from t group by f3 order by f3;
!  f3 | myaggn10a 
! ----+-----------
!  a  | {1,2,3}
!  b  | {1,2,3}
!  c  | {1,2}
! (3 rows)
! 
! select mysum2(f1, f1 + 1) from t;
!  mysum2 
! --------
!      38
! (1 row)
! 
! -- test inlining of polymorphic SQL functions
! create function bleat(int) returns int as $$
! begin
!   raise notice 'bleat %', $1;
!   return $1;
! end$$ language plpgsql;
! create function sql_if(bool, anyelement, anyelement) returns anyelement as $$
! select case when $1 then $2 else $3 end $$ language sql;
! -- Note this would fail with integer overflow, never mind wrong bleat() output,
! -- if the CASE expression were not successfully inlined
! select f1, sql_if(f1 > 0, bleat(f1), bleat(f1 + 1)) from int4_tbl;
! NOTICE:  bleat 1
! NOTICE:  bleat 123456
! NOTICE:  bleat -123455
! NOTICE:  bleat 2147483647
! NOTICE:  bleat -2147483646
!      f1      |   sql_if    
! -------------+-------------
!            0 |           1
!       123456 |      123456
!      -123456 |     -123455
!   2147483647 |  2147483647
!  -2147483647 | -2147483646
! (5 rows)
! 
! select q2, sql_if(q2 > 0, q2, q2 + 1) from int8_tbl;
!         q2         |      sql_if       
! -------------------+-------------------
!                456 |               456
!   4567890123456789 |  4567890123456789
!                123 |               123
!   4567890123456789 |  4567890123456789
!  -4567890123456789 | -4567890123456788
! (5 rows)
! 
! -- another sort of polymorphic aggregate
! CREATE AGGREGATE array_cat_accum (anyarray)
! (
!     sfunc = array_cat,
!     stype = anyarray,
!     initcond = '{}'
! );
! SELECT array_cat_accum(i)
! FROM (VALUES (ARRAY[1,2]), (ARRAY[3,4])) as t(i);
!  array_cat_accum 
! -----------------
!  {1,2,3,4}
! (1 row)
! 
! SELECT array_cat_accum(i)
! FROM (VALUES (ARRAY[row(1,2),row(3,4)]), (ARRAY[row(5,6),row(7,8)])) as t(i);
!           array_cat_accum          
! -----------------------------------
!  {"(1,2)","(3,4)","(5,6)","(7,8)"}
! (1 row)
! 
! -- another kind of polymorphic aggregate
! create function add_group(grp anyarray, ad anyelement, size integer)
!   returns anyarray
!   as $$
! begin
!   if grp is null then
!     return array[ad];
!   end if;
!   if array_upper(grp, 1) < size then
!     return grp || ad;
!   end if;
!   return grp;
! end;
! $$
!   language plpgsql immutable;
! create aggregate build_group(anyelement, integer) (
!   SFUNC = add_group,
!   STYPE = anyarray
! );
! select build_group(q1,3) from int8_tbl;
!         build_group         
! ----------------------------
!  {123,123,4567890123456789}
! (1 row)
! 
! -- this should fail because stype isn't compatible with arg
! create aggregate build_group(int8, integer) (
!   SFUNC = add_group,
!   STYPE = int2[]
! );
! ERROR:  function add_group(smallint[], bigint, integer) does not exist
! -- but we can make a non-poly agg from a poly sfunc if types are OK
! create aggregate build_group(int8, integer) (
!   SFUNC = add_group,
!   STYPE = int8[]
! );
! -- check that we can apply functions taking ANYARRAY to pg_stats
! select distinct array_ndims(histogram_bounds) from pg_stats
! where histogram_bounds is not null;
!  array_ndims 
! -------------
!            1
! (1 row)
! 
! -- such functions must protect themselves if varying element type isn't OK
! -- (WHERE clause here is to avoid possibly getting a collation error instead)
! select max(histogram_bounds) from pg_stats where tablename = 'pg_am';
! ERROR:  cannot compare arrays of different element types
! -- test variadic polymorphic functions
! create function myleast(variadic anyarray) returns anyelement as $$
!   select min($1[i]) from generate_subscripts($1,1) g(i)
! $$ language sql immutable strict;
! select myleast(10, 1, 20, 33);
!  myleast 
! ---------
!        1
! (1 row)
! 
! select myleast(1.1, 0.22, 0.55);
!  myleast 
! ---------
!     0.22
! (1 row)
! 
! select myleast('z'::text);
!  myleast 
! ---------
!  z
! (1 row)
! 
! select myleast(); -- fail
! ERROR:  function myleast() does not exist
! LINE 1: select myleast();
!                ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! -- test with variadic call parameter
! select myleast(variadic array[1,2,3,4,-1]);
!  myleast 
! ---------
!       -1
! (1 row)
! 
! select myleast(variadic array[1.1, -5.5]);
!  myleast 
! ---------
!     -5.5
! (1 row)
! 
! --test with empty variadic call parameter
! select myleast(variadic array[]::int[]);
!  myleast 
! ---------
!         
! (1 row)
! 
! -- an example with some ordinary arguments too
! create function concat(text, variadic anyarray) returns text as $$
!   select array_to_string($2, $1);
! $$ language sql immutable strict;
! select concat('%', 1, 2, 3, 4, 5);
!   concat   
! -----------
!  1%2%3%4%5
! (1 row)
! 
! select concat('|', 'a'::text, 'b', 'c');
!  concat 
! --------
!  a|b|c
! (1 row)
! 
! select concat('|', variadic array[1,2,33]);
!  concat 
! --------
!  1|2|33
! (1 row)
! 
! select concat('|', variadic array[]::int[]);
!  concat 
! --------
!  
! (1 row)
! 
! drop function concat(text, anyarray);
! -- mix variadic with anyelement
! create function formarray(anyelement, variadic anyarray) returns anyarray as $$
!   select array_prepend($1, $2);
! $$ language sql immutable strict;
! select formarray(1,2,3,4,5);
!   formarray  
! -------------
!  {1,2,3,4,5}
! (1 row)
! 
! select formarray(1.1, variadic array[1.2,55.5]);
!    formarray    
! ----------------
!  {1.1,1.2,55.5}
! (1 row)
! 
! select formarray(1.1, array[1.2,55.5]); -- fail without variadic
! ERROR:  function formarray(numeric, numeric[]) does not exist
! LINE 1: select formarray(1.1, array[1.2,55.5]);
!                ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! select formarray(1, 'x'::text); -- fail, type mismatch
! ERROR:  function formarray(integer, text) does not exist
! LINE 1: select formarray(1, 'x'::text);
!                ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! select formarray(1, variadic array['x'::text]); -- fail, type mismatch
! ERROR:  function formarray(integer, text[]) does not exist
! LINE 1: select formarray(1, variadic array['x'::text]);
!                ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! drop function formarray(anyelement, variadic anyarray);
! -- test pg_typeof() function
! select pg_typeof(null);           -- unknown
!  pg_typeof 
! -----------
!  unknown
! (1 row)
! 
! select pg_typeof(0);              -- integer
!  pg_typeof 
! -----------
!  integer
! (1 row)
! 
! select pg_typeof(0.0);            -- numeric
!  pg_typeof 
! -----------
!  numeric
! (1 row)
! 
! select pg_typeof(1+1 = 2);        -- boolean
!  pg_typeof 
! -----------
!  boolean
! (1 row)
! 
! select pg_typeof('x');            -- unknown
!  pg_typeof 
! -----------
!  unknown
! (1 row)
! 
! select pg_typeof('' || '');       -- text
!  pg_typeof 
! -----------
!  text
! (1 row)
! 
! select pg_typeof(pg_typeof(0));   -- regtype
!  pg_typeof 
! -----------
!  regtype
! (1 row)
! 
! select pg_typeof(array[1.2,55.5]); -- numeric[]
!  pg_typeof 
! -----------
!  numeric[]
! (1 row)
! 
! select pg_typeof(myleast(10, 1, 20, 33));  -- polymorphic input
!  pg_typeof 
! -----------
!  integer
! (1 row)
! 
! -- test functions with default parameters
! -- test basic functionality
! create function dfunc(a int = 1, int = 2) returns int as $$
!   select $1 + $2;
! $$ language sql;
! select dfunc();
!  dfunc 
! -------
!      3
! (1 row)
! 
! select dfunc(10);
!  dfunc 
! -------
!     12
! (1 row)
! 
! select dfunc(10, 20);
!  dfunc 
! -------
!     30
! (1 row)
! 
! select dfunc(10, 20, 30);  -- fail
! ERROR:  function dfunc(integer, integer, integer) does not exist
! LINE 1: select dfunc(10, 20, 30);
!                ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! drop function dfunc();  -- fail
! ERROR:  function dfunc() does not exist
! drop function dfunc(int);  -- fail
! ERROR:  function dfunc(integer) does not exist
! drop function dfunc(int, int);  -- ok
! -- fail: defaults must be at end of argument list
! create function dfunc(a int = 1, b int) returns int as $$
!   select $1 + $2;
! $$ language sql;
! ERROR:  input parameters after one with a default value must also have defaults
! -- however, this should work:
! create function dfunc(a int = 1, out sum int, b int = 2) as $$
!   select $1 + $2;
! $$ language sql;
! select dfunc();
!  dfunc 
! -------
!      3
! (1 row)
! 
! -- verify it lists properly
! \df dfunc
!                                            List of functions
!  Schema | Name  | Result data type |                    Argument data types                    |  Type  
! --------+-------+------------------+-----------------------------------------------------------+--------
!  public | dfunc | integer          | a integer DEFAULT 1, OUT sum integer, b integer DEFAULT 2 | normal
! (1 row)
! 
! drop function dfunc(int, int);
! -- check implicit coercion
! create function dfunc(a int DEFAULT 1.0, int DEFAULT '-1') returns int as $$
!   select $1 + $2;
! $$ language sql;
! select dfunc();
!  dfunc 
! -------
!      0
! (1 row)
! 
! create function dfunc(a text DEFAULT 'Hello', b text DEFAULT 'World') returns text as $$
!   select $1 || ', ' || $2;
! $$ language sql;
! select dfunc();  -- fail: which dfunc should be called? int or text
! ERROR:  function dfunc() is not unique
! LINE 1: select dfunc();
!                ^
! HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
! select dfunc('Hi');  -- ok
!    dfunc   
! -----------
!  Hi, World
! (1 row)
! 
! select dfunc('Hi', 'City');  -- ok
!   dfunc   
! ----------
!  Hi, City
! (1 row)
! 
! select dfunc(0);  -- ok
!  dfunc 
! -------
!     -1
! (1 row)
! 
! select dfunc(10, 20);  -- ok
!  dfunc 
! -------
!     30
! (1 row)
! 
! drop function dfunc(int, int);
! drop function dfunc(text, text);
! create function dfunc(int = 1, int = 2) returns int as $$
!   select 2;
! $$ language sql;
! create function dfunc(int = 1, int = 2, int = 3, int = 4) returns int as $$
!   select 4;
! $$ language sql;
! -- Now, dfunc(nargs = 2) and dfunc(nargs = 4) are ambiguous when called
! -- with 0 to 2 arguments.
! select dfunc();  -- fail
! ERROR:  function dfunc() is not unique
! LINE 1: select dfunc();
!                ^
! HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
! select dfunc(1);  -- fail
! ERROR:  function dfunc(integer) is not unique
! LINE 1: select dfunc(1);
!                ^
! HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
! select dfunc(1, 2);  -- fail
! ERROR:  function dfunc(integer, integer) is not unique
! LINE 1: select dfunc(1, 2);
!                ^
! HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
! select dfunc(1, 2, 3);  -- ok
!  dfunc 
! -------
!      4
! (1 row)
! 
! select dfunc(1, 2, 3, 4);  -- ok
!  dfunc 
! -------
!      4
! (1 row)
! 
! drop function dfunc(int, int);
! drop function dfunc(int, int, int, int);
! -- default values are not allowed for output parameters
! create function dfunc(out int = 20) returns int as $$
!   select 1;
! $$ language sql;
! ERROR:  only input parameters can have default values
! -- polymorphic parameter test
! create function dfunc(anyelement = 'World'::text) returns text as $$
!   select 'Hello, ' || $1::text;
! $$ language sql;
! select dfunc();
!     dfunc     
! --------------
!  Hello, World
! (1 row)
! 
! select dfunc(0);
!   dfunc   
! ----------
!  Hello, 0
! (1 row)
! 
! select dfunc(to_date('20081215','YYYYMMDD'));
!        dfunc       
! -------------------
!  Hello, 12-15-2008
! (1 row)
! 
! select dfunc('City'::text);
!     dfunc    
! -------------
!  Hello, City
! (1 row)
! 
! drop function dfunc(anyelement);
! -- check defaults for variadics
! create function dfunc(a variadic int[]) returns int as
! $$ select array_upper($1, 1) $$ language sql;
! select dfunc();  -- fail
! ERROR:  function dfunc() does not exist
! LINE 1: select dfunc();
!                ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! select dfunc(10);
!  dfunc 
! -------
!      1
! (1 row)
! 
! select dfunc(10,20);
!  dfunc 
! -------
!      2
! (1 row)
! 
! create or replace function dfunc(a variadic int[] default array[]::int[]) returns int as
! $$ select array_upper($1, 1) $$ language sql;
! select dfunc();  -- now ok
!  dfunc 
! -------
!       
! (1 row)
! 
! select dfunc(10);
!  dfunc 
! -------
!      1
! (1 row)
! 
! select dfunc(10,20);
!  dfunc 
! -------
!      2
! (1 row)
! 
! -- can't remove the default once it exists
! create or replace function dfunc(a variadic int[]) returns int as
! $$ select array_upper($1, 1) $$ language sql;
! ERROR:  cannot remove parameter defaults from existing function
! HINT:  Use DROP FUNCTION dfunc(integer[]) first.
! \df dfunc
!                                       List of functions
!  Schema | Name  | Result data type |               Argument data types               |  Type  
! --------+-------+------------------+-------------------------------------------------+--------
!  public | dfunc | integer          | VARIADIC a integer[] DEFAULT ARRAY[]::integer[] | normal
! (1 row)
! 
! drop function dfunc(a variadic int[]);
! -- Ambiguity should be reported only if there's not a better match available
! create function dfunc(int = 1, int = 2, int = 3) returns int as $$
!   select 3;
! $$ language sql;
! create function dfunc(int = 1, int = 2) returns int as $$
!   select 2;
! $$ language sql;
! create function dfunc(text) returns text as $$
!   select $1;
! $$ language sql;
! -- dfunc(narg=2) and dfunc(narg=3) are ambiguous
! select dfunc(1);  -- fail
! ERROR:  function dfunc(integer) is not unique
! LINE 1: select dfunc(1);
!                ^
! HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
! -- but this works since the ambiguous functions aren't preferred anyway
! select dfunc('Hi');
!  dfunc 
! -------
!  Hi
! (1 row)
! 
! drop function dfunc(int, int, int);
! drop function dfunc(int, int);
! drop function dfunc(text);
! --
! -- Tests for named- and mixed-notation function calling
! --
! create function dfunc(a int, b int, c int = 0, d int = 0)
!   returns table (a int, b int, c int, d int) as $$
!   select $1, $2, $3, $4;
! $$ language sql;
! select (dfunc(10,20,30)).*;
!  a  | b  | c  | d 
! ----+----+----+---
!  10 | 20 | 30 | 0
! (1 row)
! 
! select (dfunc(a := 10, b := 20, c := 30)).*;
!  a  | b  | c  | d 
! ----+----+----+---
!  10 | 20 | 30 | 0
! (1 row)
! 
! select * from dfunc(a := 10, b := 20);
!  a  | b  | c | d 
! ----+----+---+---
!  10 | 20 | 0 | 0
! (1 row)
! 
! select * from dfunc(b := 10, a := 20);
!  a  | b  | c | d 
! ----+----+---+---
!  20 | 10 | 0 | 0
! (1 row)
! 
! select * from dfunc(0);  -- fail
! ERROR:  function dfunc(integer) does not exist
! LINE 1: select * from dfunc(0);
!                       ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! select * from dfunc(1,2);
!  a | b | c | d 
! ---+---+---+---
!  1 | 2 | 0 | 0
! (1 row)
! 
! select * from dfunc(1,2,c := 3);
!  a | b | c | d 
! ---+---+---+---
!  1 | 2 | 3 | 0
! (1 row)
! 
! select * from dfunc(1,2,d := 3);
!  a | b | c | d 
! ---+---+---+---
!  1 | 2 | 0 | 3
! (1 row)
! 
! select * from dfunc(x := 20, b := 10, x := 30);  -- fail, duplicate name
! ERROR:  argument name "x" used more than once
! LINE 1: select * from dfunc(x := 20, b := 10, x := 30);
!                                               ^
! select * from dfunc(10, b := 20, 30);  -- fail, named args must be last
! ERROR:  positional argument cannot follow named argument
! LINE 1: select * from dfunc(10, b := 20, 30);
!                                          ^
! select * from dfunc(x := 10, b := 20, c := 30);  -- fail, unknown param
! ERROR:  function dfunc(x := integer, b := integer, c := integer) does not exist
! LINE 1: select * from dfunc(x := 10, b := 20, c := 30);
!                       ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! select * from dfunc(10, 10, a := 20);  -- fail, a overlaps positional parameter
! ERROR:  function dfunc(integer, integer, a := integer) does not exist
! LINE 1: select * from dfunc(10, 10, a := 20);
!                       ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! select * from dfunc(1,c := 2,d := 3); -- fail, no value for b
! ERROR:  function dfunc(integer, c := integer, d := integer) does not exist
! LINE 1: select * from dfunc(1,c := 2,d := 3);
!                       ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! drop function dfunc(int, int, int, int);
! -- test with different parameter types
! create function dfunc(a varchar, b numeric, c date = current_date)
!   returns table (a varchar, b numeric, c date) as $$
!   select $1, $2, $3;
! $$ language sql;
! select (dfunc('Hello World', 20, '2009-07-25'::date)).*;
!       a      | b  |     c      
! -------------+----+------------
!  Hello World | 20 | 07-25-2009
! (1 row)
! 
! select * from dfunc('Hello World', 20, '2009-07-25'::date);
!       a      | b  |     c      
! -------------+----+------------
!  Hello World | 20 | 07-25-2009
! (1 row)
! 
! select * from dfunc(c := '2009-07-25'::date, a := 'Hello World', b := 20);
!       a      | b  |     c      
! -------------+----+------------
!  Hello World | 20 | 07-25-2009
! (1 row)
! 
! select * from dfunc('Hello World', b := 20, c := '2009-07-25'::date);
!       a      | b  |     c      
! -------------+----+------------
!  Hello World | 20 | 07-25-2009
! (1 row)
! 
! select * from dfunc('Hello World', c := '2009-07-25'::date, b := 20);
!       a      | b  |     c      
! -------------+----+------------
!  Hello World | 20 | 07-25-2009
! (1 row)
! 
! select * from dfunc('Hello World', c := 20, b := '2009-07-25'::date);  -- fail
! ERROR:  function dfunc(unknown, c := integer, b := date) does not exist
! LINE 1: select * from dfunc('Hello World', c := 20, b := '2009-07-25...
!                       ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! drop function dfunc(varchar, numeric, date);
! -- test out parameters with named params
! create function dfunc(a varchar = 'def a', out _a varchar, c numeric = NULL, out _c numeric)
! returns record as $$
!   select $1, $2;
! $$ language sql;
! select (dfunc()).*;
!   _a   | _c 
! -------+----
!  def a |   
! (1 row)
! 
! select * from dfunc();
!   _a   | _c 
! -------+----
!  def a |   
! (1 row)
! 
! select * from dfunc('Hello', 100);
!   _a   | _c  
! -------+-----
!  Hello | 100
! (1 row)
! 
! select * from dfunc(a := 'Hello', c := 100);
!   _a   | _c  
! -------+-----
!  Hello | 100
! (1 row)
! 
! select * from dfunc(c := 100, a := 'Hello');
!   _a   | _c  
! -------+-----
!  Hello | 100
! (1 row)
! 
! select * from dfunc('Hello');
!   _a   | _c 
! -------+----
!  Hello |   
! (1 row)
! 
! select * from dfunc('Hello', c := 100);
!   _a   | _c  
! -------+-----
!  Hello | 100
! (1 row)
! 
! select * from dfunc(c := 100);
!   _a   | _c  
! -------+-----
!  def a | 100
! (1 row)
! 
! -- fail, can no longer change an input parameter's name
! create or replace function dfunc(a varchar = 'def a', out _a varchar, x numeric = NULL, out _c numeric)
! returns record as $$
!   select $1, $2;
! $$ language sql;
! ERROR:  cannot change name of input parameter "c"
! HINT:  Use DROP FUNCTION dfunc(character varying,numeric) first.
! create or replace function dfunc(a varchar = 'def a', out _a varchar, numeric = NULL, out _c numeric)
! returns record as $$
!   select $1, $2;
! $$ language sql;
! ERROR:  cannot change name of input parameter "c"
! HINT:  Use DROP FUNCTION dfunc(character varying,numeric) first.
! drop function dfunc(varchar, numeric);
! --fail, named parameters are not unique
! create function testfoo(a int, a int) returns int as $$ select 1;$$ language sql;
! ERROR:  parameter name "a" used more than once
! create function testfoo(int, out a int, out a int) returns int as $$ select 1;$$ language sql;
! ERROR:  parameter name "a" used more than once
! create function testfoo(out a int, inout a int) returns int as $$ select 1;$$ language sql;
! ERROR:  parameter name "a" used more than once
! create function testfoo(a int, inout a int) returns int as $$ select 1;$$ language sql;
! ERROR:  parameter name "a" used more than once
! -- valid
! create function testfoo(a int, out a int) returns int as $$ select $1;$$ language sql;
! select testfoo(37);
!  testfoo 
! ---------
!       37
! (1 row)
! 
! drop function testfoo(int);
! create function testfoo(a int) returns table(a int) as $$ select $1;$$ language sql;
! select * from testfoo(37);
!  a  
! ----
!  37
! (1 row)
! 
! drop function testfoo(int);
! -- test polymorphic params and defaults
! create function dfunc(a anyelement, b anyelement = null, flag bool = true)
! returns anyelement as $$
!   select case when $3 then $1 else $2 end;
! $$ language sql;
! select dfunc(1,2);
!  dfunc 
! -------
!      1
! (1 row)
! 
! select dfunc('a'::text, 'b'); -- positional notation with default
!  dfunc 
! -------
!  a
! (1 row)
! 
! select dfunc(a := 1, b := 2);
!  dfunc 
! -------
!      1
! (1 row)
! 
! select dfunc(a := 'a'::text, b := 'b');
!  dfunc 
! -------
!  a
! (1 row)
! 
! select dfunc(a := 'a'::text, b := 'b', flag := false); -- named notation
!  dfunc 
! -------
!  b
! (1 row)
! 
! select dfunc(b := 'b'::text, a := 'a'); -- named notation with default
!  dfunc 
! -------
!  a
! (1 row)
! 
! select dfunc(a := 'a'::text, flag := true); -- named notation with default
!  dfunc 
! -------
!  a
! (1 row)
! 
! select dfunc(a := 'a'::text, flag := false); -- named notation with default
!  dfunc 
! -------
!  
! (1 row)
! 
! select dfunc(b := 'b'::text, a := 'a', flag := true); -- named notation
!  dfunc 
! -------
!  a
! (1 row)
! 
! select dfunc('a'::text, 'b', false); -- full positional notation
!  dfunc 
! -------
!  b
! (1 row)
! 
! select dfunc('a'::text, 'b', flag := false); -- mixed notation
!  dfunc 
! -------
!  b
! (1 row)
! 
! select dfunc('a'::text, 'b', true); -- full positional notation
!  dfunc 
! -------
!  a
! (1 row)
! 
! select dfunc('a'::text, 'b', flag := true); -- mixed notation
!  dfunc 
! -------
!  a
! (1 row)
! 
! -- check reverse-listing of named-arg calls
! CREATE VIEW dfview AS
!    SELECT q1, q2,
!      dfunc(q1,q2, flag := q1>q2) as c3,
!      dfunc(q1, flag := q1<q2, b := q2) as c4
!      FROM int8_tbl;
! select * from dfview;
!         q1        |        q2         |        c3        |        c4         
! ------------------+-------------------+------------------+-------------------
!               123 |               456 |              456 |               123
!               123 |  4567890123456789 | 4567890123456789 |               123
!  4567890123456789 |               123 | 4567890123456789 |               123
!  4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789
!  4567890123456789 | -4567890123456789 | 4567890123456789 | -4567890123456789
! (5 rows)
! 
! \d+ dfview
!                 View "public.dfview"
!  Column |  Type  | Modifiers | Storage | Description 
! --------+--------+-----------+---------+-------------
!  q1     | bigint |           | plain   | 
!  q2     | bigint |           | plain   | 
!  c3     | bigint |           | plain   | 
!  c4     | bigint |           | plain   | 
! View definition:
!  SELECT int8_tbl.q1,
!     int8_tbl.q2,
!     dfunc(int8_tbl.q1, int8_tbl.q2, flag := int8_tbl.q1 > int8_tbl.q2) AS c3,
!     dfunc(int8_tbl.q1, flag := int8_tbl.q1 < int8_tbl.q2, b := int8_tbl.q2) AS c4
!    FROM int8_tbl;
! 
! drop view dfview;
! drop function dfunc(anyelement, anyelement, bool);
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/rowtypes.out	2014-01-02 13:19:15.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/rowtypes.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,429 ****
! --
! -- ROWTYPES
! --
! -- Make both a standalone composite type and a table rowtype
! create type complex as (r float8, i float8);
! create temp table fullname (first text, last text);
! -- Nested composite
! create type quad as (c1 complex, c2 complex);
! -- Some simple tests of I/O conversions and row construction
! select (1.1,2.2)::complex, row((3.3,4.4),(5.5,null))::quad;
!     row    |          row           
! -----------+------------------------
!  (1.1,2.2) | ("(3.3,4.4)","(5.5,)")
! (1 row)
! 
! select row('Joe', 'Blow')::fullname, '(Joe,Blow)'::fullname;
!     row     |  fullname  
! ------------+------------
!  (Joe,Blow) | (Joe,Blow)
! (1 row)
! 
! select '(Joe,von Blow)'::fullname, '(Joe,d''Blow)'::fullname;
!      fullname     |   fullname   
! ------------------+--------------
!  (Joe,"von Blow") | (Joe,d'Blow)
! (1 row)
! 
! select '(Joe,"von""Blow")'::fullname, E'(Joe,d\\\\Blow)'::fullname;
!      fullname      |    fullname     
! -------------------+-----------------
!  (Joe,"von""Blow") | (Joe,"d\\Blow")
! (1 row)
! 
! select '(Joe,"Blow,Jr")'::fullname;
!     fullname     
! -----------------
!  (Joe,"Blow,Jr")
! (1 row)
! 
! select '(Joe,)'::fullname;	-- ok, null 2nd column
!  fullname 
! ----------
!  (Joe,)
! (1 row)
! 
! select '(Joe)'::fullname;	-- bad
! ERROR:  malformed record literal: "(Joe)"
! LINE 1: select '(Joe)'::fullname;
!                ^
! DETAIL:  Too few columns.
! select '(Joe,,)'::fullname;	-- bad
! ERROR:  malformed record literal: "(Joe,,)"
! LINE 1: select '(Joe,,)'::fullname;
!                ^
! DETAIL:  Too many columns.
! create temp table quadtable(f1 int, q quad);
! insert into quadtable values (1, ((3.3,4.4),(5.5,6.6)));
! insert into quadtable values (2, ((null,4.4),(5.5,6.6)));
! select * from quadtable;
!  f1 |             q             
! ----+---------------------------
!   1 | ("(3.3,4.4)","(5.5,6.6)")
!   2 | ("(,4.4)","(5.5,6.6)")
! (2 rows)
! 
! select f1, q.c1 from quadtable;		-- fails, q is a table reference
! ERROR:  missing FROM-clause entry for table "q"
! LINE 1: select f1, q.c1 from quadtable;
!                    ^
! select f1, (q).c1, (qq.q).c1.i from quadtable qq;
!  f1 |    c1     |  i  
! ----+-----------+-----
!   1 | (3.3,4.4) | 4.4
!   2 | (,4.4)    | 4.4
! (2 rows)
! 
! create temp table people (fn fullname, bd date);
! insert into people values ('(Joe,Blow)', '1984-01-10');
! select * from people;
!      fn     |     bd     
! ------------+------------
!  (Joe,Blow) | 01-10-1984
! (1 row)
! 
! -- at the moment this will not work due to ALTER TABLE inadequacy:
! alter table fullname add column suffix text default '';
! ERROR:  cannot alter table "fullname" because column "people.fn" uses its row type
! -- but this should work:
! alter table fullname add column suffix text default null;
! select * from people;
!      fn      |     bd     
! -------------+------------
!  (Joe,Blow,) | 01-10-1984
! (1 row)
! 
! -- test insertion/updating of subfields
! update people set fn.suffix = 'Jr';
! select * from people;
!       fn       |     bd     
! ---------------+------------
!  (Joe,Blow,Jr) | 01-10-1984
! (1 row)
! 
! insert into quadtable (f1, q.c1.r, q.c2.i) values(44,55,66);
! select * from quadtable;
!  f1 |             q             
! ----+---------------------------
!   1 | ("(3.3,4.4)","(5.5,6.6)")
!   2 | ("(,4.4)","(5.5,6.6)")
!  44 | ("(55,)","(,66)")
! (3 rows)
! 
! -- The object here is to ensure that toasted references inside
! -- composite values don't cause problems.  The large f1 value will
! -- be toasted inside pp, it must still work after being copied to people.
! create temp table pp (f1 text);
! insert into pp values (repeat('abcdefghijkl', 100000));
! insert into people select ('Jim', f1, null)::fullname, current_date from pp;
! select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
!  first |        substr        | length  
! -------+----------------------+---------
!  Joe   | Blow                 |       4
!  Jim   | abcdefghijklabcdefgh | 1200000
! (2 rows)
! 
! -- Test row comparison semantics.  Prior to PG 8.2 we did this in a totally
! -- non-spec-compliant way.
! select ROW(1,2) < ROW(1,3) as true;
!  true 
! ------
!  t
! (1 row)
! 
! select ROW(1,2) < ROW(1,1) as false;
!  false 
! -------
!  f
! (1 row)
! 
! select ROW(1,2) < ROW(1,NULL) as null;
!  null 
! ------
!  
! (1 row)
! 
! select ROW(1,2,3) < ROW(1,3,NULL) as true; -- the NULL is not examined
!  true 
! ------
!  t
! (1 row)
! 
! select ROW(11,'ABC') < ROW(11,'DEF') as true;
!  true 
! ------
!  t
! (1 row)
! 
! select ROW(11,'ABC') > ROW(11,'DEF') as false;
!  false 
! -------
!  f
! (1 row)
! 
! select ROW(12,'ABC') > ROW(11,'DEF') as true;
!  true 
! ------
!  t
! (1 row)
! 
! -- = and <> have different NULL-behavior than < etc
! select ROW(1,2,3) < ROW(1,NULL,4) as null;
!  null 
! ------
!  
! (1 row)
! 
! select ROW(1,2,3) = ROW(1,NULL,4) as false;
!  false 
! -------
!  f
! (1 row)
! 
! select ROW(1,2,3) <> ROW(1,NULL,4) as true;
!  true 
! ------
!  t
! (1 row)
! 
! -- We allow operators beyond the six standard ones, if they have btree
! -- operator classes.
! select ROW('ABC','DEF') ~<=~ ROW('DEF','ABC') as true;
!  true 
! ------
!  t
! (1 row)
! 
! select ROW('ABC','DEF') ~>=~ ROW('DEF','ABC') as false;
!  false 
! -------
!  f
! (1 row)
! 
! select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail;
! ERROR:  could not determine interpretation of row comparison operator ~~
! LINE 1: select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail;
!                                 ^
! HINT:  Row comparison operators must be associated with btree operator families.
! -- Comparisons of ROW() expressions can cope with some type mismatches
! select ROW(1,2) = ROW(1,2::int8);
!  ?column? 
! ----------
!  t
! (1 row)
! 
! select ROW(1,2) in (ROW(3,4), ROW(1,2));
!  ?column? 
! ----------
!  t
! (1 row)
! 
! select ROW(1,2) in (ROW(3,4), ROW(1,2::int8));
!  ?column? 
! ----------
!  t
! (1 row)
! 
! -- Check row comparison with a subselect
! select unique1, unique2 from tenk1
! where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3)
!       and unique1 <= 20
! order by 1;
!  unique1 | unique2 
! ---------+---------
!        0 |    9998
!        1 |    2838
! (2 rows)
! 
! -- Also check row comparison with an indexable condition
! explain (costs off)
! select thousand, tenthous from tenk1
! where (thousand, tenthous) >= (997, 5000)
! order by thousand, tenthous;
!                         QUERY PLAN                         
! -----------------------------------------------------------
!  Index Only Scan using tenk1_thous_tenthous on tenk1
!    Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
! (2 rows)
! 
! select thousand, tenthous from tenk1
! where (thousand, tenthous) >= (997, 5000)
! order by thousand, tenthous;
!  thousand | tenthous 
! ----------+----------
!       997 |     5997
!       997 |     6997
!       997 |     7997
!       997 |     8997
!       997 |     9997
!       998 |      998
!       998 |     1998
!       998 |     2998
!       998 |     3998
!       998 |     4998
!       998 |     5998
!       998 |     6998
!       998 |     7998
!       998 |     8998
!       998 |     9998
!       999 |      999
!       999 |     1999
!       999 |     2999
!       999 |     3999
!       999 |     4999
!       999 |     5999
!       999 |     6999
!       999 |     7999
!       999 |     8999
!       999 |     9999
! (25 rows)
! 
! -- Check row comparisons with IN
! select * from int8_tbl i8 where i8 in (row(123,456));  -- fail, type mismatch
! ERROR:  cannot compare dissimilar column types bigint and integer at record column 1
! explain (costs off)
! select * from int8_tbl i8
! where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
!                                                  QUERY PLAN                                                  
! -------------------------------------------------------------------------------------------------------------
!  Seq Scan on int8_tbl i8
!    Filter: (i8.* = ANY (ARRAY[ROW(123::bigint, 456::bigint)::int8_tbl, '(4567890123456789,123)'::int8_tbl]))
! (2 rows)
! 
! select * from int8_tbl i8
! where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
!         q1        | q2  
! ------------------+-----
!               123 | 456
!  4567890123456789 | 123
! (2 rows)
! 
! -- Check some corner cases involving empty rowtypes
! select ROW();
!  row 
! -----
!  ()
! (1 row)
! 
! select ROW() IS NULL;
!  ?column? 
! ----------
!  t
! (1 row)
! 
! select ROW() = ROW();
! ERROR:  cannot compare rows of zero length
! LINE 1: select ROW() = ROW();
!                      ^
! -- Check ability to create arrays of anonymous rowtypes
! select array[ row(1,2), row(3,4), row(5,6) ];
!            array           
! ---------------------------
!  {"(1,2)","(3,4)","(5,6)"}
! (1 row)
! 
! -- Check ability to compare an anonymous row to elements of an array
! select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]);
!  ?column? 
! ----------
!  t
! (1 row)
! 
! select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
!  ?column? 
! ----------
!  f
! (1 row)
! 
! -- Check behavior with a non-comparable rowtype
! create type cantcompare as (p point, r float8);
! create temp table cc (f1 cantcompare);
! insert into cc values('("(1,2)",3)');
! insert into cc values('("(4,5)",6)');
! select * from cc order by f1; -- fail, but should complain about cantcompare
! ERROR:  could not identify an ordering operator for type cantcompare
! LINE 1: select * from cc order by f1;
!                                   ^
! HINT:  Use an explicit ordering operator or modify the query.
! --
! -- Test case derived from bug #5716: check multiple uses of a rowtype result
! --
! BEGIN;
! CREATE TABLE price (
!     id SERIAL PRIMARY KEY,
!     active BOOLEAN NOT NULL,
!     price NUMERIC
! );
! CREATE TYPE price_input AS (
!     id INTEGER,
!     price NUMERIC
! );
! CREATE TYPE price_key AS (
!     id INTEGER
! );
! CREATE FUNCTION price_key_from_table(price) RETURNS price_key AS $$
!     SELECT $1.id
! $$ LANGUAGE SQL;
! CREATE FUNCTION price_key_from_input(price_input) RETURNS price_key AS $$
!     SELECT $1.id
! $$ LANGUAGE SQL;
! insert into price values (1,false,42), (10,false,100), (11,true,17.99);
! UPDATE price
!     SET active = true, price = input_prices.price
!     FROM unnest(ARRAY[(10, 123.00), (11, 99.99)]::price_input[]) input_prices
!     WHERE price_key_from_table(price.*) = price_key_from_input(input_prices.*);
! select * from price;
!  id | active | price  
! ----+--------+--------
!   1 | f      |     42
!  10 | t      | 123.00
!  11 | t      |  99.99
! (3 rows)
! 
! rollback;
! --
! -- We allow I/O conversion casts from composite types to strings to be
! -- invoked via cast syntax, but not functional syntax.  This is because
! -- the latter is too prone to be invoked unintentionally.
! --
! select cast (fullname as text) from fullname;
!  fullname 
! ----------
! (0 rows)
! 
! select fullname::text from fullname;
!  fullname 
! ----------
! (0 rows)
! 
! select text(fullname) from fullname;  -- error
! ERROR:  function text(fullname) does not exist
! LINE 1: select text(fullname) from fullname;
!                ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! select fullname.text from fullname;  -- error
! ERROR:  column fullname.text does not exist
! LINE 1: select fullname.text from fullname;
!                ^
! -- same, but RECORD instead of named composite type:
! select cast (row('Jim', 'Beam') as text);
!     row     
! ------------
!  (Jim,Beam)
! (1 row)
! 
! select (row('Jim', 'Beam'))::text;
!     row     
! ------------
!  (Jim,Beam)
! (1 row)
! 
! select text(row('Jim', 'Beam'));  -- error
! ERROR:  function text(record) does not exist
! LINE 1: select text(row('Jim', 'Beam'));
!                ^
! HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
! select (row('Jim', 'Beam')).text;  -- error
! ERROR:  could not identify column "text" in record data type
! LINE 1: select (row('Jim', 'Beam')).text;
!                 ^
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/returning.out	2014-01-02 13:19:14.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/returning.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,333 ****
! --
! -- Test INSERT/UPDATE/DELETE RETURNING
! --
! -- Simple cases
! CREATE TEMP TABLE foo (f1 serial, f2 text, f3 int default 42);
! INSERT INTO foo (f2,f3)
!   VALUES ('test', DEFAULT), ('More', 11), (upper('more'), 7+9)
!   RETURNING *, f1+f3 AS sum;
!  f1 |  f2  | f3 | sum 
! ----+------+----+-----
!   1 | test | 42 |  43
!   2 | More | 11 |  13
!   3 | MORE | 16 |  19
! (3 rows)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 
! ----+------+----
!   1 | test | 42
!   2 | More | 11
!   3 | MORE | 16
! (3 rows)
! 
! UPDATE foo SET f2 = lower(f2), f3 = DEFAULT RETURNING foo.*, f1+f3 AS sum13;
!  f1 |  f2  | f3 | sum13 
! ----+------+----+-------
!   1 | test | 42 |    43
!   2 | more | 42 |    44
!   3 | more | 42 |    45
! (3 rows)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 
! ----+------+----
!   1 | test | 42
!   2 | more | 42
!   3 | more | 42
! (3 rows)
! 
! DELETE FROM foo WHERE f1 > 2 RETURNING f3, f2, f1, least(f1,f3);
!  f3 |  f2  | f1 | least 
! ----+------+----+-------
!  42 | more |  3 |     3
! (1 row)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 
! ----+------+----
!   1 | test | 42
!   2 | more | 42
! (2 rows)
! 
! -- Subplans and initplans in the RETURNING list
! INSERT INTO foo SELECT f1+10, f2, f3+99 FROM foo
!   RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan,
!     EXISTS(SELECT * FROM int4_tbl) AS initplan;
!  f1 |  f2  | f3  | subplan | initplan 
! ----+------+-----+---------+----------
!  11 | test | 141 | t       | t
!  12 | more | 141 | f       | t
! (2 rows)
! 
! UPDATE foo SET f3 = f3 * 2
!   WHERE f1 > 10
!   RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan,
!     EXISTS(SELECT * FROM int4_tbl) AS initplan;
!  f1 |  f2  | f3  | subplan | initplan 
! ----+------+-----+---------+----------
!  11 | test | 282 | t       | t
!  12 | more | 282 | f       | t
! (2 rows)
! 
! DELETE FROM foo
!   WHERE f1 > 10
!   RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan,
!     EXISTS(SELECT * FROM int4_tbl) AS initplan;
!  f1 |  f2  | f3  | subplan | initplan 
! ----+------+-----+---------+----------
!  11 | test | 282 | t       | t
!  12 | more | 282 | f       | t
! (2 rows)
! 
! -- Joins
! UPDATE foo SET f3 = f3*2
!   FROM int4_tbl i
!   WHERE foo.f1 + 123455 = i.f1
!   RETURNING foo.*, i.f1 as "i.f1";
!  f1 |  f2  | f3 |  i.f1  
! ----+------+----+--------
!   1 | test | 84 | 123456
! (1 row)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 
! ----+------+----
!   2 | more | 42
!   1 | test | 84
! (2 rows)
! 
! DELETE FROM foo
!   USING int4_tbl i
!   WHERE foo.f1 + 123455 = i.f1
!   RETURNING foo.*, i.f1 as "i.f1";
!  f1 |  f2  | f3 |  i.f1  
! ----+------+----+--------
!   1 | test | 84 | 123456
! (1 row)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 
! ----+------+----
!   2 | more | 42
! (1 row)
! 
! -- Check inheritance cases
! CREATE TEMP TABLE foochild (fc int) INHERITS (foo);
! INSERT INTO foochild VALUES(123,'child',999,-123);
! ALTER TABLE foo ADD COLUMN f4 int8 DEFAULT 99;
! SELECT * FROM foo;
!  f1  |  f2   | f3  | f4 
! -----+-------+-----+----
!    2 | more  |  42 | 99
!  123 | child | 999 | 99
! (2 rows)
! 
! SELECT * FROM foochild;
!  f1  |  f2   | f3  |  fc  | f4 
! -----+-------+-----+------+----
!  123 | child | 999 | -123 | 99
! (1 row)
! 
! UPDATE foo SET f4 = f4 + f3 WHERE f4 = 99 RETURNING *;
!  f1  |  f2   | f3  |  f4  
! -----+-------+-----+------
!    2 | more  |  42 |  141
!  123 | child | 999 | 1098
! (2 rows)
! 
! SELECT * FROM foo;
!  f1  |  f2   | f3  |  f4  
! -----+-------+-----+------
!    2 | more  |  42 |  141
!  123 | child | 999 | 1098
! (2 rows)
! 
! SELECT * FROM foochild;
!  f1  |  f2   | f3  |  fc  |  f4  
! -----+-------+-----+------+------
!  123 | child | 999 | -123 | 1098
! (1 row)
! 
! UPDATE foo SET f3 = f3*2
!   FROM int8_tbl i
!   WHERE foo.f1 = i.q2
!   RETURNING *;
!  f1  |  f2   |  f3  |  f4  |        q1        | q2  
! -----+-------+------+------+------------------+-----
!  123 | child | 1998 | 1098 | 4567890123456789 | 123
! (1 row)
! 
! SELECT * FROM foo;
!  f1  |  f2   |  f3  |  f4  
! -----+-------+------+------
!    2 | more  |   42 |  141
!  123 | child | 1998 | 1098
! (2 rows)
! 
! SELECT * FROM foochild;
!  f1  |  f2   |  f3  |  fc  |  f4  
! -----+-------+------+------+------
!  123 | child | 1998 | -123 | 1098
! (1 row)
! 
! DELETE FROM foo
!   USING int8_tbl i
!   WHERE foo.f1 = i.q2
!   RETURNING *;
!  f1  |  f2   |  f3  |  f4  |        q1        | q2  
! -----+-------+------+------+------------------+-----
!  123 | child | 1998 | 1098 | 4567890123456789 | 123
! (1 row)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 | f4  
! ----+------+----+-----
!   2 | more | 42 | 141
! (1 row)
! 
! SELECT * FROM foochild;
!  f1 | f2 | f3 | fc | f4 
! ----+----+----+----+----
! (0 rows)
! 
! DROP TABLE foochild;
! -- Rules and views
! CREATE TEMP VIEW voo AS SELECT f1, f2 FROM foo;
! CREATE RULE voo_i AS ON INSERT TO voo DO INSTEAD
!   INSERT INTO foo VALUES(new.*, 57);
! INSERT INTO voo VALUES(11,'zit');
! -- fails:
! INSERT INTO voo VALUES(12,'zoo') RETURNING *, f1*2;
! ERROR:  cannot perform INSERT RETURNING on relation "voo"
! HINT:  You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause.
! -- fails, incompatible list:
! CREATE OR REPLACE RULE voo_i AS ON INSERT TO voo DO INSTEAD
!   INSERT INTO foo VALUES(new.*, 57) RETURNING *;
! ERROR:  RETURNING list has too many entries
! CREATE OR REPLACE RULE voo_i AS ON INSERT TO voo DO INSTEAD
!   INSERT INTO foo VALUES(new.*, 57) RETURNING f1, f2;
! -- should still work
! INSERT INTO voo VALUES(13,'zit2');
! -- works now
! INSERT INTO voo VALUES(14,'zoo2') RETURNING *;
!  f1 |  f2  
! ----+------
!  14 | zoo2
! (1 row)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 | f4  
! ----+------+----+-----
!   2 | more | 42 | 141
!  11 | zit  | 57 |  99
!  13 | zit2 | 57 |  99
!  14 | zoo2 | 57 |  99
! (4 rows)
! 
! SELECT * FROM voo;
!  f1 |  f2  
! ----+------
!   2 | more
!  11 | zit
!  13 | zit2
!  14 | zoo2
! (4 rows)
! 
! CREATE OR REPLACE RULE voo_u AS ON UPDATE TO voo DO INSTEAD
!   UPDATE foo SET f1 = new.f1, f2 = new.f2 WHERE f1 = old.f1
!   RETURNING f1, f2;
! update voo set f1 = f1 + 1 where f2 = 'zoo2';
! update voo set f1 = f1 + 1 where f2 = 'zoo2' RETURNING *, f1*2;
!  f1 |  f2  | ?column? 
! ----+------+----------
!  16 | zoo2 |       32
! (1 row)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 | f4  
! ----+------+----+-----
!   2 | more | 42 | 141
!  11 | zit  | 57 |  99
!  13 | zit2 | 57 |  99
!  16 | zoo2 | 57 |  99
! (4 rows)
! 
! SELECT * FROM voo;
!  f1 |  f2  
! ----+------
!   2 | more
!  11 | zit
!  13 | zit2
!  16 | zoo2
! (4 rows)
! 
! CREATE OR REPLACE RULE voo_d AS ON DELETE TO voo DO INSTEAD
!   DELETE FROM foo WHERE f1 = old.f1
!   RETURNING f1, f2;
! DELETE FROM foo WHERE f1 = 13;
! DELETE FROM foo WHERE f2 = 'zit' RETURNING *;
!  f1 | f2  | f3 | f4 
! ----+-----+----+----
!  11 | zit | 57 | 99
! (1 row)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 | f4  
! ----+------+----+-----
!   2 | more | 42 | 141
!  16 | zoo2 | 57 |  99
! (2 rows)
! 
! SELECT * FROM voo;
!  f1 |  f2  
! ----+------
!   2 | more
!  16 | zoo2
! (2 rows)
! 
! -- Try a join case
! CREATE TEMP TABLE joinme (f2j text, other int);
! INSERT INTO joinme VALUES('more', 12345);
! INSERT INTO joinme VALUES('zoo2', 54321);
! INSERT INTO joinme VALUES('other', 0);
! CREATE TEMP VIEW joinview AS
!   SELECT foo.*, other FROM foo JOIN joinme ON (f2 = f2j);
! SELECT * FROM joinview;
!  f1 |  f2  | f3 | f4  | other 
! ----+------+----+-----+-------
!   2 | more | 42 | 141 | 12345
!  16 | zoo2 | 57 |  99 | 54321
! (2 rows)
! 
! CREATE RULE joinview_u AS ON UPDATE TO joinview DO INSTEAD
!   UPDATE foo SET f1 = new.f1, f3 = new.f3
!     FROM joinme WHERE f2 = f2j AND f2 = old.f2
!     RETURNING foo.*, other;
! UPDATE joinview SET f1 = f1 + 1 WHERE f3 = 57 RETURNING *, other + 1;
!  f1 |  f2  | f3 | f4 | other | ?column? 
! ----+------+----+----+-------+----------
!  17 | zoo2 | 57 | 99 | 54321 |    54322
! (1 row)
! 
! SELECT * FROM joinview;
!  f1 |  f2  | f3 | f4  | other 
! ----+------+----+-----+-------
!   2 | more | 42 | 141 | 12345
!  17 | zoo2 | 57 |  99 | 54321
! (2 rows)
! 
! SELECT * FROM foo;
!  f1 |  f2  | f3 | f4  
! ----+------+----+-----
!   2 | more | 42 | 141
!  17 | zoo2 | 57 |  99
! (2 rows)
! 
! SELECT * FROM voo;
!  f1 |  f2  
! ----+------
!   2 | more
!  17 | zoo2
! (2 rows)
! 
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/largeobject.out	2014-01-02 13:26:43.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/largeobject.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,445 ****
! --
! -- Test large object support
! --
! -- ensure consistent test output regardless of the default bytea format
! SET bytea_output TO escape;
! -- Load a file
! CREATE TABLE lotest_stash_values (loid oid, fd integer);
! -- lo_creat(mode integer) returns oid
! -- The mode arg to lo_creat is unused, some vestigal holdover from ancient times
! -- returns the large object id
! INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
! -- Test ALTER LARGE OBJECT
! CREATE ROLE regresslo;
! DO $$
!   BEGIN
!     EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
! 		|| ' OWNER TO regresslo';
!   END
! $$;
! SELECT
! 	rol.rolname
! FROM
! 	lotest_stash_values s
! 	JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
! 	JOIN pg_authid rol ON lo.lomowner = rol.oid;
!   rolname  
! -----------
!  regresslo
! (1 row)
! 
! -- NOTE: large objects require transactions
! BEGIN;
! -- lo_open(lobjId oid, mode integer) returns integer
! -- The mode parameter to lo_open uses two constants:
! --   INV_READ  = 0x20000
! --   INV_WRITE = 0x40000
! -- The return value is a file descriptor-like value which remains valid for the
! -- transaction.
! UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer));
! -- loread/lowrite names are wonky, different from other functions which are lo_*
! -- lowrite(fd integer, data bytea) returns integer
! -- the integer is the number of bytes written
! SELECT lowrite(fd, '
! Whose woods these are I think I know,
! His house is in the village though.
! He will not see me stopping here,
! To watch his woods fill up with snow.
! 
! My little horse must think it queer,
! To stop without a farmhouse near,
! Between the woods and frozen lake,
! The darkest evening of the year.
! 
! He gives his harness bells a shake,
! To ask if there is some mistake.
! The only other sound''s the sweep,
! Of easy wind and downy flake.
! 
! The woods are lovely, dark and deep,
! But I have promises to keep,
! And miles to go before I sleep,
! And miles to go before I sleep.
! 
!          -- Robert Frost
! ') FROM lotest_stash_values;
!  lowrite 
! ---------
!      578
! (1 row)
! 
! -- lo_close(fd integer) returns integer
! -- return value is 0 for success, or <0 for error (actually only -1, but...)
! SELECT lo_close(fd) FROM lotest_stash_values;
!  lo_close 
! ----------
!         0
! (1 row)
! 
! END;
! -- Read out a portion
! BEGIN;
! UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
! -- lo_lseek(fd integer, offset integer, whence integer) returns integer
! -- offset is in bytes, whence is one of three values:
! --  SEEK_SET (= 0) meaning relative to beginning
! --  SEEK_CUR (= 1) meaning relative to current position
! --  SEEK_END (= 2) meaning relative to end (offset better be negative)
! -- returns current position in file
! SELECT lo_lseek(fd, 422, 0) FROM lotest_stash_values;
!  lo_lseek 
! ----------
!       422
! (1 row)
! 
! -- loread/lowrite names are wonky, different from other functions which are lo_*
! -- loread(fd integer, len integer) returns bytea
! SELECT loread(fd, 35) FROM lotest_stash_values;
!                loread                
! -------------------------------------
!  The woods are lovely, dark and deep
! (1 row)
! 
! SELECT lo_lseek(fd, -19, 1) FROM lotest_stash_values;
!  lo_lseek 
! ----------
!       438
! (1 row)
! 
! SELECT lowrite(fd, 'n') FROM lotest_stash_values;
!  lowrite 
! ---------
!        1
! (1 row)
! 
! SELECT lo_tell(fd) FROM lotest_stash_values;
!  lo_tell 
! ---------
!      439
! (1 row)
! 
! SELECT lo_lseek(fd, -156, 2) FROM lotest_stash_values;
!  lo_lseek 
! ----------
!       422
! (1 row)
! 
! SELECT loread(fd, 35) FROM lotest_stash_values;
!                loread                
! -------------------------------------
!  The woods are lonely, dark and deep
! (1 row)
! 
! SELECT lo_close(fd) FROM lotest_stash_values;
!  lo_close 
! ----------
!         0
! (1 row)
! 
! END;
! -- Test resource management
! BEGIN;
! SELECT lo_open(loid, x'40000'::int) from lotest_stash_values;
!  lo_open 
! ---------
!        0
! (1 row)
! 
! ABORT;
! -- Test truncation.
! BEGIN;
! UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
! SELECT lo_truncate(fd, 10) FROM lotest_stash_values;
!  lo_truncate 
! -------------
!            0
! (1 row)
! 
! SELECT loread(fd, 15) FROM lotest_stash_values;
!     loread     
! ---------------
!  \012Whose woo
! (1 row)
! 
! SELECT lo_truncate(fd, 10000) FROM lotest_stash_values;
!  lo_truncate 
! -------------
!            0
! (1 row)
! 
! SELECT loread(fd, 10) FROM lotest_stash_values;
!                   loread                  
! ------------------------------------------
!  \000\000\000\000\000\000\000\000\000\000
! (1 row)
! 
! SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
!  lo_lseek 
! ----------
!     10000
! (1 row)
! 
! SELECT lo_tell(fd) FROM lotest_stash_values;
!  lo_tell 
! ---------
!    10000
! (1 row)
! 
! SELECT lo_truncate(fd, 5000) FROM lotest_stash_values;
!  lo_truncate 
! -------------
!            0
! (1 row)
! 
! SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
!  lo_lseek 
! ----------
!      5000
! (1 row)
! 
! SELECT lo_tell(fd) FROM lotest_stash_values;
!  lo_tell 
! ---------
!     5000
! (1 row)
! 
! SELECT lo_close(fd) FROM lotest_stash_values;
!  lo_close 
! ----------
!         0
! (1 row)
! 
! END;
! -- Test 64-bit large object functions.
! BEGIN;
! UPDATE lotest_stash_values SET fd = lo_open(loid, CAST(x'20000' | x'40000' AS integer));
! SELECT lo_lseek64(fd, 4294967296, 0) FROM lotest_stash_values;
!  lo_lseek64 
! ------------
!  4294967296
! (1 row)
! 
! SELECT lowrite(fd, 'offset:4GB') FROM lotest_stash_values;
!  lowrite 
! ---------
!       10
! (1 row)
! 
! SELECT lo_tell64(fd) FROM lotest_stash_values;
!  lo_tell64  
! ------------
!  4294967306
! (1 row)
! 
! SELECT lo_lseek64(fd, -10, 1) FROM lotest_stash_values;
!  lo_lseek64 
! ------------
!  4294967296
! (1 row)
! 
! SELECT lo_tell64(fd) FROM lotest_stash_values;
!  lo_tell64  
! ------------
!  4294967296
! (1 row)
! 
! SELECT loread(fd, 10) FROM lotest_stash_values;
!    loread   
! ------------
!  offset:4GB
! (1 row)
! 
! SELECT lo_truncate64(fd, 5000000000) FROM lotest_stash_values;
!  lo_truncate64 
! ---------------
!              0
! (1 row)
! 
! SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values;
!  lo_lseek64 
! ------------
!  5000000000
! (1 row)
! 
! SELECT lo_tell64(fd) FROM lotest_stash_values;
!  lo_tell64  
! ------------
!  5000000000
! (1 row)
! 
! SELECT lo_truncate64(fd, 3000000000) FROM lotest_stash_values;
!  lo_truncate64 
! ---------------
!              0
! (1 row)
! 
! SELECT lo_lseek64(fd, 0, 2) FROM lotest_stash_values;
!  lo_lseek64 
! ------------
!  3000000000
! (1 row)
! 
! SELECT lo_tell64(fd) FROM lotest_stash_values;
!  lo_tell64  
! ------------
!  3000000000
! (1 row)
! 
! SELECT lo_close(fd) FROM lotest_stash_values;
!  lo_close 
! ----------
!         0
! (1 row)
! 
! END;
! -- lo_unlink(lobjId oid) returns integer
! -- return value appears to always be 1
! SELECT lo_unlink(loid) from lotest_stash_values;
!  lo_unlink 
! -----------
!          1
! (1 row)
! 
! TRUNCATE lotest_stash_values;
! INSERT INTO lotest_stash_values (loid) SELECT lo_import('/var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/data/tenk.data');
! BEGIN;
! UPDATE lotest_stash_values SET fd=lo_open(loid, CAST(x'20000' | x'40000' AS integer));
! -- verify length of large object
! SELECT lo_lseek(fd, 0, 2) FROM lotest_stash_values;
!  lo_lseek 
! ----------
!    670800
! (1 row)
! 
! -- with the default BLKSZ, LOBLKSZ = 2048, so this positions us for a block
! -- edge case
! SELECT lo_lseek(fd, 2030, 0) FROM lotest_stash_values;
!  lo_lseek 
! ----------
!      2030
! (1 row)
! 
! -- this should get half of the value from page 0 and half from page 1 of the
! -- large object
! SELECT loread(fd, 36) FROM lotest_stash_values;
!                              loread                              
! -----------------------------------------------------------------
!  AAA\011FBAAAA\011VVVVxx\0122513\01132\0111\0111\0113\01113\0111
! (1 row)
! 
! SELECT lo_tell(fd) FROM lotest_stash_values;
!  lo_tell 
! ---------
!     2066
! (1 row)
! 
! SELECT lo_lseek(fd, -26, 1) FROM lotest_stash_values;
!  lo_lseek 
! ----------
!      2040
! (1 row)
! 
! SELECT lowrite(fd, 'abcdefghijklmnop') FROM lotest_stash_values;
!  lowrite 
! ---------
!       16
! (1 row)
! 
! SELECT lo_lseek(fd, 2030, 0) FROM lotest_stash_values;
!  lo_lseek 
! ----------
!      2030
! (1 row)
! 
! SELECT loread(fd, 36) FROM lotest_stash_values;
!                        loread                        
! -----------------------------------------------------
!  AAA\011FBAAAAabcdefghijklmnop1\0111\0113\01113\0111
! (1 row)
! 
! SELECT lo_close(fd) FROM lotest_stash_values;
!  lo_close 
! ----------
!         0
! (1 row)
! 
! END;
! SELECT lo_export(loid, '/var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/lotest.txt') FROM lotest_stash_values;
!  lo_export 
! -----------
!          1
! (1 row)
! 
! \lo_import 'results/lotest.txt'
! \set newloid :LASTOID
! -- just make sure \lo_export does not barf
! \lo_export :newloid 'results/lotest2.txt'
! -- This is a hack to test that export/import are reversible
! -- This uses knowledge about the inner workings of large object mechanism
! -- which should not be used outside it.  This makes it a HACK
! SELECT pageno, data FROM pg_largeobject WHERE loid = (SELECT loid from lotest_stash_values)
! EXCEPT
! SELECT pageno, data FROM pg_largeobject WHERE loid = :newloid;
!  pageno | data 
! --------+------
! (0 rows)
! 
! SELECT lo_unlink(loid) FROM lotest_stash_values;
!  lo_unlink 
! -----------
!          1
! (1 row)
! 
! \lo_unlink :newloid
! \lo_import 'results/lotest.txt'
! \set newloid_1 :LASTOID
! SELECT lo_create(0, lo_get(:newloid_1)) AS newloid_2
! \gset
! SELECT md5(lo_get(:newloid_1)) = md5(lo_get(:newloid_2));
!  ?column? 
! ----------
!  t
! (1 row)
! 
! SELECT lo_get(:newloid_1, 0, 20);
!                   lo_get                   
! -------------------------------------------
!  8800\0110\0110\0110\0110\0110\0110\011800
! (1 row)
! 
! SELECT lo_get(:newloid_1, 10, 20);
!                   lo_get                   
! -------------------------------------------
!  \0110\0110\0110\011800\011800\0113800\011
! (1 row)
! 
! SELECT lo_put(:newloid_1, 5, decode('afafafaf', 'hex'));
!  lo_put 
! --------
!  
! (1 row)
! 
! SELECT lo_get(:newloid_1, 0, 20);
!                      lo_get                      
! -------------------------------------------------
!  8800\011\257\257\257\2570\0110\0110\0110\011800
! (1 row)
! 
! SELECT lo_put(:newloid_1, 4294967310, 'foo');
!  lo_put 
! --------
!  
! (1 row)
! 
! SELECT lo_get(:newloid_1);
! ERROR:  large object read request is too large
! SELECT lo_get(:newloid_1, 4294967294, 100);
!                                lo_get                                
! ---------------------------------------------------------------------
!  \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000foo
! (1 row)
! 
! \lo_unlink :newloid_1
! \lo_unlink :newloid_2
! TRUNCATE lotest_stash_values;
! DROP ROLE regresslo;
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/with.out	2014-01-02 13:19:06.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/with.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,2157 ****
! --
! -- Tests for common table expressions (WITH query, ... SELECT ...)
! --
! -- Basic WITH
! WITH q1(x,y) AS (SELECT 1,2)
! SELECT * FROM q1, q1 AS q2;
!  x | y | x | y 
! ---+---+---+---
!  1 | 2 | 1 | 2
! (1 row)
! 
! -- Multiple uses are evaluated only once
! SELECT count(*) FROM (
!   WITH q1(x) AS (SELECT random() FROM generate_series(1, 5))
!     SELECT * FROM q1
!   UNION
!     SELECT * FROM q1
! ) ss;
!  count 
! -------
!      5
! (1 row)
! 
! -- WITH RECURSIVE
! -- sum of 1..100
! WITH RECURSIVE t(n) AS (
!     VALUES (1)
! UNION ALL
!     SELECT n+1 FROM t WHERE n < 100
! )
! SELECT sum(n) FROM t;
!  sum  
! ------
!  5050
! (1 row)
! 
! WITH RECURSIVE t(n) AS (
!     SELECT (VALUES(1))
! UNION ALL
!     SELECT n+1 FROM t WHERE n < 5
! )
! SELECT * FROM t;
!  n 
! ---
!  1
!  2
!  3
!  4
!  5
! (5 rows)
! 
! -- recursive view
! CREATE RECURSIVE VIEW nums (n) AS
!     VALUES (1)
! UNION ALL
!     SELECT n+1 FROM nums WHERE n < 5;
! SELECT * FROM nums;
!  n 
! ---
!  1
!  2
!  3
!  4
!  5
! (5 rows)
! 
! CREATE OR REPLACE RECURSIVE VIEW nums (n) AS
!     VALUES (1)
! UNION ALL
!     SELECT n+1 FROM nums WHERE n < 6;
! SELECT * FROM nums;
!  n 
! ---
!  1
!  2
!  3
!  4
!  5
!  6
! (6 rows)
! 
! -- This is an infinite loop with UNION ALL, but not with UNION
! WITH RECURSIVE t(n) AS (
!     SELECT 1
! UNION
!     SELECT 10-n FROM t)
! SELECT * FROM t;
!  n 
! ---
!  1
!  9
! (2 rows)
! 
! -- This'd be an infinite loop, but outside query reads only as much as needed
! WITH RECURSIVE t(n) AS (
!     VALUES (1)
! UNION ALL
!     SELECT n+1 FROM t)
! SELECT * FROM t LIMIT 10;
!  n  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
! (10 rows)
! 
! -- UNION case should have same property
! WITH RECURSIVE t(n) AS (
!     SELECT 1
! UNION
!     SELECT n+1 FROM t)
! SELECT * FROM t LIMIT 10;
!  n  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
! (10 rows)
! 
! -- Test behavior with an unknown-type literal in the WITH
! WITH q AS (SELECT 'foo' AS x)
! SELECT x, x IS OF (unknown) as is_unknown FROM q;
!   x  | is_unknown 
! -----+------------
!  foo | t
! (1 row)
! 
! WITH RECURSIVE t(n) AS (
!     SELECT 'foo'
! UNION ALL
!     SELECT n || ' bar' FROM t WHERE length(n) < 20
! )
! SELECT n, n IS OF (text) as is_text FROM t;
!             n            | is_text 
! -------------------------+---------
!  foo                     | t
!  foo bar                 | t
!  foo bar bar             | t
!  foo bar bar bar         | t
!  foo bar bar bar bar     | t
!  foo bar bar bar bar bar | t
! (6 rows)
! 
! --
! -- Some examples with a tree
! --
! -- department structure represented here is as follows:
! --
! -- ROOT-+->A-+->B-+->C
! --      |         |
! --      |         +->D-+->F
! --      +->E-+->G
! CREATE TEMP TABLE department (
! 	id INTEGER PRIMARY KEY,  -- department ID
! 	parent_department INTEGER REFERENCES department, -- upper department ID
! 	name TEXT -- department name
! );
! INSERT INTO department VALUES (0, NULL, 'ROOT');
! INSERT INTO department VALUES (1, 0, 'A');
! INSERT INTO department VALUES (2, 1, 'B');
! INSERT INTO department VALUES (3, 2, 'C');
! INSERT INTO department VALUES (4, 2, 'D');
! INSERT INTO department VALUES (5, 0, 'E');
! INSERT INTO department VALUES (6, 4, 'F');
! INSERT INTO department VALUES (7, 5, 'G');
! -- extract all departments under 'A'. Result should be A, B, C, D and F
! WITH RECURSIVE subdepartment AS
! (
! 	-- non recursive term
! 	SELECT name as root_name, * FROM department WHERE name = 'A'
! 	UNION ALL
! 	-- recursive term
! 	SELECT sd.root_name, d.* FROM department AS d, subdepartment AS sd
! 		WHERE d.parent_department = sd.id
! )
! SELECT * FROM subdepartment ORDER BY name;
!  root_name | id | parent_department | name 
! -----------+----+-------------------+------
!  A         |  1 |                 0 | A
!  A         |  2 |                 1 | B
!  A         |  3 |                 2 | C
!  A         |  4 |                 2 | D
!  A         |  6 |                 4 | F
! (5 rows)
! 
! -- extract all departments under 'A' with "level" number
! WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
! (
! 	-- non recursive term
! 	SELECT 1, * FROM department WHERE name = 'A'
! 	UNION ALL
! 	-- recursive term
! 	SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
! 		WHERE d.parent_department = sd.id
! )
! SELECT * FROM subdepartment ORDER BY name;
!  level | id | parent_department | name 
! -------+----+-------------------+------
!      1 |  1 |                 0 | A
!      2 |  2 |                 1 | B
!      3 |  3 |                 2 | C
!      3 |  4 |                 2 | D
!      4 |  6 |                 4 | F
! (5 rows)
! 
! -- extract all departments under 'A' with "level" number.
! -- Only shows level 2 or more
! WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
! (
! 	-- non recursive term
! 	SELECT 1, * FROM department WHERE name = 'A'
! 	UNION ALL
! 	-- recursive term
! 	SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
! 		WHERE d.parent_department = sd.id
! )
! SELECT * FROM subdepartment WHERE level >= 2 ORDER BY name;
!  level | id | parent_department | name 
! -------+----+-------------------+------
!      2 |  2 |                 1 | B
!      3 |  3 |                 2 | C
!      3 |  4 |                 2 | D
!      4 |  6 |                 4 | F
! (4 rows)
! 
! -- "RECURSIVE" is ignored if the query has no self-reference
! WITH RECURSIVE subdepartment AS
! (
! 	-- note lack of recursive UNION structure
! 	SELECT * FROM department WHERE name = 'A'
! )
! SELECT * FROM subdepartment ORDER BY name;
!  id | parent_department | name 
! ----+-------------------+------
!   1 |                 0 | A
! (1 row)
! 
! -- inside subqueries
! SELECT count(*) FROM (
!     WITH RECURSIVE t(n) AS (
!         SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 500
!     )
!     SELECT * FROM t) AS t WHERE n < (
!         SELECT count(*) FROM (
!             WITH RECURSIVE t(n) AS (
!                    SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 100
!                 )
!             SELECT * FROM t WHERE n < 50000
!          ) AS t WHERE n < 100);
!  count 
! -------
!     98
! (1 row)
! 
! -- use same CTE twice at different subquery levels
! WITH q1(x,y) AS (
!     SELECT hundred, sum(ten) FROM tenk1 GROUP BY hundred
!   )
! SELECT count(*) FROM q1 WHERE y > (SELECT sum(y)/100 FROM q1 qsub);
!  count 
! -------
!     50
! (1 row)
! 
! -- via a VIEW
! CREATE TEMPORARY VIEW vsubdepartment AS
! 	WITH RECURSIVE subdepartment AS
! 	(
! 		 -- non recursive term
! 		SELECT * FROM department WHERE name = 'A'
! 		UNION ALL
! 		-- recursive term
! 		SELECT d.* FROM department AS d, subdepartment AS sd
! 			WHERE d.parent_department = sd.id
! 	)
! 	SELECT * FROM subdepartment;
! SELECT * FROM vsubdepartment ORDER BY name;
!  id | parent_department | name 
! ----+-------------------+------
!   1 |                 0 | A
!   2 |                 1 | B
!   3 |                 2 | C
!   4 |                 2 | D
!   6 |                 4 | F
! (5 rows)
! 
! -- Check reverse listing
! SELECT pg_get_viewdef('vsubdepartment'::regclass);
!                     pg_get_viewdef                     
! -------------------------------------------------------
!   WITH RECURSIVE subdepartment AS (                   +
!                   SELECT department.id,               +
!                      department.parent_department,    +
!                      department.name                  +
!                     FROM department                   +
!                    WHERE (department.name = 'A'::text)+
!          UNION ALL                                    +
!                   SELECT d.id,                        +
!                      d.parent_department,             +
!                      d.name                           +
!                     FROM department d,                +
!                      subdepartment sd                 +
!                    WHERE (d.parent_department = sd.id)+
!          )                                            +
!   SELECT subdepartment.id,                            +
!      subdepartment.parent_department,                 +
!      subdepartment.name                               +
!     FROM subdepartment;
! (1 row)
! 
! SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
!                    pg_get_viewdef                    
! -----------------------------------------------------
!   WITH RECURSIVE subdepartment AS (                 +
!                   SELECT department.id,             +
!                      department.parent_department,  +
!                      department.name                +
!                     FROM department                 +
!                    WHERE department.name = 'A'::text+
!          UNION ALL                                  +
!                   SELECT d.id,                      +
!                      d.parent_department,           +
!                      d.name                         +
!                     FROM department d,              +
!                      subdepartment sd               +
!                    WHERE d.parent_department = sd.id+
!          )                                          +
!   SELECT subdepartment.id,                          +
!      subdepartment.parent_department,               +
!      subdepartment.name                             +
!     FROM subdepartment;
! (1 row)
! 
! -- Another reverse-listing example
! CREATE VIEW sums_1_100 AS
! WITH RECURSIVE t(n) AS (
!     VALUES (1)
! UNION ALL
!     SELECT n+1 FROM t WHERE n < 100
! )
! SELECT sum(n) FROM t;
! \d+ sums_1_100
!               View "public.sums_1_100"
!  Column |  Type  | Modifiers | Storage | Description 
! --------+--------+-----------+---------+-------------
!  sum    | bigint |           | plain   | 
! View definition:
!  WITH RECURSIVE t(n) AS (
!                  VALUES (1)
!         UNION ALL
!                  SELECT t_1.n + 1
!                    FROM t t_1
!                   WHERE t_1.n < 100
!         )
!  SELECT sum(t.n) AS sum
!    FROM t;
! 
! -- corner case in which sub-WITH gets initialized first
! with recursive q as (
!       select * from department
!     union all
!       (with x as (select * from q)
!        select * from x)
!     )
! select * from q limit 24;
!  id | parent_department | name 
! ----+-------------------+------
!   0 |                   | ROOT
!   1 |                 0 | A
!   2 |                 1 | B
!   3 |                 2 | C
!   4 |                 2 | D
!   5 |                 0 | E
!   6 |                 4 | F
!   7 |                 5 | G
!   0 |                   | ROOT
!   1 |                 0 | A
!   2 |                 1 | B
!   3 |                 2 | C
!   4 |                 2 | D
!   5 |                 0 | E
!   6 |                 4 | F
!   7 |                 5 | G
!   0 |                   | ROOT
!   1 |                 0 | A
!   2 |                 1 | B
!   3 |                 2 | C
!   4 |                 2 | D
!   5 |                 0 | E
!   6 |                 4 | F
!   7 |                 5 | G
! (24 rows)
! 
! with recursive q as (
!       select * from department
!     union all
!       (with recursive x as (
!            select * from department
!          union all
!            (select * from q union all select * from x)
!         )
!        select * from x)
!     )
! select * from q limit 32;
!  id | parent_department | name 
! ----+-------------------+------
!   0 |                   | ROOT
!   1 |                 0 | A
!   2 |                 1 | B
!   3 |                 2 | C
!   4 |                 2 | D
!   5 |                 0 | E
!   6 |                 4 | F
!   7 |                 5 | G
!   0 |                   | ROOT
!   1 |                 0 | A
!   2 |                 1 | B
!   3 |                 2 | C
!   4 |                 2 | D
!   5 |                 0 | E
!   6 |                 4 | F
!   7 |                 5 | G
!   0 |                   | ROOT
!   1 |                 0 | A
!   2 |                 1 | B
!   3 |                 2 | C
!   4 |                 2 | D
!   5 |                 0 | E
!   6 |                 4 | F
!   7 |                 5 | G
!   0 |                   | ROOT
!   1 |                 0 | A
!   2 |                 1 | B
!   3 |                 2 | C
!   4 |                 2 | D
!   5 |                 0 | E
!   6 |                 4 | F
!   7 |                 5 | G
! (32 rows)
! 
! -- recursive term has sub-UNION
! WITH RECURSIVE t(i,j) AS (
! 	VALUES (1,2)
! 	UNION ALL
! 	SELECT t2.i, t.j+1 FROM
! 		(SELECT 2 AS i UNION ALL SELECT 3 AS i) AS t2
! 		JOIN t ON (t2.i = t.i+1))
! 	SELECT * FROM t;
!  i | j 
! ---+---
!  1 | 2
!  2 | 3
!  3 | 4
! (3 rows)
! 
! --
! -- different tree example
! --
! CREATE TEMPORARY TABLE tree(
!     id INTEGER PRIMARY KEY,
!     parent_id INTEGER REFERENCES tree(id)
! );
! INSERT INTO tree
! VALUES (1, NULL), (2, 1), (3,1), (4,2), (5,2), (6,2), (7,3), (8,3),
!        (9,4), (10,4), (11,7), (12,7), (13,7), (14, 9), (15,11), (16,11);
! --
! -- get all paths from "second level" nodes to leaf nodes
! --
! WITH RECURSIVE t(id, path) AS (
!     VALUES(1,ARRAY[]::integer[])
! UNION ALL
!     SELECT tree.id, t.path || tree.id
!     FROM tree JOIN t ON (tree.parent_id = t.id)
! )
! SELECT t1.*, t2.* FROM t AS t1 JOIN t AS t2 ON
! 	(t1.path[1] = t2.path[1] AND
! 	array_upper(t1.path,1) = 1 AND
! 	array_upper(t2.path,1) > 1)
! 	ORDER BY t1.id, t2.id;
!  id | path | id |    path     
! ----+------+----+-------------
!   2 | {2}  |  4 | {2,4}
!   2 | {2}  |  5 | {2,5}
!   2 | {2}  |  6 | {2,6}
!   2 | {2}  |  9 | {2,4,9}
!   2 | {2}  | 10 | {2,4,10}
!   2 | {2}  | 14 | {2,4,9,14}
!   3 | {3}  |  7 | {3,7}
!   3 | {3}  |  8 | {3,8}
!   3 | {3}  | 11 | {3,7,11}
!   3 | {3}  | 12 | {3,7,12}
!   3 | {3}  | 13 | {3,7,13}
!   3 | {3}  | 15 | {3,7,11,15}
!   3 | {3}  | 16 | {3,7,11,16}
! (13 rows)
! 
! -- just count 'em
! WITH RECURSIVE t(id, path) AS (
!     VALUES(1,ARRAY[]::integer[])
! UNION ALL
!     SELECT tree.id, t.path || tree.id
!     FROM tree JOIN t ON (tree.parent_id = t.id)
! )
! SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON
! 	(t1.path[1] = t2.path[1] AND
! 	array_upper(t1.path,1) = 1 AND
! 	array_upper(t2.path,1) > 1)
! 	GROUP BY t1.id
! 	ORDER BY t1.id;
!  id | count 
! ----+-------
!   2 |     6
!   3 |     7
! (2 rows)
! 
! -- this variant tickled a whole-row-variable bug in 8.4devel
! WITH RECURSIVE t(id, path) AS (
!     VALUES(1,ARRAY[]::integer[])
! UNION ALL
!     SELECT tree.id, t.path || tree.id
!     FROM tree JOIN t ON (tree.parent_id = t.id)
! )
! SELECT t1.id, t2.path, t2 FROM t AS t1 JOIN t AS t2 ON
! (t1.id=t2.id);
!  id |    path     |         t2         
! ----+-------------+--------------------
!   1 | {}          | (1,{})
!   2 | {2}         | (2,{2})
!   3 | {3}         | (3,{3})
!   4 | {2,4}       | (4,"{2,4}")
!   5 | {2,5}       | (5,"{2,5}")
!   6 | {2,6}       | (6,"{2,6}")
!   7 | {3,7}       | (7,"{3,7}")
!   8 | {3,8}       | (8,"{3,8}")
!   9 | {2,4,9}     | (9,"{2,4,9}")
!  10 | {2,4,10}    | (10,"{2,4,10}")
!  11 | {3,7,11}    | (11,"{3,7,11}")
!  12 | {3,7,12}    | (12,"{3,7,12}")
!  13 | {3,7,13}    | (13,"{3,7,13}")
!  14 | {2,4,9,14}  | (14,"{2,4,9,14}")
!  15 | {3,7,11,15} | (15,"{3,7,11,15}")
!  16 | {3,7,11,16} | (16,"{3,7,11,16}")
! (16 rows)
! 
! --
! -- test cycle detection
! --
! create temp table graph( f int, t int, label text );
! insert into graph values
! 	(1, 2, 'arc 1 -> 2'),
! 	(1, 3, 'arc 1 -> 3'),
! 	(2, 3, 'arc 2 -> 3'),
! 	(1, 4, 'arc 1 -> 4'),
! 	(4, 5, 'arc 4 -> 5'),
! 	(5, 1, 'arc 5 -> 1');
! with recursive search_graph(f, t, label, path, cycle) as (
! 	select *, array[row(g.f, g.t)], false from graph g
! 	union all
! 	select g.*, path || row(g.f, g.t), row(g.f, g.t) = any(path)
! 	from graph g, search_graph sg
! 	where g.f = sg.t and not cycle
! )
! select * from search_graph;
!  f | t |   label    |                   path                    | cycle 
! ---+---+------------+-------------------------------------------+-------
!  1 | 2 | arc 1 -> 2 | {"(1,2)"}                                 | f
!  1 | 3 | arc 1 -> 3 | {"(1,3)"}                                 | f
!  2 | 3 | arc 2 -> 3 | {"(2,3)"}                                 | f
!  1 | 4 | arc 1 -> 4 | {"(1,4)"}                                 | f
!  4 | 5 | arc 4 -> 5 | {"(4,5)"}                                 | f
!  5 | 1 | arc 5 -> 1 | {"(5,1)"}                                 | f
!  1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"}                         | f
!  1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"}                         | f
!  1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"}                         | f
!  2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"}                         | f
!  4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"}                         | f
!  5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"}                         | f
!  1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"}                 | f
!  1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"}                 | f
!  1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"}                 | f
!  2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"}                 | f
!  4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"}                 | f
!  5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"}                 | f
!  1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"}         | f
!  1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"}         | f
!  1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"}         | t
!  2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"}         | f
!  4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"}         | t
!  5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"}         | t
!  2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f
! (25 rows)
! 
! -- ordering by the path column has same effect as SEARCH DEPTH FIRST
! with recursive search_graph(f, t, label, path, cycle) as (
! 	select *, array[row(g.f, g.t)], false from graph g
! 	union all
! 	select g.*, path || row(g.f, g.t), row(g.f, g.t) = any(path)
! 	from graph g, search_graph sg
! 	where g.f = sg.t and not cycle
! )
! select * from search_graph order by path;
!  f | t |   label    |                   path                    | cycle 
! ---+---+------------+-------------------------------------------+-------
!  1 | 2 | arc 1 -> 2 | {"(1,2)"}                                 | f
!  2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"}                         | f
!  1 | 3 | arc 1 -> 3 | {"(1,3)"}                                 | f
!  1 | 4 | arc 1 -> 4 | {"(1,4)"}                                 | f
!  4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"}                         | f
!  5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"}                 | f
!  1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"}         | f
!  2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f
!  1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"}         | f
!  1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"}         | t
!  2 | 3 | arc 2 -> 3 | {"(2,3)"}                                 | f
!  4 | 5 | arc 4 -> 5 | {"(4,5)"}                                 | f
!  5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"}                         | f
!  1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"}                 | f
!  2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"}         | f
!  1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"}                 | f
!  1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"}                 | f
!  4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"}         | t
!  5 | 1 | arc 5 -> 1 | {"(5,1)"}                                 | f
!  1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"}                         | f
!  2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"}                 | f
!  1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"}                         | f
!  1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"}                         | f
!  4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"}                 | f
!  5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"}         | t
! (25 rows)
! 
! --
! -- test multiple WITH queries
! --
! WITH RECURSIVE
!   y (id) AS (VALUES (1)),
!   x (id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5)
! SELECT * FROM x;
!  id 
! ----
!   1
!   2
!   3
!   4
!   5
! (5 rows)
! 
! -- forward reference OK
! WITH RECURSIVE
!     x(id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5),
!     y(id) AS (values (1))
!  SELECT * FROM x;
!  id 
! ----
!   1
!   2
!   3
!   4
!   5
! (5 rows)
! 
! WITH RECURSIVE
!    x(id) AS
!      (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
!    y(id) AS
!      (VALUES (1) UNION ALL SELECT id+1 FROM y WHERE id < 10)
!  SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
!  id | id 
! ----+----
!   1 |  1
!   2 |  2
!   3 |  3
!   4 |  4
!   5 |  5
!   6 |   
!   7 |   
!   8 |   
!   9 |   
!  10 |   
! (10 rows)
! 
! WITH RECURSIVE
!    x(id) AS
!      (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
!    y(id) AS
!      (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 10)
!  SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
!  id | id 
! ----+----
!   1 |  1
!   2 |  2
!   3 |  3
!   4 |  4
!   5 |  5
!   6 |   
! (6 rows)
! 
! WITH RECURSIVE
!    x(id) AS
!      (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
!    y(id) AS
!      (SELECT * FROM x UNION ALL SELECT * FROM x),
!    z(id) AS
!      (SELECT * FROM x UNION ALL SELECT id+1 FROM z WHERE id < 10)
!  SELECT * FROM z;
!  id 
! ----
!   1
!   2
!   3
!   2
!   3
!   4
!   3
!   4
!   5
!   4
!   5
!   6
!   5
!   6
!   7
!   6
!   7
!   8
!   7
!   8
!   9
!   8
!   9
!  10
!   9
!  10
!  10
! (27 rows)
! 
! WITH RECURSIVE
!    x(id) AS
!      (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
!    y(id) AS
!      (SELECT * FROM x UNION ALL SELECT * FROM x),
!    z(id) AS
!      (SELECT * FROM y UNION ALL SELECT id+1 FROM z WHERE id < 10)
!  SELECT * FROM z;
!  id 
! ----
!   1
!   2
!   3
!   1
!   2
!   3
!   2
!   3
!   4
!   2
!   3
!   4
!   3
!   4
!   5
!   3
!   4
!   5
!   4
!   5
!   6
!   4
!   5
!   6
!   5
!   6
!   7
!   5
!   6
!   7
!   6
!   7
!   8
!   6
!   7
!   8
!   7
!   8
!   9
!   7
!   8
!   9
!   8
!   9
!  10
!   8
!   9
!  10
!   9
!  10
!   9
!  10
!  10
!  10
! (54 rows)
! 
! --
! -- Test WITH attached to a data-modifying statement
! --
! CREATE TEMPORARY TABLE y (a INTEGER);
! INSERT INTO y SELECT generate_series(1, 10);
! WITH t AS (
! 	SELECT a FROM y
! )
! INSERT INTO y
! SELECT a+20 FROM t RETURNING *;
!  a  
! ----
!  21
!  22
!  23
!  24
!  25
!  26
!  27
!  28
!  29
!  30
! (10 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!  21
!  22
!  23
!  24
!  25
!  26
!  27
!  28
!  29
!  30
! (20 rows)
! 
! WITH t AS (
! 	SELECT a FROM y
! )
! UPDATE y SET a = y.a-10 FROM t WHERE y.a > 20 AND t.a = y.a RETURNING y.a;
!  a  
! ----
!  11
!  12
!  13
!  14
!  15
!  16
!  17
!  18
!  19
!  20
! (10 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!  11
!  12
!  13
!  14
!  15
!  16
!  17
!  18
!  19
!  20
! (20 rows)
! 
! WITH RECURSIVE t(a) AS (
! 	SELECT 11
! 	UNION ALL
! 	SELECT a+1 FROM t WHERE a < 50
! )
! DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
!  a  
! ----
!  11
!  12
!  13
!  14
!  15
!  16
!  17
!  18
!  19
!  20
! (10 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
! (10 rows)
! 
! DROP TABLE y;
! --
! -- error cases
! --
! -- INTERSECT
! WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
! 	SELECT * FROM x;
! ERROR:  recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
! LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x...
!                        ^
! WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x)
! 	SELECT * FROM x;
! ERROR:  recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
! LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FR...
!                        ^
! -- EXCEPT
! WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
! 	SELECT * FROM x;
! ERROR:  recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
! LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
!                        ^
! WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x)
! 	SELECT * FROM x;
! ERROR:  recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
! LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM ...
!                        ^
! -- no non-recursive term
! WITH RECURSIVE x(n) AS (SELECT n FROM x)
! 	SELECT * FROM x;
! ERROR:  recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
! LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x)
!                        ^
! -- recursive term in the left hand side (strictly speaking, should allow this)
! WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1)
! 	SELECT * FROM x;
! ERROR:  recursive reference to query "x" must not appear within its non-recursive term
! LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1)
!                                               ^
! CREATE TEMPORARY TABLE y (a INTEGER);
! INSERT INTO y SELECT generate_series(1, 10);
! -- LEFT JOIN
! WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
! 	UNION ALL
! 	SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10)
! SELECT * FROM x;
! ERROR:  recursive reference to query "x" must not appear within an outer join
! LINE 3:  SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10)
!                                        ^
! -- RIGHT JOIN
! WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
! 	UNION ALL
! 	SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10)
! SELECT * FROM x;
! ERROR:  recursive reference to query "x" must not appear within an outer join
! LINE 3:  SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10)
!                            ^
! -- FULL JOIN
! WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
! 	UNION ALL
! 	SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10)
! SELECT * FROM x;
! ERROR:  recursive reference to query "x" must not appear within an outer join
! LINE 3:  SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10)
!                            ^
! -- subquery
! WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x
!                           WHERE n IN (SELECT * FROM x))
!   SELECT * FROM x;
! ERROR:  recursive reference to query "x" must not appear within a subquery
! LINE 2:                           WHERE n IN (SELECT * FROM x))
!                                                             ^
! -- aggregate functions
! WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM x)
!   SELECT * FROM x;
! ERROR:  aggregate functions are not allowed in a recursive query's recursive term
! LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) F...
!                                                           ^
! WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM x)
!   SELECT * FROM x;
! ERROR:  aggregate functions are not allowed in a recursive query's recursive term
! LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FRO...
!                                                           ^
! -- ORDER BY
! WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1)
!   SELECT * FROM x;
! ERROR:  ORDER BY in a recursive query is not implemented
! LINE 1: ...VE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1)
!                                                                      ^
! -- LIMIT/OFFSET
! WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1)
!   SELECT * FROM x;
! ERROR:  OFFSET in a recursive query is not implemented
! LINE 1: ... AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1)
!                                                                      ^
! -- FOR UPDATE
! WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x FOR UPDATE)
!   SELECT * FROM x;
! ERROR:  FOR UPDATE/SHARE in a recursive query is not implemented
! -- target list has a recursive query name
! WITH RECURSIVE x(id) AS (values (1)
!     UNION ALL
!     SELECT (SELECT * FROM x) FROM x WHERE id < 5
! ) SELECT * FROM x;
! ERROR:  recursive reference to query "x" must not appear within a subquery
! LINE 3:     SELECT (SELECT * FROM x) FROM x WHERE id < 5
!                                   ^
! -- mutual recursive query (not implemented)
! WITH RECURSIVE
!   x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id < 5),
!   y (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 5)
! SELECT * FROM x;
! ERROR:  mutual recursion between WITH items is not implemented
! LINE 2:   x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id ...
!           ^
! -- non-linear recursion is not allowed
! WITH RECURSIVE foo(i) AS
!     (values (1)
!     UNION ALL
!        (SELECT i+1 FROM foo WHERE i < 10
!           UNION ALL
!        SELECT i+1 FROM foo WHERE i < 5)
! ) SELECT * FROM foo;
! ERROR:  recursive reference to query "foo" must not appear more than once
! LINE 6:        SELECT i+1 FROM foo WHERE i < 5)
!                                ^
! WITH RECURSIVE foo(i) AS
!     (values (1)
!     UNION ALL
! 	   SELECT * FROM
!        (SELECT i+1 FROM foo WHERE i < 10
!           UNION ALL
!        SELECT i+1 FROM foo WHERE i < 5) AS t
! ) SELECT * FROM foo;
! ERROR:  recursive reference to query "foo" must not appear more than once
! LINE 7:        SELECT i+1 FROM foo WHERE i < 5) AS t
!                                ^
! WITH RECURSIVE foo(i) AS
!     (values (1)
!     UNION ALL
!        (SELECT i+1 FROM foo WHERE i < 10
!           EXCEPT
!        SELECT i+1 FROM foo WHERE i < 5)
! ) SELECT * FROM foo;
! ERROR:  recursive reference to query "foo" must not appear within EXCEPT
! LINE 6:        SELECT i+1 FROM foo WHERE i < 5)
!                                ^
! WITH RECURSIVE foo(i) AS
!     (values (1)
!     UNION ALL
!        (SELECT i+1 FROM foo WHERE i < 10
!           INTERSECT
!        SELECT i+1 FROM foo WHERE i < 5)
! ) SELECT * FROM foo;
! ERROR:  recursive reference to query "foo" must not appear more than once
! LINE 6:        SELECT i+1 FROM foo WHERE i < 5)
!                                ^
! -- Wrong type induced from non-recursive term
! WITH RECURSIVE foo(i) AS
!    (SELECT i FROM (VALUES(1),(2)) t(i)
!    UNION ALL
!    SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10)
! SELECT * FROM foo;
! ERROR:  recursive query "foo" column 1 has type integer in non-recursive term but type numeric overall
! LINE 2:    (SELECT i FROM (VALUES(1),(2)) t(i)
!                    ^
! HINT:  Cast the output of the non-recursive term to the correct type.
! -- rejects different typmod, too (should we allow this?)
! WITH RECURSIVE foo(i) AS
!    (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i)
!    UNION ALL
!    SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10)
! SELECT * FROM foo;
! ERROR:  recursive query "foo" column 1 has type numeric(3,0) in non-recursive term but type numeric overall
! LINE 2:    (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i)
!                    ^
! HINT:  Cast the output of the non-recursive term to the correct type.
! -- disallow OLD/NEW reference in CTE
! CREATE TEMPORARY TABLE x (n integer);
! CREATE RULE r2 AS ON UPDATE TO x DO INSTEAD
!     WITH t AS (SELECT OLD.*) UPDATE y SET a = t.n FROM t;
! ERROR:  cannot refer to OLD within WITH query
! --
! -- test for bug #4902
! --
! with cte(foo) as ( values(42) ) values((select foo from cte));
!  column1 
! ---------
!       42
! (1 row)
! 
! with cte(foo) as ( select 42 ) select * from ((select foo from cte)) q;
!  foo 
! -----
!   42
! (1 row)
! 
! -- test CTE referencing an outer-level variable (to see that changed-parameter
! -- signaling still works properly after fixing this bug)
! select ( with cte(foo) as ( values(f1) )
!          select (select foo from cte) )
! from int4_tbl;
!      foo     
! -------------
!            0
!       123456
!      -123456
!   2147483647
!  -2147483647
! (5 rows)
! 
! select ( with cte(foo) as ( values(f1) )
!           values((select foo from cte)) )
! from int4_tbl;
!    column1   
! -------------
!            0
!       123456
!      -123456
!   2147483647
!  -2147483647
! (5 rows)
! 
! --
! -- test for nested-recursive-WITH bug
! --
! WITH RECURSIVE t(j) AS (
!     WITH RECURSIVE s(i) AS (
!         VALUES (1)
!         UNION ALL
!         SELECT i+1 FROM s WHERE i < 10
!     )
!     SELECT i FROM s
!     UNION ALL
!     SELECT j+1 FROM t WHERE j < 10
! )
! SELECT * FROM t;
!  j  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!   5
!   6
!   7
!   8
!   9
!  10
!   6
!   7
!   8
!   9
!  10
!   7
!   8
!   9
!  10
!   8
!   9
!  10
!   9
!  10
!  10
! (55 rows)
! 
! --
! -- test WITH attached to intermediate-level set operation
! --
! WITH outermost(x) AS (
!   SELECT 1
!   UNION (WITH innermost as (SELECT 2)
!          SELECT * FROM innermost
!          UNION SELECT 3)
! )
! SELECT * FROM outermost;
!  x 
! ---
!  1
!  2
!  3
! (3 rows)
! 
! WITH outermost(x) AS (
!   SELECT 1
!   UNION (WITH innermost as (SELECT 2)
!          SELECT * FROM outermost  -- fail
!          UNION SELECT * FROM innermost)
! )
! SELECT * FROM outermost;
! ERROR:  relation "outermost" does not exist
! LINE 4:          SELECT * FROM outermost  
!                                ^
! DETAIL:  There is a WITH item named "outermost", but it cannot be referenced from this part of the query.
! HINT:  Use WITH RECURSIVE, or re-order the WITH items to remove forward references.
! WITH RECURSIVE outermost(x) AS (
!   SELECT 1
!   UNION (WITH innermost as (SELECT 2)
!          SELECT * FROM outermost
!          UNION SELECT * FROM innermost)
! )
! SELECT * FROM outermost;
!  x 
! ---
!  1
!  2
! (2 rows)
! 
! WITH RECURSIVE outermost(x) AS (
!   WITH innermost as (SELECT 2 FROM outermost) -- fail
!     SELECT * FROM innermost
!     UNION SELECT * from outermost
! )
! SELECT * FROM outermost;
! ERROR:  recursive reference to query "outermost" must not appear within a subquery
! LINE 2:   WITH innermost as (SELECT 2 FROM outermost) 
!                                            ^
! --
! -- This test will fail with the old implementation of PARAM_EXEC parameter
! -- assignment, because the "q1" Var passed down to A's targetlist subselect
! -- looks exactly like the "A.id" Var passed down to C's subselect, causing
! -- the old code to give them the same runtime PARAM_EXEC slot.  But the
! -- lifespans of the two parameters overlap, thanks to B also reading A.
! --
! with
! A as ( select q2 as id, (select q1) as x from int8_tbl ),
! B as ( select id, row_number() over (partition by id) as r from A ),
! C as ( select A.id, array(select B.id from B where B.id = A.id) from A )
! select * from C;
!         id         |                array                
! -------------------+-------------------------------------
!                456 | {456}
!   4567890123456789 | {4567890123456789,4567890123456789}
!                123 | {123}
!   4567890123456789 | {4567890123456789,4567890123456789}
!  -4567890123456789 | {-4567890123456789}
! (5 rows)
! 
! --
! -- Test CTEs read in non-initialization orders
! --
! WITH RECURSIVE
!   tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)),
!   iter (id_key, row_type, link) AS (
!       SELECT 0, 'base', 17
!     UNION ALL (
!       WITH remaining(id_key, row_type, link, min) AS (
!         SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER ()
!         FROM tab INNER JOIN iter USING (link)
!         WHERE tab.id_key > iter.id_key
!       ),
!       first_remaining AS (
!         SELECT id_key, row_type, link
!         FROM remaining
!         WHERE id_key=min
!       ),
!       effect AS (
!         SELECT tab.id_key, 'new'::text, tab.link
!         FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key
!         WHERE e.row_type = 'false'
!       )
!       SELECT * FROM first_remaining
!       UNION ALL SELECT * FROM effect
!     )
!   )
! SELECT * FROM iter;
!  id_key | row_type | link 
! --------+----------+------
!       0 | base     |   17
!       1 | true     |   17
!       2 | true     |   17
!       3 | true     |   17
!       4 | true     |   17
!       5 | true     |   17
!       6 | true     |   17
! (7 rows)
! 
! WITH RECURSIVE
!   tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)),
!   iter (id_key, row_type, link) AS (
!       SELECT 0, 'base', 17
!     UNION (
!       WITH remaining(id_key, row_type, link, min) AS (
!         SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER ()
!         FROM tab INNER JOIN iter USING (link)
!         WHERE tab.id_key > iter.id_key
!       ),
!       first_remaining AS (
!         SELECT id_key, row_type, link
!         FROM remaining
!         WHERE id_key=min
!       ),
!       effect AS (
!         SELECT tab.id_key, 'new'::text, tab.link
!         FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key
!         WHERE e.row_type = 'false'
!       )
!       SELECT * FROM first_remaining
!       UNION ALL SELECT * FROM effect
!     )
!   )
! SELECT * FROM iter;
!  id_key | row_type | link 
! --------+----------+------
!       0 | base     |   17
!       1 | true     |   17
!       2 | true     |   17
!       3 | true     |   17
!       4 | true     |   17
!       5 | true     |   17
!       6 | true     |   17
! (7 rows)
! 
! --
! -- Data-modifying statements in WITH
! --
! -- INSERT ... RETURNING
! WITH t AS (
!     INSERT INTO y
!     VALUES
!         (11),
!         (12),
!         (13),
!         (14),
!         (15),
!         (16),
!         (17),
!         (18),
!         (19),
!         (20)
!     RETURNING *
! )
! SELECT * FROM t;
!  a  
! ----
!  11
!  12
!  13
!  14
!  15
!  16
!  17
!  18
!  19
!  20
! (10 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!  11
!  12
!  13
!  14
!  15
!  16
!  17
!  18
!  19
!  20
! (20 rows)
! 
! -- UPDATE ... RETURNING
! WITH t AS (
!     UPDATE y
!     SET a=a+1
!     RETURNING *
! )
! SELECT * FROM t;
!  a  
! ----
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!  11
!  12
!  13
!  14
!  15
!  16
!  17
!  18
!  19
!  20
!  21
! (20 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!  11
!  12
!  13
!  14
!  15
!  16
!  17
!  18
!  19
!  20
!  21
! (20 rows)
! 
! -- DELETE ... RETURNING
! WITH t AS (
!     DELETE FROM y
!     WHERE a <= 10
!     RETURNING *
! )
! SELECT * FROM t;
!  a  
! ----
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
! (9 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!  11
!  12
!  13
!  14
!  15
!  16
!  17
!  18
!  19
!  20
!  21
! (11 rows)
! 
! -- forward reference
! WITH RECURSIVE t AS (
! 	INSERT INTO y
! 		SELECT a+5 FROM t2 WHERE a > 5
! 	RETURNING *
! ), t2 AS (
! 	UPDATE y SET a=a-11 RETURNING *
! )
! SELECT * FROM t
! UNION ALL
! SELECT * FROM t2;
!  a  
! ----
!  11
!  12
!  13
!  14
!  15
!   0
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
! (16 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!   0
!   1
!   2
!   3
!   4
!   5
!   6
!  11
!   7
!  12
!   8
!  13
!   9
!  14
!  10
!  15
! (16 rows)
! 
! -- unconditional DO INSTEAD rule
! CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD
!   INSERT INTO y VALUES(42) RETURNING *;
! WITH t AS (
! 	DELETE FROM y RETURNING *
! )
! SELECT * FROM t;
!  a  
! ----
!  42
! (1 row)
! 
! SELECT * FROM y;
!  a  
! ----
!   0
!   1
!   2
!   3
!   4
!   5
!   6
!  11
!   7
!  12
!   8
!  13
!   9
!  14
!  10
!  15
!  42
! (17 rows)
! 
! DROP RULE y_rule ON y;
! -- check merging of outer CTE with CTE in a rule action
! CREATE TEMP TABLE bug6051 AS
!   select i from generate_series(1,3) as t(i);
! SELECT * FROM bug6051;
!  i 
! ---
!  1
!  2
!  3
! (3 rows)
! 
! WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
! INSERT INTO bug6051 SELECT * FROM t1;
! SELECT * FROM bug6051;
!  i 
! ---
!  1
!  2
!  3
! (3 rows)
! 
! CREATE TEMP TABLE bug6051_2 (i int);
! CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
!  INSERT INTO bug6051_2
!  SELECT NEW.i;
! WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
! INSERT INTO bug6051 SELECT * FROM t1;
! SELECT * FROM bug6051;
!  i 
! ---
! (0 rows)
! 
! SELECT * FROM bug6051_2;
!  i 
! ---
!  1
!  2
!  3
! (3 rows)
! 
! -- a truly recursive CTE in the same list
! WITH RECURSIVE t(a) AS (
! 	SELECT 0
! 		UNION ALL
! 	SELECT a+1 FROM t WHERE a+1 < 5
! ), t2 as (
! 	INSERT INTO y
! 		SELECT * FROM t RETURNING *
! )
! SELECT * FROM t2 JOIN y USING (a) ORDER BY a;
!  a 
! ---
!  0
!  1
!  2
!  3
!  4
! (5 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!   0
!   1
!   2
!   3
!   4
!   5
!   6
!  11
!   7
!  12
!   8
!  13
!   9
!  14
!  10
!  15
!  42
!   0
!   1
!   2
!   3
!   4
! (22 rows)
! 
! -- data-modifying WITH in a modifying statement
! WITH t AS (
!     DELETE FROM y
!     WHERE a <= 10
!     RETURNING *
! )
! INSERT INTO y SELECT -a FROM t RETURNING *;
!   a  
! -----
!    0
!   -1
!   -2
!   -3
!   -4
!   -5
!   -6
!   -7
!   -8
!   -9
!  -10
!    0
!   -1
!   -2
!   -3
!   -4
! (16 rows)
! 
! SELECT * FROM y;
!   a  
! -----
!   11
!   12
!   13
!   14
!   15
!   42
!    0
!   -1
!   -2
!   -3
!   -4
!   -5
!   -6
!   -7
!   -8
!   -9
!  -10
!    0
!   -1
!   -2
!   -3
!   -4
! (22 rows)
! 
! -- check that WITH query is run to completion even if outer query isn't
! WITH t AS (
!     UPDATE y SET a = a * 100 RETURNING *
! )
! SELECT * FROM t LIMIT 10;
!   a   
! ------
!  1100
!  1200
!  1300
!  1400
!  1500
!  4200
!     0
!  -100
!  -200
!  -300
! (10 rows)
! 
! SELECT * FROM y;
!    a   
! -------
!   1100
!   1200
!   1300
!   1400
!   1500
!   4200
!      0
!   -100
!   -200
!   -300
!   -400
!   -500
!   -600
!   -700
!   -800
!   -900
!  -1000
!      0
!   -100
!   -200
!   -300
!   -400
! (22 rows)
! 
! -- check that run to completion happens in proper ordering
! TRUNCATE TABLE y;
! INSERT INTO y SELECT generate_series(1, 3);
! CREATE TEMPORARY TABLE yy (a INTEGER);
! WITH RECURSIVE t1 AS (
!   INSERT INTO y SELECT * FROM y RETURNING *
! ), t2 AS (
!   INSERT INTO yy SELECT * FROM t1 RETURNING *
! )
! SELECT 1;
!  ?column? 
! ----------
!         1
! (1 row)
! 
! SELECT * FROM y;
!  a 
! ---
!  1
!  2
!  3
!  1
!  2
!  3
! (6 rows)
! 
! SELECT * FROM yy;
!  a 
! ---
!  1
!  2
!  3
! (3 rows)
! 
! WITH RECURSIVE t1 AS (
!   INSERT INTO yy SELECT * FROM t2 RETURNING *
! ), t2 AS (
!   INSERT INTO y SELECT * FROM y RETURNING *
! )
! SELECT 1;
!  ?column? 
! ----------
!         1
! (1 row)
! 
! SELECT * FROM y;
!  a 
! ---
!  1
!  2
!  3
!  1
!  2
!  3
!  1
!  2
!  3
!  1
!  2
!  3
! (12 rows)
! 
! SELECT * FROM yy;
!  a 
! ---
!  1
!  2
!  3
!  1
!  2
!  3
!  1
!  2
!  3
! (9 rows)
! 
! -- triggers
! TRUNCATE TABLE y;
! INSERT INTO y SELECT generate_series(1, 10);
! CREATE FUNCTION y_trigger() RETURNS trigger AS $$
! begin
!   raise notice 'y_trigger: a = %', new.a;
!   return new;
! end;
! $$ LANGUAGE plpgsql;
! CREATE TRIGGER y_trig BEFORE INSERT ON y FOR EACH ROW
!     EXECUTE PROCEDURE y_trigger();
! WITH t AS (
!     INSERT INTO y
!     VALUES
!         (21),
!         (22),
!         (23)
!     RETURNING *
! )
! SELECT * FROM t;
! NOTICE:  y_trigger: a = 21
! NOTICE:  y_trigger: a = 22
! NOTICE:  y_trigger: a = 23
!  a  
! ----
!  21
!  22
!  23
! (3 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!  21
!  22
!  23
! (13 rows)
! 
! DROP TRIGGER y_trig ON y;
! CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH ROW
!     EXECUTE PROCEDURE y_trigger();
! WITH t AS (
!     INSERT INTO y
!     VALUES
!         (31),
!         (32),
!         (33)
!     RETURNING *
! )
! SELECT * FROM t LIMIT 1;
! NOTICE:  y_trigger: a = 31
! NOTICE:  y_trigger: a = 32
! NOTICE:  y_trigger: a = 33
!  a  
! ----
!  31
! (1 row)
! 
! SELECT * FROM y;
!  a  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!  21
!  22
!  23
!  31
!  32
!  33
! (16 rows)
! 
! DROP TRIGGER y_trig ON y;
! CREATE OR REPLACE FUNCTION y_trigger() RETURNS trigger AS $$
! begin
!   raise notice 'y_trigger';
!   return null;
! end;
! $$ LANGUAGE plpgsql;
! CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH STATEMENT
!     EXECUTE PROCEDURE y_trigger();
! WITH t AS (
!     INSERT INTO y
!     VALUES
!         (41),
!         (42),
!         (43)
!     RETURNING *
! )
! SELECT * FROM t;
! NOTICE:  y_trigger
!  a  
! ----
!  41
!  42
!  43
! (3 rows)
! 
! SELECT * FROM y;
!  a  
! ----
!   1
!   2
!   3
!   4
!   5
!   6
!   7
!   8
!   9
!  10
!  21
!  22
!  23
!  31
!  32
!  33
!  41
!  42
!  43
! (19 rows)
! 
! DROP TRIGGER y_trig ON y;
! DROP FUNCTION y_trigger();
! -- WITH attached to inherited UPDATE or DELETE
! CREATE TEMP TABLE parent ( id int, val text );
! CREATE TEMP TABLE child1 ( ) INHERITS ( parent );
! CREATE TEMP TABLE child2 ( ) INHERITS ( parent );
! INSERT INTO parent VALUES ( 1, 'p1' );
! INSERT INTO child1 VALUES ( 11, 'c11' ),( 12, 'c12' );
! INSERT INTO child2 VALUES ( 23, 'c21' ),( 24, 'c22' );
! WITH rcte AS ( SELECT sum(id) AS totalid FROM parent )
! UPDATE parent SET id = id + totalid FROM rcte;
! SELECT * FROM parent;
!  id | val 
! ----+-----
!  72 | p1
!  82 | c11
!  83 | c12
!  94 | c21
!  95 | c22
! (5 rows)
! 
! WITH wcte AS ( INSERT INTO child1 VALUES ( 42, 'new' ) RETURNING id AS newid )
! UPDATE parent SET id = id + newid FROM wcte;
! SELECT * FROM parent;
!  id  | val 
! -----+-----
!  114 | p1
!   42 | new
!  124 | c11
!  125 | c12
!  136 | c21
!  137 | c22
! (6 rows)
! 
! WITH rcte AS ( SELECT max(id) AS maxid FROM parent )
! DELETE FROM parent USING rcte WHERE id = maxid;
! SELECT * FROM parent;
!  id  | val 
! -----+-----
!  114 | p1
!   42 | new
!  124 | c11
!  125 | c12
!  136 | c21
! (5 rows)
! 
! WITH wcte AS ( INSERT INTO child2 VALUES ( 42, 'new2' ) RETURNING id AS newid )
! DELETE FROM parent USING wcte WHERE id = newid;
! SELECT * FROM parent;
!  id  | val  
! -----+------
!  114 | p1
!  124 | c11
!  125 | c12
!  136 | c21
!   42 | new2
! (5 rows)
! 
! -- check EXPLAIN VERBOSE for a wCTE with RETURNING
! EXPLAIN (VERBOSE, COSTS OFF)
! WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
! DELETE FROM a USING wcte WHERE aa = q2;
!                    QUERY PLAN                   
! ------------------------------------------------
!  Delete on public.a
!    CTE wcte
!      ->  Insert on public.int8_tbl
!            Output: int8_tbl.q2
!            ->  Result
!                  Output: 42::bigint, 47::bigint
!    ->  Nested Loop
!          Output: a.ctid, wcte.*
!          Join Filter: (a.aa = wcte.q2)
!          ->  Seq Scan on public.a
!                Output: a.ctid, a.aa
!          ->  CTE Scan on wcte
!                Output: wcte.*, wcte.q2
!    ->  Nested Loop
!          Output: b.ctid, wcte.*
!          Join Filter: (b.aa = wcte.q2)
!          ->  Seq Scan on public.b
!                Output: b.ctid, b.aa
!          ->  CTE Scan on wcte
!                Output: wcte.*, wcte.q2
!    ->  Nested Loop
!          Output: c.ctid, wcte.*
!          Join Filter: (c.aa = wcte.q2)
!          ->  Seq Scan on public.c
!                Output: c.ctid, c.aa
!          ->  CTE Scan on wcte
!                Output: wcte.*, wcte.q2
!    ->  Nested Loop
!          Output: d.ctid, wcte.*
!          Join Filter: (d.aa = wcte.q2)
!          ->  Seq Scan on public.d
!                Output: d.ctid, d.aa
!          ->  CTE Scan on wcte
!                Output: wcte.*, wcte.q2
! (34 rows)
! 
! -- error cases
! -- data-modifying WITH tries to use its own output
! WITH RECURSIVE t AS (
! 	INSERT INTO y
! 		SELECT * FROM t
! )
! VALUES(FALSE);
! ERROR:  recursive query "t" must not contain data-modifying statements
! LINE 1: WITH RECURSIVE t AS (
!                        ^
! -- no RETURNING in a referenced data-modifying WITH
! WITH t AS (
! 	INSERT INTO y VALUES(0)
! )
! SELECT * FROM t;
! ERROR:  WITH query "t" does not have a RETURNING clause
! LINE 4: SELECT * FROM t;
!                       ^
! -- data-modifying WITH allowed only at the top level
! SELECT * FROM (
! 	WITH t AS (UPDATE y SET a=a+1 RETURNING *)
! 	SELECT * FROM t
! ) ss;
! ERROR:  WITH clause containing a data-modifying statement must be at the top level
! LINE 2:  WITH t AS (UPDATE y SET a=a+1 RETURNING *)
!               ^
! -- most variants of rules aren't allowed
! CREATE RULE y_rule AS ON INSERT TO y WHERE a=0 DO INSTEAD DELETE FROM y;
! WITH t AS (
! 	INSERT INTO y VALUES(0)
! )
! VALUES(FALSE);
! ERROR:  conditional DO INSTEAD rules are not supported for data-modifying statements in WITH
! DROP RULE y_rule ON y;
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/xml_1.out	2014-01-02 13:19:06.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/xml.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,805 ****
! CREATE TABLE xmltest (
!     id int,
!     data xml
! );
! INSERT INTO xmltest VALUES (1, '<value>one</value>');
! ERROR:  unsupported XML feature
! LINE 1: INSERT INTO xmltest VALUES (1, '<value>one</value>');
!                                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! INSERT INTO xmltest VALUES (2, '<value>two</value>');
! ERROR:  unsupported XML feature
! LINE 1: INSERT INTO xmltest VALUES (2, '<value>two</value>');
!                                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! INSERT INTO xmltest VALUES (3, '<wrong');
! ERROR:  unsupported XML feature
! LINE 1: INSERT INTO xmltest VALUES (3, '<wrong');
!                                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT * FROM xmltest;
!  id | data 
! ----+------
! (0 rows)
! 
! SELECT xmlcomment('test');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlcomment('-test');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlcomment('test-');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlcomment('--test');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlcomment('te st');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlconcat(xmlcomment('hello'),
!                  xmlelement(NAME qux, 'foo'),
!                  xmlcomment('world'));
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlconcat('hello', 'you');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlconcat('hello', 'you');
!                          ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlconcat(1, 2);
! ERROR:  argument of XMLCONCAT must be type xml, not type integer
! LINE 1: SELECT xmlconcat(1, 2);
!                          ^
! SELECT xmlconcat('bad', '<syntax');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlconcat('bad', '<syntax');
!                          ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlconcat('<foo/>', NULL, '<?xml version="1.1" standa...
!                          ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml version="1.1" standalone="no"?><bar/>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlconcat('<?xml version="1.1"?><foo/>', NULL, '<?xml...
!                          ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlconcat(NULL);
!  xmlconcat 
! -----------
!  
! (1 row)
! 
! SELECT xmlconcat(NULL, NULL);
!  xmlconcat 
! -----------
!  
! (1 row)
! 
! SELECT xmlelement(name element,
!                   xmlattributes (1 as one, 'deuce' as two),
!                   'content');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name element,
!                   xmlattributes ('unnamed and wrong'));
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name element, xmlelement(name nested, 'stuff'));
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name duplicate, xmlattributes(1 as a, 2 as b, 3 as a));
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name num, 37);
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name foo, text 'bar');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name foo, xml 'bar');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name foo, text 'b<a/>r');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name foo, xml 'b<a/>r');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name foo, array[1, 2, 3]);
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SET xmlbinary TO base64;
! SELECT xmlelement(name foo, bytea 'bar');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SET xmlbinary TO hex;
! SELECT xmlelement(name foo, bytea 'bar');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name foo, xmlattributes(true as bar));
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name foo, xmlattributes('2009-04-09 00:24:37'::timestamp as bar));
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name foo, xmlattributes('infinity'::timestamp as bar));
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlelement(name foo, xmlattributes('<>&"''' as funny, xml 'b<a/>r' as funnier));
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(content 'abc');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(content '<abc>x</abc>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(content '<invalidentity>&</invalidentity>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(content '<undefinedentity>&idontexist;</undefinedentity>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(content '<invalidns xmlns=''&lt;''/>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(content '<relativens xmlns=''relative''/>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(content '<twoerrors>&idontexist;</unbalanced>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(content '<nosuchprefix:tag/>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(document 'abc');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(document '<abc>x</abc>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(document '<invalidentity>&</abc>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(document '<undefinedentity>&idontexist;</abc>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(document '<invalidns xmlns=''&lt;''/>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(document '<relativens xmlns=''relative''/>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(document '<twoerrors>&idontexist;</unbalanced>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlparse(document '<nosuchprefix:tag/>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name foo);
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name xml);
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name xmlstuff);
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name foo, 'bar');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name foo, 'in?>valid');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name foo, null);
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name xml, null);
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name xmlstuff, null);
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name "xml-stylesheet", 'href="mystyle.css" type="text/css"');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name foo, '   bar');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlroot(xml '<foo/>', version no value, standalone no value);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlroot(xml '<foo/>', version no value, standalone no...
!                            ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlroot(xml '<foo/>', version '2.0');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlroot(xml '<foo/>', version '2.0');
!                            ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlroot(xml '<foo/>', version no value, standalone ye...
!                            ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no value, standalone yes);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlroot(xml '<?xml version="1.1"?><foo/>', version no...
!                            ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version '1.1', standalone no);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlroot(xmlroot(xml '<foo/>', version '1.0'), version...
!                                    ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>...
!                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value, standalone no value);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>...
!                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>', version no value);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlroot('<?xml version="1.1" standalone="yes"?><foo/>...
!                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlroot (
!   xmlelement (
!     name gazonk,
!     xmlattributes (
!       'val' AS name,
!       1 + 1 AS num
!     ),
!     xmlelement (
!       NAME qux,
!       'foo'
!     )
!   ),
!   version '1.0',
!   standalone yes
! );
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
!  xmlserialize 
! --------------
! (0 rows)
! 
! SELECT xmlserialize(content 'good' as char(10));
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlserialize(content 'good' as char(10));
!                                     ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlserialize(document 'bad' as text);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xmlserialize(document 'bad' as text);
!                                      ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml '<foo>bar</foo>' IS DOCUMENT;
! ERROR:  unsupported XML feature
! LINE 1: SELECT xml '<foo>bar</foo>' IS DOCUMENT;
!                    ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
! ERROR:  unsupported XML feature
! LINE 1: SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
!                    ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml '<abc/>' IS NOT DOCUMENT;
! ERROR:  unsupported XML feature
! LINE 1: SELECT xml '<abc/>' IS NOT DOCUMENT;
!                    ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml 'abc' IS NOT DOCUMENT;
! ERROR:  unsupported XML feature
! LINE 1: SELECT xml 'abc' IS NOT DOCUMENT;
!                    ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT '<>' IS NOT DOCUMENT;
! ERROR:  unsupported XML feature
! LINE 1: SELECT '<>' IS NOT DOCUMENT;
!                ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlagg(data) FROM xmltest;
!  xmlagg 
! --------
!  
! (1 row)
! 
! SELECT xmlagg(data) FROM xmltest WHERE id > 10;
!  xmlagg 
! --------
!  
! (1 row)
! 
! SELECT xmlelement(name employees, xmlagg(xmlelement(name name, name))) FROM emp;
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! -- Check mapping SQL identifier to XML name
! SELECT xmlpi(name ":::_xml_abc135.%-&_");
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlpi(name "123");
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
! ERROR:  unsupported XML feature
! LINE 1: PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
!                                               ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SET XML OPTION DOCUMENT;
! EXECUTE foo ('<bar/>');
! ERROR:  prepared statement "foo" does not exist
! EXECUTE foo ('bad');
! ERROR:  prepared statement "foo" does not exist
! SET XML OPTION CONTENT;
! EXECUTE foo ('<bar/>');
! ERROR:  prepared statement "foo" does not exist
! EXECUTE foo ('good');
! ERROR:  prepared statement "foo" does not exist
! -- Test backwards parsing
! CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
! CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
! ERROR:  unsupported XML feature
! LINE 1: CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
!                                                  ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp;
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! CREATE VIEW xmlview5 AS SELECT xmlparse(content '<abc>x</abc>');
! CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version no value, standalone yes);
! ERROR:  unsupported XML feature
! LINE 1: CREATE VIEW xmlview7 AS SELECT xmlroot(xml '<foo/>', version...
!                                                    ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
! ERROR:  unsupported XML feature
! LINE 1: ...EATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as ...
!                                                              ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
! ERROR:  unsupported XML feature
! LINE 1: ...EATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as ...
!                                                              ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT table_name, view_definition FROM information_schema.views
!   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
!  table_name |                                view_definition                                 
! ------------+--------------------------------------------------------------------------------
!  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
!  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
! (2 rows)
! 
! -- Text XPath expressions evaluation
! SELECT xpath('/value', data) FROM xmltest;
!  xpath 
! -------
! (0 rows)
! 
! SELECT xpath(NULL, NULL) IS NULL FROM xmltest;
!  ?column? 
! ----------
! (0 rows)
! 
! SELECT xpath('', '<!-- error -->');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('', '<!-- error -->');
!                          ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('//text()', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('//text()', '<local:data xmlns:local="http://12...
!                                  ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>', ARRAY[ARRAY['loc', 'http://127.0.0.1']]);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('//loc:piece/@id', '<local:data xmlns:local="ht...
!                                         ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('//b', '<a>one <b>two</b> three <b>etc</b></a>'...
!                             ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('//text()', '<root>&lt;</root>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('//text()', '<root>&lt;</root>');
!                                  ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('//@value', '<root value="&lt;"/>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('//@value', '<root value="&lt;"/>');
!                                  ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('''<<invalid>>''', '<root/>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('''<<invalid>>''', '<root/>');
!                                         ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('count(//*)', '<root><sub/><sub/></root>');
!                                    ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('count(//*)=0', '<root><sub/><sub/></root>');
!                                      ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('count(//*)=3', '<root><sub/><sub/></root>');
!                                      ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('name(/*)', '<root><sub/><sub/></root>');
!                                  ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath('/nosuchtag', '<root/>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('/nosuchtag', '<root/>');
!                                    ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! -- Test xmlexists and xpath_exists
! SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
! ERROR:  unsupported XML feature
! LINE 1: ...sts('//town[text() = ''Toronto'']' PASSING BY REF '<towns><t...
!                                                              ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>');
! ERROR:  unsupported XML feature
! LINE 1: ...sts('//town[text() = ''Cwmbran'']' PASSING BY REF '<towns><t...
!                                                              ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>');
! ERROR:  unsupported XML feature
! LINE 1: ...LECT xmlexists('count(/nosuchtag)' PASSING BY REF '<root/>')...
!                                                              ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath_exists('//town[text() = ''Toronto'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
! ERROR:  unsupported XML feature
! LINE 1: ...ELECT xpath_exists('//town[text() = ''Toronto'']','<towns><t...
!                                                              ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><town>Bidford-on-Avon</town><town>Cwmbran</town><town>Bristol</town></towns>'::xml);
! ERROR:  unsupported XML feature
! LINE 1: ...ELECT xpath_exists('//town[text() = ''Cwmbran'']','<towns><t...
!                                                              ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath_exists('count(/nosuchtag)', '<root/>'::xml);
!                                                  ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
! ERROR:  unsupported XML feature
! LINE 1: INSERT INTO xmltest VALUES (4, '<menu><beers><name>Budvar</n...
!                                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</name><cost>free</cost><name>Carling</name><cost>lots</cost></beers></menu>'::xml);
! ERROR:  unsupported XML feature
! LINE 1: INSERT INTO xmltest VALUES (5, '<menu><beers><name>Molson</n...
!                                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Budvar</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
! ERROR:  unsupported XML feature
! LINE 1: INSERT INTO xmltest VALUES (6, '<myns:menu xmlns:myns="http:...
!                                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http://myns.com"><myns:beers><myns:name>Molson</myns:name><myns:cost>free</myns:cost><myns:name>Carling</myns:name><myns:cost>lots</myns:cost></myns:beers></myns:menu>'::xml);
! ERROR:  unsupported XML feature
! LINE 1: INSERT INTO xmltest VALUES (7, '<myns:menu xmlns:myns="http:...
!                                        ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data);
!  count 
! -------
!      0
! (1 row)
! 
! SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF);
!  count 
! -------
!      0
! (1 row)
! 
! SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data);
!  count 
! -------
!      0
! (1 row)
! 
! SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data);
!  count 
! -------
!      0
! (1 row)
! 
! SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beer',data);
!  count 
! -------
!      0
! (1 row)
! 
! SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers',data);
!  count 
! -------
!      0
! (1 row)
! 
! SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/menu/beers/name[text() = ''Molson'']',data);
!  count 
! -------
!      0
! (1 row)
! 
! SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beer',data,ARRAY[ARRAY['myns','http://myns.com']]);
!  count 
! -------
!      0
! (1 row)
! 
! SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers',data,ARRAY[ARRAY['myns','http://myns.com']]);
!  count 
! -------
!      0
! (1 row)
! 
! SELECT COUNT(id) FROM xmltest WHERE xpath_exists('/myns:menu/myns:beers/myns:name[text() = ''Molson'']',data,ARRAY[ARRAY['myns','http://myns.com']]);
!  count 
! -------
!      0
! (1 row)
! 
! CREATE TABLE query ( expr TEXT );
! INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']');
! SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);
!  count 
! -------
!      0
! (1 row)
! 
! -- Test xml_is_well_formed and variants
! SELECT xml_is_well_formed_document('<foo>bar</foo>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed_document('abc');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed_content('<foo>bar</foo>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed_content('abc');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SET xmloption TO DOCUMENT;
! SELECT xml_is_well_formed('abc');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<abc/>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<foo>bar</foo>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<foo>bar</foo');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<foo><bar>baz</foo>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<local:data xmlns:local="http://127.0.0.1"><local:piece id="1">number one</local:piece><local:piece id="2" /></local:data>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<invalidentity>&</abc>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<undefinedentity>&idontexist;</abc>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<invalidns xmlns=''&lt;''/>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<relativens xmlns=''relative''/>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT xml_is_well_formed('<twoerrors>&idontexist;</unbalanced>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SET xmloption TO CONTENT;
! SELECT xml_is_well_formed('abc');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! -- Since xpath() deals with namespaces, it's a bit stricter about
! -- what's well-formed and what's not. If we don't obey these rules
! -- (i.e. ignore namespace-related errors from libxml), xpath()
! -- fails in subtle ways. The following would for example produce
! -- the xml value
! --   <invalidns xmlns='<'/>
! -- which is invalid because '<' may not appear un-escaped in
! -- attribute values.
! -- Since different libxml versions emit slightly different
! -- error messages, we suppress the DETAIL in this test.
! \set VERBOSITY terse
! SELECT xpath('/*', '<invalidns xmlns=''&lt;''/>');
! ERROR:  unsupported XML feature at character 20
! \set VERBOSITY default
! -- Again, the XML isn't well-formed for namespace purposes
! SELECT xpath('/*', '<nosuchprefix:tag/>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('/*', '<nosuchprefix:tag/>');
!                            ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! -- XPath deprecates relative namespaces, but they're not supposed to
! -- throw an error, only a warning.
! SELECT xpath('/*', '<relativens xmlns=''relative''/>');
! ERROR:  unsupported XML feature
! LINE 1: SELECT xpath('/*', '<relativens xmlns=''relative''/>');
!                            ^
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! -- External entity references should not leak filesystem information.
! SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>&c;</foo>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
! -- This might or might not load the requested DTD, but it mustn't throw error.
! SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>');
! ERROR:  unsupported XML feature
! DETAIL:  This functionality requires the server to be built with libxml support.
! HINT:  You need to rebuild PostgreSQL using --with-libxml.
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/stats.out	2014-01-02 13:19:06.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/stats.out	2014-01-02 13:27:01.000000000 +0100
***************
*** 1,114 ****
! --
! -- Test Statistics Collector
! --
! -- Must be run after tenk2 has been created (by create_table),
! -- populated (by create_misc) and indexed (by create_index).
! --
! -- conditio sine qua non
! SHOW track_counts;  -- must be on
!  track_counts 
! --------------
!  on
! (1 row)
! 
! -- ensure that both seqscan and indexscan plans are allowed
! SET enable_seqscan TO on;
! SET enable_indexscan TO on;
! -- for the moment, we don't want index-only scans here
! SET enable_indexonlyscan TO off;
! -- wait to let any prior tests finish dumping out stats;
! -- else our messages might get lost due to contention
! SELECT pg_sleep(2.0);
!  pg_sleep 
! ----------
!  
! (1 row)
! 
! -- save counters
! CREATE TEMP TABLE prevstats AS
! SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
!        (b.heap_blks_read + b.heap_blks_hit) AS heap_blks,
!        (b.idx_blks_read + b.idx_blks_hit) AS idx_blks
!   FROM pg_catalog.pg_stat_user_tables AS t,
!        pg_catalog.pg_statio_user_tables AS b
!  WHERE t.relname='tenk2' AND b.relname='tenk2';
! -- function to wait for counters to advance
! create function wait_for_stats() returns void as $$
! declare
!   start_time timestamptz := clock_timestamp();
!   updated bool;
! begin
!   -- we don't want to wait forever; loop will exit after 30 seconds
!   for i in 1 .. 300 loop
! 
!     -- check to see if indexscan has been sensed
!     SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated
!       FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
!      WHERE st.relname='tenk2' AND cl.relname='tenk2';
! 
!     exit when updated;
! 
!     -- wait a little
!     perform pg_sleep(0.1);
! 
!     -- reset stats snapshot so we can test again
!     perform pg_stat_clear_snapshot();
! 
!   end loop;
! 
!   -- report time waited in postmaster log (where it won't change test output)
!   raise log 'wait_for_stats delayed % seconds',
!     extract(epoch from clock_timestamp() - start_time);
! end
! $$ language plpgsql;
! -- do a seqscan
! SELECT count(*) FROM tenk2;
!  count 
! -------
!  10000
! (1 row)
! 
! -- do an indexscan
! SELECT count(*) FROM tenk2 WHERE unique1 = 1;
!  count 
! -------
!      1
! (1 row)
! 
! -- force the rate-limiting logic in pgstat_report_tabstat() to time out
! -- and send a message
! SELECT pg_sleep(1.0);
!  pg_sleep 
! ----------
!  
! (1 row)
! 
! -- wait for stats collector to update
! SELECT wait_for_stats();
!  wait_for_stats 
! ----------------
!  
! (1 row)
! 
! -- check effects
! SELECT st.seq_scan >= pr.seq_scan + 1,
!        st.seq_tup_read >= pr.seq_tup_read + cl.reltuples,
!        st.idx_scan >= pr.idx_scan + 1,
!        st.idx_tup_fetch >= pr.idx_tup_fetch + 1
!   FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
!  WHERE st.relname='tenk2' AND cl.relname='tenk2';
!  ?column? | ?column? | ?column? | ?column? 
! ----------+----------+----------+----------
!  t        | t        | t        | t
! (1 row)
! 
! SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages,
!        st.idx_blks_read + st.idx_blks_hit >= pr.idx_blks + 1
!   FROM pg_statio_user_tables AS st, pg_class AS cl, prevstats AS pr
!  WHERE st.relname='tenk2' AND cl.relname='tenk2';
!  ?column? | ?column? 
! ----------+----------
!  t        | t
! (1 row)
! 
! -- End of Stats Test
--- 1 ----
! psql: FATAL:  the database system is in recovery mode

======================================================================

#42Nicolas Barbier
nicolas.barbier@gmail.com
In reply to: David Rowley (#1)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

2013/12/15 David Rowley <dgrowleyml@gmail.com>:

I've been working on speeding up aggregate functions when used in the
context of a window's with non fixed frame heads.

1. Fully implement negative transition functions for SUM and AVG.

I would like to mention that this functionality is also extremely
useful to have for the incremental maintenance of materialized views
that use aggregation (which IMHO is one of the most useful kinds).

(Simply imagine a view of the form “SELECT a, agg_function(b) FROM
table GROUP BY a”, a lot of rows in the table, a lot of rows in each
group, and changes that both remove and add new rows.)

For this to work, two things are needed:

(1) A way to apply a value normally (already supported) and inversely
(i.e., this patch) to the current “internal state” of an aggregation.

(2) A way to store the “internal state” of an aggregation in the
materialized view’s “extent” (i.e., the physical rows that represent
the view’s contents, which may or may not be slightly different from
what you get when you do SELECT * FROM matview). As (AFAIK) that state
is stored as a normal value, the maintenance code could just take the
value, store it in the extent, and next time retrieve it again and
perform normal or inverse transitions. When selecting from the
matview, the state could be retrieved, and the final function applied
to it to yield the value to be returned.

To understand (2), assume that one wants to store an AVG() in a
materialized view; To be able to update the value incrementally, one
needs to actually store the SUM() and COUNT(), and perform the
division when selecting from the materialized view. Or it could
(initially) be decided to define AVG() as “not supporting fast
incremental maintenance,” and require the user (if he/she wants fast
incremental maintenance) to put SUM() and COUNT() in the materialized
view manually, and perform the division manually when wanting to
retrieve the average.

Nicolas

--
A. Because it breaks the logical sequence of discussion.
Q. Why is top posting bad?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#43Erik Rijkers
er@xs4all.nl
In reply to: Erik Rijkers (#41)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, January 2, 2014 13:36, Erik Rijkers wrote:

On Thu, January 2, 2014 13:05, David Rowley wrote:

here's a slightly updated patch
[inverse_transition_functions_v1.8.patch.gz ]

patch applies, and compiles (although with new warnings).
But make check complains loudly: see attached.

warnings:

The TRACE_POSTGRESQL_SORT_DONE warnings were not from your patch; sorry about that. They occur on HEAD too (with a debug
compile).

tuplesort.c:935:44: warning: comparison between pointer and integer [enabled by default]
TRACE_POSTGRESQL_SORT_DONE(state->tapeset != NULL, spaceUsed);
^
tuplesort.c:935:2: note: in expansion of macro �
TRACE_POSTGRESQL_SORT_DONE(state->tapeset != NULL, spaceUsed);

The 'make check' failure remains a problem

The output I sent earlier today was for this configure:

./configure --prefix=/var/data1/pg_stuff/pg_installations/pgsql.inverse --with-pgport=6594 \
--bindir=/var/data1/pg_stuff/pg_installations/pgsql.inverse/bin \
--libdir=/var/data1/pg_stuff/pg_installations/pgsql.inverse/lib \
--quiet --enable-depend --enable-cassert --enable-debug --with-perl \
--with-openssl --with-libxml --enable-dtrace

(and that's still repeatable)

Perhaps this helps:

with another configure:

./configure --prefix=/var/data1/pg_stuff/pg_installations/pgsql.inverse --with-pgport=6594
--bindir=/var/data1/pg_stuff/pg_installations/pgsql.inverse/bin.fast
--libdir=/var/data1/pg_stuff/pg_installations/pgsql.inverse/lib.fast --quiet --enable-depend --with-perl --with-openssl
--with-libxml

I get only this single 'make check' error:

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/expected/window.out	2014-01-02 16:19:48.000000000 +0100
--- /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/window.out	2014-01-02 16:21:43.000000000 +0100
***************
*** 1188,1195 ****
   sum
  ------
   6.01
!     5
!     3
  (3 rows)
  SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
--- 1188,1195 ----
   sum
  ------
   6.01
!  5.00
!  3.00
  (3 rows)

SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)

======================================================================

Centos 5.7
gcc 4.8.2

Thanks; and Happy New Year

Erik Rijkers

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Tom Lane
tgl@sss.pgh.pa.us
In reply to: Erik Rijkers (#43)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

"Erik Rijkers" <er@xs4all.nl> writes:

The TRACE_POSTGRESQL_SORT_DONE warnings were not from your patch; sorry about that. They occur on HEAD too (with a debug
compile).

tuplesort.c:935:44: warning: comparison between pointer and integer [enabled by default]
TRACE_POSTGRESQL_SORT_DONE(state->tapeset != NULL, spaceUsed);
^
tuplesort.c:935:2: note: in expansion of macro �
TRACE_POSTGRESQL_SORT_DONE(state->tapeset != NULL, spaceUsed);

FWIW, I don't see any such warnings on either of the machines I have that
will accept --enable-dtrace. state->tapeset is a pointer, so these
warnings look a tad bogus.

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

#45David Rowley
dgrowleyml@gmail.com
In reply to: Erik Rijkers (#43)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Fri, Jan 3, 2014 at 5:33 AM, Erik Rijkers <er@xs4all.nl> wrote:

*** /var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/
test/regress/expected/window.out 2014-01-02 16:19:48.000000000 +0100
---
/var/data1/pg_stuff/pg_sandbox/pgsql.inverse/src/test/regress/results/window.out
2014-01-02 16:21:43.000000000 +0100
***************
*** 1188,1195 ****
sum
------
6.01
! 5
! 3
(3 rows)

SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND
UNBOUNDED FOLLOWING)
--- 1188,1195 ----
sum
------
6.01
!  5.00
!  3.00
(3 rows)

I've left those failures in for now in the hope to generate some discussion
on if we can inverse transition for sum(numeric). Please see my email
before the previous one for details. To fix it pg_aggregate.h just needs to
be changed to remove the inverse transition for sum(numeric).

#46Erik Rijkers
er@xs4all.nl
In reply to: Erik Rijkers (#43)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, January 2, 2014 17:33, Erik Rijkers wrote:

On Thu, January 2, 2014 13:36, Erik Rijkers wrote:

On Thu, January 2, 2014 13:05, David Rowley wrote:

here's a slightly updated patch
[inverse_transition_functions_v1.8.patch.gz ]

patch applies, and compiles (although with new warnings).
But make check complains loudly

To figure out where this 'make check' failed, I lifted a few statements from the offending sql:
src/test/regress/sql/window.sql (see snafu.sh below).

That reliably crashes the server. It is caused by a SUM, but only when configured like this (i.e. *not* configured for
speed) :

$ pg_config --configure
'--prefix=/home/aardvark/pg_stuff/pg_installations/pgsql.inverse'
'--bindir=/home/aardvark/pg_stuff/pg_installations/pgsql.inverse/bin'
'--libdir=/home/aardvark/pg_stuff/pg_installations/pgsql.inverse/lib' '--with-pgport=6554' '--enable-depend'
'--enable-cassert' '--enable-debug' '--with-openssl' '--with-perl' '--with-libxml' '--with-libxslt' '--with-ossp-uuid'
'--with-zlib'

$ cat snafu.sh
#!/bin/sh

echo "
--
-- WINDOW FUNCTIONS
--

drop TABLE if exists empsalary ;

-- CREATE TEMPORARY TABLE empsalary (
CREATE TABLE empsalary (
depname varchar,
empno bigint,
salary int,
enroll_date date
);

INSERT INTO empsalary VALUES
('develop', 10, 5200, '2007-08-01'),
('sales', 1, 5000, '2006-10-01'),
('personnel', 5, 3500, '2007-12-10'),
('sales', 4, 4800, '2007-08-08'),
('personnel', 2, 3900, '2006-12-23'),
('develop', 7, 4200, '2008-01-01'),
('develop', 9, 4500, '2008-01-01'),
('sales', 3, 4800, '2007-08-01'),
('develop', 8, 6000, '2006-10-01'),
('develop', 11, 5200, '2007-08-15');

-- SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;

" | psql

echo "select * from empsalary;" | psql

echo "
SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
" | psql

$ ./snafu.sh
Timing is on.
DROP TABLE
Time: 1.093 ms
CREATE TABLE
Time: 80.161 ms
INSERT 0 10
Time: 11.964 ms
Timing is on.
depname | empno | salary | enroll_date
-----------+-------+--------+-------------
develop | 10 | 5200 | 2007-08-01
sales | 1 | 5000 | 2006-10-01
personnel | 5 | 3500 | 2007-12-10
sales | 4 | 4800 | 2007-08-08
personnel | 2 | 3900 | 2006-12-23
develop | 7 | 4200 | 2008-01-01
develop | 9 | 4500 | 2008-01-01
sales | 3 | 4800 | 2007-08-01
develop | 8 | 6000 | 2006-10-01
develop | 11 | 5200 | 2007-08-15
(10 rows)

Time: 1.854 ms
Timing is on.
connection to server was lost

So, to repeat, this runs fine on a server compiled for speed.

I haven't looked any further (whether perhaps more statements are faulty)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Erik Rijkers
er@xs4all.nl
In reply to: Erik Rijkers (#46)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Fri, January 3, 2014 00:09, Erik Rijkers wrote:

connection to server was lost

So, to repeat, this runs fine on a server compiled for speed.

I forgot to append the log messages:

2014-01-03 00:19:17.073 CET 14054 LOG: database system is ready to accept connections
TRAP: FailedAssertion("!(!((bool) ((invtransfn_oid) != ((Oid) 0))))", File: "parse_agg.c", Line: 1255)
2014-01-03 00:19:29.605 CET 14054 LOG: server process (PID 14143) was terminated by signal 6: Aborted
2014-01-03 00:19:29.605 CET 14054 DETAIL: Failed process was running: SELECT depname, empno, salary, sum(salary) OVER
(PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
2014-01-03 00:19:29.605 CET 14054 LOG: terminating any other active server processes
2014-01-03 00:19:29.607 CET 14054 LOG: all server processes terminated; reinitializing
etc. etc.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48David Rowley
dgrowleyml@gmail.com
In reply to: Erik Rijkers (#47)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Fri, Jan 3, 2014 at 12:23 PM, Erik Rijkers <er@xs4all.nl> wrote:

On Fri, January 3, 2014 00:09, Erik Rijkers wrote:

connection to server was lost

So, to repeat, this runs fine on a server compiled for speed.

I forgot to append the log messages:

2014-01-03 00:19:17.073 CET 14054 LOG: database system is ready to accept
connections
TRAP: FailedAssertion("!(!((bool) ((invtransfn_oid) != ((Oid) 0))))",
File: "parse_agg.c", Line: 1255)
2014-01-03 00:19:29.605 CET 14054 LOG: server process (PID 14143) was
terminated by signal 6: Aborted
2014-01-03 00:19:29.605 CET 14054 DETAIL: Failed process was running:
SELECT depname, empno, salary, sum(salary) OVER
(PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
2014-01-03 00:19:29.605 CET 14054 LOG: terminating any other active
server processes
2014-01-03 00:19:29.607 CET 14054 LOG: all server processes terminated;
reinitializing
etc. etc.

hmm, yeah, compiling and testing a build with assets enabled... That's a
good idea!
I probably should have tried that :)
I've attached another patch which should fix this problem.

The single failing SUM(numeric) regression test is still in there and it is
a known failure to do the extra trailing zeros that it can now produce that
would not be present in an unpatched version. It's there purely in the hope
to generate some discussion about it to find out if we can use inverse
transitions for sum(numeric) or not.

Regards

David Rowley

Attachments:

inverse_transition_functions_v1.9.patch.gzapplication/x-gzip; name=inverse_transition_functions_v1.9.patch.gzDownload
#49Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#8)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 15 December 2013 01:57, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Josh Berkus <josh@agliodbs.com> writes:

I think even the FLOAT case deserves some consideration. What's the
worst-case drift?

Complete loss of all significant digits.

The case I was considering earlier of single-row windows could be made
safe (I think) if we apply the negative transition function first, before
incorporating the new row(s). Then for example if you've got float8 1e20
followed by 1, you compute (1e20 - 1e20) + 1 and get the right answer.
It's not so good with two-row windows though:

Table correct sum of negative-transition
this + next value result
1e20 1e20 1e20 + 1 = 1e20
1 1 1e20 - 1e20 + 0 = 0
0

In general, folks who do aggregate operations on
FLOATs aren't expecting an exact answer, or one which is consistent
beyond a certain number of significant digits.

Au contraire. People who know what they're doing expect the results
to be what an IEEE float arithmetic unit would produce for the given
calculation. They know how the roundoff error ought to behave, and they
will not thank us for doing a calculation that's not the one specified.
I will grant you that there are plenty of clueless people out there
who *don't* know this, but they shouldn't be using float arithmetic
anyway.

And Dave is right: how many bug reports would we get about "NUMERIC is
fast, but FLOAT is slow"?

I've said this before, but: we can make it arbitrarily fast if we don't
have to get the right answer. I'd rather get "it's slow" complaints
than "this is the wrong answer" complaints.

Hi,

Reading over this, I realised that there is a problem with NaN
handling --- once the state becomes NaN, it can never recover. So the
results using the inverse transition function don't match HEAD in
cases like this:

create table t(a int, b numeric);
insert into t values(1,1),(2,2),(3,'NaN'),(4,3),(5,4);
select a, b,
sum(b) over(order by a rows between 1 preceding and current row)
from t;

which in HEAD produces:

a | b | sum
---+-----+-----
1 | 1 | 1
2 | 2 | 3
3 | NaN | NaN
4 | 3 | NaN
5 | 4 | 7
(5 rows)

but with this patch produces:

a | b | sum
---+-----+-----
1 | 1 | 1
2 | 2 | 3
3 | NaN | NaN
4 | 3 | NaN
5 | 4 | NaN
(5 rows)

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#49)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

Reading over this, I realised that there is a problem with NaN
handling --- once the state becomes NaN, it can never recover. So the
results using the inverse transition function don't match HEAD in
cases like this:

Ouch! That takes out numeric, float4, and float8 in one fell swoop.

Given the relative infrequency of NaNs in most data, it seems like
it might still be possible to get a speedup if we could use inverse
transitions until we hit a NaN, then do it the hard way until the
NaN is outside the window, then go back to inverse transitions.
I'm not sure though if this is at all practical from an implementation
standpoint. We certainly don't want the core code knowing about
anything as datatype-specific as a NaN, but maybe the inverse transition
function could have an API that allows reporting "I can't do it here,
fall back to the hard way".

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

#51Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#50)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan9, 2014, at 17:15 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

Reading over this, I realised that there is a problem with NaN
handling --- once the state becomes NaN, it can never recover. So the
results using the inverse transition function don't match HEAD in
cases like this:

Ouch! That takes out numeric, float4, and float8 in one fell swoop.

For numeric, it seems that this could be overcome by having the state
be a pair (s numeric, n numeric). s would track the sum of non-NaNs
summands and n would track the number of NaN summands. The final
function would return NaN if n > 0 and s otherwise. The pair could
be represented as a value of type numeric[] to avoid having to invent
a new type for this.

For float 4 and float8, wasn't the consensus that the potential
lossy-ness of addition makes this impossible anyway, even without the
NaN issue? But...

Given the relative infrequency of NaNs in most data, it seems like
it might still be possible to get a speedup if we could use inverse
transitions until we hit a NaN, then do it the hard way until the
NaN is outside the window, then go back to inverse transitions.
I'm not sure though if this is at all practical from an implementation
standpoint. We certainly don't want the core code knowing about
anything as datatype-specific as a NaN, but maybe the inverse transition
function could have an API that allows reporting "I can't do it here,
fall back to the hard way".

that sounds like it might be possible to make things work for float4
and float8 afterall, if we can determine whether a particular addition
was lossy or not.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#51)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

For float 4 and float8, wasn't the consensus that the potential
lossy-ness of addition makes this impossible anyway, even without the
NaN issue? But...

Well, that was my opinion, I'm not sure if it was consensus ;-).
But NaN is an orthogonal problem I think. I'm not sure whether it
has analogues in other data types.

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

#53Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#52)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan9, 2014, at 18:09 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

For float 4 and float8, wasn't the consensus that the potential
lossy-ness of addition makes this impossible anyway, even without the
NaN issue? But...

Well, that was my opinion, I'm not sure if it was consensus ;-).

I'd say your example showing how it could produce completely bogus
results was pretty convincing...

But NaN is an orthogonal problem I think. I'm not sure whether it
has analogues in other data types.

Transfer functions which are partially invertible are not that
uncommon, I'd say. Browsing through 9.3's list of aggregate functions,
the following come to mind

max()
Values smaller than the maximum can be removed, removing the current
maximum requires a rescan. By remembering the N largest values,
the number of required rescans can be reduced, but never fully
eliminated. Same works for min().

bool_or()
FALSE can be removed, removing TRUE requires a rescan. Could be made
fully invertible by counting the number of TRUE and FALSE values,
similar to my suggestion for how to handle NaN for sum(numeric).
Same works for bool_and().

bit_or()
Like boo_or(), 0 can be removed, everything else requires a rescan.
Same works for bit_and()

Plus, any aggregate with a strict transfer function would be in
exactly the same situation regarding NULL as sum(numeric) is regarding
NaN. AFAIK we don't have any such aggregate in core, though.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#54David Rowley
dgrowleyml@gmail.com
In reply to: Dean Rasheed (#49)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Fri, Jan 10, 2014 at 4:09 AM, Dean Rasheed <dean.a.rasheed@gmail.com>wrote:

Hi,

Reading over this, I realised that there is a problem with NaN
handling --- once the state becomes NaN, it can never recover. So the
results using the inverse transition function don't match HEAD in
cases like this:

create table t(a int, b numeric);
insert into t values(1,1),(2,2),(3,'NaN'),(4,3),(5,4);
select a, b,
sum(b) over(order by a rows between 1 preceding and current row)
from t;

which in HEAD produces:

a | b | sum
---+-----+-----
1 | 1 | 1
2 | 2 | 3
3 | NaN | NaN
4 | 3 | NaN
5 | 4 | 7
(5 rows)

but with this patch produces:

a | b | sum
---+-----+-----
1 | 1 | 1
2 | 2 | 3
3 | NaN | NaN
4 | 3 | NaN
5 | 4 | NaN
(5 rows)

Nice catch! Thanks for having a look at the patch.

Ok, so I thought about this and I don't think it's too big a problem at to
fix it all. I think it can be handled very similar to how I'm taking care
of NULL values in window frame. For these, I simply keep a count of them in
an int64 and when the last one leaves the aggregate context things can
continue as normal.

Lucky for us that all numeric aggregation (and now inverse aggregation)
goes through 2 functions. do_numeric_accum() and the new inverse version of
it do_numeric_discard(), both these functions operate on a NumericAggState
which in the attached I've changed the isNaN bool field to a NaNCount int64
field. I'm just doing NaNCount++ when we get a NaN value in
do_numeric_accum and NaNCount-- in do_numeric_discard(), in the final
functions I'm just checking if NaNCount > 0.

Though this implementation does fix the reported problem unfortunately it
may have an undesired performance impact for numeric aggregate functions
when not uses in the context of a window.. Let me explain what I mean:

Previously there was some code in do_numeric_accum() which did:

if (state->isNaN || NUMERIC_IS_NAN(newval))
{
state->isNaN = true;
return;
}

Which meant that it didn't bother adding new perfectly valid numerics to
the aggregate totals when there was an NaN encountered previously. I had to
change this to continue on regardless as we still need to keep the totals
just in case all the NaN values are removed and the totals are required
once again. This means that the non-window version of SUM(numeric) and
AVG(numeric) and and the stddev aggregates for numeric pay a price and have
to keep on totaling after encountering NaN values. :(

If there was a way to know if the function was being called in a window
context or a normal aggregate context then we probably almost completely
restore that possible performance regression just by skipping the totaling
when not in windows context. I really don't know how common NaN values are
in the real world to know if this matters too much. I'd hazard a guess that
more people would benefit from inverse transitions on numeric types more,
but I have nothing to back that up.

I've attached version 2 of the patch which fixes the NaN problem and adds a
regression test to cover it.

Thanks again for testing this and finding the problem.

Regards

David Rowley

Show quoted text

Regards,
Dean

Attachments:

inverse_transition_functions_v2.0.patch.gzapplication/x-gzip; name=inverse_transition_functions_v2.0.patch.gzDownload
#55David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#50)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Fri, Jan 10, 2014 at 5:15 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

Reading over this, I realised that there is a problem with NaN
handling --- once the state becomes NaN, it can never recover. So the
results using the inverse transition function don't match HEAD in
cases like this:

Ouch! That takes out numeric, float4, and float8 in one fell swoop.

Given the relative infrequency of NaNs in most data, it seems like
it might still be possible to get a speedup if we could use inverse
transitions until we hit a NaN, then do it the hard way until the
NaN is outside the window, then go back to inverse transitions.
I'm not sure though if this is at all practical from an implementation
standpoint. We certainly don't want the core code knowing about
anything as datatype-specific as a NaN, but maybe the inverse transition
function could have an API that allows reporting "I can't do it here,
fall back to the hard way".

I had thought about that API, not for numeric as I think I've managed to
find another solution, it was for MAX and MIN.

I posted an idea about it here:
/messages/by-id/CAApHDvqu+yGW0vbPBb+yxHrPG5VcY_kiFYi8xmxFo8KYOczP3A@mail.gmail.com
but it didn't generate much interest at the time and I didn't have any
ideas on how the inverse aggregate functions would communicate this
inability to remove the value to the caller. Perhaps it would be an idea
still, but I had put it to the back of my mind in favour of tuplestore
indexes that could be created on the fly based on the row position within
the frame and the aggregate's sort operator on the aggregate value.
This would mean that MAX and MIN values could be found quickly all the time
rather than just when the value being removed happened not to affect the
current maximum or minimum. It's not something I have planned for this
patch though and I'd have lots of questions around memory allocation before
I'd want to start any work on it.

Regards

David Rowley

Show quoted text

regards, tom lane

#56David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#53)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

bool_or()
FALSE can be removed, removing TRUE requires a rescan. Could be made
fully invertible by counting the number of TRUE and FALSE values,
similar to my suggestion for how to handle NaN for sum(numeric).
Same works for bool_and().

bit_or()
Like boo_or(), 0 can be removed, everything else requires a rescan.
Same works for bit_and()

Interesting, I'd not thought of any way to optimise these ones, but I had
originally thought about allowing the inverse transition functions to
report if they could perform the inverse transition based on the value they
received and if they reported failure, then perform the rescan.
I just don't quite know yet the base way for the inverse transition
function to communicate this to the caller yet. If you have any ideas on
the best way to do this then I'd really like to hear them.

Regards

David Rowley

#57Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: David Rowley (#54)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 10 January 2014 08:12, David Rowley <dgrowleyml@gmail.com> wrote:

On Fri, Jan 10, 2014 at 4:09 AM, Dean Rasheed <dean.a.rasheed@gmail.com>
wrote:

Hi,

Reading over this, I realised that there is a problem with NaN
handling --- once the state becomes NaN, it can never recover. So the
results using the inverse transition function don't match HEAD in
cases like this:

create table t(a int, b numeric);
insert into t values(1,1),(2,2),(3,'NaN'),(4,3),(5,4);
select a, b,
sum(b) over(order by a rows between 1 preceding and current row)
from t;

which in HEAD produces:

a | b | sum
---+-----+-----
1 | 1 | 1
2 | 2 | 3
3 | NaN | NaN
4 | 3 | NaN
5 | 4 | 7
(5 rows)

but with this patch produces:

a | b | sum
---+-----+-----
1 | 1 | 1
2 | 2 | 3
3 | NaN | NaN
4 | 3 | NaN
5 | 4 | NaN
(5 rows)

Nice catch! Thanks for having a look at the patch.

Ok, so I thought about this and I don't think it's too big a problem at to
fix it all. I think it can be handled very similar to how I'm taking care of
NULL values in window frame. For these, I simply keep a count of them in an
int64 and when the last one leaves the aggregate context things can continue
as normal.

Lucky for us that all numeric aggregation (and now inverse aggregation) goes
through 2 functions. do_numeric_accum() and the new inverse version of it
do_numeric_discard(), both these functions operate on a NumericAggState
which in the attached I've changed the isNaN bool field to a NaNCount int64
field. I'm just doing NaNCount++ when we get a NaN value in do_numeric_accum
and NaNCount-- in do_numeric_discard(), in the final functions I'm just
checking if NaNCount > 0.

Cool, that sounds like a neat fix.

Though this implementation does fix the reported problem unfortunately it
may have an undesired performance impact for numeric aggregate functions
when not uses in the context of a window.. Let me explain what I mean:

Previously there was some code in do_numeric_accum() which did:

if (state->isNaN || NUMERIC_IS_NAN(newval))
{
state->isNaN = true;
return;
}

Which meant that it didn't bother adding new perfectly valid numerics to the
aggregate totals when there was an NaN encountered previously. I had to
change this to continue on regardless as we still need to keep the totals
just in case all the NaN values are removed and the totals are required once
again. This means that the non-window version of SUM(numeric) and
AVG(numeric) and and the stddev aggregates for numeric pay a price and have
to keep on totaling after encountering NaN values. :(

I suspect that NaNs almost never occur in practice, so the fact that
it might now take longer to tell you that the sum is NaN doesn't worry
me. More important is that it always gives the right answer.

Note, if anyone ever did this for floats, +/- Infinity would also need
to be handled, so you'd have to maintain 3 counts and deal with logic
like Infinity - Infinity = NaN.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#58Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#56)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan10, 2014, at 09:34 , David Rowley <dgrowleyml@gmail.com> wrote:

I just don't quite know yet the base way for the inverse transition function to communicate this to the caller yet. If you have any ideas on the best way to do this then I'd really like to hear them.

Could they maybe just return NULL as the new state? It would mean that
aggregates that do want to provide an inverse transition function couldn't
use NULL as a valid aggregate state, but do we need to support that?

Looking at the code it seems that for quite a few existing aggregates,
the state remains NULL until the first non-NULL input is processed. But
that doesn't hurt much - those aggregates can remain as they are until
someone wants to add an inverse transfer function. Once that happens,
there's a choice between living with needless rescans on trailing NULL
inputs or changing the state type.

This solution isn't particularly pretty, but I don't currently see a good
alternative that allows implementing inverse transfer functions is something
other than C and avoid needless overhead for those which are written in C.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#58)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

On Jan10, 2014, at 09:34 , David Rowley <dgrowleyml@gmail.com> wrote:

I just don't quite know yet the base way for the inverse transition function to communicate this to the caller yet. If you have any ideas on the best way to do this then I'd really like to hear them.

Could they maybe just return NULL as the new state? It would mean that
aggregates that do want to provide an inverse transition function couldn't
use NULL as a valid aggregate state, but do we need to support that?

Yeah, I was going to suggest the same. Seems like it wouldn't be that
difficult to come up with some alternative representation for "everything
seen so far was NULL", if you needed to.

Looking at the code it seems that for quite a few existing aggregates,
the state remains NULL until the first non-NULL input is processed. But
that doesn't hurt much - those aggregates can remain as they are until
someone wants to add an inverse transfer function. Once that happens,
there's a choice between living with needless rescans on trailing NULL
inputs or changing the state type.

Also, it might be reasonable for both the regular and the inverse
transition functions to be strict. If a null entering the window
does not matter, then a null exiting the window doesn't either, no?

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

#60Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#59)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan10, 2014, at 15:49 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

Looking at the code it seems that for quite a few existing aggregates,
the state remains NULL until the first non-NULL input is processed. But
that doesn't hurt much - those aggregates can remain as they are until
someone wants to add an inverse transfer function. Once that happens,
there's a choice between living with needless rescans on trailing NULL
inputs or changing the state type.

Also, it might be reasonable for both the regular and the inverse
transition functions to be strict. If a null entering the window
does not matter, then a null exiting the window doesn't either, no?

That's not true, I think, unless we're special-casing strict transition
functions somewhere. AFAICS, an aggregate with a strict transition function
will produce the state NULL whenever any of the inputs was NULL, i.e. we won't
ever transition out of the NULL state once we got there. The inverse
transition function, however, would *have* to be able to transition away
from the NULL state, which requires it to be non-strict. Otherwise, how would
the state aver become non-NULL after the last NULL input leaves the window?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#60)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

On Jan10, 2014, at 15:49 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Also, it might be reasonable for both the regular and the inverse
transition functions to be strict. If a null entering the window
does not matter, then a null exiting the window doesn't either, no?

That's not true, I think, unless we're special-casing strict transition
functions somewhere. AFAICS, an aggregate with a strict transition function
will produce the state NULL whenever any of the inputs was NULL, i.e. we won't
ever transition out of the NULL state once we got there.

Nope, not the case; read xaggr.sgml and/or the CREATE AGGREGATE reference
page. An aggregate with a strict transition function essentially just
ignores null input rows. I suspect the inverse transition function could
just be made strict with a similar special-case rule (viz, keep the old
transition value when deleting a null input from the window); but maybe
I'm missing something and it has to work harder than that anyway.

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

#62Kevin Grittner
kgrittn@ymail.com
In reply to: Florian Pflug (#53)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> wrote:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

For float 4 and float8, wasn't the consensus that the potential
lossy-ness of addition makes this impossible anyway, even
without the NaN issue? But...

Well, that was my opinion, I'm not sure if it was consensus ;-).

I'd say your example showing how it could produce completely
bogus results was pretty convincing...

Aggregates on approximate (floating-point) numbers are not nearly
as consistent as many people probably assume.  Picture for a minute
a table where a column contains positive floating point numbers
happen to be located in the heap in increasing order, perhaps as
the result of a CLUSTER on an index on the column.  SELECT
sum(colname) FROM tablename; would tend to give the most accurate
answer possible when a sequence scan was run -- unless there
happened to be a seqscan already half-way through the heap.  Then
the result would be different.  So the exact same query against the
exact same data, with no intervening modifications or maintenance
activity could give one answer most of the time, and give various
other answers depending on concurrent SELECT queries.

Given that this is already the case with aggregates on floating
point approximate numbers, why should we rule out an optimization
which only makes rounding errors more likely to be visible?  The
real issue here is that if you are using an approximate data type
and expecting exact answers, you will have problems.

That's not to say that approximations are useless.  If you
represent the circumference of the earth with a double precision
number you're dealing with an expected rounding error of about a
foot.  That's close enough for many purposes.  The mistake is
assuming that it will be exact or that rounding errors cannot
accumulate.  In situations where SQL does not promise particular
ordering of operations, it should not be assumed; so any
expectations of a specific or repeatable result from a sum or
average of approximate numbers is misplaced.

But NaN is an orthogonal problem I think.

Agreed.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#63Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kevin Grittner (#62)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Kevin Grittner <kgrittn@ymail.com> writes:

Aggregates on approximate (floating-point) numbers are not nearly
as consistent as many people probably assume.� Picture for a minute
a table where a column contains positive floating point numbers
happen to be located in the heap in increasing order, perhaps as
the result of a CLUSTER on an index on the column.� SELECT
sum(colname) FROM tablename; would tend to give the most accurate
answer possible when a sequence scan was run -- unless there
happened to be a seqscan already half-way through the heap.� Then
the result would be different.

I don't think that argument holds any water. In the first place, somebody
could turn off synchronize_seqscans if they needed to have the calculation
done the same way every time (and I recall questions from users who ended
up doing exactly that, shortly after we introduced synchronize_seqscans).
In the second place, for most use-cases it'd be pretty foolish to rely on
physical heap order, so somebody who was really trying to sum float8s
accurately would likely do

select sum(x order by x) from ...

This is a well-defined, numerically stable calculation, and I don't want
to see us put in non-defeatable optimizations that break it.

The real issue here is that if you are using an approximate data type
and expecting exact answers, you will have problems.

That's a canard. People who know what they're doing (admittedly a
minority) do not expect exact answers, but they do expect to be able to
specify how to do the calculation in a way that minimizes roundoff errors.
The inverse-transition-function approach breaks that, and it does so at a
level where the user can't work around it, short of building his own
aggregates.

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

#64Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#63)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

I wrote:

Kevin Grittner <kgrittn@ymail.com> writes:

The real issue here is that if you are using an approximate data type
and expecting exact answers, you will have problems.

That's a canard. People who know what they're doing (admittedly a
minority) do not expect exact answers, but they do expect to be able to
specify how to do the calculation in a way that minimizes roundoff errors.
The inverse-transition-function approach breaks that, and it does so at a
level where the user can't work around it, short of building his own
aggregates.

Although, having said that ... maybe "build your own aggregate" would
be a reasonable suggestion for people who need this? I grant that
it's going to be a minority requirement, maybe even a small minority
requirement. People who have the chops to get this sort of thing right
can probably manage a custom aggregate definition.

The constraint this would pose on the float4 and float8 implementations
is that it be possible to use their transition and final functions in
a custom aggregate declaration while leaving off the inverse function;
or, if that combination doesn't work for some reason, we have to continue
to provide the previous transition/final functions for use in user
aggregates.

Suitable documentation would be needed too, of course.

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

#65Florian Pflug
fgp@phlo.org
In reply to: Kevin Grittner (#62)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan10, 2014, at 18:14 , Kevin Grittner <kgrittn@ymail.com> wrote:

Given that this is already the case with aggregates on floating
point approximate numbers, why should we rule out an optimization
which only makes rounding errors more likely to be visible? The
real issue here is that if you are using an approximate data type
and expecting exact answers, you will have problems.

Because without the optimization, only the values which you
*actually* process for a given result determine whether you lose
precision or not. With the optimization, OTOH, values which have
*nothing* to do with the result in question can nevertheless
make it completely bogus.

SUM() is a good example. As long as all your values are positive,
the amount of precision you lose is bound by the number of input
values. If I sum over 10 values, the worst that can happen is that
the first values is large enough to prevent the other 9 values from
influencing the result. That limits the relative error to something
like 9*epsilon, where epsilon is the relative precision of the
floating point type, i.e. 1e-15 or so for double. In other words,
as long as your frames are less than 10e13 rows long, the relative
error will stay below 1%.

But with the optimization, that is no longer true. If you sum
from, say, CURRENT ROW to UNBOUNDED FOLLOWING, the relative error
of the result in one row now depends on the magnitude of values
*preceding* that row, even though that value isn't in the frame.
And since we now internally subtract, not only add, the relative
error is no longer bound by the number of rows in the frame.

Here's the corresponding SELECT (which is basically the same
as Tom's example upthread):
select
n,
x::float,
sum(x::float) over (
order by n rows between current row and unbounded following
)
from (values
(1, 1e20),
(2, 1),
(3, 2)
) as t(n, x)
order by n;

Currently that returns
n | x | sum
---+-------+-------
1 | 1e+20 | 1e+20
2 | 1 | 3
3 | 2 | 2

but with an inverse transfer function, it may very well return
n | x | sum
---+-------+-------
1 | 1e+20 | 1e+20
2 | 1 | 0
3 | 2 | -1

That's not to say that approximations are useless. If you
represent the circumference of the earth with a double precision
number you're dealing with an expected rounding error of about a
foot. That's close enough for many purposes. The mistake is
assuming that it will be exact or that rounding errors cannot
accumulate. In situations where SQL does not promise particular
ordering of operations, it should not be assumed; so any
expectations of a specific or repeatable result from a sum or
average of approximate numbers is misplaced.

But this isn't about ordering, it's replacing one computation
with a completely different one that just happens to be equivalent
*algebraically*.

To me, the proposed optimization for float is akin to C compiler
which decided to evaluate
a + b + c + … z
as
-a + (2a - b) + (2b - c) + … + (2y - z) + 2z
Algebraically, these are the same, but it'd still be insane to
do that.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#64)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan10, 2014, at 19:08 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

Kevin Grittner <kgrittn@ymail.com> writes:

The real issue here is that if you are using an approximate data type
and expecting exact answers, you will have problems.

That's a canard. People who know what they're doing (admittedly a
minority) do not expect exact answers, but they do expect to be able to
specify how to do the calculation in a way that minimizes roundoff errors.
The inverse-transition-function approach breaks that, and it does so at a
level where the user can't work around it, short of building his own
aggregates.

Although, having said that ... maybe "build your own aggregate" would
be a reasonable suggestion for people who need this? I grant that
it's going to be a minority requirement, maybe even a small minority
requirement. People who have the chops to get this sort of thing right
can probably manage a custom aggregate definition.

So we'd put a footgun into the hands of people who don't know what they're
doing, to be fired for performance's sake, and leave it to the people
who know what they are doing to put the safety on?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#66)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

On Jan10, 2014, at 19:08 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Although, having said that ... maybe "build your own aggregate" would
be a reasonable suggestion for people who need this? I grant that
it's going to be a minority requirement, maybe even a small minority
requirement. People who have the chops to get this sort of thing right
can probably manage a custom aggregate definition.

So we'd put a footgun into the hands of people who don't know what they're
doing, to be fired for performance's sake, and leave it to the people
who know what they are doing to put the safety on?

If I may put words in Kevin's mouth, I think his point is that having
float8 sum() at all is a foot-gun, and that's hard to deny. You need
to know how to use it safely.

A compromise compromise might be to provide these alternative "safer"
aggregates built-in. Or, depending on what color you like your bikeshed,
leave the standard aggregates alone and define "fast_sum" etc for the less
safe versions. In any case it'd be incumbent on us to document the
tradeoffs.

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

#68Mark Dilger
markdilger@yahoo.com
In reply to: Florian Pflug (#65)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

FYI, I'm using the verb "rewind" to talk about using the negative transition aggregation function to get a prior value.  I don't know if this is the right verb.

Conceptually, when aggregating over floating point numbers, there is some infinitely precise theoretical value, and the computation is approximating it.  Call the infinitely precise value 'r'.  Call the computed value 'c', which is the result of the aggregation function.  (For float4_agg and float8_agg, typeof(c) == float8).

The problem you have with rewinding an aggregate is that you don't know if you are getting the same value of c that you would have gotten from a
rescan.  But if you have a type that tracks a margin [min,max] where typeof(min) == typeof(max) is higher precision than typeof(c), then you can track:

min <= r <= max

By setting the rounding mode down, then up, when computing the next value of min and max, respectively.  (Extra flag bits or booleans could track whether you've encountered +inf, -inf, Nan, and any other oddball cases, with corresponding special logic that has been discussed already upthread.)  In many but not all cases:

min != max

but

(typeof(c))min == (typeof(c))max

Because the margin of error is small enough not to render different values when cast to the lower precision typeof(c).

You could rewind the aggregation whenever this second case holds, and only force a rescan when it does not.  This would render the same results for queries whether they were performed with rewinds or with rescans.  The results might differ from older versions of postgres, but only in that they might be more accurate, with less accumulated rounding errors, owing to the higher precision state transition variable.

For many modern platforms, typeof(min)
could be __float128 using libquadmath, or something similar to that.  If not available at compile time, it could be float64 instead.  Even then, you'd still know that rewinding was possible when min == max and not otherwise, which is useful for cases of aggregation over exact values.

I admit I've done a bit of handwaving on the computation of the margin and the handling of floating-point rounding issues, but I believe the implementation details are tractible.

mark

On Friday, January 10, 2014 10:10 AM, Florian Pflug <fgp@phlo.org> wrote:

On Jan10, 2014, at 18:14 , Kevin Grittner <kgrittn@ymail.com> wrote:

Given that this is already the case with aggregates on floating
point approximate numbers, why should we rule out an optimization
which only makes rounding errors more likely to be visible?  The
real issue here is that if you are using an approximate data type
and expecting exact answers, you will have problems.

Because without the optimization, only the values which you
*actually* process for a given result determine whether you lose
precision or not. With the optimization, OTOH, values which have
*nothing* to do with the result in question can nevertheless
make it completely bogus.

SUM() is a good example. As long as all your values are positive,
the amount of precision you lose is bound by the number of input
values. If I sum over 10 values, the worst that can happen is that
the first values is large enough to prevent the other 9 values from
influencing the result. That limits the relative error to something
like 9*epsilon, where epsilon is the relative precision of the
floating point type, i.e. 1e-15 or so for double. In other words,
as long as your frames are less than 10e13 rows long, the relative
error will stay below 1%.

But with the optimization, that is no longer true. If you sum
from, say, CURRENT ROW to UNBOUNDED FOLLOWING, the relative error
of the result in one row now depends on the magnitude of values
*preceding* that row, even though that value isn't in the frame.
And since we now internally subtract, not only add, the relative
error is no longer bound by the number of rows in the frame.

Here's the corresponding SELECT (which is basically the same
as Tom's example upthread):
select
  n,
  x::float,
  sum(x::float) over (
    order by n rows between current row and unbounded following
  )
  from (values
    (1, 1e20),
    (2, 1),
    (3, 2)
  ) as t(n, x)
  order by n;

Currently that returns
  n |  x  |  sum 
  ---+-------+-------
  1 | 1e+20 | 1e+20
  2 |    1 |    3
  3 |    2 |    2

but with an inverse transfer function, it may very well return
  n |  x  |  sum 
  ---+-------+-------
  1 | 1e+20 | 1e+20
  2 |    1 |    0
  3 |    2 |    -1

That's not to say that approximations are

useless.  If you

represent the circumference of the earth with a double precision
number you're dealing with an expected rounding error of about a
foot.  That's close enough for many purposes.  The mistake is
assuming that it will be exact or that rounding errors cannot
accumulate.  In situations where SQL does not promise particular
ordering of operations, it should not be assumed; so any
expectations of a specific or repeatable result from a sum or
average of approximate numbers is misplaced.

But this isn't about ordering, it's replacing one computation
with a completely different one that just happens to be equivalent
*algebraically*.

To me, the proposed optimization
for float is akin to C compiler
which decided to evaluate
  a + b + c + … z
as
  -a + (2a - b) + (2b - c) + … + (2y - z) + 2z
Algebraically, these are the same, but it'd still be insane to
do that.

best regards,

Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#64)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, Jan 11, 2014 at 7:08 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Although, having said that ... maybe "build your own aggregate" would
be a reasonable suggestion for people who need this? I grant that
it's going to be a minority requirement, maybe even a small minority
requirement. People who have the chops to get this sort of thing right
can probably manage a custom aggregate definition.

I more or less wrote off the idea of inverse transition functions after
your example upthread. I had thought that perhaps if we could get inverse
transitions in there for SUM(numeric) then people who need more speed could
just cast their value to numeric then back to float or double precision
after aggregation takes place. I had to delay writing any documentation
around that as I'm still not sure if we can have sum(numeric) use an
inverse transition function due to the fact that it can introduce extra
zeros after the decimal point.

As the patch stands at the moment, I currently have a regression test which
currently fails due to these extra zeros after the decimal point:

-- This test currently fails due extra trailing 0 digits.
SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND
UNBOUNDED FOLLOWING)
FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);

Patched produces:
6.01
5.00
3.00
Unpatched produces:
6.01
5
3

With inverse transitions this query still produces correct results, it just
does not produces the numeric in the same format as it does without
performing inverse transitions. Personally I'd rather focus on trying to
get SUM(numeric) in there for 9.4 and maybe focus on floating point stuff
at a later date as casting to numeric can be the work around for users who
complain about the speed. Or if they really want they can create their own
aggregate, using an existing built in function as the inverse transition,
like float8_mi.

There's certain things that currently seem a big magical to me when it
comes to numeric, for example I've no idea why the following query produces
20 0's after the decimal point for 1 and only 16 for 2.

select n::numeric / 1 from generate_series(1,2) g(n);

To me it does not look very consistent at all and I'm really wondering if
there is some special reason why we bother including the useless zeros at
the end at all. I've written a patch which gets rid of them in numeric_out,
but I had not planned on posting it here in case it gets laughed off stage
due to some special reason we have for keeping those zeros that I don't
know about.

Can anyone explain to me why we have these unneeded zeros in numeric when
the precision is not supplied?

Regards

David Rowley

Show quoted text

The constraint this would pose on the float4 and float8 implementations
is that it be possible to use their transition and final functions in
a custom aggregate declaration while leaving off the inverse function;
or, if that combination doesn't work for some reason, we have to continue
to provide the previous transition/final functions for use in user
aggregates.

Suitable documentation would be needed too, of course.

regards, tom lane

#70Jim Nasby
jim@nasby.net
In reply to: Tom Lane (#67)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 1/10/14, 1:07 PM, Tom Lane wrote:

Florian Pflug<fgp@phlo.org> writes:

On Jan10, 2014, at 19:08 , Tom Lane<tgl@sss.pgh.pa.us> wrote:

Although, having said that ... maybe "build your own aggregate" would
be a reasonable suggestion for people who need this? I grant that
it's going to be a minority requirement, maybe even a small minority
requirement. People who have the chops to get this sort of thing right
can probably manage a custom aggregate definition.

So we'd put a footgun into the hands of people who don't know what they're
doing, to be fired for performance's sake, and leave it to the people
who know what they are doing to put the safety on?

If I may put words in Kevin's mouth, I think his point is that having
float8 sum() at all is a foot-gun, and that's hard to deny. You need
to know how to use it safely.

And IMHO if you've got something that's going to produce bad data if you do it wrong, I'd rather that the error be as large as possible so that you're more likely to discover it and fix it...
--
Jim C. Nasby, Data Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#61)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan10, 2014, at 17:46 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

On Jan10, 2014, at 15:49 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Also, it might be reasonable for both the regular and the inverse
transition functions to be strict. If a null entering the window
does not matter, then a null exiting the window doesn't either, no?

That's not true, I think, unless we're special-casing strict transition
functions somewhere. AFAICS, an aggregate with a strict transition function
will produce the state NULL whenever any of the inputs was NULL, i.e. we won't
ever transition out of the NULL state once we got there.

Nope, not the case; read xaggr.sgml and/or the CREATE AGGREGATE reference
page. An aggregate with a strict transition function essentially just
ignores null input rows.

I wasn't aware of that, sorry for the noise.

I suspect the inverse transition function could
just be made strict with a similar special-case rule (viz, keep the old
transition value when deleting a null input from the window); but maybe
I'm missing something and it has to work harder than that anyway.

This seems to hold at least if the state never becomes NULL, i.e. if there's
a non-NULL initiate state (S) and the transfer function never becomes NULL. If
the transfer function (I) and inverse transfer function (T) obey that
I(T(...T(T(S,a),b)...,z), a) = T(...T(S,b)...,z)
for a non-NULL a, then setting T(s,NULL) = I(s,NULL) = s makes the same
thing work for NULL and non-NULL values for a.

If, however, a strict state function T ever returns NULL, things break.
If T(S,a) is NULL in the above, the whole expression becomes NULL because
T is strict (we only special-case the *value* input, not the state input).
But T(...T(S,b)...,z) might very well be non-NULL. That's pretty much
the same situation, unsurprisingly, as with NaNs.

A variant of that problem arises also if the initial state is NULL.
We then wait for the first non-NULL value, and use that as the initial
state. When we later apply the inverse transition function, we might or
might not have to reset the state back to NULL, depending on whether there
are other non-NULL values in the frame or not. This case can be solved by
tracking the number of non-NULL rows in the current frame - whenever
that number if 0, the the output value is NULL, otherwise it's determined
by the state.

So in conclusion, things work for pairs of strict transfer and inverse
transfer functions AFAICS, as long as we do the non-null tracking and
complain should the transfer function ever return NULL.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#72Florian Pflug
fgp@phlo.org
In reply to: Jim Nasby (#70)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan11, 2014, at 01:24 , Jim Nasby <jim@nasby.net> wrote:

On 1/10/14, 1:07 PM, Tom Lane wrote:

Florian Pflug<fgp@phlo.org> writes:

On Jan10, 2014, at 19:08 , Tom Lane<tgl@sss.pgh.pa.us> wrote:

Although, having said that ... maybe "build your own aggregate" would
be a reasonable suggestion for people who need this? I grant that
it's going to be a minority requirement, maybe even a small minority
requirement. People who have the chops to get this sort of thing right
can probably manage a custom aggregate definition.

So we'd put a footgun into the hands of people who don't know what they're
doing, to be fired for performance's sake, and leave it to the people
who know what they are doing to put the safety on?

If I may put words in Kevin's mouth, I think his point is that having
float8 sum() at all is a foot-gun, and that's hard to deny. You need
to know how to use it safely.

And IMHO if you've got something that's going to produce bad data if you do it
wrong, I'd rather that the error be as large as possible so that you're more
likely to discover it and fix it...

To that principle, I agree, I just don't think it applies here. An inverse
transition function greatly *increases* the chance of bogus results for
sum(float). It also breaks some rather natural assumptions that one might
make about sum(float)'s behaviour. For example, sums over single-element
frames current return the one row's value unchanged. That's no longer true
universally true with an inverse transition function. Even for an approximate
type, that's a bid too weird for my taste.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#67)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Fri, Jan 10, 2014 at 2:07 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

On Jan10, 2014, at 19:08 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Although, having said that ... maybe "build your own aggregate" would
be a reasonable suggestion for people who need this? I grant that
it's going to be a minority requirement, maybe even a small minority
requirement. People who have the chops to get this sort of thing right
can probably manage a custom aggregate definition.

So we'd put a footgun into the hands of people who don't know what they're
doing, to be fired for performance's sake, and leave it to the people
who know what they are doing to put the safety on?

If I may put words in Kevin's mouth, I think his point is that having
float8 sum() at all is a foot-gun, and that's hard to deny. You need
to know how to use it safely.

Yeah, but Florian's point is that not all foot-guns are created equal.
The fact that we're walking around with a loaded BB-gun in our hip
pocket is not a good reason to replace it with a howitzer.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#74Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#69)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan10, 2014, at 22:27 , David Rowley <dgrowleyml@gmail.com> wrote:

As the patch stands at the moment, I currently have a regression test
which currently fails due to these extra zeros after the decimal point:

-- This test currently fails due extra trailing 0 digits.
SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);

Patched produces:
6.01
5.00
3.00
Unpatched produces:
6.01
5
3

Hm, that's annoying. I checked the standard to see if it offers any guidance
here, but it doesn't look like it does - the standard just says that SUM() ought
to return a type with the same scale as the input type has. That's fine if
every NUMERIC type has a fixed scale, but that's not the case in postgres -
we allow values of unconstrained NUMERIC types (i.e., numeric types without an
explicitly specified precision or scale) to each define their own scale.

To fix this, we'd have to track the maximum scale within the current frame.
That's easier than the general problem of providing an inverse transition
function for MAX, because AFAIK we limit the scale to at most 1000. So it'd
be sufficient to track the number of times we saw each scale, and also the
current maximum. Once we reduce the current maximum's count back to zero
in the inverse transition function, we'd scan from that value to the left to
find the next non-zero entry.

We could also choose to ignore this, although I'm not sure I really like that.
It seems entirely OK at first sight - after all, the values all stay the same,
we just emit a different number of trailing zeros. But it still causes results
to be affected by values, even if only in the number of trailing zeros, which
lie outside the value's range. That seems like more non-determinism than as
database should show.

With inverse transitions this query still produces correct results, it just does
not produces the numeric in the same format as it does without performing inverse
transitions. Personally I'd rather focus on trying to get SUM(numeric) in there
for 9.4

I think it'd be worthwile to get this into 9.4, if that's still an option,
even if we only support COUNT.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#75Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#74)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

I think it'd be worthwile to get this into 9.4, if that's still an option,
even if we only support COUNT.

My thought is

(1) we can certainly implement inverse transitions for COUNT() and the
integer variants of SUM(), and that alone would be a killer feature for
some people.

(2) the float and numeric variants should be implemented under nondefault
names (I'm thinking FAST_SUM(), but bikeshed away). People who need
extra speed and don't mind the slightly different results can alter
their queries to use these variants.

One reason I'm thinking this is that whatever we do to ameliorate
the semantic issues is going to slow down the forward transition
function --- to no benefit unless the aggregate is being used as a
window function in a moving window. So I'm less than convinced
that we *should* implement any of these designs in the default
aggregates, even if we get to the point where we arguably *could*
do it with little risk of functional differences.

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

#76Gavin Flower
GavinFlower@archidevsys.co.nz
In reply to: Tom Lane (#75)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 14/01/14 14:29, Tom Lane wrote:
[...]

(2) the float and numeric variants should be implemented under
nondefault names (I'm thinking FAST_SUM(), but bikeshed away). People
who need extra speed and don't mind the slightly different results can
alter their queries to use these variants. One reason I'm thinking
this is that whatever we do to ameliorate the semantic issues is going
to slow down the forward transition function --- to no benefit unless
the aggregate is being used as a window function in a moving window.
So I'm less than convinced that we *should* implement any of these
designs in the default aggregates, even if we get to the point where
we arguably *could* do it with little risk of functional differences.
regards, tom lane

How SUM_FAST() instead, then it will more likely to be close to SUM() in
an index?

Cheers,
Gavin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#77David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#74)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Jan 14, 2014 at 2:00 PM, Florian Pflug <fgp@phlo.org> wrote:

On Jan10, 2014, at 22:27 , David Rowley <dgrowleyml@gmail.com> wrote:

As the patch stands at the moment, I currently have a regression test
which currently fails due to these extra zeros after the decimal point:

-- This test currently fails due extra trailing 0 digits.
SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND

UNBOUNDED FOLLOWING)

FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);

Patched produces:
6.01
5.00
3.00
Unpatched produces:
6.01
5
3

Hm, that's annoying. I checked the standard to see if it offers any
guidance
here, but it doesn't look like it does - the standard just says that SUM()
ought
to return a type with the same scale as the input type has. That's fine if
every NUMERIC type has a fixed scale, but that's not the case in postgres -
we allow values of unconstrained NUMERIC types (i.e., numeric types
without an
explicitly specified precision or scale) to each define their own scale.

Thanks for digging that out in the standard and thanks for all that
information you supplied here
/messages/by-id/0FA6C08E-2166-405B-83F7-63B196B88CA3@phlo.org
too.
Sorry I've not had the chance to reply yet. I was kind of hoping that the
answer would be more in my favour to help with inverse transitions for
sum(numeric).

To fix this, we'd have to track the maximum scale within the current frame.
That's easier than the general problem of providing an inverse transition
function for MAX, because AFAIK we limit the scale to at most 1000. So it'd
be sufficient to track the number of times we saw each scale, and also the
current maximum. Once we reduce the current maximum's count back to zero
in the inverse transition function, we'd scan from that value to the left
to
find the next non-zero entry.

I've been thinking about this, but I had thought that the maximum dscale
was bigger than 1000. The docs seem to claim 16383 here -->
http://www.postgresql.org/docs/devel/static/datatype-numeric.html I'd go
ahead and implement this if that number was smaller, but I'm thinking
zeroing out an array of 16383 elements on first call to do_numeric_accum
might be too big an overhead to write off as "background noise". If it was
20 or even 100 then I'd probably try for that.

I think the overhead for each call after that would likely be ok as it
would probably just be an operation like
state->scaleCount[X.dscale]++; which I would imagine would be a very small
percentage overhead on normal aggregate functions. Of course the inverse
would have to do the harder work of looping backwards over the array until
it found an element with the count above 0 and setting that as the current
maximum. I think this would be a winner if it wasn't for the high initial
hit of zeroing that 16383 element array.. Or 1000 whichever.

Show quoted text

We could also choose to ignore this, although I'm not sure I really like
that.
It seems entirely OK at first sight - after all, the values all stay the
same,
we just emit a different number of trailing zeros. But it still causes
results
to be affected by values, even if only in the number of trailing zeros,
which
lie outside the value's range. That seems like more non-determinism than as
database should show.

With inverse transitions this query still produces correct results, it

just does

not produces the numeric in the same format as it does without

performing inverse

transitions. Personally I'd rather focus on trying to get SUM(numeric)

in there

for 9.4

I think it'd be worthwile to get this into 9.4, if that's still an option,
even if we only support COUNT.

best regards,
Florian Pflug

#78David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#75)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Jan 14, 2014 at 2:29 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

I think it'd be worthwile to get this into 9.4, if that's still an

option,

even if we only support COUNT.

My thought is

(1) we can certainly implement inverse transitions for COUNT() and the
integer variants of SUM(), and that alone would be a killer feature for
some people.

100% Agreed about sum(int) and count, but I've so far managed only to find
problems with SUM(numeric), other inverse transition functions seem just
fine with numeric types. e.g. AVG(numeric) and STDDEV(numeric), I can't
produce any results that differ from the unpatched head... but perhaps
someone can give me a case where things could vary?

I've been testing with something like:
select avg(n) over w,SUM(3::float) over w
from (values(1,10::numeric),(2,0),(3,0)) v(i,n)
window w as (order by i rows between current row and unbounded following);

where I included the sum(float) to force the old school brute force
transitions to be performed. I then compared the results to the ones given
without the sum(float) tagged on the end. I've so far not found anything
out of place.

(2) the float and numeric variants should be implemented under nondefault

names (I'm thinking FAST_SUM(), but bikeshed away). People who need
extra speed and don't mind the slightly different results can alter
their queries to use these variants.

I'm not as keen on this idea, as if someone later thought of a nice way to
allow the inverse functions to provide exactly the same result as if they
were not used then the would become redundant legacy that would likely have
to be supported forever and a day, and I know we already have things like
that, but of course we want to minimise that as much as possible.

If AVG(numeric) and the stddev numeric aggregates turn out to be valid
then numeric_avg_accum_inv() must remain in core code to support that and
this is the exact function that is required for a user to define their own
SUM(numeric) with. We could leave it at that and document what a user must
do to create their own FAST_SUM then perhaps leave the including these in
core decision to later based maybe on the number of complaints about
SUM(numeric) being slow when used in windows with a moving frame head.

(For me) It does not seem too unreasonable to think that one day we might
decide that:

SELECT AVG(n) FROM (VALUES(10::NUMERIC(10,3)),(0),(0)) v(n);

return 3.333 instead of 3.3333333333333333 like it does now.

If we ever did that then we could support these numeric inverse transitions
on SUM() without having to worry about weird extra trailing zeros. Instead
we'd just give the user what they asked for, when they asked for it.
Reasons like that make me think that we might be jumping the gun a little
on FAST_SUM() as it could end up redundant more quickly than we might
initially think.

One reason I'm thinking this is that whatever we do to ameliorate
the semantic issues is going to slow down the forward transition
function --- to no benefit unless the aggregate is being used as a
window function in a moving window. So I'm less than convinced
that we *should* implement any of these designs in the default
aggregates, even if we get to the point where we arguably *could*
do it with little risk of functional differences.

So far there's only 1 case of possible slowdown of forward transitions and
that's with numeric types.
I had to change do_numeric_accum() to add a NaN Counter increment and also
change the logic so that it continues summing non NaN values even after the
first NaN was encountered.

Would you see this as too big a performance risk to include in core?

If not then I think we might be able to support AVG(numeric) and
STDDEV(numeric) functions as the patch sits without having to worry that
there might be an extra overhead on forward transitions that include NaN
values.

Florian has done some really good work researching the standards around
this area. His research seems to indicate that the results should be of the
same scale as the inputs, which the current patch does not do, providing
you assume that the user is showing us the intended scale based on the
numbers that we're working with rather than a scale specified by the column
or cast. Florian's idea of how to fix the scale on inverse transition seems
pretty valid to me and I think the overhead of it could be made pretty
minimal. I'm just not sure that I can implement it in such a way as to make
the overhead small enough to look like background noise. But I'm very
willing to give it a try and see...

*** some moments pass ***

I think unless anyone has some objections I'm going to remove the inverse
transition for SUM(numeric) and modify the documents to tell the user how
to build their own FAST_SUM(numeric) using the built in functions to do it.
I'm starting to think that playing around with resetting numeric scale will
turn a possible 9.4 patch into a 9.5/10.0 patch. I see no reason why what's
there so far, minus sum(numeric), can't go in...

If so then there's 36 aggregate functions ticked off my "create an inverse
transition function for" list! I personally think that's a pretty good win.
I'd rather do this than battle and miss deadlines for 9.4. I'd find that
pretty annoying.

Regards

David Rowley

#79David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#78)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Jan 14, 2014 at 9:09 PM, David Rowley <dgrowleyml@gmail.com> wrote:

I think unless anyone has some objections I'm going to remove the inverse
transition for SUM(numeric) and modify the documents to tell the user how
to build their own FAST_SUM(numeric) using the built in functions to do it.
I'm starting to think that playing around with resetting numeric scale will
turn a possible 9.4 patch into a 9.5/10.0 patch. I see no reason why what's
there so far, minus sum(numeric), can't go in...

Of course its only now that I discover that this is not possible to do this:

CREATE AGGREGATE fast_sum (numeric)
(
stype = numeric,
sfunc = numeric_avg_accum,
invfunc = numeric_avg_accum_inv,
finalfunc = numeric_sum
);

because SUM(numeric) uses an internal type to store the transition state.

hmmm, built-in fast_sum anyone?
Is there any simple way to limit these to only be used in the context of a
window? If so is it worth it?
Would we want fast_sum() for float too?

Regards

David Rowley

#80David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#78)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Jan 14, 2014 at 9:09 PM, David Rowley <dgrowleyml@gmail.com> wrote:

I think unless anyone has some objections I'm going to remove the inverse
transition for SUM(numeric) and modify the documents to tell the user how
to build their own FAST_SUM(numeric) using the built in functions to do it.
I'm starting to think that playing around with resetting numeric scale will
turn a possible 9.4 patch into a 9.5/10.0 patch. I see no reason why what's
there so far, minus sum(numeric), can't go in...

If so then there's 36 aggregate functions ticked off my "create an inverse
transition function for" list! I personally think that's a pretty good win.
I'd rather do this than battle and miss deadlines for 9.4. I'd find that
pretty annoying.

Here's a patch which removes sum(numeric) and changes the documents a
little to remove a reference to using sum(numeric) to workaround the fact
that there's no inverse transitions for sum(float). I also made a small
change in the aggregates.sql tests to check that the aggfnoid <= 9999.

I've also pulled the regression tests that I had added for sum(numeric) as
they no longer test anything new. All tests are now passing.

With the attached patch I feel like I've left users a bit high and dry for
their sum(numeric) needs. I guess there is no true workaround as even if
they created their functions in SQL using simple + and - arithmetic, they
would likely suffer from NaN recovery problems. I'm starting to come around
to Tom's FAST_SUM idea as I simply can't see any fool proof workaround that
could be created without writing things in C.

The only problems I see with the FAST_SUM idea are that the number of
trailing zeros may appear a little random based on if inverse transitions
are used or are not used... Keep in mind that inverse transitions are not
performed if any aggregate in the window does not support them OR if any
aggregate in the frame contains a volatile function in the aggregate's
parameters or the FILTER (WHERE clause). Does this matter or can we just
document to warn about that?

If there's a few more +1s for FAST_SUM(numeric) then let me know and I'll
add it.
If anyone feels strongly against adding FAST_SUM then please let the
reasons for that known too.
Or failing that, if anyone has any other ideas that have not yet been
written on this thread, please post them so we can discuss.

Regards

David Rowley

Show quoted text

Regards

David Rowley

Attachments:

inverse_transition_functions_v2.1.patch.gzapplication/x-gzip; name=inverse_transition_functions_v2.1.patch.gzDownload
#81Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#80)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan14, 2014, at 11:06 , David Rowley <dgrowleyml@gmail.com> wrote:

Here's a patch which removes sum(numeric) and changes the documents a little to remove a reference to using sum(numeric) to workaround the fact that there's no inverse transitions for sum(float). I also made a small change in the aggregates.sql tests to check that the aggfnoid <= 9999.

I've looked over the patch, here a few comments.

For STRICT pairs of transfer and inverse transfer functions we should complain if any of them ever return NULL. That can never be correct anyway, since a STRICT function cannot undo a non-NULL -> NULL transition.

The same goes for non-STRICT, at least if we ever want to allow an inverse transfer function to indicate "Sorry, cannot undo in this case, please rescan". If we allowed NULL as just another state value now, we couldn't easily undo that later, so we'd rob ourselves of the obvious way for the inverse transfer function to indicate this condition to its caller.

The notnullcount machinery seems to apply to both STRICT and non-STRICT transfer function pairs. Shouldn't that be constrained to STRICT transfer function pairs? For non-STRICT pairs, it's up to the transfer functions to deal with NULL inputs however they please, no?

The logic around movedaggbase in eval_windowaggregates() seems a bit convoluted. Couldn't the if be moved before the code that pulls aggregatedbase up to frameheadpos using the inverse aggregation function?

Also, could we easily check whether the frames corresponding to the individual rows are all either identical or disjoint, and don't use the inverse transfer function then? Currently, for a frame which contains either just the current row, or all the current row's peers, I think we'd use the inverse transfer function to fully un-add the old frame, and then add back the new frame.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#82Jim Nasby
jim@nasby.net
In reply to: Gavin Flower (#76)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 1/13/14, 7:41 PM, Gavin Flower wrote:

On 14/01/14 14:29, Tom Lane wrote:
[...]

(2) the float and numeric variants should be implemented under nondefault names (I'm thinking FAST_SUM(), but bikeshed away). People who need extra speed and don't mind the slightly different results can alter their queries to use these variants. One reason I'm thinking this is that whatever we do to ameliorate the semantic issues is going to slow down the forward transition function --- to no benefit unless the aggregate is being used as a window function in a moving window. So I'm less than convinced that we *should* implement any of these designs in the default aggregates, even if we get to the point where we arguably *could* do it with little risk of functional differences. regards, tom lane

How SUM_FAST() instead, then it will more likely to be close to SUM() in an index?

+1. That's what I do in cases like this.
-- 
Jim C. Nasby, Data Architect                       jim@nasby.net
512.569.9461 (cell)                         http://jim.nasby.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#83Robert Haas
robertmhaas@gmail.com
In reply to: David Rowley (#79)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Jan 14, 2014 at 4:16 AM, David Rowley <dgrowleyml@gmail.com> wrote:

On Tue, Jan 14, 2014 at 9:09 PM, David Rowley <dgrowleyml@gmail.com> wrote:

I think unless anyone has some objections I'm going to remove the inverse
transition for SUM(numeric) and modify the documents to tell the user how to
build their own FAST_SUM(numeric) using the built in functions to do it. I'm
starting to think that playing around with resetting numeric scale will turn
a possible 9.4 patch into a 9.5/10.0 patch. I see no reason why what's there
so far, minus sum(numeric), can't go in...

Of course its only now that I discover that this is not possible to do this:

CREATE AGGREGATE fast_sum (numeric)
(
stype = numeric,
sfunc = numeric_avg_accum,
invfunc = numeric_avg_accum_inv,
finalfunc = numeric_sum
);

because SUM(numeric) uses an internal type to store the transition state.

hmmm, built-in fast_sum anyone?
Is there any simple way to limit these to only be used in the context of a
window? If so is it worth it?
Would we want fast_sum() for float too?

Maybe these additional "fast" functions (one might also say
"inaccurate") should live in an extension, in contrib.

It strikes me that for numeric what you really need is to just tell
the sum operation, whether through a parameter or otherwise, how many
decimal places to show in the output. Obviously that's not a
practical change for sum() itself, but if we're inventing new stuff it
can be done.

For floats, things are not so good. The data type is inexact by
nature, and I think cases where you get substantially wrong answers
will be common enough that people who attempt to use whatever we
devise in this area will have sum trouble. *ducks*

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#84Florian Pflug
fgp@phlo.org
In reply to: Robert Haas (#83)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan15, 2014, at 19:56 , Robert Haas <robertmhaas@gmail.com> wrote:

It strikes me that for numeric what you really need is to just tell
the sum operation, whether through a parameter or otherwise, how many
decimal places to show in the output. Obviously that's not a
practical change for sum() itself, but if we're inventing new stuff it
can be done.

You can already do that, just cast the result of SUM(numeric) to
an appropriately constrained NUMERIC type, i.e. to NUMERIC(prec, scale).

BTW, AVG() and STDDEV() have the same issue. The problem is just partially
masked by the division by N (or N-1) at the end, because we always emit as
least 16 fractional digits when dividing. So you have to have an input
value with a larger scale than that to trigger it.

For the following query

select avg(x) over (order by i rows between current row and 1 following)
from (values
(1,1), (2,1), (3,0.000000000000000000000000000000001), (4,1), (5,1)
) t(i,x);

9.3 returns
avg
-------------------------------------
1.00000000000000000000
0.500000000000000000000000000000001
0.500000000000000000000000000000001
1.00000000000000000000
1.00000000000000000000

but HEAD+patch returns
avg
-------------------------------------
1.00000000000000000000
0.500000000000000000000000000000001
0.500000000000000000000000000000001
1.000000000000000000000000000000000
1.000000000000000000000000000000000

I have to admit that I'm *very* tempted to suggest we simply ignore this -
but that *would* mean accepting that windowed aggregates are non-
deterministic in the sense that their result (even if only in the number
of trailing zeros) depends on values outside of the frame. Which, I guess,
is a box that best stays closed...

I'm currently thinking the best way forward is to get a basic patch
without any NUMERIC stuff committed, and to revisit this after that's done.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#85Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#84)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

I'm currently thinking the best way forward is to get a basic patch
without any NUMERIC stuff committed, and to revisit this after that's done.

+1. I liked Robert's suggestion that the "fast" aggregates be implemented
as a contrib module rather than directly in core, too. If we do that then
it's perfectly reasonable to handle that as a separate patch.

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

#86Florian Pflug
fgp@phlo.org
In reply to: Florian Pflug (#81)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan14, 2014, at 17:39 , Florian Pflug <fgp@phlo.org> wrote:

On Jan14, 2014, at 11:06 , David Rowley <dgrowleyml@gmail.com> wrote:

Here's a patch which removes sum(numeric) and changes the documents a little to remove a reference to using sum(numeric) to workaround the fact that there's no inverse transitions for sum(float). I also made a small change in the aggregates.sql tests to check that the aggfnoid <= 9999.

I've looked over the patch, here a few comments.

For STRICT pairs of transfer and inverse transfer functions we should complain if any of them ever return NULL. That can never be correct anyway, since a STRICT function cannot undo a non-NULL -> NULL transition.

The same goes for non-STRICT, at least if we ever want to allow an inverse transfer function to indicate "Sorry, cannot undo in this case, please rescan". If we allowed NULL as just another state value now, we couldn't easily undo that later, so we'd rob ourselves of the obvious way for the inverse transfer function to indicate this condition to its caller.

The notnullcount machinery seems to apply to both STRICT and non-STRICT transfer function pairs. Shouldn't that be constrained to STRICT transfer function pairs? For non-STRICT pairs, it's up to the transfer functions to deal with NULL inputs however they please, no?

I modified the patch to do this, and ran into a problem. Currently, aggregates with state type "internal" cannot have a strict transfer function, even if they behave like they did, i.e. ignore non-NULL inputs. This is because the only possible initial value for state type "internal" is NULL, and it's up to the transfer function to create the state - usually upon seeing the first non-NULL input. Now, currently that isn't a huge problem - the transfer function simply has to check for NULL input values itself, and if it's indeed conceptually strict, it just returns in this case.

With inverse transfer functions, however, each such pair of forward and inverse transfer functions would also need to duplicate the NULL-counting logic from nodeWindowAgg.c if it want's to be conceptually strict, i.e. ignore NULL inputs, but return NULL if there are *only* NULL inputs (or no inputs at all). That seems like a lot of duplicated code in the long run.

In essence, what we'd want for strict pairs of forward and inverse transfer functions is for the forward transfer function to be strict in all arguments except the state, and the inverse to be strict according to the usual definition. We can't express that property of the forward transfer function within pg_proc, but we don't really have to - a local hack in nodeWindowAgg.c suffices. So what I'm proposing is:

We allow the forward transfer function to be non-strict even if the inverse is strict, but only if the initial value is NULL. In that case we behave as if the forward transfer function was strict, except that upon seeing the first non-NULL input we call it with a NULL state. The return value must still be non-NULL in all cases.

Thoughts?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#87Florian Pflug
fgp@phlo.org
In reply to: Florian Pflug (#86)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan16, 2014, at 02:32 , Florian Pflug <fgp@phlo.org> wrote:

On Jan14, 2014, at 17:39 , Florian Pflug <fgp@phlo.org> wrote:

On Jan14, 2014, at 11:06 , David Rowley <dgrowleyml@gmail.com> wrote:

Here's a patch which removes sum(numeric) and changes the documents a little to remove a reference to using sum(numeric) to workaround the fact that there's no inverse transitions for sum(float). I also made a small change in the aggregates.sql tests to check that the aggfnoid <= 9999.

I've looked over the patch, here a few comments.

For STRICT pairs of transfer and inverse transfer functions we should complain if any of them ever return NULL. That can never be correct anyway, since a STRICT function cannot undo a non-NULL -> NULL transition.

The same goes for non-STRICT, at least if we ever want to allow an inverse transfer function to indicate "Sorry, cannot undo in this case, please rescan". If we allowed NULL as just another state value now, we couldn't easily undo that later, so we'd rob ourselves of the obvious way for the inverse transfer function to indicate this condition to its caller.

The notnullcount machinery seems to apply to both STRICT and non-STRICT transfer function pairs. Shouldn't that be constrained to STRICT transfer function pairs? For non-STRICT pairs, it's up to the transfer functions to deal with NULL inputs however they please, no?

I modified the patch to do this, and ran into a problem. Currently, aggregates with state type "internal" cannot have a strict transfer function, even if they behave like they did, i.e. ignore non-NULL inputs. This is because the only possible initial value for state type "internal" is NULL, and it's up to the transfer function to create the state - usually upon seeing the first non-NULL input. Now, currently that isn't a huge problem - the transfer function simply has to check for NULL input values itself, and if it's indeed conceptually strict, it just returns in this case.

With inverse transfer functions, however, each such pair of forward and inverse transfer functions would also need to duplicate the NULL-counting logic from nodeWindowAgg.c if it want's to be conceptually strict, i.e. ignore NULL inputs, but return NULL if there are *only* NULL inputs (or no inputs at all). That seems like a lot of duplicated code in the long run.

In essence, what we'd want for strict pairs of forward and inverse transfer functions is for the forward transfer function to be strict in all arguments except the state, and the inverse to be strict according to the usual definition. We can't express that property of the forward transfer function within pg_proc, but we don't really have to - a local hack in nodeWindowAgg.c suffices. So what I'm proposing is:

We allow the forward transfer function to be non-strict even if the inverse is strict, but only if the initial value is NULL. In that case we behave as if the forward transfer function was strict, except that upon seeing the first non-NULL input we call it with a NULL state. The return value must still be non-NULL in all cases.

Ok, I tried this and it worked out quite OK.

Updated patch is attached. It passes regression tests, but those currently don't seem to include any aggregates which *don't* ignore NULL values, so that case is probably untested. Also, it allows non-strict forward transfer functions together with strict inverse transfer functions even for non-NULL initial states now. It seemed rather pedantic to forbid this.

BTW, as it stands, the patch currently uses the inverse transition function only when *all* the aggregates belonging to one window clause provide one. After working with the code a bit, I think that a bit of reshuffling would allow that distinction to be made on a per-aggregate basis. The question is, is it worth it?

best regards,
Florian Pflug

Attachments:

inverse_transition_functions_v2.2_fgp.patch.gzapplication/x-gzip; name=inverse_transition_functions_v2.2_fgp.patch.gzDownload
#88David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#87)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, Jan 16, 2014 at 3:47 PM, Florian Pflug <fgp@phlo.org> wrote:

BTW, as it stands, the patch currently uses the inverse transition
function only when *all* the aggregates belonging to one window clause
provide one. After working with the code a bit, I think that a bit of
reshuffling would allow that distinction to be made on a per-aggregate
basis. The question is, is it worth it?

I didn't think it was all that worth it due to the fact that we'd still
need to process every tuple in the frame's scope for each aggregate which
has no inverse transition function, of course, there would be slightly less
work to do in such cases. I guess I just assumed that work load types that
would benefit from inverse transitions would have many rows instead of many
aggregates and few rows.

I guess to implement the aggregated up to marker variables would just need
to be per aggregate rather than per window.

If you think it would be worth it I can modify the patch to work that way.

Regards

David Rowley

Show quoted text

best regards,
Florian Pflug

#89David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#84)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, Jan 16, 2014 at 9:45 AM, Florian Pflug <fgp@phlo.org> wrote:

BTW, AVG() and STDDEV() have the same issue. The problem is just partially
masked by the division by N (or N-1) at the end, because we always emit as
least 16 fractional digits when dividing. So you have to have an input
value with a larger scale than that to trigger it.

For the following query

select avg(x) over (order by i rows between current row and 1 following)
from (values
(1,1), (2,1), (3,0.000000000000000000000000000000001), (4,1), (5,1)
) t(i,x);

9.3 returns
avg
-------------------------------------
1.00000000000000000000
0.500000000000000000000000000000001
0.500000000000000000000000000000001
1.00000000000000000000
1.00000000000000000000

but HEAD+patch returns
avg
-------------------------------------
1.00000000000000000000
0.500000000000000000000000000000001
0.500000000000000000000000000000001
1.000000000000000000000000000000000
1.000000000000000000000000000000000

Uhhh, that's bad news indeed. That means that I'll need to remove not only
all inverse transition functions for all aggregates on numeric types, but
also avg for int types, the stddev* functions for everything, since they
internally use numeric. I guess that only leaves SUM for smallint, int,
bigint, cash and interval, along with count(exp), count(*)...

I have to admit that I'm *very* tempted to suggest we simply ignore this -
but that *would* mean accepting that windowed aggregates are non-
deterministic in the sense that their result (even if only in the number
of trailing zeros) depends on values outside of the frame. Which, I guess,
is a box that best stays closed...

Yeah, I can understand the temptation but I agree we can't go changing
results.

I'm currently thinking the best way forward is to get a basic patch
without any NUMERIC stuff committed, and to revisit this after that's done.

Agreed... I'll warm up my delete key.

Regards

David Rowley

Show quoted text

best regards,
Florian Pflug

#90David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#75)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Jan 14, 2014 at 2:29 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

One reason I'm thinking this is that whatever we do to ameliorate
the semantic issues is going to slow down the forward transition
function --- to no benefit unless the aggregate is being used as a
window function in a moving window. So I'm less than convinced
that we *should* implement any of these designs in the default
aggregates, even if we get to the point where we arguably *could*
do it with little risk of functional differences.

regards, tom lane

I was thinking about this earlier today and came up with an idea that
perhaps aggregates could support 2 transition functions. 1 for normal
aggregation and for windows with UNBOUNDED PRECEDING. The 2nd transition
function could be used when there is a possibility that we would need to
perform an inverse transition. This 2nd transition function could do all
the extra tracking it needed without having to worry that it would slow
down normal aggregation. With numeric that might be tracking each numeric's
scale as it enters and exits the window frame. It might even be possible to
perform inverse transitions with float and double here, we could just
internally use numeric, and have the final function convert back to the
original type. Though perhaps that might have the side effect of making
floating point calculations too accurate? Or at least not matching the IEEE
standards.

Of course, I'm not thinking this could be part of this patch, but I thought
that I'd post the idea while all this is fresh in people's heads.

David

#91David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#81)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Wed, Jan 15, 2014 at 5:39 AM, Florian Pflug <fgp@phlo.org> wrote:

The notnullcount machinery seems to apply to both STRICT and non-STRICT

transfer function pairs. Shouldn't that be constrained to STRICT transfer
function pairs? For non-STRICT pairs, it's up to the transfer functions to
deal with NULL inputs however they please, no?

The reason I had to track the notnullcount was because for non-strict was
because:

select sum(n) over (order by i rows between current row and unbounded
following) from (values(1,1),(2,NULL)) v(i,n);

would otherwise return

1
0

sum is not strict, so I guess we need to track notnullcount for non-strict
functions.

See the code around if (peraggstate->notnullcount == 0)
in retreat_windowaggregate().

The logic around movedaggbase in eval_windowaggregates() seems a bit
convoluted. Couldn't the if be moved before the code that pulls
aggregatedbase up to frameheadpos using the inverse aggregation function?

I had a look at this and even tried moving the code to before the inverse
transitions, but it looks like that would only work if I added more tests
to the if condition to ensure that we're not about to perform inverse
transitions. To me it just seemed more bullet proof the way it is rather
than duplicating logic and having to ensure that it stays duplicated. But
saying that I don't think I've fully got my head around why the original
code is valid in the first place. I would have imagined that it should
contain a check for FRAMEOPTION_START_UNBOUNDED_FOLLOWING, but if that
sounds like complete rubbish then I'll put it down to my head still being
fried from work.

Also, could we easily check whether the frames corresponding to the
individual rows are all either identical or disjoint, and don't use the
inverse transfer function then? Currently, for a frame which contains
either just the current row, or all the current row's peers, I think we'd
use the inverse transfer function to fully un-add the old frame, and then
add back the new frame.

I didn't know there was a situation where this could happen. Could you give
me an example query and I'll run it through the debugger to have a look at
what's going on. But sure, if this is possible and I understand what you
mean then I guess it would be a good optimisation to detect this and throw
away the previous results and start fresh.

Thanks for all of your reviewing on this so far.

Regards

David Rowley

Show quoted text

best regards,
Florian Pflug

#92David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#84)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, Jan 16, 2014 at 9:45 AM, Florian Pflug <fgp@phlo.org> wrote:

I'm currently thinking the best way forward is to get a basic patch
without any NUMERIC stuff committed, and to revisit this after that's done.

Attached is a version to that effect. The number of inverse transition
functions is down to 8 from 36.

I managed to keep avg(interval) in there as it's all integer based division.
sum for all int types remain, plus sum for interval and cash. count(exp)
and count(*) also naturally remain.

sum(bigint) became a bit weird as it uses numeric types internally, so I
had to keep the do_numeric_discard() function to support it. Previously
this function did NaNCount-- if it was removing a NaN numeric, but since I
got rid of the NaNCounter as it was no longer required I just decided to
throw an error if we encounter a numeric with NaN. I thought this was ok as
the only place the function is being called the numeric value being passed
is built from a bigint which obviously can't be NaN. It may be better to
just get rid of do_numeric_discard() and stick the sub_var(&(state->sumX),
newval, &(state->sumX)); into int8_avg_accum_inv(). I don't have a
preference as to which as I think there's reasons for both ways. Perhaps
even the name int8_avg_accum_inv() is misleading as we never use it for
avg(bigint). Anyway, I'll leave it as is for now as I just can't decide
which way is better.

Florian, I've not had a chance to look at your updated v2.2 patch yet
sorry. I'll try and get some time tomorrow evening.

Regards

David Rowley

Show quoted text

best regards,
Florian Pflug

Attachments:

inverse_transition_functions_v2.3.patch.gzapplication/x-gzip; name=inverse_transition_functions_v2.3.patch.gzDownload
#93Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#91)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan16, 2014, at 09:07 , David Rowley <dgrowleyml@gmail.com> wrote:

On Wed, Jan 15, 2014 at 5:39 AM, Florian Pflug <fgp@phlo.org> wrote:

The notnullcount machinery seems to apply to both STRICT and non-STRICT transfer function pairs. Shouldn't that be constrained to STRICT transfer function pairs? For non-STRICT pairs, it's up to the transfer functions to deal with NULL inputs however they please, no?

The reason I had to track the notnullcount was because for non-strict was because:

select sum(n) over (order by i rows between current row and unbounded following) from (values(1,1),(2,NULL)) v(i,n);

would otherwise return
1
0

sum is not strict, so I guess we need to track notnullcount for non-strict functions.
See the code around if (peraggstate->notnullcount == 0) in retreat_windowaggregate().

Yeah, I figured that was the reason, but you can't fix it that way. See /messages/by-id/8E857D95-CBA4-4974-A238-9DD7F61BEA48@phlo.org for a detailed explanation why. My 2.2 patch allows pairs of non-strict forward and strict inverse transfer functions exactly because of this - i.e., basically it decrees that if there's an inverse transfer function, the strict setting of the *inverse* function determines the aggregates NULL behaviour. The forward transfer function is then never called
for NULL inputs, but it *is* called with the NULL state for the first non-NULL input, and *must* then return a non-NULL state (hence it's technically not strict, it's strict only in the inputs, not in the state).

BTW, I just realized I failed to update CREATE AGGREGATE's logic when I did that in 2.2. That doesn't matter for the built-in aggregates since these aren't created with CREATE AGGREGATE, but it's obviously wrong. I'll post a fixed patched.

The logic around movedaggbase in eval_windowaggregates() seems a bit convoluted. Couldn't the if be moved before the code that pulls aggregatedbase up to frameheadpos using the inverse aggregation function?

I had a look at this and even tried moving the code to before the inverse transitions, but it looks like that would only work if I added more tests to the if condition to ensure that we're not about to perform inverse transitions. To me it just seemed more bullet proof the way it is rather than duplicating logic and having to ensure that it stays duplicated. But saying that I don't think I've fully got my head around why the original code is valid in the first place. I would have imagined that it should contain a check for FRAMEOPTION_START_UNBOUNDED_FOLLOWING, but if that sounds like complete rubbish then I'll put it down to my head still being fried from work.

Ok, I get this now. That code really just is asking "is the previous row's frame the same as the current row's frame" in that "if" where you added movedagg. What confused me was the rather weird way that condition is expressed, which turns out is due to the fact that at the point of the if, we don't know the new frame's end. Now, we could use update_frametailpos() to find that, but that's potentially costly, especially if the tuple store was spilled to disk. So instead, the code relies on the fact that only if the frame end is "n FOLLOWING/PRECEDING" can the current row lie within the previous row's frame without the two frame's ends being necessarily the same.

For added confusion, that "if" never explicitly checks whether the frame's *heads* coincide - the previous code seems to have relied on the impossibility of having "aggregatedbase <= current < aggregatedupto" after re-initializing the aggregation, because then aggregatedbase = aggregatedupto = 0. That's why you can't just move the "if" before the "frameheadpos == aggregatedbase" check. But you can *if* you also check whether "aggregatedbase == frameheadpos" in the if - which is clearer than relying on that rather subtle assumption anyway.

BTW, the your patch will also fail badly if the frame head ever moves backwards - the invariant that "aggregatedbase == frameheadpos" after the inverse transition loop will then be violated. Now, this should never happen, because we require that the offset in "n PRECEDING/FOLLOWING" is constant, but we should probably still check for this and elog().

That check was implicit in old code, because it advanced the tuplestore mark, so if the frame head moved backwards, re-scanning the frame would have failed. That brings me to another todo - as it stands, that mark gets never advanced if we're doing inverse aggregation. To fix that, we need a call to WinSetMarkPosition() somewhere in eval_windowaggregates().

After doing this things, eval_windowaggregates ended up calling initalize_windowframeaggregates at a single place again, so I inlined it back into eval_windowaggregates(). I also made it use temp_slot_1 in the inverse aggregation loop, since that seemed safe - the code assumes some invariants about aggregatedbase and agg_row_slow.

Updated patch is attached (2.4_fgp).

Also, could we easily check whether the frames corresponding to the individual rows are all either identical or disjoint, and don't use the inverse transfer function then? Currently, for a frame which contains either just the current row, or all the current row's peers, I think we'd use the inverse transfer function to fully un-add the old frame, and then add back the new frame.

I didn't know there was a situation where this could happen. Could you give me an example query and I'll run it through the debugger to have a look at what's going on. But sure, if this is possible and I understand what you mean then I guess it would be a good optimisation to detect this and throw away the previous results and start fresh.

The case I had in mind there was "RANGE BETWEEN CURRENT ROW AND CURRENT ROW", i.e. each row's frame consists of all it's ordering peers. Haven't looked into this yet.

best regards,
Florian Pflug

Attachments:

inverse_transition_functions_v2.4_fgp.patch.gzapplication/x-gzip; name=inverse_transition_functions_v2.4_fgp.patch.gzDownload
#94Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: David Rowley (#92)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 01/16/2014 01:02 PM, David Rowley wrote:

sum(bigint) became a bit weird as it uses numeric types internally, so I
had to keep the do_numeric_discard() function to support it.

It's pretty weird that we have implemented sum(bigint) that way. I
understand that the result is a numeric so that it won't overflow, but
implementing it by converting every value to numeric is naive.

I propose that we reimplement sum(bigint) in a more efficient way: For
the internal state, let's use an int8 and a numeric overflow field. The
transition function adds to the int8 variable, and checks for overflow.
On overflow, increment the numeric field by one. In the final function,
multiply the numeric by 2^64, and add the residual int8 value.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#95Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#94)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

I propose that we reimplement sum(bigint) in a more efficient way: For
the internal state, let's use an int8 and a numeric overflow field. The
transition function adds to the int8 variable, and checks for overflow.
On overflow, increment the numeric field by one. In the final function,
multiply the numeric by 2^64, and add the residual int8 value.

It'd probably be sufficient to handle it as two int64 fields (handmade
128-bit arithmetic, or maybe even not so handmade if that ever gets
reasonably common among C compilers). You're assuming the final output
is still numeric, right?

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

#96Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Tom Lane (#95)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 01/16/2014 08:59 PM, Tom Lane wrote:

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

I propose that we reimplement sum(bigint) in a more efficient way: For
the internal state, let's use an int8 and a numeric overflow field. The
transition function adds to the int8 variable, and checks for overflow.
On overflow, increment the numeric field by one. In the final function,
multiply the numeric by 2^64, and add the residual int8 value.

It'd probably be sufficient to handle it as two int64 fields (handmade
128-bit arithmetic, or maybe even not so handmade if that ever gets
reasonably common among C compilers).

True. That would be sufficient for summing 2^64 int8s of INT64_MAX. That
sounds like enough, especially considering that that count() will
overflow after that too.

You're assuming the final output is still numeric, right?

Yep.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#97Andres Freund
andres@2ndquadrant.com
In reply to: Heikki Linnakangas (#96)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 2014-01-16 21:07:33 +0200, Heikki Linnakangas wrote:

On 01/16/2014 08:59 PM, Tom Lane wrote:

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

I propose that we reimplement sum(bigint) in a more efficient way: For
the internal state, let's use an int8 and a numeric overflow field. The
transition function adds to the int8 variable, and checks for overflow.
On overflow, increment the numeric field by one. In the final function,
multiply the numeric by 2^64, and add the residual int8 value.

It'd probably be sufficient to handle it as two int64 fields (handmade
128-bit arithmetic, or maybe even not so handmade if that ever gets
reasonably common among C compilers).

True. That would be sufficient for summing 2^64 int8s of INT64_MAX. That
sounds like enough, especially considering that that count() will overflow
after that too.

You'll have to handle adding negative values and underflow as
well.
Maybe it's instead sufficient to just have flag indicating that you're
working with a state that hasn't overflowed so far and just plain int8
math as long as that's the case, and entirely fall back to the current
path once overflowed. That will probably be slightly faster and easily
handle the majority of cases since overflowing int8 ought to be pretty
rare in the real world.

Greetings,

Andres Freund

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#98Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#97)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Andres Freund <andres@2ndquadrant.com> writes:

You'll have to handle adding negative values and underflow as
well.

Right.

Maybe it's instead sufficient to just have flag indicating that you're
working with a state that hasn't overflowed so far and just plain int8
math as long as that's the case, and entirely fall back to the current
path once overflowed. That will probably be slightly faster and easily
handle the majority of cases since overflowing int8 ought to be pretty
rare in the real world.

Dunno, I think that a transition state containing both an int64 and
a (presumably separately palloc'd) numeric will be a real PITA.
And it will not be faster, because the principal drag on performance
will just be the overflow test, which you have to do either way.

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

#99Andres Freund
andres@2ndquadrant.com
In reply to: Tom Lane (#98)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 2014-01-16 14:23:47 -0500, Tom Lane wrote:

Maybe it's instead sufficient to just have flag indicating that you're
working with a state that hasn't overflowed so far and just plain int8
math as long as that's the case, and entirely fall back to the current
path once overflowed. That will probably be slightly faster and easily
handle the majority of cases since overflowing int8 ought to be pretty
rare in the real world.

Dunno, I think that a transition state containing both an int64 and
a (presumably separately palloc'd) numeric will be a real PITA.

Yea, not sure myself. I just dislike the idea of having a good part of a
128bit math implementation for a single transition function.

Another alternative would be a configure check for compiler/native
128bit math and fall back to the current implementation if none is
provided... That should give decent performance with a pretty low amount
of code for most platforms.

And it will not be faster, because the principal drag on performance
will just be the overflow test, which you have to do either way.

Well, you don't need to check the second variable for lots of
operations. Say, the current sum is 0 and you add a -1. With the two
variables scheme that requires checking the second variable,
manipulating it etc.

Greetings,

Andres Freund

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#100Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#99)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Andres Freund <andres@2ndquadrant.com> writes:

On 2014-01-16 14:23:47 -0500, Tom Lane wrote:

Dunno, I think that a transition state containing both an int64 and
a (presumably separately palloc'd) numeric will be a real PITA.

Yea, not sure myself. I just dislike the idea of having a good part of a
128bit math implementation for a single transition function.

Not sure how you figure that we need very much new code beyond the
overflow test.

Well, you don't need to check the second variable for lots of
operations. Say, the current sum is 0 and you add a -1. With the two
variables scheme that requires checking the second variable,
manipulating it etc.

I'm envisioning just

state->lowhalf += input;
if (overflowed_up)
state->highhalf++;
else if (overflowed_down)
state->highhalf--;

The only thing that might take a moment's thought, or extra cycles in the
normal case, is extending the overflow test so that it can tell whether
we need to increment or decrement the upper half.

[ thinks a bit... ] Note that I'm supposing that the state is defined
as (highhalf * 2^64) + lowhalf, not that we need the two variables to
be exactly a 128-bit twos-complement value, which is what I think
you're talking about.

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

#101Florian Pflug
fgp@phlo.org
In reply to: Florian Pflug (#93)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

I had some more fun with this, the result is v2.5 of the patch (attached). Changes are explained below.

On Jan16, 2014, at 19:10 , Florian Pflug <fgp@phlo.org> wrote:

On Jan16, 2014, at 09:07 , David Rowley <dgrowleyml@gmail.com> wrote:

On Wed, Jan 15, 2014 at 5:39 AM, Florian Pflug <fgp@phlo.org> wrote:

The notnullcount machinery seems to apply to both STRICT and non-STRICT transfer function pairs. Shouldn't that be constrained to STRICT transfer function pairs? For non-STRICT pairs, it's up to the transfer functions to deal with NULL inputs however they please, no?

The reason I had to track the notnullcount was because for non-strict was because:

select sum(n) over (order by i rows between current row and unbounded following) from (values(1,1),(2,NULL)) v(i,n);

would otherwise return
1
0

sum is not strict, so I guess we need to track notnullcount for non-strict functions.
See the code around if (peraggstate->notnullcount == 0) in retreat_windowaggregate().

Yeah, I figured that was the reason, but you can't fix it that way. See /messages/by-id/8E857D95-CBA4-4974-A238-9DD7F61BEA48@phlo.org for a detailed explanation why. My 2.2 patch allows pairs of non-strict forward and strict inverse transfer functions exactly because of this - i.e., basically it decrees that if there's an inverse transfer function, the strict setting of the *inverse* function determines the aggregates NULL behaviour. The forward transfer function is then never called
for NULL inputs, but it *is* called with the NULL state for the first non-NULL input, and *must* then return a non-NULL state (hence it's technically not strict, it's strict only in the inputs, not in the state).

BTW, I just realized I failed to update CREATE AGGREGATE's logic when I did that in 2.2. That doesn't matter for the built-in aggregates since these aren't created with CREATE AGGREGATE, but it's obviously wrong. I'll post a fixed patched.

Fixed now.

The logic around movedaggbase in eval_windowaggregates() seems a bit convoluted. Couldn't the if be moved before the code that pulls aggregatedbase up to frameheadpos using the inverse aggregation function?

I had a look at this and even tried moving the code to before the inverse transitions, but it looks like that would only work if I added more tests to the if condition to ensure that we're not about to perform inverse transitions. To me it just seemed more bullet proof the way it is rather than duplicating logic and having to ensure that it stays duplicated. But saying that I don't think I've fully got my head around why the original code is valid in the first place. I would have imagined that it should contain a check for FRAMEOPTION_START_UNBOUNDED_FOLLOWING, but if that sounds like complete rubbish then I'll put it down to my head still being fried from work.

Ok, I get this now. That code really just is asking "is the previous row's frame the same as the current row's frame" in that "if" where you added movedagg. What confused me was the rather weird way that condition is expressed, which turns out is due to the fact that at the point of the if, we don't know the new frame's end. Now, we could use update_frametailpos() to find that, but that's potentially costly, especially if the tuple store was spilled to disk. So instead, the code relies on the fact that only if the frame end is "n FOLLOWING/PRECEDING" can the current row lie within the previous row's frame without the two frame's ends being necessarily the same.

For added confusion, that "if" never explicitly checks whether the frame's *heads* coincide - the previous code seems to have relied on the impossibility of having "aggregatedbase <= current < aggregatedupto" after re-initializing the aggregation, because then aggregatedbase = aggregatedupto = 0. That's why you can't just move the "if" before the "frameheadpos == aggregatedbase" check. But you can *if* you also check whether "aggregatedbase == frameheadpos" in the if - which is clearer than relying on that rather subtle assumption anyway.

BTW, the your patch will also fail badly if the frame head ever moves backwards - the invariant that "aggregatedbase == frameheadpos" after the inverse transition loop will then be violated. Now, this should never happen, because we require that the offset in "n PRECEDING/FOLLOWING" is constant, but we should probably still check for this and elog().

That check was implicit in old code, because it advanced the tuplestore mark, so if the frame head moved backwards, re-scanning the frame would have failed. That brings me to another todo - as it stands, that mark gets never advanced if we're doing inverse aggregation. To fix that, we need a call to WinSetMarkPosition() somewhere in eval_windowaggregates().

After doing this things, eval_windowaggregates ended up calling initalize_windowframeaggregates at a single place again, so I inlined it back into eval_windowaggregates(). I also made it use temp_slot_1 in the inverse aggregation loop, since that seemed safe - the code assumes some invariants about aggregatedbase and agg_row_slow.

Updated patch is attached (2.4_fgp).

I've now shuffled things around so that we can use inverse transition functions
even if only some aggregates provide them, and to allow inverse transition
functions to force a restart by returning NULL. The rules regarding NULL handling
are now the following

(A) Transition functions without an inverse behave as they always did
(B) Forward transition functions with an inverse may not return NULL,
not even for all-NULL inputs.
(C) If the inverse transition function returns NULL, the aggregation is
restarted from the beginning, just as If no inverse transition
function had been provided.
(D) If the transition function is strict, the inverse must also be.
(E) NULLs are skipped if the inverse transition function is strict,
regardless of whether the forward transition function is.

The rules may seem a bit weird, but they're the only solution I found which

* Doesn't require every invertible transition function with "internal"
state to implement it's own NULL counting. This requires (E), since
transition functions with state type "internal" need to be callable
with a NULL state - since nobody else can allocate the state object.

* Still allows transition functions to handle NULL values however they
please - they just have to have a non-strict inverse to opt out of (E).
They cannot use NULL states however they please, though.

* Allows partial inverse transition functions, i.e. inverse transition
functions which sometimes report "sorry, please restart". The only
easy way to report this is to have the inverse transition function
return NULL, which means we cannot allow NULL as just another state
value, i.e. this mandates (B,C)

(D) is purely to avoid having to deal with any special-cases that might
arise from allowing this.

I've also moved the volatile functions check into initialize_peragg() -
having it in ExecWindowAgg() made no sense to me since it's dealing
with static information. Also, ExecWindowAgg() may get called multiple
times, e.g. if the window functions are on the inner side of a
nested loop join or within a subselect.

Also, could we easily check whether the frames corresponding to the individual rows are all either identical or disjoint, and don't use the inverse transfer function then? Currently, for a frame which contains either just the current row, or all the current row's peers, I think we'd use the inverse transfer function to fully un-add the old frame, and then add back the new frame.

I didn't know there was a situation where this could happen. Could you give me an example query and I'll run it through the debugger to have a look at what's going on. But sure, if this is possible and I understand what you mean then I guess it would be a good optimisation to detect this and throw away the previous results and start fresh.

The case I had in mind there was "RANGE BETWEEN CURRENT ROW AND CURRENT ROW", i.e. each row's frame consists of all it's ordering peers. Haven't looked into this yet.

It now resets the aggregation when the new frame doesn't overlap the old one, i.e. if frameheadpos > aggregatedupto.

The patch passes regression test, but I haven't done any other testing yet.
Also, the documentation doesn't explain the NULL and STRICT semantics yet.

best regards,
Florian Pflug

Attachments:

inverse_transition_functions_v2.5_fgp.patch.gzapplication/x-gzip; name=inverse_transition_functions_v2.5_fgp.patch.gzDownload
#102David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#101)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Fri, Jan 17, 2014 at 3:05 PM, Florian Pflug <fgp@phlo.org> wrote:

I had some more fun with this, the result is v2.5 of the patch (attached).
Changes are explained below.

Looks like you've been busy on this! Thank you for implementing all the
changes you talked about.
I've now started working the 2.5 patch you sent and I've ripped out all the
numeric inverse transition functions on that patch and implemented inverse
transitions for max and min using the new NULL returning method that you've
implemented. I've also made a very fast pass and fixed up a few comments
and some random white space. I've not gotten to the documents yet.

I did add a whole bunch of regression tests for all of the inverse
transition functions that are used for max and min. I'm just calling these
like:
SELECT int4larger_inv(3,2),int4larger_inv(3,3),int4larger_inv(3,4);
Rather than using the aggregate as a window function each time. I thought
it was better to validate each of those functions individually then put in
various tests that target a smaller number of aggregates with various
inputs.

A few things still to do that I'll try and get to soon.
1. More testing
2. Docs updated.
3. Perhaps I'll look at adding bitand and bitor inverse transition functions
4. ?

Thanks again for putting so much effort into this.
If you make any more changes would it be possible for you to base them on
the attached patch instead of the last one you sent?

Perhaps if there's much more hacking to do we could start pushing to a
guthub repo to make it easier to work together on this.

Regards

David Rowley

Attachments:

inverse_transition_functions_v2.6.patch.gzapplication/x-gzip; name=inverse_transition_functions_v2.6.patch.gzDownload
#103Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#102)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan17, 2014, at 15:14 , David Rowley <dgrowleyml@gmail.com> wrote:

If you make any more changes would it be possible for you to base them on the attached patch instead of the last one you sent?

Sure! The only reason I didn't rebase my last patch onto yours was that having the numeric stuff in there meant potentially better test coverage. Thanks for doing the merge!

Perhaps if there's much more hacking to do we could start pushing to a guthub repo to make it easier to work together on this.

Yeah, that seems like a good idea. Easier to keep track of then a bunch of patches floating around. Could you push your latest version?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#104David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#101)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Fri, Jan 17, 2014 at 3:05 PM, Florian Pflug <fgp@phlo.org> wrote:

I've now shuffled things around so that we can use inverse transition
functions
even if only some aggregates provide them, and to allow inverse transition
functions to force a restart by returning NULL. The rules regarding NULL
handling
are now the following

Maybe this is me thinking out loud, but I'm just thinking about the numeric
case again.
Since the patch can now handle inverse transition functions returning NULL
when they fail to perform inverse transitions, I'm wondering if we could
add an "expectedscale" to NumericAggState, set it to -1 initially, when we
get the first value set the expectedscale to the dscale of that numeric,
then if we get anything but that value we'll set the expectedscale back to
-1 again, if we are asked to perform an inverse transition with a
expectedscale as -1 we'll return null, otherwise we can perform the inverse
transition...

Thoughts?

Regards

David Rowley

#105Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#104)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan17, 2014, at 20:34 , David Rowley <dgrowleyml@gmail.com> wrote:

On Fri, Jan 17, 2014 at 3:05 PM, Florian Pflug <fgp@phlo.org> wrote:

I've now shuffled things around so that we can use inverse transition functions
even if only some aggregates provide them, and to allow inverse transition
functions to force a restart by returning NULL. The rules regarding NULL handling
are now the following

Maybe this is me thinking out loud, but I'm just thinking about the numeric case again.
Since the patch can now handle inverse transition functions returning NULL when they
fail to perform inverse transitions, I'm wondering if we could add an "expectedscale"
to NumericAggState, set it to -1 initially, when we get the first value set the
expectedscale to the dscale of that numeric, then if we get anything but that value
we'll set the expectedscale back to -1 again, if we are asked to perform an inverse
transition with a expectedscale as -1 we'll return null, otherwise we can perform
the inverse transition...

You could do better than that - the numeric problem amounts to tracking the maximum
scale AFAICS, so it'd be sufficient to restart if we remove a value whose scale equals
the current maximum. But if we do SUM(numeric) at all, I think we should do so
without requiring restarts - I still think the overhead of tracking the maximum scale
within the aggregated values isn't that bad. If we zero the dscale counters lazily,
the numbers of entries we have to zero is bound by the maximum dscale we encounter.
Since actually summing N digits is probably more expensive than zeroing them, and since
we pay the zeroing overhead only once per aggregation but save potentially many
summations, I'm pretty sure we come out ahead by quite some margin.

It'd be interesting to do float() similar to the way you describe, though. We might
not be able to guarantee that we yield exactly the same result as without inverse
aggregation, but we might be able to bound the error. That might make it acceptable
to do this - as Kevin pointed out, float is always an approximation anyway. I haven't
really thought that through, though...

Anyway, with time running out fast if we want to get this into 9.4, I think we should
focus on getting this into a committable state right now.

I've started to look over what you've pushed to github, and it looks mostly fine.
I have a few comments - mostly cosmetic stuff - that I'll post once I finished reading
through it. I also plan to do some basic performance testing to verify that my
reshuffling of eval_windowaggregates() doesn't hurt aggregates without an inverse
transition function.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#106David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#105)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, Jan 18, 2014 at 10:17 AM, Florian Pflug <fgp@phlo.org> wrote:

On Jan17, 2014, at 20:34 , David Rowley <dgrowleyml@gmail.com> wrote:

On Fri, Jan 17, 2014 at 3:05 PM, Florian Pflug <fgp@phlo.org> wrote:

I've now shuffled things around so that we can use inverse transition

functions

even if only some aggregates provide them, and to allow inverse

transition

functions to force a restart by returning NULL. The rules regarding

NULL handling

are now the following

Maybe this is me thinking out loud, but I'm just thinking about the

numeric case again.

Since the patch can now handle inverse transition functions returning

NULL when they

fail to perform inverse transitions, I'm wondering if we could add an

"expectedscale"

to NumericAggState, set it to -1 initially, when we get the first value

set the

expectedscale to the dscale of that numeric, then if we get anything but

that value

we'll set the expectedscale back to -1 again, if we are asked to perform

an inverse

transition with a expectedscale as -1 we'll return null, otherwise we

can perform

the inverse transition...

You could do better than that - the numeric problem amounts to tracking
the maximum
scale AFAICS, so it'd be sufficient to restart if we remove a value whose
scale equals
the current maximum. But if we do SUM(numeric) at all, I think we should
do so
without requiring restarts - I still think the overhead of tracking the
maximum scale
within the aggregated values isn't that bad. If we zero the dscale
counters lazily,
the numbers of entries we have to zero is bound by the maximum dscale we
encounter.
Since actually summing N digits is probably more expensive than zeroing
them, and since
we pay the zeroing overhead only once per aggregation but save potentially
many
summations, I'm pretty sure we come out ahead by quite some margin.

We'll work that out, I don't think it will take very long to code up your
idea either.
I just thought that my idea was good enough and very cheap too, won't all
numerics that are actually stored in a column have the same scale anyway?
Is it not only been a problem because we've been testing with random
numeric literals the whole time?

The test turned out to become:
if (state->expectedScale == -1)
state->expectedScale = X.dscale;
else if (state->expectedScale != X.dscale)
state->expectedScale = -2;

In do_numeric_accum, then when we do the inverse I just check if
expectedScale < 0 then return NULL.

I'm not set on it, and I'm willing to try the lazy zeroing of the scale
tracker array, but I'm just not quite sure what extra real use cases it
covers that the one above does not. Perhaps my way won't perform inverse
transitions if the query did sum(numericCol / 10) or so.

I'll be committing this to the github repo very soon, so we can hack away
at the scale tracking code once it's back in.

David Rowley

#107David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#106)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, Jan 18, 2014 at 10:42 AM, David Rowley <dgrowleyml@gmail.com> wrote:

You could do better than that - the numeric problem amounts to tracking
the maximum
scale AFAICS, so it'd be sufficient to restart if we remove a value whose
scale equals
the current maximum. But if we do SUM(numeric) at all, I think we should
do so
without requiring restarts - I still think the overhead of tracking the
maximum scale
within the aggregated values isn't that bad. If we zero the dscale
counters lazily,
the numbers of entries we have to zero is bound by the maximum dscale we
encounter.
Since actually summing N digits is probably more expensive than zeroing
them, and since
we pay the zeroing overhead only once per aggregation but save
potentially many
summations, I'm pretty sure we come out ahead by quite some margin.

We'll work that out, I don't think it will take very long to code up your
idea either.
I just thought that my idea was good enough and very cheap too, won't all
numerics that are actually stored in a column have the same scale anyway?
Is it not only been a problem because we've been testing with random
numeric literals the whole time?

The test turned out to become:
if (state->expectedScale == -1)
state->expectedScale = X.dscale;
else if (state->expectedScale != X.dscale)
state->expectedScale = -2;

In do_numeric_accum, then when we do the inverse I just check if
expectedScale < 0 then return NULL.

I'm not set on it, and I'm willing to try the lazy zeroing of the scale
tracker array, but I'm just not quite sure what extra real use cases it
covers that the one above does not. Perhaps my way won't perform inverse
transitions if the query did sum(numericCol / 10) or so.

I'll be committing this to the github repo very soon, so we can hack away
at the scale tracking code once it's back in.

Ok, we're back up to 86 aggregate function / type combinations with inverse
transition functions.
I've commited my latest work up to github and here's a fresh patch which I
will need to do more tests on.

The test (below) that used to fail a few versions ago is back in there and
it's now passing.

SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND
UNBOUNDED FOLLOWING)
FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);

In this case it won't use inverse transitions because the forward
transition function detects that the scale is not fixed.

I tested throwing some numerics into a table and I'm pretty happy with the
results.

create table num (num numeric(10,2) not null);
insert into num (num) select * from generate_series(1,20000);
select sum(num) over(order by num rows between current row and unbounded
following) from num; -- 124ms
select sum(num / 10) over(order by num rows between current row and
unbounded following) from num; -- 254ms
select sum(num / 1) over(order by num rows between current row and
unbounded following) from num; -- 108156.917 ms

The divide by 1 case is slow because of that weird 20 trailing zero instead
of 16 when dividing a numeric by 1 and that causes the inverse transition
function to return NULL because the scale changes.

I've not tested an unpatched version yet to see how that divide by 1 query
performs on that but I'll get to that soon.

I'm thinking that the idea about detecting the numeric range with floating
point types and performing an inverse transition providing the range has
not gone beyond some defined danger zone is not material for this patch...
I think it would be not a great deal of work to code, but the testing
involved would probably make this patch not possible for 9.4

The latest version of the patch is attached.

Regards

David Rowley

Attachments:

inverse_transition_functions_v2.7.patch.gzapplication/x-gzip; name=inverse_transition_functions_v2.7.patch.gzDownload
����Rinverse_transition_functions_v2.7.patch�[io�D���W,����i���"�"^���KE{�;x�����<3c{�:5)��B��8{�������� P���0Sz�O�=���t�y:�Q2�]��&-����7��������A�{x���������m��jgg���G���������������WJ�>0q�.��R�0�Xy���{�����&��~�����fi�eAh"�LO�a������
����bn�R3������zvhf&�=�?)�0���f!�����f�R�g�l>e��4����>��s����Z��M�8MU?Dk��
�XG��I��&�_���HM���Fgf)�3�j��>����?
���v�A��;9m�F;)&�
���?�����������n��~z3�O���G�3z�E��7�:�����3�<��9S[�{7K�u��W�������7�/�w���3K�V��)�}w=\�@FB����\�N�)����������
}*���Bpt��z-�Zc'�#�8�dt9x����X��_]^���]Gc/�}�����^��]]���$�����%i�y��	y�����������=��k�����\"��;G=r���������H}rs����D]�\o���q�W���OQN����������Jr��N��fef��z��-gg�VH��PR#��P����\>�U�|W����(;7^�,�wTvo��v�LU E�ev�Nl��q������U�&3u��x��DUF=�(7� �v[�3i��[[c����iy7��Z�[���G���,��%SI�����7����o>�;�����:"�>�#_�f�<^i>���&L�%<K�4���d�%��:S������N
}N�2�������M�b��k�_�~
��4&����h�2EvM�d�� �g�Zf1��
�dY9�����Q,|�f/���0��ZO�~����<�����w�?�p�Ge=7lO
 ��/��Vei
+#�2a��a�n��ma
�)3��i>3��x�����{"�2A^�P��u����p	
�����Ng���S���)��+�eVF��6m����{x����Q�+B�vBK�_�l�N�L��%y�m����^��v+�� �P~� ���vU�A������6��w�g�;�8uX��b��
wS��d�$X&��jD��t��f��bFk�t(5��9��Df�_����J���^N$��#�S��/�	��/����*ve����3�"��/���jEL��}f���ICc+�
��G-?7���,��eB�|u��T���m�����6{�&���������z��^��lRyncW������Oe������
�Dg���X�1�����
��"E���=���E��6��4!�TQ+*&U/f�����_UdO��	��
^��+���f���F�����������j���W��.���C��I3{`.���K��E���!�����|61)Dad��j�Rs�O��������O�%Q�,��� ���������pz�@���N��cQ����+(��D��D��6���2�:%--������INP1O���1�0�
������i��JBQaK��JF�2�OV�EA�y��4�9e�:
g!�����p]�-�������H*6�hu�<R��=9�R��.EB�����`���w��	��V��~u&ns��KX"�l�d�-aC8%�3���X�c�%a.�B������ $Z�_�E*�D�������d-2��%i����NL����G�Da3?�R�)������k9p���<hI���K{�h$���yD�b�Z�,|���>�kpP���;��Qxwb^�B���c��D����0����qV������6�
}��"�+P
�(J1��a�["���7�����e�ZD�I-��$�} ���:u�'��q������1��^�3b�H"V�Z���IDq���1I��mR5W��x?���!L`b���t����
�?�������j�b��}h���k�.��d������`."�ND�������*:��}W�Jhk:�gO@�	�Doj3J����%��I�L�)�,+�F�;$���,]T���P�Kx5Y�����:<�S�M��V������aaZV�3$�Vl*���I8��\E�����mv3(4�Bb}1�wZ�%��)<i������0��Qk\�3��'��$�n]����98-b���
��Gm�"|H�����;@���r!���hV�)���u��5Fe�L�Q�K��q��F6N���<�����V$��D�9S��a��9�W��sE*	���Q-[�4��"�vr���P�?��]v�vY��D0<�|$�,C��}�D$�9����a��c%�5�r1kw�d[������*��c�f���k��=��&���gy��7�.]�yj���b��c�;���c���vn���&n��x?w�������s.��<2%�m��;�y�gy����!:��n/L�	���������Co�N���<�*n���N���z�������{h�#j��||�������������Y>������/u����Fx�>AX��kkEh$6�L!������rj�p���}��N�������^E��:e�u���3�b��Y��,�
�g�n�$�������O����[Tn��vQ�������Oj2J��%}�����.��_�n>��������h�?8���2g��2i�%�����_]����]�i������b���=n��&��tk�Omm�-n������x7B2t�����\g�����y.%����6��w�P��u��]w��u�7tF/0�S�����Hg��h�e���:�W0��Uk2J]���������g��p��$7&2����=�X{+��$!K�bJp�!m>�#����������OT����M����v��>���FH���z������������r�"[�d8C?G����*���
K�P%��m%E,�'8���TdtqO"���2�X�	�EI�S>'<��{4�~#�6��2��ug	�:��h{Y��0�`z�.��^�[6����g5��M��kYd�e0��W	2�n �j ��
�����/w��m�C7J��������@<mV�6H���
�x�"�`.�1-7����`�+����/n�N��2$�[�7W�W���������OM�iq�-�$Do|f��.G�4�_�]9��� )t�6="_8�
��J�	�ov��_�  �[�9w[}:��������k��f��C�e�?g��c�C�8�w�JK�.^����H���q|��(5�;r�"�:~ks��sS��t�y����!xp%f��Y��.L�;>p��M)I9��J|b+�P���F�o����}~����%�iY�
������~��a�p�sL��}����@��������\���Bx�N���fT-�{	�vRnH;W�=ZHq�t��|��t��#B���!L����#ya�����}R��g�n�������E{�&�����������U���laM [��u1�^^/��_^�|1x�*�mU�v�%�����l�]6eY�,3�7���u��������'OjJ��Y��
t�-]8�����j�
�T.����(�`'��wN�7��0�Br�U���_���|�'�`�I!���|�����p\����E��*��Ua���SP���%����j�������������U��+�{�������@����aE;!9�XsM���(���Y����*[��$��g�u%m<U������|l1��>���^���<y���dx?]�|�{���i���v �c���;�fm��`S��f�k��!~��Qm
~���NH������9��U���T.���/s������61����QH�Q{x���s�{�/ac�I�7����������J��V������KrUf������b�3�JrVrz@��z�����	�G���y6��j�P��3sW�W��)O����zrm�v��kJS��z���������F!+b�K�ru��3��l���r��1�O�"�nP���9�x�����t��L���n$s|��l����Gq�a����0�����a��X��|�)Dzj)����$�K�`�HG��bK�����[������yG�o��yi&��2��(O}����7�eB�ZO�=��G|�L���%�?N{���3��f�zI9�<��N���TX�R���R#��u�Z�um��������',���`%!m2���L�j���T*"OGQuo�,��yE�&m�\��&GG��b��~�[3X!2�C]���;}�}��}��Jt�d���I?0�-���c�����.)n�/5�.S���7k�`3@���� V^�;'����W��g��"��^f���{6�4��!k%q�PL����n+����"|Oo����`�&�v�x�=�)�A��6�Q���+>�];!	�
�i���P�M���\�K������������HZ��wu3�������]��6��>���������DXq-��s,�x~QE�#�l����8�����b
��@N"P"[�I������G<ddL��x����t�V��;��3M��>]b)�r���C���u|������)�o�4��C:doq��uu^������/�����j)���jE��X�C��Y��(E�w��C3��W%���_ugGtKF���X�Q����y� ���9�_b������A���*�6H���zU,����4�}O�]�.]���O+
v����O�&L�t3����h�]J*�����Xb��h!�� ��N�Im��)�_?�n�G{�A��PG�@\wX�h���6ZM�H�;��1����zD8��V-�}>U���?���<j2L��]�H�+3�]���L�<
ZH;�K�t�����C���U��"�r4�%
����u1�
7��b��u[|	y�w�\�zk��5��@c��e@F����/�/b����>qM����d2��������_�����5p�1��%zs%em/%����������J�3��`lNV��D��%��qg��:��L���0$����=$��|���[\=�8\*\����0�|,K�SV����������D��Nb/����eQ,\z_	���i/� r]J������~�Q������h$��B����y	�6��;��������z:�����2�AZ5�����@�	���������@h��~�am��d���%k"y)�@�"_��}�52��d����(�tP}T�^�@�vn����"o��~�^�������D����i�>qm���M�|��'^������N�������5�I��� ����ry!��b�}��DOK}^B��%P����}���;.��;%�1���|V�\�|�y��Pn��B��\0`�[���|�������mGf�.�U����q��0��?|���1��-G�83�o�j!pa~�k)����_mHb:#Y>���������(�w��U'?����������m3�oa�_��>�����u�[�S��T�[��#�H�OQ��(�\+��&F�.y�cW�a_�������������;��`�q}��7M
/��=S,�T�����@Q�D�YL�_d~5e-�q1�vz���9�	D���'���/^��}�t������p�bC`2�p�#5��������OQ�%��|q�KP�l��/� �x�_�gE0�������Q7�����%�.����:'8#����?��g$�Xf�dB���������������k������G`����`)g4����@�x���5 n�!���c��u]\3@�v���������F�l�%�����D����}���Q,fT�S(�_�[���f�}I�h��3�S����o���g�yT�����%��{�.I'���r!�T�����b*��M���A���C#������o
`�3$��9��r �9n���-C'������3���L4����mz�Ry��Z�OyKO]Md�}^�T1� �p��>�.��##b�9���f`�8=5�>=��3F�g�A�)�l����Lr�4�����>t�E��1��(�x�7�,�����I�
�0��w.�z3���m���=6��6A��;������7�wib�w�$w��d� �8�����I�V}�����tH�$,>K�i ��+�'XIu�x��>8�kLh/�	�P����	��BsRk>�8
V�r�@�k��P��5������1oY�}[Zm!��6���0{��u����)��p��pg���k��W�lwW����\<��l�_j��@I���B����:�<H��HF��B�r>�UH�����!1L�h�|yy���d)c�����[��+g
�Tr+H]*$��9�VQD"^��=�s��/Q4e�T�(���]������9~*3��m^j!J!LL\�PD��5��j5�^a���j�(iq���3��l����.��2D�(�����(E��3���Z\���n;���
*M�o��e�����F��>�����������Jj9?�fL��:
4x�;�3�]�w0 G[�����Mx�S#��#�^��WF!-.��b�s��'�nl��1rH�<�����S>P,�����������J�44�Z���_9���0C����;!��S�����K�H��2���B��{���f��{����A�{)l�_D(H=����12��D;�f�}�������*���o>�����6"kCD��fC�!��y8|�r�����_�&(����@���H�lNQ��kM���X���F�0�M��F=7���Iyvl��c��'���P���~���f��^g���C�d�z�0N~�0�@�lAK�XT�����^,�	����&�De�E��R-����X]b=n���F�"��Y�h�xj!{�Gs�R����0i�;�I�������C����I�G���}f�8<'���u��bY��3I�>�b+��\\�1D;d�3kjU=��K��ZGM]�.�v�#0�'������_���1����tT�s�xV�����|
=.-�$������S[�Q������^������\�����k�Z8���,�xYU�@U�R�8A/vu�X
�j��BoCf���>�����:?V6+���-ut_�� ��Om�/����B�U���\�K�H�����v[38�������qenk�1����`o������ ��H�tdo�{���E5z��V�u�L�[�l��k�Q����,�x#�$f��L��.+�F���0��
��h���o`���8�f��9�I�WtP�D@<���"0#��)K!���-e��C�qq�"����,�A\V�����MJXhm�����q��8�������������QX!�����V_CZ=��Z\}�^�_j�H�� �Q�l��eeLS$ �V�Z%.��L�2�G�����nNXb-
4�C-���)�,���1Hq	"�����������!�JB�	��Z� �%(-�F����A`=��o~��>:y��TV��c�������
a
��g��UZ��e��9��^I�����`�H^T)W6����o�/�EL�b������LX�TV�#��J>���\wG���r)����#�v1E�&/���4��x���5q@�����O;�3@?k��4�zT��)O�=�t��*j� ����jd1X�����z�M,�������`����I�=�����"��z0$���m�V�%y���z��������_�I���X�&9$����yOg>d�����8�.h��G��L��uB�c�K����x���P.�v�����N(?��BP�����C���O��|����
`tV�Kv��A`�.j���O �>{�=?����A���W��A6�:y;�5iw�-�����b�m)\��������*�!+�xFf�i�����L'�S���^	>	�j���9�l�
�B�W0���CS,�^������]�w@�w��'�|��N�����Z���'�X'v��<@���'8���M��R�����sO&{�vT;��)U1w��WS��#�*��_I^��9��
�����M1K�_m'�(���eFs5�%M�9������%��y+�g]|sn{BG;�5�'�8%=�A�F�Z��k�J-���������5s8G��D�;��>�������3�)���L�=��5�A
���h�����3o
��� ��v�vy�usx�9'��\DTH������h�7yh2�q�����n���Z�
�0�N����c��UAn*f�6\�z��%?�d;ae���]�g*z�7a��
���b�n��/C?I=���ax��:dt���L��e�3�������w�C5Y�yl�O���xA���	��]�����W����%�(���pI���p�H��B�F���w��3��L�80�Qs������0��rp�u*7�=�n'��n�!�-v%����u�B�L����qf��Vq�LXb t�� �6������o�����`/TK{a�^�e����Z���U��[^\�P[AN8Z���i+�+>%������k!R8�Z����G#�#BdBK�T&�����
RTlr)ie��>�x�M_T��5P����{W��<
�?�_a`Z����(0��p��
������\�vv����e�nS��L[{����V��n�u���j�.N�&"/����#�ip�U.�R)� �C
��h#�A@��=VQ��OP���x4�
J��4�]@�5 f�f����L����M������|MR������V�U��+�������:DE�Eu�f�:�$_Dr���3�[~���,��c+. �����;��u9�Oy��B��e+Z�, �V�~[T���'��h���1���u�6]�T�^��]8���]3���
E�����O9�D�S�<��W/����P�V����������UXD�A�����C��"
r"�QA���9K�������P���M�i�u�����t��.m�Xn���- *�X9V�=�����76���(������o�.V����N?1	���9#	'�
�XP��zh��w��������t�� l�]o>d��|�v����:�9-!�v��x[w�pl9�v�I@b�����#.6�t7R!���iN�~�.��Lq�f�r#Ze�����x�[�GB�m�:��O��CO���y��JQ�������4n������-���89���0��n�x)@	o�!&�)�M�~��z��~Ll���f�d�	]��C�gI�|�}�H$�-���L���5��f
O�:F�y��B~����8 J[a
�r:�q��n������}a?�vY�W���*��e�1^��Y�~3�
��~?x��v|���n����<�k��f���d
33�}=�h�Oh#T��9��JFhY?���z�[�g�k����V��k���,��8�Z=�W��T/'p�X�����9�TC�N���#=�I�_.�l�Of$���:��%D��]�����Re��$��J��������+�q���G�<��'Y���cr4��9�M~��g\,�XC=nC�,m]�{O�L�M���
�5O��t8h/l�m��.�)(r}/�[
$�2�9��n�,�d��Pfa��.����m�����D������<�;_q�7u�z�0��	YH��&Az�h���m�t�����V�#],������W��x��^�H�o�t.�����a?�E�Mn*�%��R�Q@�7�?8���M��'�\�L	"�x�������V����(7�SN�JM��z�J$JCv�Ldn�D���`�E�Zw<�p����
�c�t1�m��u���$�#�	�r��iU9�obD7:���������R��+&��{��
Rt���"�j��a:O���YT��������:���l#o2�=2Jd���P|'7�2tx��fE��J��Pj�O! ��Op����0����A�g����rv��V[TJ��	m�xkDB��A\ ��G��D�t�cN'&{�NC���@�vW
p�"������ot���)�.�jy�v�Ip1v}���=���D����
.N����$U��u��H�S��Ns����W"�����45l�c��N�:��6�_E(�+�������e��/}��)���t8�5�Id�L�~�����|��N�a�X�q�F
����C)���Ri��F2������Hy�����5�3����KYj3������� �~�M����|��\]0�3�z�����'Wg���!�N�AN�=;}ox��|�>���M~4���>Yx���Hq�����K$�q(��C��6'��E&
�H�6���Y�9�y���.����>7��7�����<�7�V�%<�Z��V47qm0\�0����c1zR���~V��A;G�����y������/��I�*^�S�0A{-Y~i�it�0Nf�K&{>.��T�T�Z#�=�[�5-%����[��paC���B�i8��A�f�:@0�7yYC���\<V�b&w�o�Y2�����-�$����� w<������+��B��6���M!F/q�GG�����sBPP�S��N�������K��%��m��I��L�����Z(}^\�Zh�3��ym�#p���%UV	a���ium�,�F��dC���dr�j�y�0S���RA&K-��T��>� ��T��h"�`M&Qi�
 B�D���A�?M��#�z`��S��S�J�W����	�p�N��Y�A�k��A�Cu{n#6'o��^��.�!dC`�j���F��Q�,j}T��n�{���$/y�,����v}����l2������kT�&���*��"�W�Smp����y�
�1���@k|za;���� �IF�|A?
T��aM�Zm^��'��7k:#N8���d��7k&��j�$~���L�K���j���{�4�s�:�W����g�K�Y_���6m/"5�G\$j�d��7�L�A��N��c�6�����JS�n��l��bS���KkYF���y:�dz�^���r,��]l���}���@� N/�>>=���G����_
OO��FU�����:��M'�X�Q�n�:�}�n�z�in�"������b�3�E��J ���n����Q8E�����~4P�����o�sG~b?(0�;��J�����p��7��y�0
�����'�u�Hm~��",|?�$z��� Sj����R~�����F�a*�8�B��l"���S�����O>�0��i�z����)W0FC��w�x��3@�6���m3+T�*����fQ��WlG�?���S!���|<�r"����~E�.nW?��(#�_�z^k-������Q�&�Z~T~��.�K�`����U������%�U�xJPtb��9���}��������FY�����R(K����b����B�,��c\x����s������(Ff�A�
2+�-�$;"���=�qNA���3�V���yR�����s�b�#N����d���\l4��������������d1�]�-5���f �	�h1�`�{^��no[�cZF��`���bE-�� q�������j����V�NN����cx�E��������@9�QlF� ��m�4��B>�����h���$@�����a<��D�`�.�J���`��}���k�����������"����c���~����P
�C�@'6d�d]�RP/��L(��Y�_����Fy@��@���8Gq�'V#�Y�Fu��C�A��Q��"��P�X�����+x����_����������!#�?�`,Mm������O�>~����M�fQ���0��/��K���e�����`S���5�vPZ���w����|��YlPD(2���V/���/u�N��yw�Q��^���/�-6�Q|���oD�4L[wp��l���
E��:�@��u�.X�neXW_Y��t��F�������K
,�������d<�#�@�xul����`�z^���>�X��$�&�*[W����@��I�����TTO��� 9������ /��M�mF:�KY���f#(�H����A`���J���T��<;H��$��1	_���@�������|���x�[�I�Q+����M����:��4^hM/�"���O^v�q��,����S���_�\u��4TT�"�r8��>��S�`�g�0-��������`j��H|��#D��%�C0�=u#0�����u3������G�O�~���V��W�yry\|t��	����iN�P���*�e������x?-/��R�0U���T�&���"��Ny���;�����w�8�~U��PNgX&���"�#��v)�+������Hj{�	�B���6�y��j@V�4A���8��*��P������y�0������B���#�=� H��&vM#^AwM���F�����x���6q4�v��v��hW[~���D.L=���Ek����C�c\�U����dM����+�3� )�]��x��-oNWM�Y��z��({��m��}PQdiNA�u������/��Z4L[o$�C�o��!��y�N��&�I&)�8&F�`�!���D�E�[�4z�3.�"���A�{���6L)��X�l�ZKx�n��CmR����X����)8�d9��������'?���K1.��|a�B������';�6�|��U>4y#?m�0�	M�YX�|��E���3p{��)_��U��_��
V7�X�A�6X�h�ojn�b�ay@�/%��tC#c1%�/��C''�e�#q��8���Z���H|v�����r��Er�"@u�eLo�d���������+H�gJw���Ls���q�1�XkzQ�,�<M�4��Je�9E�'�����c�O����r@Tkf��%�L)�	�.�B�8��+���R�Ob@%|w�~p:'�~�5���M����?�����
�o~Nw�(Kr�-�wKt�����
�;'W��P�M�w>
�jH8j���v��j���������d�s��^M�f
���c2?��eh
;41i���8H
/}���H�?8���tH��lx�������T�9�m{��fJ��������0W���);���H.���dF����y6�H�J�2z��Cq���/p��D���d��^��I����Jf�]�t��R��+N������4����$d�9V��(�M!��`C�n^�������{��B�k43�����}���
?u?�2�:�)O}��_AK�L-j'ghr��kq�i~�m����Q�P7�XeD+Pu�u��*�a��+L����b���bT9���R�:B���"u}�@wN����s��C�ow�@-��r�4�������o�����@���n���!Fx��<��A����������S�w�<�#;f��|�f�4[�����vu��;���oZ7��Wo�����PHV���Y~���x���`�P����O9����*)�2���3pQ\����;�~x�!�g���:��|�i��\�oLO=��R��sCtX6k����=�!9���L�b���H��=A�I�mN�N �a$��f)�T\&8�
�m�X	(��9�/��]G��I�),x�����Y�m��G8�07"k�&�M�6�����#Pl��M����$����A��}9]��-1^2g�m����K�R�z�9rP��%p�\���s5�Q���K���]o�-y�ly��AC�(u���DF�5�d=[���P
�Dc�fK��}:Z�1�Al�D�6Q�������\���m!�����������g���bG�9m��Ea�6���&;f�R��j�/���I��lm��P����G	B�bw��������8#�6qh^d.�V���j�O	�-j��!Z5�.S�6�������J�aw�&y�y���H�Y�n���"S�V�����0��-�4�[;���-�A�ID��Fv��l�M\��>�8��b���r�3p�A�'�r�)��������A�%��_q�qwN!%]P��=��4Lx�KM3
lh��hB3���F����+����G@�h�\��/�2�B�|)��#�LW�2T�*�a3��"@IT0��2�U6��a��_���T�k����^i��
S�QPS��S_4i�kf�������fz����G(�W=��v���|����c���B
�w�9,l������J����G�����	���k�&D
�)
�q2(����)������O��b�sW��k��l��T/������Up�z����b~�!����`��g�S�4,�%��rO
C�Q5���)�jt��b��W��?�L����S��d�jX����s��)�#�OeP��&m��o��i\:�s��a	h[��"���xb��Wl�����lJZ���r�8�W�Y����\�AZXO����/����7xL�[�����(u�b:��D�N����4F����Z��3[���[�0��K:b�K�i���w���% ���S�+�����Et�`��jS�V�����h�$���C�l8�O;���_=�N��zQS/$%�[1�j�JqN�����<�C�|�����(�E7��K�-��p�`1]~�����+��=_H�"_2�6�Lx�O�we�M�@����	JC�M��K*��B�%�(�.�4���������[T���d�c{3��~O�S_!�`h���IV���-�,�,����P���h�u�
�q3�e�Qs�������k:=�)�F}^�-�hzN��$k���-J���i`�F0��_�@����X����L���>�������nw#���������7�{�E�<V��������M|�q�o�Yt��K����R93��
�N����)_wNVT�!Y�>k�<�
�A��\���&��6u��'������g�����)"�s�$@��DWV$F*����VI��`Z��qV2�p�fZff8�@`b�S�}�LIf5K����2��"�i����N|��,��+P:��%J'Q��������l=]�z����F\�A�$y��$�24S�t��P���	Q��o�y<A���'Q\���aY��^�WdI�9j���e���#��@5��K�(b�����x�������[f�F|c�y���$�x���x����!g$�;��������
�f�?$��!�	���P�I��<F��A��s���l��(s�>e{��-���yL37����_��p1���R�`&���AygRS#�@�( e5[��Hf��*���R*�TA>�F�0�b��/F�0���C"�Zg��g�T
��`TR��$O����0k(�GT��UWe(�_[F��h������6
�����������:=���;1���?��FK>)jG'�hY?���R�jr,������7�p[��f�}�,�;\���0��
#T>�f��l�u��f�pS���e�9X���k�-'��aUEUH���K"��T>%	d������v{���{���|n�[�Fk�?H������OC�:����)u����/��~����L*��4\^z��0>�Q�?@���D�#,�?�L�Z:
A���n?>xU0�����=Vio�-�����w�����#-�}��>>F���M*/����������2F�@m��s�WT���t��$`��HJc��pot��I~�����n�{���Z�o����ZR1�|�����j����c��5h�]s���V@�����N[C������bm]��_-u��'��N�����*�����]EF�8z��>�����0E���90����_6z�?G��=��2�g@�|\;���!��W?�z��q=��#��?K�/��v��&)82�5����I����7�S�DT��U���j2���v���PyUqH���d��B~k�H|U/�Q��$�,�G$���?��(w����:������=Mtd��!�N���T������it!e��%�;��O�5���w|�R��?���F�%�6.V<d��L�����1'"{7�
u��~�<G���
Z��1����S`�j�{Q��v�~�-XX��p���WM7 �%[��_�>%��?O��=��?���1�a��S7� Jl�"����BGu�Y���N�����tc�F}]&A������?�Z\��|V�"����U�g�`��K�/�d
��iW��^��^�'��&���Ha\$p�k+��U�4��	�P��&3k7df
v����+&���U7O�U�C6�W���E�6��
;�]Q�n��A�6��w�,sQ-��Q�L����e�X���#�f/Z����J���a�br�p���P�"��D�8���S��L���K�%ir17��P�j:~�*b��^��!�;cJ������`�����u�q|bV�M;)`�+>��LP1�#3JD�����3.�.������U?v���G:G�
g������N2�/'i���dx��������s�h��B���_���Kb��&w�81��	<�����x����</����l�c���1a�J���9�b-x�/f��@�%X/�1���iwF��dE�A�e:�-=�8���Wt~���+&AfE��',Z�����	���������Ud��1u�0.V��F�
�G�J���|�?P��i�
RO�Y���Oo�3a����T	n��aa�v��C��$^�h�h��	���8S#�ks
0�?ol$����q?rA�,��j:���Qu������!8�n������i�R�P>Y����C���[������������{OFL�2����4e�:�H���g:t���;��L�QD�G��hP��X�R���dW�h|��D�����C+�=��?�`���o������0kC�v�r�1��5��9L4�<`���H�NW���m
��6�8
���oD�E��/����R>s.c��j�B�����6
�g�)�"D���8i��O0`!`��Tui�"���T���;_��CR��������w�;��s����d��=������	#5��-#"�5���D��0�$P��a.�lz�����HT�51���,V�aN�2"�3"Y�	���?g.��::{���y��a_�XMM�!�����7�d$�G*6*��!�
9oPmz�����r��:D�s�����Y�����
��K��q�<�W|��YiN��E�������d�_����Mu���q�gjpx^�xi�Q���E��)���Sh*ta���@@��5�BJ��-�(*�����tC
h4	��l�~e��w�-��`
\a��t�f8�\Y�R����&�����G�:a�*^~_-��U�&t����s���
g7.��~<�&K�����'�d�%����U6������$����A"������d|p�`�o���4C`��sr�SNg���+����PJ9�����'�V7�������u�9;yq��\�������I������_�`�;w���q6���C�l�B�U���m�.���u���-<�S�$��	�����h�����|�&�lz�y��iP� ,��o�_��k�u����Z��a`}=�t��q\ic�	�9�������T���������m���/WK�m:f�{k���57,�:[W���6���,w�Q2����DF��&� Vs#=�����,e�[fe?���;P�x3���~����,���"�������}�	��Qn�O�G���j\8���a�
�6��:�)q��-Nh@7�ZK�`�'��ug;|[
���m���<~Q�����t��7���62�H^���Ky��h;��3O|�g���3
7O-�td�N������a=�i�L��!���������g��>����	��F���b(���5?����
�0�����V/�<��)(u����z���C�aSd�}��Ge)�m,�����f]v�#|��:`����,[[����z
O]�9�1(��0Ac�4�y:P?�i��'��E��$X^3!w�!�44�M��(D������w�r���i&dC$�z����-}��.�c&uH�B9��T��������<M�Y(���O���|s�8�����x6��2|�'��<p3b���pG�:	��������c��J�)%RU2��R
!��:���ci�IX;�A�r���D�$!|t~����i@�9�����5���r�^����/�i��hG���N�8�dQ��@(/�J�q�t��/���v� �L`#�XG��u�������L���?B����w[W��u��m��������������m��`s�>0E�p�L1��:W2+���s�����J�����r�z�[����<!���p2��}W��g�1vd��~�k�Q�@�����]���}�sZ��K7��Z�u���VS�U!��#��U*�.S��1%g��-�';�����[m��(~��6�m
@�6~�e�������}]aW�����e:��C�C'��M(o=�C��D.� ��@��_�k�^h���d���[������L7K������ ��C�`�)��aPx�����E�@����
�Va��.����E�|�(�%��AH����6�M�&*��o��f�m��}���E�?��}��
Z�����#�Ip�	x�*�����"��g��f#��UCr�e��=�p�C�<jW�����X�D�7�(��w�e^�~���/����� ���r��zC�V	��%�((`v}F*�HA�>3�`���\����P�~���Te�*[�'���0VPA��S��v�V��
���������i�0-(1�&�����
�+r�S~2��<��C�6�������5m@T�]���A]
��
e�BPA����CPB�t�w9����EsT��
��������	�cG�L����53y<���4�'&���yI}��V&�����#�{*��
K��=�C6&0����a�`%�'&������1�<�;�7)y&�����f8O���k��a �{&��P��j�}�r���+Hc(%�{&�� ��j�L.�s�_\F	��
�h�.���d����J����K��v�_~�2�<������^6g)��_���SPA��Pe*���g�LP�3V)��]	����u%4��]�����2`T�EjeH-��h�[9t�J���24T�K�J��9�Dm0_y����nZ��V���e����B��P�/�Y�C��A	M�U�sA]�F���
,���(c<�
�ur{y�|7�G��9/��?�o���\�����^\�?U�����wEY���=������>Sq}����[o2������Y���<�7��f��B��x��'���Z�B���\��7O�wg	�lw�qP�m/�������?�V�N���c����>=��n�9N��.�IW����=���=���]��]9��eTk��5bQ��������������������l��������srM`���?/_O��p����O	�x=�_?�rz?���]��|H�).�D�bP)�F��a"@���������I�>��`T��xt�g�>;UQ��������\���b�8�m9�'�=�$$���!���X�A�����@��������&�l��l*��Dj�"��X3������`f*��h���r$���U�Q���6p3*����������^��|tCH���
���82��@��E�HE(��pRR�C�-����\x���P&�!�����Rz@&�9����8����)I�#��B�f�(�6��J<d�]P<dN�#�hDC��f.)2'�\��2��q�"�(����)27�B\��"/�=RB���Y7��������A�����`��3k_��j�EV����[^�O�~�i1�>�C�!����e��3�
l�3���a����Yp��%������f#4�_��m�?r��y�������u��G�(���B�/e	��I��8���\�Hi�@pJ$hp��pE��X��u8�o6xF��h��s���^��x�m�^��("|t:Mi!���]p�^oWv�>������BrK�~��V�W�
�Ne��j�s.�����
��.��$�@�� HRq]��m�n�D���p)��y���F�cW8�P��������&��A�]�91�u,�&y&�����1X�y!�3uw&Z;1�b���w!��I���W�
q�f��I�a��+�AE�}3D$���W��Dl�J1q2f���_�2��|
�n��e
�25B�>y]�3)���:���v�����P�I����g���iH��MRqX�m�>�e��#�e}F���v�Q��<���#��>}���d>v�1.�8k�����wm�x����]���V��mq<X�~x��FWF]�%��y
g��}6!���	�������J�[��M�1�F��`x��c{g���(x6���L+%�������D��R�)���PCj^��MZ�@L[�-�k,���*�|�$�����<�$���y�&*A�{&W����h�w5�>���++�d�\�X���+�y��rec5G8R�p��A�b��))W<V������
�gIH:GBVI����!#�o��;D��T��yP��{)&��or�k��kj8�8Y����k}�/�h%���>��������������w��w����<\�C���`����S��W~X�
����h��������������z��7"��C�R�ZU�z����)e�EL�F��HiCv���{�o\�K*����?wna�~=�e�a=m%%���Z�vy��)_�����&�����#�m�!�kF�7��1����S��?���y�~8t	�����"������<p���������8�J��'.;�syz��O?��c��Ci�O�N�)}�P<�b�%����.IpI�.��.��.����-�����9��9q2�*K$T�������X���,��?�nT
J�:
N|vq$"�|���q��3�m��R���WP�z�<�P�un�>~���y�,:��
#�
���hdy	��X�Df�F�WM�
�����r�t�r�Q
Q`)�^��>�V /3Idb�DV��Ne�^�PR�`���*����n��2o���N�\:y�CQ���,����T���2����B3�K)�fMd���'!Z�`�<�����)�����[<�����f�?�%(�8�x��r�C���,�1[t}��xF��k����e�����^ow�6D��m�J�������������Qr����s��G���?��s���2�Xa
G��&�������V���`�
�W$Z��q�qi���A_+l�x���R���x���$)�{��
Z ���U��
�3N[����0���z�r_	��h��VK!�XK���dRV�y\I:���:S���*(&{0��.�v�7?�s3>���e��AX�S[���T����������OLH�����)/u���O�;�n?��������M`������������E�����������e1�-R�
-~�����g�KiF���Tr���li������^�G���
�>������w���H�/f6;5�X�����pX�����A`��I�Kw��j���Cj��W�h���r�T6-1�����H�_h��,������z��p ��V.9����0�c2Xp+��`�G��d�W&�����*
��O���M��m��c:���`{�z%�;0@(�2�U�TjY�V��V����*���g4a�Vqj��,��d�%������pC!B�P���Sy���������M���(��2A[������,���=�&A3�%��^e��+��$�g���o6V��L��+��Dj���H�����C
C�<^�Z1��
�����cg��M
��&��>@��]�����}�]���K3)���.��4����:6P�+���K������~�|�n�g����W\��=�|?LL�
�������d�������H�0]'�����$���������q���
0��v����A�k��O ��/������h��DP(hR��^�����!Nm�3q�p�-k���/h�7D'��Z�CB��V������Y��D�V��x�������z���O�J����yP��������x��JW'.n�H.�����e���/EYQ.
P"d��L���w��[91;������t��T
�y�Qb���pH�F\R>�#v*��v�����n������N�k��K��k���4�.o^c�xe�B��x�$�����
�E�3��O�z����X�1�3�cpvV���K��)7�?EP�R �Y��@�����f.�^�����m��$�$�%�Z0�}������g���5r=\0�}	��ll��6������8���������%@��a�X���`yO�+K)���(�o��q1$��[��E��km
���Z��/t���$F3�%2�T\�vXW�A�#S\���}�0��CN�Pjh���i��=���l�BHO��h�|�L�*�-��dF�V���������b�����e5�1�Rk0:\�>���C�J�gR��PaG2�$��F6sb��]j':��r�����qCSqc��Qf���|v�c�y�hp��Y�{��3>��e������2����b�7}1&��.��@�^�
�i��1���`��+bj�k&���E���UX�X���OLg���[�^#>�%/�Z�AC���8l����!>C5
ps�������6N?�_��J�4��8 JJQI���������$��R��gv'��u\����g���;3������1��@�K^V�q��F�� �&~��G�d��-D�Z���d���
��=���O�z�T8���;�9kgj�G��/��@�169���:���n\��dfZ,��X�s���,�]�N�,���k_�+����3KRi<�����a�[����pDj;��c	�.��Qu2,b'��u���mcX���:���[4����3�
���8o�.�`�N��[2��?����fFCd��j������E���W����7Ag�{���d���%X.��(�b��>�sZ���Jz����W���,�b�1m:y9�R��v�g�j��S����.���bRk4E4�,cW%�PTBq�{���Ut���~r�J�s����^=7�G�UyF������E���H;i���r	�����3Q�+��]�C�X��T�A�
'D_o�	,�����i5y����5�yS�),��"���"�x�������N]�
�T8���ge��t�C�d���t0p������jy��B�I{{�?�\���W�����^X�_���q3�@��`j��;�����EL�*�`��>z
@Y�=Za�qO����q��)7������$w�"��G�#�]_�b��#�l�2�C�F��
&#\��;�ez�u�TV+S�����~�#wc���/`=5��y���M;�-,y'��G�Q�>��h��;�n�(�����P��war�O
7��,�S�����1��D�tK�?X�B!%���:IO��V��q�����k���tDTT3-J�����������g>8�ER"_Pa�wzXP;���y���!�S��z7�$7��,�s�Vn�/�S��P�p��
��Y��@�J&��|�`z\�����p�_2S�p�A�lO�'`X�Om����9�1����(�a1n9l�*���c���1��X�>_����A���d�'TLG9��DZY�
D��'J_�-�
U�N$���I���B��XDV�I�xiCt8w�Nj��L�"�Q�$��|�A�����Y��:���-�)%!�?�"������`���Y�Wh���X�O�5zr���g���B�hP�)(>����Plbso-o@'+H��{�N�l4:��;��1�he%@S�#�_��lz��.��?�V6��q�"�U���ri�� �>t���33y�7�&��%���b:�
@�SJG��,B����]�������Fj��)7��hb��Q0!-��Q��;8:=|����I8�?SQSH���T���i7=��j�M���n��]���yF����u6}�%��:�Ot,j�O�^����.z����%��
x9���qs \�v����73��H�U�3K��F���p9����2��
<	g��fS�7�����l�4-�/�O"@&Z��^�-�ny+���oA�P0a��8$�prb}����q��vM���C�k�0Z������W\����$-CGM*Y~X�BN#�h��r�����f���;��,��N�������YQm��:bV8���_��8��lo&Em�v�����&�4�������� �r���� �f��?���1�<�V	._�.[�.[�.W���_������9
����V�6���mv�������=d
��H�k��������h�X�`x�Q�kb�y��(�'�Pzr�-z����"��	���rGY�02������oT\�h�	�b�6�6��mmQ/�m.���p���}^�G��T���?��4�$��I>��������y��K!a�>P�Q�K(0����Z!�b�aYyx�2���.>4|�*�����;������!���q�:�V���B(��7�g�:�.�����,xG�����#��ve-
k����.w=�$Ls�$�V�t�I��=��#��W�Ynsd�V��x�M����Jhgz�G�:\��A�:�l�.����p9�#H��r�p��Ix��98��~
�=�c�;����3�}
Fd�\���_K
AZ�@r:M����BB�����
����,K���K\���B�
]�wy�.�=����eY�g���`�7�L���z/�F��J��[^����!�h��0C7&���I�f���`�L�l+!h-���1i\:6���7�4�Z;�ftpqC�{�j�,�x�x�X\�\��������n^�y���C��	G�s�}9�\V�d���8���U�d�La6~�p��-�mk���f�0��7�[By#j?1(R�Uf��-�<4n#X~�t��\�)+�7e���ZG�
G��NY��)+��t�|�7������i��n�X��i��,�z��R�Srp����S�#(�cB�~��=\���a5��h���]�\�����'��D�E�<���G+m����v�aL�����
~�!H��j^OY���Ec�\E��-X_����`��g���-1d��H���n�i4?[V'�@������1J�k�ax���2�U��I���Pj��(��p�+�~����i�z ��h�m N@����j���l|�5S,����XQ+�����ta���2V^F'b�%����+�%���7�/����+1O��1_D�Wv$6R����/~��T1x�g�^�/��8��l���)����j��jU��rUV�E��X~R���7�u��-�_|%q�;!��w��Uf��.�	���6�A��/E�W����'}A�/z�;�R������4;��E4���a ���#@+J�c������
���0F�Uj���HmdV�U�����I�����G^�p}x�
�1x��ZM.I���H8���!�:ju�7��b����r������gG'g��Cx�P\�O�2��Yd�����r8	G |Y�,���aF�d�x�<��*C��h�����UW�2�~�T>���A�Q�"P}

*�.����DI,M�O0Y�����{��?���������M=X���m���E�Of��/g�I�V~�E�E
f�U����^]�{������������7��b�z%�����z��G�]q������de�I�W&��LJ*�X��!Q�������������3�ZK���x�C<wi�3V����C��^��x�r)A����-��G������w�#�E�,�g�Y�����,��F���x����r"��v�������1���h�h��g9��q�D��7���M�������k5�`>Jm�����`P?�3�����3e@�W��^vP�]W�{"s�����W�����8]Y�����o��
z��������br�g���O����?��`]�������R|��z�����u���]�D��_������tO����o�z�������e�����U���������"���[ir"�L��P�G/�_�)��4�dX�a5�\�2wA�S�Qs��INj�E�-2�����������Hl�
����$�]��V�_f*����������>sd��#��e��48I0FYA�����PE�_^���d�U�;7d�,�<�����������!W��4������i�c�28mQqIln0�o7�������Yb�q�_�Rl�9�c������Z�q����M�!�%K�f��gl�S8��f�#�X����Z���
��h��������e���6'/K)�MR�0�b���r�������V�q��P~���x]�U�I�C���
����N����Y�!�@,R�\]�������1,��d���n�n�m���ty�V�EW[������������L��y���R}N�R��^��"���^�C]��`�m��6��N��
�[#�:M�+T��P_�G}�%�"]��/���S�Y�����/����l��B}/����K����X_�O}�zyur�����������:
�w��He	U��i���M,Ji�
h������x	9M\g��R���'3���d�tj��m$��(�c���$�)�4��� ��(h���$�<L�D���[���H?���������T��1;��������4�i�J��Q��aZ���?��0���s^��J��F<��h0H�@L@�������g����o�������P�e��M�x��3��AOci�/'v���k�J?�q;��U�F�GK�q�j%�4-?����z�f��|u+:���"����P��?�6�vU�k@��>�JZ�2*CVBQk��B.����\�������5R+�h�~Vh���)���<����I/�vSc�F�=z3?bGb��)[,�����#9�R��z�Y.�|�\	���Qk�iw}�}������+�d7310��f-,��%����x+�Y/���7�JD2���X�)�P��_�R�dmN��7������u�g\g�H���^�=:�� �`��FP��s0����s���\Xh�����H�W���E���T���z�2�V�H��r!7z-�0q�.%uzy����<���w�l�{�IP��$o�)C�����u^pA=1��dH��A�SLO��y�jl��7���O���/Y���@���j��G����/�4�|R3z*���k;uZ��u&_s��������`�"�
��$`^�4P�(�E ��$s�8���C5��jR���	�o�p���;�o� ���qq58��:$x���)x2?�+��q���P�Q(�"�F��6"$�N��D��w]��6��8��"��#th�����A�q�(s�(k�(#(��I;�E�
${\v.��O/�E��s?G=0B��q�S����@,�6"`S�����������Uc���0?����`$~68h�����#�Wa	v�_�����|��n��l�I�l�����E���Hl�
��S���Zn����{w�=	x��XMcw��x��F`���8)s���`�0KbaX^^H|@��.]�cW��K7���,p�c�Y7S�0_�[��m��x��s���"�ld��%-��%��K�d����)�,�,0����0��Wsvu��I��br%����G�`�)���cO-��D�����:��:���s�U%P�d���G+��3i���"�+|l��W5K�1pA ��f����X�DN��u���kn�^�D��wI�"��1�"@��r��mO4�J����g�ag�N�c��n
B���A���>~�<�}�/��tDsg�$H�(X�8#��O�jP�.�+D��rD8W��o\~ �W��P>�c�\����`��Z?	�io�'��dO}�!�p�S��Xi�@��#	��!Y��`�l��M�y���R�7��'��ie�|�v�&r;%N�w��Reg�4�����H��������R�� �F?�N���D�
��QH�8��H$��(��M0h��qiA|"���
�GLx�U,*�Y�H���1��Y��H��� 4t�}m��Q#�:nV2*,P��n��8�������te]�k���V��
�	
���pT�$�}U������XkY�ht�K�]��1F@#Q��N!d��� �#Z��k%�`����A4@��m���mL���#��@�>PJi.�`X�+%t_@[�E�J�Ke���@��i��<L�
��W�*
�hWF��Op��M!���n?b�5T�=����Hc������*��<&��:K�ug��}�r'�������1Z/���0��^�������
]Qi��Z��������G�v�t�Z��]�iAV[Z����{a��D��t.�T�#�C?V��u���PC:�)'�RM�@�K�*cK�r*�.hX]��M�88���j�5�P���2���vs��T(#��t������j=�B���H�;iZ��
&����jfY���GX����V�
kI.�$�h����Siv�5&�p��OU$,�4Z�Lf�a0h�0|�r�VX���0�K��0%?,�{d�O�'��� ��� �� `14Z
��<��nSrV4�������Q����}�%�|hH���@F��T*b��(q%�8k�����LE��T��.�3H�"I���J)��=����.(��W��\
�LY�2,m���r�f������4�c3}��!���kA�B1
�d?*�l5S���%tl����i��3'�z(A{����{X�����^��/j���IK�"'Y-���|4�������W���D�����Y�g��:�WZEX��%�&��]��D{QW��C����o��6�i1��i��E�����@�����i�z����^��j��km����������^�F������?*�~�"�%a������}���y/)��<��Rg�
�_��{9+���: ���dQ�|���dM�`� ����I7�A:!������B�K0�.n�/�x�������^����������
���$~/�{��y����;�A���}��_��A
�@�@�����H{������������-iz�\�����x��LvKI��Z��+^��w# 7�;y��'\;U�d��K�f1����i{7��>�����O_��E�5�����%��Dr?d9��=!I���D&q��
#108Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#106)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

David Rowley <dgrowleyml@gmail.com> writes:

I just thought that my idea was good enough and very cheap too, won't all
numerics that are actually stored in a column have the same scale anyway?

No; unconstrained numeric columns (ie, if you just say "numeric") don't
force their contents to any particular scale. It might be that we don't
have to optimize for that case, since it's not in the SQL spec, but it
is definitely supported by PG.

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

#109Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#107)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

First, I've go the feeling that I should somehow update the commitfest app,
but I don't really know in which way. Should I put myself in as a reviewer,
or as a second author? Or neither? Suggestions welcome...

On Jan17, 2014, at 23:34 , David Rowley <dgrowleyml@gmail.com> wrote:

The test turned out to become:
if (state->expectedScale == -1)
state->expectedScale = X.dscale;
else if (state->expectedScale != X.dscale)
state->expectedScale = -2;

In do_numeric_accum, then when we do the inverse I just check if
expectedScale < 0 then return NULL.

Ok, so this will rescan if and only if the dscales of all inputs match.
I still that's overly pessimistic - we've only got a problem when we
removed the input with the largest dscale, no? So my suggestion would be

state->maxDScale = MAX(X.dscale, state->maxDScale);

in do_numeric_accum, and in the inverse

if (state->maxDScane == X.dscale)
return PG_RETURN_NULL;

I'd think that this avoids more restarts without about the same effort,
but I haven't tried this though, so maybe I'm missing something.

I'm not set on it, and I'm willing to try the lazy zeroing of the scale
tracker array, but I'm just not quite sure what extra real use cases it
covers that the one above does not. Perhaps my way won't perform inverse
transitions if the query did sum(numericCol / 10) or so.

Dunno how many people SUM over numerics with different dscales. Easily
possible that it's few enough to not be worth fussing over.

create table num (num numeric(10,2) not null);
insert into num (num) select * from generate_series(1,20000);
select sum(num) over(order by num rows between current row and unbounded following) from num; -- 124ms
select sum(num / 10) over(order by num rows between current row and unbounded following) from num; -- 254ms
select sum(num / 1) over(order by num rows between current row and unbounded following) from num; -- 108156.917 ms

The divide by 1 case is slow because of that weird 20 trailing zero
instead of 16 when dividing a numeric by 1 and that causes the inverse
transition function to return NULL because the scale changes.

I've not tested an unpatched version yet to see how that divide by 1 query
performs on that but I'll get to that soon.

So without the patch, all three queries should perform simiarly, i.e. take
about 10 seconds, right? If so, the improvement is fantastic!

I'm thinking that the idea about detecting the numeric range with floating
point types and performing an inverse transition providing the range has
not gone beyond some defined danger zone is not material for this patch...
I think it would be not a great deal of work to code, but the testing involved
would probably make this patch not possible for 9.4

Yeah, I never imagined that this would happen for 9.4.

The latest version of the patch is attached.

OK, there are a few more comments

* build_aggregate_fnexprs() should allow NULL to be passed for invtransfn_oid,
I think. I don't quite like that we construct that even for plain aggregates,
and avoiding requires just an additional if.

* Don't we need to check for volatile function in the filter expression too?

* As it stands, I don't think intXand_inv and intXor_inv are worth it, since
the case where they return non-NULL is awefully slim (only for inputs
containing only 1 respectively only zeros). We should either track the number
of zeros and ones per bit, which would allow us to always perform inverse
transitions, or just drop them.

* Quite a few of the inverse transition functions are marked strict, yet
contain code to handle NULL inputs. You can just remove that code - the system
makes sure that strict functions never receive NULL arguments. Affected are,
AFAICS numeric_accum_inv, numeric_avg_accum_inv, int2_accum_inv,
int4_accum_inv, int8_accum_inv, int8_avg_accum_inv, int2_sum_inv, int4_sum_inv,
int8_sum_inv. Not sure that list is exhaustive...

* For any of the new inverse transition functions, I'd be inclined to just
elog() if they're called directly and not as an aggregate. In particular
those which check for that anyway, plus the *smaller_inv and *larger_inv
ones. I don't see why anyone would ever want to call these directly - and
if we elog() now, we can easy change these functions later, because no external
code can depend on them. E.g., maybe someone wants to keep the top N elements
in the MIN/MAX aggregates one day...

* The number of new regression tests seems a bit excessive. I don't think there
really a policy what to test and what not, but in this case I think it suffices
if we test the basic machinery, plus the more complex functions. But e.g. most
of the SUM and AVG aggregates use numeric_accum or numeric_avg_accum internally,
and the wrapper code basically just does datatype conversion, so testing a few
cases seems enough there. What I think we *should* test, but don't do currently,
is whether the core machinery performs the expected calls of the forward and
reverse transition function. I was thinking about creating an aggregate in the
regression tests which simply concatenates all the calls into a string, e.g.
you might get "F:1 F:2 F:3 I:1" if we aggregated 1,2,3 and then removed 1.
I think that should be possible with an SQL-language forward and inverse
transfer function, but I haven't tried. I can try, if you want.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#110David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#109)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, Jan 18, 2014 at 2:20 PM, Florian Pflug <fgp@phlo.org> wrote:

First, I've go the feeling that I should somehow update the commitfest app,
but I don't really know in which way. Should I put myself in as a reviewer,
or as a second author? Or neither? Suggestions welcome...

We I guess you're both now, but it's a bit weird to be author and reviewer
so I've put your name against author too, hopefully Dean can review our
combined results and we can review each other's work at the same time.

On Jan17, 2014, at 23:34 , David Rowley <dgrowleyml@gmail.com> wrote:

The test turned out to become:
if (state->expectedScale == -1)
state->expectedScale = X.dscale;
else if (state->expectedScale != X.dscale)
state->expectedScale = -2;

In do_numeric_accum, then when we do the inverse I just check if
expectedScale < 0 then return NULL.

Ok, so this will rescan if and only if the dscales of all inputs match.
I still that's overly pessimistic - we've only got a problem when we
removed the input with the largest dscale, no? So my suggestion would be

state->maxDScale = MAX(X.dscale, state->maxDScale);

in do_numeric_accum, and in the inverse

if (state->maxDScane == X.dscale)
return PG_RETURN_NULL;

I'd think that this avoids more restarts without about the same effort,
but I haven't tried this though, so maybe I'm missing something.

This is not quite right as it means if all the values are the same then
we reject inverse transitions since state->maxScale will always be equal to
X.dscale.
But you are right about the overly strict code I've put in, we should allow
values with a less than the maximum dscale to be unaggregated without
complaint. To implement this I needed a maxScaleCounter variable too so we
only reject when the maxScaleCounter gets back to 0 again.

Note that after this fix the results for my quick benchmark now look like:

create table num (num numeric(10,2) not null);
insert into num (num) select * from generate_series(1,20000);
select sum(num) over(order by num rows between current row and unbounded
following) from num; -- 113 ms
select sum(num / 10) over(order by num rows between current row and
unbounded following) from num; -- 156ms
select sum(num / 1) over(order by num rows between current row and
unbounded following) from num; -- 144 ms

So it seems to be much less prone to falling back to brute force forward
transitions.
It also seems the / 10 version must have had to previously do 1 brute force
rescan but now it looks like it can do it in 1 scan.

I'm not set on it, and I'm willing to try the lazy zeroing of the scale

tracker array, but I'm just not quite sure what extra real use cases it
covers that the one above does not. Perhaps my way won't perform inverse
transitions if the query did sum(numericCol / 10) or so.

Dunno how many people SUM over numerics with different dscales. Easily
possible that it's few enough to not be worth fussing over.

Going by Tom's comments in the post above this is possible just by having
an unconstrained numeric column, but I guess there's still a good chance
that even those unconstrained numbers have the same scale or at least the
scale will likely not vary wildly enough to make us have to perform brute
force forward transitions for each row.

create table num (num numeric(10,2) not null);
insert into num (num) select * from generate_series(1,20000);
select sum(num) over(order by num rows between current row and unbounded

following) from num; -- 124ms

select sum(num / 10) over(order by num rows between current row and

unbounded following) from num; -- 254ms

select sum(num / 1) over(order by num rows between current row and

unbounded following) from num; -- 108156.917 ms

The divide by 1 case is slow because of that weird 20 trailing zero
instead of 16 when dividing a numeric by 1 and that causes the inverse
transition function to return NULL because the scale changes.

I've not tested an unpatched version yet to see how that divide by 1

query

performs on that but I'll get to that soon.

So without the patch, all three queries should perform simiarly, i.e. take
about 10 seconds, right? If so, the improvement is fantastic!

Well, it's actually 100 seconds, not 10. I tested the worse case
performance against an unpatched head and got 107 seconds instead of the
108. So I'm guessing that's pretty good as worse case is not really any
worse and the worse case is pretty hard to get to. I guess the results
would have to all have a different scale with the biggest scale on the
first aggregated values... Reaching that worse case just seems impossible
in a real world workload.

I'm thinking that the idea about detecting the numeric range with

floating

point types and performing an inverse transition providing the range has
not gone beyond some defined danger zone is not material for this

patch...

I think it would be not a great deal of work to code, but the testing

involved

would probably make this patch not possible for 9.4

Yeah, I never imagined that this would happen for 9.4.

The latest version of the patch is attached.

OK, there are a few more comments

* build_aggregate_fnexprs() should allow NULL to be passed for
invtransfn_oid,
I think. I don't quite like that we construct that even for plain
aggregates,
and avoiding requires just an additional if.

I'm not quite sure what you mean on this. It passes InvalidOid in normal

aggregate calls (search for: "InvalidOid, /* invtrans is not needed here
*/") and only looks up the function in build_aggregate_fnexprs if
(OidIsValid(invtransfn_oid)) is true. I'm not sure how this can be improved
since that function is used for window aggregates and normal aggregates.

* Don't we need to check for volatile function in the filter expression
too?

I did manual testing on this before and the volatility test for the
aggregate arguments seems to cover this. I didn't look into why but it just
did. I've not test this again since your refactoring. I could test this
easily before when my numeric case was changing the results because of the
dscale problem, I noticed that if I did FILTER(WHERE random() > 0) that the
extra trailing zeros would disappear.
The problem now is that it's pretty hard to determine if an inverse
transition took place, the only way we can really tell is performance. I'll
see if I can invent a new test case for this by creating a user defined
aggregate as you described. I'm thinking just append '+' to a string for
transitions and '-' to a string for inverse transitions, then just make
sure we only have a string of '+'s when doing something like filter(where
random() >= 0).

* As it stands, I don't think intXand_inv and intXor_inv are worth it,
since
the case where they return non-NULL is awefully slim (only for inputs
containing only 1 respectively only zeros). We should either track the
number
of zeros and ones per bit, which would allow us to always perform inverse
transitions, or just drop them.

I did think of this when I wrote them. I thought that the removing 0 case
might be quite common and worth it, but I thought the ~0 case would be less
common, but I just thought it was weird to do one without the other.
To do more tracking on these it looks like we'd need to change those
aggregates to use an state type that is internal and I think the extra
tracking would mean looping over a 8, 32 or 64 element array of int64's for
each value, I just don't think that would be a winner performance wise
since the code that's there is pretty much a handful of CPU cycles. It's
probably far more worth it for the bool and/or aggregates. We could just
keep track of the values aggregated and the count of values as "true" and
return true if those are the same in the case of "AND", then check the true
count is > 0 in the case of "OR". I'd feel more strongly to go and do that
if I'd actually ever used those aggregates for anything.

* Quite a few of the inverse transition functions are marked strict, yet
contain code to handle NULL inputs. You can just remove that code - the
system
makes sure that strict functions never receive NULL arguments. Affected
are,
AFAICS numeric_accum_inv, numeric_avg_accum_inv, int2_accum_inv,
int4_accum_inv, int8_accum_inv, int8_avg_accum_inv, int2_sum_inv,
int4_sum_inv,
int8_sum_inv. Not sure that list is exhaustive...

Should be able to get a list from:
select proname,proisstrict from pg_proc where proisstrict = true and oid
in(select agginvtransfn from pg_aggregate);

I might not have time for this today though, so if you feel like checking
these that would be really helpful.

* For any of the new inverse transition functions, I'd be inclined to just
elog() if they're called directly and not as an aggregate. In particular
those which check for that anyway, plus the *smaller_inv and *larger_inv
ones. I don't see why anyone would ever want to call these directly - and
if we elog() now, we can easy change these functions later, because no
external
code can depend on them. E.g., maybe someone wants to keep the top N
elements
in the MIN/MAX aggregates one day...

Yeah I guess the way it is now may mean we'd need to support legacy
functions for ever and a day if we changed the way they worked, but I'm not
sure if adding a check to see if it was used in an aggregate function
changes that, as a user could be using the built in function in their own
user defined aggregate, so there could still be complaints if we removed
them from a major version. What would be needed is some way to have
functions internally but publish these functions to the user, say only
visible from initdb or something. I don't think that's part of this patch
though. Maybe just the fact that they're undocumented helps give them more
ability to be removed later.

* The number of new regression tests seems a bit excessive. I don't think
there
really a policy what to test and what not, but in this case I think it
suffices
if we test the basic machinery, plus the more complex functions. But
e.g. most
of the SUM and AVG aggregates use numeric_accum or numeric_avg_accum
internally,
and the wrapper code basically just does datatype conversion, so testing
a few
cases seems enough there. What I think we *should* test, but don't do
currently,
is whether the core machinery performs the expected calls of the forward
and
reverse transition function. I was thinking about creating an aggregate
in the
regression tests which simply concatenates all the calls into a string,
e.g.
you might get "F:1 F:2 F:3 I:1" if we aggregated 1,2,3 and then removed
1.
I think that should be possible with an SQL-language forward and inverse
transfer function, but I haven't tried. I can try, if you want.

I agree that there are quite a lot of tests and I think that's why I took a
different approach when it came to all this little *larger_inv and
smaller_inv functions, there were just so many! I thought the aggregate
tests would run in the blank of a eye anyway, but perhaps I should consider
other things than just processing time. I created most of the aggregate
call tests by writing a whole load of queries on an unpatched version then
ran the tests and took the output of that as my expected results for my
patched copy. These were really useful to check for regression when I was
working hard on nodeWindowAgg.c. I'd find it hard to pick and choose what
to remove giving that they all test something slightly different, even if
it's just a different final function. They did pick up some failures
earlier when I forgot to change the strict property on int8_avg_accum_inv.
Maybe someone else has an opinion on that the number of tests?

Overall, I think it's really starting to take shape now and the list of
things to do are pretty small. I'm really happy to see so many aggregate
functions with inverse transition functions now!

Regards

David Rowley

Show quoted text

best regards,
Florian Pflug

#111David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#110)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, Jan 18, 2014 at 6:15 PM, David Rowley <dgrowleyml@gmail.com> wrote:

On Sat, Jan 18, 2014 at 2:20 PM, Florian Pflug <fgp@phlo.org> wrote:

* Don't we need to check for volatile function in the filter expression
too?

I did manual testing on this before and the volatility test for the
aggregate arguments seems to cover this. I didn't look into why but it just
did. I've not test this again since your refactoring. I could test this
easily before when my numeric case was changing the results because of the
dscale problem, I noticed that if I did FILTER(WHERE random() > 0) that the
extra trailing zeros would disappear.
The problem now is that it's pretty hard to determine if an inverse
transition took place, the only way we can really tell is performance. I'll
see if I can invent a new test case for this by creating a user defined
aggregate as you described. I'm thinking just append '+' to a string for
transitions and '-' to a string for inverse transitions, then just make
sure we only have a string of '+'s when doing something like filter(where
random() >= 0).

I've added a test case for this and it seem work as expected:
https://github.com/david-rowley/postgres/commit/43a5021e8f8ae1af272e7e21a842d1b0d5cbe577

Regards

David Rowley

#112Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#110)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan18, 2014, at 06:15 , David Rowley <dgrowleyml@gmail.com> wrote:

On Sat, Jan 18, 2014 at 2:20 PM, Florian Pflug <fgp@phlo.org> wrote:
On Jan17, 2014, at 23:34 , David Rowley <dgrowleyml@gmail.com> wrote:

The test turned out to become:
if (state->expectedScale == -1)
state->expectedScale = X.dscale;
else if (state->expectedScale != X.dscale)
state->expectedScale = -2;

In do_numeric_accum, then when we do the inverse I just check if
expectedScale < 0 then return NULL.

Ok, so this will rescan if and only if the dscales of all inputs match.
I still that's overly pessimistic - we've only got a problem when we
removed the input with the largest dscale, no? So my suggestion would be

<sniped>

I'd think that this avoids more restarts without about the same effort,
but I haven't tried this though, so maybe I'm missing something.

This is not quite right as it means if all the values are the same then
we reject inverse transitions since state->maxScale will always be equal
to X.dscale.
But you are right about the overly strict code I've put in, we should allow
values with a less than the maximum dscale to be unaggregated without
complaint. To implement this I needed a maxScaleCounter variable too so we
only reject when the maxScaleCounter gets back to 0 again.

Ups, sorry, yeah. Sounds sensible.

BTW, this made me realize that MIN and MAX currently have the same issue -
they'll rescan if the inputs are all equal. We could avoid that by doing what
you did with dscale - track the number of times we've seen the maximum. I
wonder if that would be worth it - it would, unfortunately, require use to
use state type "internal" there too, and hence to add final functions for all
the MIN/MAX aggregates. But that seems excessive. So for now, let's just
live with that.

If we really *do* want to optimize this case, we could
come to it from a completely different angle. Aggregates already special-case
MIN and MAX to be able to use an index to evalutate SELECT MAX(c) FROM t.
If we provided a way for the transition function to call the sort operator
specified by SORTOP in CREATE AGGREGATE, one generic triple of forward and
inverse transition function and final function would work for all the
MIN and MAX aggregates. But that's material for a separate patch for 9.5

Note that after this fix the results for my quick benchmark now look like:

create table num (num numeric(10,2) not null);
insert into num (num) select * from generate_series(1,20000);
select sum(num) over(order by num rows between current row and unbounded following) from num; -- 113 ms
select sum(num / 10) over(order by num rows between current row and unbounded following) from num; -- 156ms
select sum(num / 1) over(order by num rows between current row and unbounded following) from num; -- 144 ms

So it seems to be much less prone to falling back to brute force forward
transitions.
It also seems the / 10 version must have had to previously do 1 brute
force rescan but now it looks like it can do it in 1 scan.

I'm not set on it, and I'm willing to try the lazy zeroing of the scale
tracker array, but I'm just not quite sure what extra real use cases it
covers that the one above does not. Perhaps my way won't perform inverse
transitions if the query did sum(numericCol / 10) or so.

Dunno how many people SUM over numerics with different dscales. Easily
possible that it's few enough to not be worth fussing over.

Going by Tom's comments in the post above this is possible just by having an
unconstrained numeric column, but I guess there's still a good chance that
even those unconstrained numbers have the same scale or at least the scale
will likely not vary wildly enough to make us have to perform brute force
forward transitions for each row.

Yeah, I'm convinced by now that your approach is the right trade-off there.
Those who do have values with wildly different dscales in their columns can
always add a cast to normalize them, if they experience a lot of restarts.

So let's just add a sentence or two to the SUM(numeric) documentation about
this, and be done.

* build_aggregate_fnexprs() should allow NULL to be passed for invtransfn_oid,
I think. I don't quite like that we construct that even for plain aggregates,
and avoiding requires just an additional if.

I'm not quite sure what you mean on this. It passes InvalidOid in normal
aggregate calls (search for: "InvalidOid, /* invtrans is not needed here */")
and only looks up the function in build_aggregate_fnexprs if
(OidIsValid(invtransfn_oid)) is true. I'm not sure how this can be improved
since that function is used for window aggregates and normal aggregates.

I was thinking about checking for **invtransfnexpr = NULL, and not assigning
if it is. But on second thought, you're right - the additional variable doesn't
really hurt. So let's leave it as it is.

* Don't we need to check for volatile function in the filter expression too?

I did manual testing on this before and the volatility test for the aggregate
arguments seems to cover this. I didn't look into why but it just did. I've
not test this again since your refactoring. I could test this easily before
when my numeric case was changing the results because of the dscale problem,
I noticed that if I did FILTER(WHERE random() > 0) that the extra trailing
zeros would disappear. The problem now is that it's pretty hard to determine
if an inverse transition took place, the only way we can really tell is
performance. I'll see if I can invent a new test case for this by creating a
user defined aggregate as you described. I'm thinking just append '+' to a
string for transitions and '-' to a string for inverse transitions, then
just make sure we only have a string of '+'s when doing something like
filter(where random() >= 0).

For your other mail I get that this works as expected. Thanks for testing this!

* As it stands, I don't think intXand_inv and intXor_inv are worth it, since
the case where they return non-NULL is awefully slim (only for inputs
containing only 1 respectively only zeros). We should either track the number
of zeros and ones per bit, which would allow us to always perform inverse
transitions, or just drop them.

I did think of this when I wrote them. I thought that the removing 0 case might
be quite common and worth it, but I thought the ~0 case would be less common,
but I just thought it was weird to do one without the other.
To do more tracking on these it looks like we'd need to change those aggregates
to use an state type that is internal and I think the extra tracking would mean
looping over a 8, 32 or 64 element array of int64's for each value, I just don't
think that would be a winner performance wise since the code that's there is
pretty much a handful of CPU cycles.

Yeah, this is similar to the SUM(numeric) problem in that we *could* avoid
all restarts, but the overhead of doing so is quite high. But whereas in the
SUM(numeric) case we manage to reduce the overhead while still optimizing most
cases, here I think we optimize nearly none. My vote is for dropping these
functions entirely, but I don't feel particularly strongly about this...

It's probably far more worth it for the bool and/or aggregates. We could just
keep track of the values aggregated and the count of values as "true" and return
true if those are the same in the case of "AND", then check the true count
is > 0 in the case of "OR". I'd feel more strongly to go and do that if I'd
actually ever used those aggregates for anything.

That, OTOH, would be worthwhile I think. I'll go do that, though probably
not today. I hope to get to it sometime tomorrow.

* Quite a few of the inverse transition functions are marked strict, yet
contain code to handle NULL inputs. You can just remove that code - the system
makes sure that strict functions never receive NULL arguments. Affected are,
AFAICS numeric_accum_inv, numeric_avg_accum_inv, int2_accum_inv,
int4_accum_inv, int8_accum_inv, int8_avg_accum_inv, int2_sum_inv, int4_sum_inv,
int8_sum_inv. Not sure that list is exhaustive...

Should be able to get a list from:
select proname,proisstrict from pg_proc where proisstrict = true and oid in(select
agginvtransfn from pg_aggregate);

I might not have time for this today though, so if you feel like checking these
that would be really helpful.

Yeah, I'll do that, also tomorrow hopefully.

* For any of the new inverse transition functions, I'd be inclined to just
elog() if they're called directly and not as an aggregate. In particular
those which check for that anyway, plus the *smaller_inv and *larger_inv
ones. I don't see why anyone would ever want to call these directly - and
if we elog() now, we can easy change these functions later, because no external
code can depend on them. E.g., maybe someone wants to keep the top N elements
in the MIN/MAX aggregates one day...

Yeah I guess the way it is now may mean we'd need to support legacy functions
for ever and a day if we changed the way they worked, but I'm not sure if adding
a check to see if it was used in an aggregate function changes that, as a user
could be using the built in function in their own user defined aggregate, so
there could still be complaints if we removed them from a major version. What
would be needed is some way to have functions internally but publish these
functions to the user, say only visible from initdb or something. I don't think
that's part of this patch though. Maybe just the fact that they're undocumented
helps give them more ability to be removed later.

Hm, true. Still, I think I'd prefer us to elog() for those new functions which
explicitly check whether there's an aggregation context anyway. Can do that if you
want.

* The number of new regression tests seems a bit excessive. I don't think there
really a policy what to test and what not, but in this case I think it suffices
if we test the basic machinery, plus the more complex functions. But e.g. most
of the SUM and AVG aggregates use numeric_accum or numeric_avg_accum internally,
and the wrapper code basically just does datatype conversion, so testing a few
cases seems enough there. What I think we *should* test, but don't do currently,
is whether the core machinery performs the expected calls of the forward and
reverse transition function. I was thinking about creating an aggregate in the
regression tests which simply concatenates all the calls into a string, e.g.
you might get "F:1 F:2 F:3 I:1" if we aggregated 1,2,3 and then removed 1.
I think that should be possible with an SQL-language forward and inverse
transfer function, but I haven't tried. I can try, if you want.

I agree that there are quite a lot of tests and I think that's why I took a different
approach when it came to all this little *larger_inv and smaller_inv functions, there
were just so many! I thought the aggregate tests would run in the blank of a eye
anyway, but perhaps I should consider other things than just processing time. I
created most of the aggregate call tests by writing a whole load of queries on an
unpatched version then ran the tests and took the output of that as my expected
results for my patched copy. These were really useful to check for regression when
I was working hard on nodeWindowAgg.c. I'd find it hard to pick and choose what to
remove giving that they all test something slightly different, even if it's just a
different final function. They did pick up some failures earlier when I forgot to
change the strict property on int8_avg_accum_inv. Maybe someone else has an opinion
on that the number of tests?

I think the basic guideline for whether to test something in the regression test or
not is not so much "did it catch an error during development", but rather "could this
catch errors inadvertedly introduced later". That's not to say you shouldn't use
more test during development - your procedure there's fine - the question is just
whether to cut them down when the patch's done. For all these rather trivial inverse
function, I think we can trust that if they work once, they're not going to break
unless someone changes the function itself - they don't really have any outside
dependencies. That's also what the window functions regression test does today, I
think - it doesn't really test all possible cases exhaustively.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#113David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#112)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Jan 19, 2014 at 3:22 AM, Florian Pflug <fgp@phlo.org> wrote:

BTW, this made me realize that MIN and MAX currently have the same issue -
they'll rescan if the inputs are all equal. We could avoid that by doing
what
you did with dscale - track the number of times we've seen the maximum. I
wonder if that would be worth it - it would, unfortunately, require use to
use state type "internal" there too, and hence to add final functions for
all
the MIN/MAX aggregates. But that seems excessive. So for now, let's just
live with that.

Yeah, it's an idea... I had actually talked a bit about it before when
first I posted about inverse transition functions.

/messages/by-id/CAApHDvqu+yGW0vbPBb+yxHrPG5VcY_kiFYi8xmxFo8KYOczP3A@mail.gmail.com

But I've only realised today that it might be a no go. Let me explain...

I just finished implementing the inverse transition functions for bool_and
and bool_or, these aggregates had a sort operator which I assume would have
allowed an index scan to be performed, but since I had to change the first
argument of these aggregates to internal and that meant I had to get rid of
the sort operator... So I'm not actually sure that we really should
implement inverse transition functions for bool_and and bool_or because of
this. Never-the-less I commited a patch to the github repo which implements
them. I guess this sort operator problem completely writes off doing
something similar for MAX and MIN as that would mean no index scan would be
possible for these aggregates!

If we really *do* want to optimize this case, we could
come to it from a completely different angle. Aggregates already
special-case
MIN and MAX to be able to use an index to evalutate SELECT MAX(c) FROM t.
If we provided a way for the transition function to call the sort operator
specified by SORTOP in CREATE AGGREGATE, one generic triple of forward and
inverse transition function and final function would work for all the
MIN and MAX aggregates. But that's material for a separate patch for 9.5

Note that after this fix the results for my quick benchmark now look

like:

create table num (num numeric(10,2) not null);
insert into num (num) select * from generate_series(1,20000);
select sum(num) over(order by num rows between current row and unbounded

following) from num; -- 113 ms

select sum(num / 10) over(order by num rows between current row and

unbounded following) from num; -- 156ms

select sum(num / 1) over(order by num rows between current row and

unbounded following) from num; -- 144 ms

So it seems to be much less prone to falling back to brute force forward
transitions.
It also seems the / 10 version must have had to previously do 1 brute
force rescan but now it looks like it can do it in 1 scan.

I'm not set on it, and I'm willing to try the lazy zeroing of the scale
tracker array, but I'm just not quite sure what extra real use cases it
covers that the one above does not. Perhaps my way won't perform

inverse

transitions if the query did sum(numericCol / 10) or so.

Dunno how many people SUM over numerics with different dscales. Easily
possible that it's few enough to not be worth fussing over.

Going by Tom's comments in the post above this is possible just by

having an

unconstrained numeric column, but I guess there's still a good chance

that

even those unconstrained numbers have the same scale or at least the

scale

will likely not vary wildly enough to make us have to perform brute force
forward transitions for each row.

Yeah, I'm convinced by now that your approach is the right trade-off there.
Those who do have values with wildly different dscales in their columns can
always add a cast to normalize them, if they experience a lot of restarts.

So let's just add a sentence or two to the SUM(numeric) documentation about
this, and be done.

I had a quick look and I couldn't decide the best place to write about
specific details on inverse transition functions. The best place I could
see was to add a note under the aggregates table here:
http://www.postgresql.org/docs/devel/static/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE

I did think of this when I wrote them. I thought that the removing 0

case might

be quite common and worth it, but I thought the ~0 case would be less

common,

but I just thought it was weird to do one without the other.
To do more tracking on these it looks like we'd need to change those

aggregates

to use an state type that is internal and I think the extra tracking

would mean

looping over a 8, 32 or 64 element array of int64's for each value, I

just don't

think that would be a winner performance wise since the code that's

there is

pretty much a handful of CPU cycles.

Yeah, this is similar to the SUM(numeric) problem in that we *could* avoid
all restarts, but the overhead of doing so is quite high. But whereas in
the
SUM(numeric) case we manage to reduce the overhead while still optimizing
most
cases, here I think we optimize nearly none. My vote is for dropping these
functions entirely, but I don't feel particularly strongly about this...

Ok, I've reverted the patch which implemented the bitwise aggregate inverse
transition functions.

It's probably far more worth it for the bool and/or aggregates. We

could just

keep track of the values aggregated and the count of values as "true"

and return

true if those are the same in the case of "AND", then check the true

count

is > 0 in the case of "OR". I'd feel more strongly to go and do that if

I'd

actually ever used those aggregates for anything.

That, OTOH, would be worthwhile I think. I'll go do that, though probably
not today. I hope to get to it sometime tomorrow.

I've commited a patch to the github repo to do this.
https://github.com/david-rowley/postgres/commit/121b0823753cedf33bb94f646df3176b77f28500
but I'm not sure if we can keep it as I had to remove the sort op as I
explained above.

* Quite a few of the inverse transition functions are marked strict, yet
contain code to handle NULL inputs. You can just remove that code -

the system

makes sure that strict functions never receive NULL arguments.

Affected are,

AFAICS numeric_accum_inv, numeric_avg_accum_inv, int2_accum_inv,
int4_accum_inv, int8_accum_inv, int8_avg_accum_inv, int2_sum_inv,

int4_sum_inv,

int8_sum_inv. Not sure that list is exhaustive...

Should be able to get a list from:
select proname,proisstrict from pg_proc where proisstrict = true and oid

in(select

agginvtransfn from pg_aggregate);

I might not have time for this today though, so if you feel like

checking these

that would be really helpful.

Yeah, I'll do that, also tomorrow hopefully.

I think I beat you to it.But I'm not quite sure what to do
in numeric_avg_accum_inv() where it does:
if (state == NULL)
state = makeNumericAggState(fcinfo, false);

Perhaps we should just add an Assert(state != NULL); here, since we
shouldn't be calling inverse transitions without actually calling the
transition function, and the transition function should initialise the
state. I think this is valid as we can't actually call that function
manually because of the internal type. So it won't be possible to cause an
assert failure by doing select numeric_avg_accum_inv(NULL,20);

* For any of the new inverse transition functions, I'd be inclined to

just

elog() if they're called directly and not as an aggregate. In

particular

those which check for that anyway, plus the *smaller_inv and

*larger_inv

ones. I don't see why anyone would ever want to call these directly -

and

if we elog() now, we can easy change these functions later, because no

external

code can depend on them. E.g., maybe someone wants to keep the top N

elements

in the MIN/MAX aggregates one day...

Yeah I guess the way it is now may mean we'd need to support legacy

functions

for ever and a day if we changed the way they worked, but I'm not sure

if adding

a check to see if it was used in an aggregate function changes that, as

a user

could be using the built in function in their own user defined

aggregate, so

there could still be complaints if we removed them from a major version.

What

would be needed is some way to have functions internally but publish

these

functions to the user, say only visible from initdb or something. I

don't think

that's part of this patch though. Maybe just the fact that they're

undocumented

helps give them more ability to be removed later.

Hm, true. Still, I think I'd prefer us to elog() for those new functions
which
explicitly check whether there's an aggregation context anyway. Can do
that if you
want.

I've not done this yet, but may look at it later. I'd quite like a 2nd
opinion on it before I go and implement this as I'm not quite sure of what
value it will bring. I don't think it means that one day we can remove
these functions if we find a better way of doing things as there it would
still be possible to use them in a user defined aggregate. It just probably
narrows the chances, but it does stick a small overhead in each function to
test for this.

* The number of new regression tests seems a bit excessive. I don't

think there

really a policy what to test and what not, but in this case I think it

suffices

if we test the basic machinery, plus the more complex functions. But

e.g. most

of the SUM and AVG aggregates use numeric_accum or numeric_avg_accum

internally,

and the wrapper code basically just does datatype conversion, so

testing a few

cases seems enough there. What I think we *should* test, but don't do

currently,

is whether the core machinery performs the expected calls of the

forward and

reverse transition function. I was thinking about creating an

aggregate in the

regression tests which simply concatenates all the calls into a

string, e.g.

you might get "F:1 F:2 F:3 I:1" if we aggregated 1,2,3 and then

removed 1.

I think that should be possible with an SQL-language forward and

inverse

transfer function, but I haven't tried. I can try, if you want.

I agree that there are quite a lot of tests and I think that's why I

took a different

approach when it came to all this little *larger_inv and smaller_inv

functions, there

were just so many! I thought the aggregate tests would run in the blank

of a eye

anyway, but perhaps I should consider other things than just processing

time. I

created most of the aggregate call tests by writing a whole load of

queries on an

unpatched version then ran the tests and took the output of that as my

expected

results for my patched copy. These were really useful to check for

regression when

I was working hard on nodeWindowAgg.c. I'd find it hard to pick and

choose what to

remove giving that they all test something slightly different, even if

it's just a

different final function. They did pick up some failures earlier when I

forgot to

change the strict property on int8_avg_accum_inv. Maybe someone else has

an opinion

on that the number of tests?

I think the basic guideline for whether to test something in the
regression test or
not is not so much "did it catch an error during development", but rather
"could this
catch errors inadvertedly introduced later". That's not to say you
shouldn't use
more test during development - your procedure there's fine - the question
is just
whether to cut them down when the patch's done. For all these rather
trivial inverse
function, I think we can trust that if they work once, they're not going
to break
unless someone changes the function itself - they don't really have any
outside
dependencies. That's also what the window functions regression test does
today, I
think - it doesn't really test all possible cases exhaustively.

Ok, we can think about that later then. I don't want to remove any just
yet, even if we think the patch is getting close another review from
someone else could mean it needs more big changes before it's ready to go.

Show quoted text

best regards,
Florian Pflug

#114David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#113)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Jan 19, 2014 at 5:27 PM, David Rowley <dgrowleyml@gmail.com> wrote:

It's probably far more worth it for the bool and/or aggregates. We

could just

keep track of the values aggregated and the count of values as "true"

and return

true if those are the same in the case of "AND", then check the true

count

is > 0 in the case of "OR". I'd feel more strongly to go and do that

if I'd

actually ever used those aggregates for anything.

That, OTOH, would be worthwhile I think. I'll go do that, though probably
not today. I hope to get to it sometime tomorrow.

I've commited a patch to the github repo to do this.

https://github.com/david-rowley/postgres/commit/121b0823753cedf33bb94f646df3176b77f28500
but I'm not sure if we can keep it as I had to remove the sort op as I
explained above.

I think I'm going to have to revert the patch which implements the inverse
transition function for bool_and and bool_or.
I tested on an instance of 9.3.2 and the following queries use index scans.

create table booltest (b boolean not null);
insert into booltest (b) select false from generate_series(1,20000) g(n);
insert into booltest (b) values(true);

create index booltest_b_idx ON booltest(b);
vacuum analyze booltest;

explain select bool_or(b) from booltest;
explain select bool_and(b) from booltest;

I'm guessing there is no way to have an internal state type on the
aggregate and a sort operator on the aggregate.

I wonder if it is worth creating naive inverse transition functions similar
to max()'s and min()'s inverse transition functions. I guess on average
they've got about a 50% chance of being used and likely for some work loads
it would be a win.

What's your thoughts?

Regards
David Rowley

#115David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#107)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, Jan 18, 2014 at 11:34 AM, David Rowley <dgrowleyml@gmail.com> wrote:

The latest version of the patch is attached.

I've attached an updated version of the patch.

I'm now using github to track the changes on the patch, so I've included
the commit sha in the file name of the latest commit that this patch
includes, but I've also included the date.

Please see https://github.com/david-rowley/postgres/commits/invtrans for
what's been changed.

Right now I don't think there is very much left to do. Perhaps the
documents need some examples of creating inverse transition functions, I
was not sure, so I left them out for now.

Regards

David Rowley

Show quoted text

Regards

David Rowley

Attachments:

inverse_transition_functions_d00df99_2014-01-20.gzapplication/x-gzip; name=inverse_transition_functions_d00df99_2014-01-20.gzDownload
#116Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#113)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan19, 2014, at 05:27 , David Rowley <dgrowleyml@gmail.com> wrote:

I just finished implementing the inverse transition functions for bool_and
and bool_or, these aggregates had a sort operator which I assume would have
allowed an index scan to be performed, but since I had to change the first
argument of these aggregates to internal and that meant I had to get rid of
the sort operator...

Why does having transition type "internal" prevent you from specifying a
sort operator? The sort operator's argument types must match the *input*
type of the aggregate, not the transition type.

Here's a pure SQL implementation of an optimized bool_and called myand_agg
that uses state type bigint[] and specifies a sort operator.

create or replace function myboolagg_fwd(counts bigint[], value bool)
returns bigint[] as $$
select array[
counts[1] + case value when true then 0 else 1 end,
counts[2] + case value when true then 1 else 0 end
]
$$ language sql strict immutable;

create or replace function myboolagg_inv(counts bigint[], value bool)
returns bigint[] as $$
select array[
counts[1] - case value when true then 0 else 1 end,
counts[2] - case value when true then 1 else 0 end
]
$$ language sql strict immutable;

create or replace function myboolagg_and(counts bigint[])
returns bool as $$
select case counts[1] when 0 then true else false end
$$ language sql strict immutable;

create aggregate myand_agg (bool) (
stype = bigint[],
sfunc = myboolagg_fwd,
invfunc = myboolagg_inv,
finalfunc = myboolagg_and,
sortop = <,
initcond = '{0,0}'
);

With this, doing

create table boolvals as
select i, random() < 0.5 as v from generate_series(1,10000) i;
create index on boolvals(v);

explain analyze select myand_agg(v) from boolvals;

yields

Result (cost=0.33..0.34 rows=1 width=0) (actual time=0.067..0.067 rows=1 loops=1)
InitPlan 1 (returns $0)
-> Limit (cost=0.29..0.33 rows=1 width=1) (actual time=0.061..0.061 rows=1 loops=1)
-> Index Only Scan using boolvals_v_idx on boolvals (cost=0.29..474.41 rows=9950 width=1) (actual time=0.061..0.061 rows=1 loops=1)
Index Cond: (v IS NOT NULL)
Heap Fetches: 1
Total runtime: 0.100 ms

which looks fine, no?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#117David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#116)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mon, Jan 20, 2014 at 5:53 AM, Florian Pflug <fgp@phlo.org> wrote:

On Jan19, 2014, at 05:27 , David Rowley <dgrowleyml@gmail.com> wrote:

I just finished implementing the inverse transition functions for

bool_and

and bool_or, these aggregates had a sort operator which I assume would

have

allowed an index scan to be performed, but since I had to change the

first

argument of these aggregates to internal and that meant I had to get

rid of

the sort operator...

Why does having transition type "internal" prevent you from specifying a
sort operator? The sort operator's argument types must match the *input*
type of the aggregate, not the transition type.

Here's a pure SQL implementation of an optimized bool_and called myand_agg
that uses state type bigint[] and specifies a sort operator.

create or replace function myboolagg_fwd(counts bigint[], value bool)
returns bigint[] as $$
select array[
counts[1] + case value when true then 0 else 1 end,
counts[2] + case value when true then 1 else 0 end
]
$$ language sql strict immutable;

create or replace function myboolagg_inv(counts bigint[], value bool)
returns bigint[] as $$
select array[
counts[1] - case value when true then 0 else 1 end,
counts[2] - case value when true then 1 else 0 end
]
$$ language sql strict immutable;

create or replace function myboolagg_and(counts bigint[])
returns bool as $$
select case counts[1] when 0 then true else false end
$$ language sql strict immutable;

create aggregate myand_agg (bool) (
stype = bigint[],
sfunc = myboolagg_fwd,
invfunc = myboolagg_inv,
finalfunc = myboolagg_and,
sortop = <,
initcond = '{0,0}'
);

With this, doing

create table boolvals as
select i, random() < 0.5 as v from generate_series(1,10000) i;
create index on boolvals(v);

explain analyze select myand_agg(v) from boolvals;

yields

Result (cost=0.33..0.34 rows=1 width=0) (actual time=0.067..0.067 rows=1
loops=1)
InitPlan 1 (returns $0)
-> Limit (cost=0.29..0.33 rows=1 width=1) (actual time=0.061..0.061
rows=1 loops=1)
-> Index Only Scan using boolvals_v_idx on boolvals
(cost=0.29..474.41 rows=9950 width=1) (actual time=0.061..0.061 rows=1
loops=1)
Index Cond: (v IS NOT NULL)
Heap Fetches: 1
Total runtime: 0.100 ms

which looks fine, no?

hmm, yeah you're right. I guess I didn't quite think through what the sort
comparison was comparing with, for some reason I had it in my head that it
was the aggregate state and not another value in a btree index.

I've applied that patch again and put in the sort operators.

Thanks for looking at that.

Regards

David Rowley

Show quoted text

best regards,
Florian Pflug

#118Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#117)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan19, 2014, at 20:00 , David Rowley <dgrowleyml@gmail.com> wrote:

I've applied that patch again and put in the sort operators.

I've push a new version to https://github.com/fgp/postgres/tree/invtrans
which includes

* A bunch of missing declaration for *_inv functions

* An assert that the frame end doesn't move backwards - I realized that
it is after all easy to do that, if it's done after the loop which adds
the new values, not before.

* EXPLAIN VERBOSE ANALYZE now shows the max. number of forward aggregate
transitions per row and aggregate. It's a bit imprecise, because it doesn't
track the count per aggregate, but it's still a good metric for how well
the inverse transition functions work. If the number is close to one, you
know that very few rescans are happening.

* I've also renamed INVFUNC to INVSFUNC. That's a pretty invasive change, and
it's the last commit, so if you object to that, then you can merge up to
eafa72330f23f7c970019156fcc26b18dd55be27 instead of
de3d9148be9732c4870b76af96c309eaf1d613d7.

A few more things I noticed, all minor stuff

* do_numeric_discard()'s inverseTransValid flag is unnecessary. First, if the
inverse transition function returns NULL once, we never call it again, so the
flag won't have any practical effect. And second, assume we ever called the
forward transition function after the inverse fail, and then retried the inverse.
In the case of do_numeric_discard(), that actually *could* allow the inverse
to suddenly succeed - if the call to the forward function increased the dscale
beyond that of the element we tried to remove, removal would suddenly be
possible again. We never do that, of course, and it seems unlikely we ever
will. But it's still weird to have code which serves no other purpose than to
pessimize a case which would otherwise just work fine.

* The state == NULL checks in all the strict inverse transition functions are
redundant.

I haven't taken a close look at the documentation yet, I hope to be able to
do that tomorrow. Otherwise, things look good as far as I'm concerned.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#119Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#117)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan19, 2014, at 20:00 , David Rowley <dgrowleyml@gmail.com> wrote:

I've applied that patch again and put in the sort operators.

I've push a new version to https://github.com/fgp/postgres/tree/invtrans
This branch includes the following changes

* A bunch of missing declaration for *_inv functions

* An assert that the frame end doesn't move backwards - I realized that
it is after all easy to do that, if it's done after the loop which adds
the new values, not before.

* EXPLAIN VERBOSE ANALYZE now shows the max. number of forward aggregate
transitions per row and aggregate. It's a bit imprecise, because it doesn't
track the count per aggregate, but it's still a good metric for how well
the inverse transition functions work. If the number is close to one, you
know that very few rescans are happening.

* I've also renamed INVFUNC to INVSFUNC. That's a pretty invasive change, and
it's the last commit, so if you object to that, then you can merge up to
eafa72330f23f7c970019156fcc26b18dd55be27 instead of
de3d9148be9732c4870b76af96c309eaf1d613d7.

A few more things I noticed, all minor stuff

* do_numeric_discard()'s inverseTransValid flag is unnecessary. First, if the
inverse transition function returns NULL once, we never call it again, so the
flag won't have any practical effect. And second, assume we ever called the
forward transition function after the inverse fail, and then retried the inverse.
In the case of do_numeric_discard(), that actually *could* allow the inverse
to suddenly succeed - if the call to the forward function increased the dscale
beyond that of the element we tried to remove, removal would suddenly be
possible again. We never do that, of course, and it seems unlikely we ever
will. But it's still weird to have code which serves no other purpose than to
pessimize a case which would otherwise just work fine.

* The state == NULL checks in all the strict inverse transition functions are
redundant.

I haven't taken a close look at the documentation yet, I hope to be able to
do that tomorrow. Otherwise, things look good as far as I'm concerned.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#120David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#118)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mon, Jan 20, 2014 at 2:45 PM, Florian Pflug <fgp@phlo.org> wrote:

On Jan19, 2014, at 20:00 , David Rowley <dgrowleyml@gmail.com> wrote:

I've applied that patch again and put in the sort operators.

I've push a new version to https://github.com/fgp/postgres/tree/invtrans
which includes

* A bunch of missing declaration for *_inv functions

Thanks, I've applied that.

* An assert that the frame end doesn't move backwards - I realized that
it is after all easy to do that, if it's done after the loop which adds
the new values, not before.

I've applied this too, but I'm wondering why an elog for if the head moves
back, but an assert if the tail moves back?

* EXPLAIN VERBOSE ANALYZE now shows the max. number of forward aggregate
transitions per row and aggregate. It's a bit imprecise, because it
doesn't
track the count per aggregate, but it's still a good metric for how well
the inverse transition functions work. If the number is close to one, you
know that very few rescans are happening.

I've not looked at this yet and I don't think I'll have time tonight, but
it sounds interesting. I guess it might be quite nice to have a way to see
this especially with the way the numeric stuff works, it might be actually
pretty hard to otherwise know how many inverse transition "failures" there
had been. Do you think it's also worth tracking the inverse transition
failures too?

* I've also renamed INVFUNC to INVSFUNC. That's a pretty invasive change,

and
it's the last commit, so if you object to that, then you can merge up to
eafa72330f23f7c970019156fcc26b18dd55be27 instead of
de3d9148be9732c4870b76af96c309eaf1d613d7.

Seems like sfunc really should be tfunc then we could have invtfunc. I'd
probably understand this better if I knew what the 's' was for in sfunc.
I've not applied this just yet. Do you have a reason why you think it's
better?

A few more things I noticed, all minor stuff

* do_numeric_discard()'s inverseTransValid flag is unnecessary. First, if
the
inverse transition function returns NULL once, we never call it again,
so the
flag won't have any practical effect. And second, assume we ever called
the
forward transition function after the inverse fail, and then retried the
inverse.
In the case of do_numeric_discard(), that actually *could* allow the
inverse
to suddenly succeed - if the call to the forward function increased the
dscale
beyond that of the element we tried to remove, removal would suddenly be
possible again. We never do that, of course, and it seems unlikely we
ever
will. But it's still weird to have code which serves no other purpose
than to
pessimize a case which would otherwise just work fine.

hmm, yeah of course, you are right. I've removed this now.

* The state == NULL checks in all the strict inverse transition functions
are
redundant.

ok, I've removed these and added comments to note that these functions
should be declared strict.

I haven't taken a close look at the documentation yet, I hope to be able to
do that tomorrow. Otherwise, things look good as far as I'm concerned.

Thanks, yeah those really do need a review. I've lost a bit of direction
with them and I'm not quite sure just how much detail to go in to with it.
I'd like to explain a bit that users who need to use their numeric columns
in window aggregates might want to think about having a defined scale to
the numeric rather than an undefined scale and explain that this is because
the inverse transition function for numeric bails out if it loses the
maximum seen dscale. Though it does seem generally a good idea to have a
defined scale, but then I guess you've got to have a bit of knowledge about
the numbers you're storing in that case. I'm not quite sure how to put that
into words friendly enough for the documents just yet and or exactly where
to put the words. So any ideas or patches you have around that would be
great.

Once again thanks for all your work on this.

Regards

David Rowley

Show quoted text

best regards,
Florian Pflug

#121David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#120)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mon, Jan 20, 2014 at 8:42 PM, David Rowley <dgrowleyml@gmail.com> wrote:

On Mon, Jan 20, 2014 at 2:45 PM, Florian Pflug <fgp@phlo.org> wrote:

* EXPLAIN VERBOSE ANALYZE now shows the max. number of forward aggregate
transitions per row and aggregate. It's a bit imprecise, because it
doesn't
track the count per aggregate, but it's still a good metric for how well
the inverse transition functions work. If the number is close to one,
you
know that very few rescans are happening.

I've not looked at this yet and I don't think I'll have time tonight, but
it sounds interesting. I guess it might be quite nice to have a way to see
this especially with the way the numeric stuff works, it might be actually
pretty hard to otherwise know how many inverse transition "failures" there
had been. Do you think it's also worth tracking the inverse transition
failures too?

I've merged this patch but I attempted to get it into a bit more of a ready
state by moving the code out into a helper function the same way as the
other explain stuff is done. I've not touched explain before so do let me
know if I've made it worse.

https://github.com/david-rowley/postgres/commits/invtrans

Regards

David Rowley

#122Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#120)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan20, 2014, at 08:42 , David Rowley <dgrowleyml@gmail.com> wrote:

On Mon, Jan 20, 2014 at 2:45 PM, Florian Pflug <fgp@phlo.org> wrote:
* An assert that the frame end doesn't move backwards - I realized that
it is after all easy to do that, if it's done after the loop which adds
the new values, not before.

I've applied this too, but I'm wondering why an elog for if the head moves
back, but an assert if the tail moves back?

When I put the frame head check in, I was concerned that the code might crash
or loop endlessly if aggregatedbase was ever larger than frameheadpos, so
I made it elog(), just for safety.

The frame end check, OTOH, is done at the very end, so it doesn't protect
against much, it just documents that it's not supposed to happen.

But yeah, it's bit weird. Feel free to turn the frame end check into an
elog() too if you want to.

* I've also renamed INVFUNC to INVSFUNC. That's a pretty invasive change, and
it's the last commit, so if you object to that, then you can merge up to
eafa72330f23f7c970019156fcc26b18dd55be27 instead of
de3d9148be9732c4870b76af96c309eaf1d613d7.

Seems like sfunc really should be tfunc then we could have invtfunc. I'd probably
understand this better if I knew what the 's' was for in sfunc. I've not applied
this just yet. Do you have a reason why you think it's better?

My issue with just "invfunc" is mainly that it's too generic - it doesn't tell
you what it's supposed to be the inverse of.

I've always assumed that 's' in 'sfunc' and 'stype' stands for 'state', and that
the naming is inspired by control theory, where the function which acts on the
state space is often called S.

Thanks, yeah those really do need a review. I've lost a bit of direction with
them and I'm not quite sure just how much detail to go in to with it. I'd like
to explain a bit that users who need to use their numeric columns in window
aggregates might want to think about having a defined scale to the numeric rather
than an undefined scale and explain that this is because the inverse transition
function for numeric bails out if it loses the maximum seen dscale. Though it
does seem generally a good idea to have a defined scale, but then I guess you've
got to have a bit of knowledge about the numbers you're storing in that case.
I'm not quite sure how to put that into words friendly enough for the documents
just yet and or exactly where to put the words. So any ideas or patches you have
around that would be great.

Here's what I image the docs should look like, very roughly

* In CREATE AGGREGATE, we should state the precise axioms we assume about forward
and inverse transition functions. The last time I read the text there, it was
a bit ambiguous about whether inverse transition functions assume commutativity,
i.e. whether we assume that we can remove inputs other than the first one from
the aggregation. Currently, all the inverse transition functions we have are,
in fact, commutative, because all the forward transition function are also. But
we could e.g. add an inverse to array_agg and string_agg, and those would
obviously need this ordering guarantee. I'd also like us to explain that the
"inverse" in "inverse transition function" shouldn't be taken too literally. For
forward transition function F, inverse transition function G, and inputs a,b,...
we *don't require that G(F(s,a),a) == s. We we do require is that if I is the
initial state, then

G(F(...F(F(I,a),b)...,z),a) == F(...F(I,b)...,z).

(Well, actually we don't strictly require even that, the two states don't
need to be identical, they just need to produce identical outputs when passed
to the final function)

* In CREATE AGGREGATE, we should also explain the NULL semantics you get
with various combinations of strict and non-strict forward and inverse
transition functions. I think a table listing all the combinations is in order
there, with a column explaining the semantics you get. We should also clearly
state that once you provide an inverse transition function, NULL isn't a valid
state value anymore, except as an initial state, i.e. that the forward transition
function must never return NULL in this case.

* The window function page should explain the performance hazards when
a moving frame head is involved. Ideally, we'd list which aggregates never
have to restart, and which sometimes might, and what you can do to avoid that.
We can also tell people to use EXPLAIN VERBOSE ANALYZE to check whether
restarts are occurring. This is were we'd tell people to cast their numeric
operands to a type with defined scale to avoid restarts, and were we'd state
the SUM(float) *does* restart always due to precision loss issues.

BTW, something that we haven't addressed at all is how inverse transition
functions interact with what is called "ordered-set aggregates". I haven't
wrapped my head around these fully, I think, so I'm still not sure if there's
anything to do there or not. Can those even be used as window functions?
Should we forbid ordered-set aggregates from specifying an inverse transition
function?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#123David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#5)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Dec 15, 2013 at 2:00 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Greg Stark <stark@mit.edu> writes:

On 14 Dec 2013 15:40, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

I think you *can't* cover them for the float types; roundoff error
would mean you don't get the same answers as before.

I was going to say the same thing. But then I started to wonder....

What's

so special about the answers we used to give? They are also subject to
round off and the results are already quite questionable in those cases.

Well, we can't easily do better than the old answers, and the new ones
might be arbitrarily worse. Example: sum or average across single-row
windows ought to be exact in any case, but it might be arbitrarily wrong
with the negative-transition technique.

More generally, this is supposed to be a performance enhancement only;
it's not supposed to change the results.

It came to me that it might be possible to implement inverse transitions
for floating point aggregates by just detecting if precision has been lost
during forward transitions.

I've written the test to do this as:

IF state.value + value = state.value AND value <> 0 THEN
newstate.precision_lost := true; newstate.value := state.value; ELSE
newstate.precision_lost := false; newstate.value := state.value + value;
END IF;
The inverse transition function checks the precision_lost and if it's true
it returns NULL. The core code is now implemented (thanks to Florian) to
re-aggregate when NULL is returned from the inverse transition function.

I've attached an implementation of this with the transition functions
written in plpgsql.
I don't really know for sure yet if it can handle all cases and give the
exact same results as it would without inverse transitions, but it
certainly fixes the error case which was presented

Using the attached on HEAD of
https://github.com/david-rowley/postgres/commits/invtrans

explain (analyze, verbose)
select mysum(v) over (order by i rows between current row and unbounded
following) from (values(1,1e20),(2,1)) b(i,v);

Gives me the expected results of 1e20 and 1, instead of my original attempt
which gave 1e20 and 0.

I guess the extra tracking on forward transition might mean this would not
be practical to implement in C for sum(float), but I just wanted to run the
idea through a few heads to see if anyone can present a case where it can
still produce wrong results.

If it seems sound enough, then I may implement it in C to see how much
overhead it adds to forward aggregation for floating point types, but even
if it did add too much overhead to forward aggregation it might be worth
allowing aggregates to have 2 forward transition functions and if the 2nd
one exists then it could be used in windowing functions where the frame
does not have "unbounded following".

Any thoughts?

Regards

David Rowley

Attachments:

float_invtrans.sqltext/plain; charset=US-ASCII; name=float_invtrans.sqlDownload
#124Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#123)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan21, 2014, at 10:53 , David Rowley <dgrowleyml@gmail.com> wrote:

It came to me that it might be possible to implement inverse transitions
for floating point aggregates by just detecting if precision has been
lost during forward transitions.

I've written the test to do this as:

IF state.value + value = state.value AND value <> 0 THEN
newstate.precision_lost := true;
newstate.value := state.value;
ELSE
newstate.precision_lost := false;
newstate.value := state.value + value;
END IF;

The inverse transition function checks the precision_lost and if it's true it
returns NULL. The core code is now implemented (thanks to Florian) to
re-aggregate when NULL is returned from the inverse transition function.

That's not sufficient, I fear. You can lose all significant digits of the value
and still have precision_lost = false afterwards. Try summing over 1e16, 1.01.
"SELECT 1e16::float8 + 1.01::float8 = 1e16::float8" returns FALSE, yet
"SELECT 1e16::float8 + 1.01::float8 - 1e16::float8" returns "2" where "1.01"
would have been correct. That's still too much precision loss.

I'm quite certain that the general idea has merit, but the actual
invertibility condition is going to be more involved. If you want to play with
this, I think the first step has to be to find a set of guarantees that
SUM(float) is supposed to meet. Currently, SUM(float) guarantees that if the
summands all have the same sign, the error is bound by C * N, where C is some
(machine-specific?) constant (about 1e-15 or so), and N is the number of input
rows. Or at least so I think from looking at SUMs over floats in descending
order, which I guess is the worst case. You could, for example, try to see if
you can find a invertibility conditions which guarantees the same, but allows
C to be larger. That would put a bound on the number of digits lost by the new
SUM(float) compared to the old one.

I don't have high hopes for this getting int 9.4, though.

If it seems sound enough, then I may implement it in C to see how much
overhead it adds to forward aggregation for floating point types, but even
if it did add too much overhead to forward aggregation it might be worth
allowing aggregates to have 2 forward transition functions and if the 2nd
one exists then it could be used in windowing functions where the frame
does not have "unbounded following".

I don't think adding yet another type of aggregation function is the
solution here.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#125Florian Pflug
fgp@phlo.org
In reply to: Florian Pflug (#122)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan20, 2014, at 15:20 , Florian Pflug <fgp@phlo.org> wrote:

* In CREATE AGGREGATE, we should state the precise axioms we assume about forward
and inverse transition functions. The last time I read the text there, it was
a bit ambiguous about whether inverse transition functions assume commutativity,
i.e. whether we assume that we can remove inputs other than the first one from
the aggregation. Currently, all the inverse transition functions we have are,
in fact, commutative, because all the forward transition function are also. But
we could e.g. add an inverse to array_agg and string_agg, and those would
obviously need this ordering guarantee. I'd also like us to explain that the
"inverse" in "inverse transition function" shouldn't be taken too literally. For
forward transition function F, inverse transition function G, and inputs a,b,...
we *don't require that G(F(s,a),a) == s. We we do require is that if I is the
initial state, then

G(F(...F(F(I,a),b)...,z),a) == F(...F(I,b)...,z).

(Well, actually we don't strictly require even that, the two states don't
need to be identical, they just need to produce identical outputs when passed
to the final function)

* In CREATE AGGREGATE, we should also explain the NULL semantics you get
with various combinations of strict and non-strict forward and inverse
transition functions. I think a table listing all the combinations is in order
there, with a column explaining the semantics you get. We should also clearly
state that once you provide an inverse transition function, NULL isn't a valid
state value anymore, except as an initial state, i.e. that the forward transition
function must never return NULL in this case.

I gave that a shot, the results are at https://github.com/fgp/postgres/tree/invtrans

* The window function page should explain the performance hazards when
a moving frame head is involved. Ideally, we'd list which aggregates never
have to restart, and which sometimes might, and what you can do to avoid that.
We can also tell people to use EXPLAIN VERBOSE ANALYZE to check whether
restarts are occurring. This is were we'd tell people to cast their numeric
operands to a type with defined scale to avoid restarts, and were we'd state
the SUM(float) *does* restart always due to precision loss issues.

BTW, something that we haven't addressed at all is how inverse transition
functions interact with what is called "ordered-set aggregates". I haven't
wrapped my head around these fully, I think, so I'm still not sure if there's
anything to do there or not. Can those even be used as window functions?
Should we forbid ordered-set aggregates from specifying an inverse transition
function?

It seems that these aren't valid window function anyway, so there's nothing
to do for now, I think.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#126David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#112)
2 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Jan 19, 2014 at 3:22 AM, Florian Pflug <fgp@phlo.org> wrote:

On Jan18, 2014, at 06:15 , David Rowley <dgrowleyml@gmail.com> wrote:>
Note that after this fix the results for my quick benchmark now look like:

create table num (num numeric(10,2) not null);
insert into num (num) select * from generate_series(1,20000);
select sum(num) over(order by num rows between current row and unbounded

following) from num; -- 113 ms

select sum(num / 10) over(order by num rows between current row and

unbounded following) from num; -- 156ms

select sum(num / 1) over(order by num rows between current row and

unbounded following) from num; -- 144 ms

So it seems to be much less prone to falling back to brute force forward
transitions.
It also seems the / 10 version must have had to previously do 1 brute
force rescan but now it looks like it can do it in 1 scan.

I've performed some more benchmarks on this patch tonight. The results and
full recreation scripts are attached along with the patch it was tested
against.

The benchmarks contain a bit of a mix of different possible workloads, I've
tried to show the worst and best case for everything I can think of that
has a best and worst case. The best case with the patched version is
naturally very good when the frame is unbounded following and the frame
head moves down on each row, that is providing the inverse transition can
actually take place.

I've noticed a slight performance regression on the worse case for max()
and min() aggregates. Note that these inverse transition functions are
implemented to return failure when the value being removed is equal to the
current state. e.g, if max() is 10 and we remove 9, then we can report
success, but if max is 10 and we remove 10 we report failure. The failed
attempt to remove in this case will be costing us a little bit more than it
was previously.

I'm not quite sure how likely it is to be a realistic workload, but a query
such as:

select max(id) over (order by id DESC rows between current row and
unbounded following) from test_numeric;

will fail to perform inverse transitions on *EVERY* row.
For int types this regression is only about 2-3%, but it will likely
increase as the comparison function gets more expensive. I wondered if it
would be worth attempting to check the window's order by and compare that
to the aggregate's sort operator and not perform inverse transitions in
this case. Although a query such as this:

select first_value(id) over (order by id DESC rows between current row and
unbounded following) from test_numeric;

would give the same results to the user and will likely be much much faster
anyway, so perhaps detecting that is not worth it.

The performance regression seems a bit unavoidable in the case where the
order by col is not the same as the max's column, but the 2 columns happen
to sort the results in the same way. I can't really think of a way to
detect this. But then I think you'll get back what you lost even if you
could perform just 1 inverse transition in the entire processing of the
window, so that case is probably quite unlikely to hit and if you compare
it to the possible cases that it would improve.

I focused quite a bit on testing the performance of SUM(numeric) due to a
few extra lines of code that I had to add into do_numeric_accum() to allow
tracking of the maximum dscale'd numeric. My plain aggregate test on this
showed no measurable regression, though there will be a couple of extra CPU
cycles in there on each call to the function.

I didn't really bother doing much performance testing on aggregates like
count(*) and sum(int) as these can always perform inverse transitions, the
best cases for each of the other queries should be used as a guide for how
much performance will increase.

I'm pretty happy to see that for processing as little as 20 thousand rows
that the patch can increase performance by up to 1000 times! And this
increase will just get bigger with more rows.

Here's some sample results from the attached txt file:

select sum(num_fixed_scale) over(order by id rows between current row and
unbounded following) from test_numeric;
Results in miliseconds

Patched:
50.958
63.820
64.719
52.860
64.921

Unpatched:
61358.856
61180.370
62642.495
61416.385
62383.012

Comp. Patched Unpatched
average 59.4556 61796.2236 Avg Increase (times) 1039.367589 103936.76%
median 63.82 61416.385 Median Increase (times) 962.3375901 96233.76%

If anyone can think of any other workloads that they would like tested
please let me know.

Regards

David Rowley.

Attachments:

benchmarks.txttext/plain; charset=US-ASCII; name=benchmarks.txtDownload
-- Setup data

create table test_numeric (
  id serial not null primary key,
  partitionid int not null,
  num_fixed_scale numeric(10,2) not null,
  num_variable_scale numeric not null
);
  
insert into test_numeric (partitionid,num_fixed_scale,num_variable_scale) 
select i % 50 as partitionid, -- so we can PARTITION BY something.
       cast(i as numeric),
       cast(i::text || '.' || lpad('1',i % 10,'0') as numeric)
from generate_series(1,20000) s(i);


create table test_float (
  id serial not null,
  partitionid int not null,
  float_4 real not null,
  float_8 double precision not null,
  primary key (id)
);


insert into test_float (partitionid,float_4,float_8) 
select i % 50 as partitionid, -- so we can PARTITION BY something.
       cast(i::text || '.' || lpad('1',6 - (i % 6),'0') as real),
       cast(i::text || '.' || lpad('1',6 - (i % 6),'0') as double precision)
from generate_series(1,20000) s(i);


/***********************
 Fixed scale numeric
************************/


-- Should always be able to inverse transition.
select sum(num_fixed_scale) over(order by id rows between current row and unbounded following) from test_numeric;
/* Results in miliseconds 

Patched: 
50.958
63.820
64.719
52.860
64.921

Unpatched:
61358.856
61180.370
62642.495
61416.385
62383.012

Comp.	Patched	Unpatched			
average	59.4556	61796.2236	Avg Increase (times)	1039.367589	103936.76%
median	63.82	61416.385	Median Increase (times)	962.3375901	96233.76%

*/

-- always inverse transition sliding window at most 21 rows tall.
-- this should always be able to perform inverse transitions.
select sum(num_fixed_scale) over(order by id rows between 10 preceding and 10 following) from test_numeric;

/* Results in muliseconds

Patched:
86.511
119.326
117.112
104.280
109.103

Unpatched:
201.511
206.530
190.573
209.594
188.201

Comp.	Patched	Unpatched			
average	107.2664	199.2818	Avg Increase (times)	1.857821275	185.78%
median	109.103	201.511	Median Increase (times)	1.84697946	184.70%

*/

-- same as above but with partition by.
select sum(num_fixed_scale) over(partition by partitionid order by id rows between 10 preceding and 10 following) from test_numeric;

/* Results in muliseconds

Patched:
119.247
115.929
115.964
128.497
128.967

Unpatched:
215.987
203.738
214.281
215.797
224.511

Comp.	Patched	Unpatched			
average	121.720	214.862	Avg Increase (times)	1.765210219	176.52%
median	119.247	215.797	Median Increase (times)	1.809663975	180.97%

*/

-- no inverse transitions.
select sum(num_fixed_scale) over(order by id) from test_numeric;

/* Results in miliseconds

Patched:
56.063
52.682
50.710
62.129
55.812

Unpatched:
46.229
62.251
59.218
63.113
53.349

Comp.	Patched	Unpatched			
avg		59.4556	61796.2236	Avg Increase (times)	1039.367589	103936.76%
median	63.82	61416.385	Median Increase (times)	962.3375901	96233.76%

*/

-- normal aggregate
select sum(num_fixed_scale) from test_numeric;

/* Results in miliseconds

Patched:
13.476
12.612
12.457
12.797
12.564

Unpatched:
11.292
13.158
11.849
13.565
12.986

Comp.	Patched	Unpatched			
avg		12.7812	12.57	Avg Increase (times)	0.98347573	98.35%
median	12.612	12.986	Median Increase (times)	1.029654297	102.97%

*/

/***********************
 Variable scale numeric
************************/
-- tests for numeric with a really bad case of changing scale.
select sum(num_variable_scale) over(order by id rows between current row and unbounded following) from test_numeric;

/* Results in miliseconds

Patched:
69.246
58.574
70.410
65.415
62.043

Unpatched:
67073.005
66895.327
67511.977
69317.587
68647.335

Comp.	Patched	Unpatched			
avg		65.1376	67889.0462	Avg Increase (times)	1042.240522	104224.05%
median	65.415	67511.977	Median Increase (times)	1032.056516	103205.65%


*/

--explain (analyze true, verbose true)
select sum(num_variable_scale) over(order by id rows between 10 preceding and 10 following) from test_numeric;

/* Results in miliseconds

Patched:
112.186
123.454
113.636
111.475
109.149

Unpatched
202.757
202.288
206.305
218.756
202.841

Comp.	Patched	Unpatched			
average	113.98	206.5894	Avg Increase (times)	1.812505703	181.25%
median	112.186	202.841	Median Increase (times)	1.808077657	180.81%

*/

-- same as above but with partition by.
select sum(num_variable_scale) over(partition by partitionid order by id rows between 10 preceding and 10 following) from test_numeric;

/* Results in miliseconds

Patched:
112.828
121.361
131.561	
139.665
150.856

Unpatched:
212.636
237.160
207.431
212.518
198.667

Comp.	Patched	Unpatched			
average	131.2542	213.6824	Avg Increase (times)	1.628004285	162.80%
median	131.561	212.518	Median Increase (times)	1.615357135	161.54%

*/

-- no inverse transitions.
select sum(num_variable_scale) over(order by id) from test_numeric;

/* Results in miliseconds

Patched:
58.420
61.845
57.822
60.797
54.084

Unpatched:
55.116
65.192
47.140
62.661
57.197

Comp.	Patched	Unpatched			
average	58.5936	57.4612	Avg Increase (times)	0.980673657	98.07%
median	58.42	57.197	Median Increase (times)	0.979065389	97.91%

*/

-- normal aggregate
select sum(num_variable_scale) from test_numeric;

/* Results in miliseconds

Patched:
6.955
13.834
14.286
14.208
14.240

Unpatched:
14.109
13.939
14.292
8.209
14.364

Comp.	Patched	Unpatched			
average	12.7046	12.9826	Avg Increase (times)	1.021881838	102.19%
median	14.208	14.109	Median Increase (times)	0.993032095	99.30%

*/

/***********************
 MAX(int) test
************************/

-- best case for inverse transitions
select max(id) over (order by id rows between current row and unbounded following) from test_numeric;

/* Results in miliseconds

Patched:
50.259
56.310
43.048
49.984
48.939

Unpatched:
36799.741
36559.310
36595.636
36507.597
36670.881

Comp.	Patched	Unpatched			
average	49.708	36626.633	Avg Increase (times)	736.835781	73683.58%
median	49.984	36595.636	Median Increase (times)	732.147007	73214.70%

*/

-- worst case
select max(id) over (order by id DESC rows between current row and unbounded following) from test_numeric;

/* Results in miliseconds

Patched:
37477.440
37849.856
37485.064

Unpatched:
36397.710
36619.854
38127.652

Comp.	Patched	Unpatched			
average	37604.12	37048.40533	Avg Increase (times)	0.985221974	98.52%
median	37485.064	36619.854	Median Increase (times)	0.97691854	97.69%

*/

select count(id) over (order by id rows between current row and unbounded following) from test_numeric;

/* Results in miliseconds

Patched:

53.961
51.007
47.533
55.729
50.744

Unpatched:
38123.509
38492.724
38215.065
38940.284
38336.407

Comp.	Patched	Unpatched			
average	51.7948	38421.5978	Avg Increase (times)	741.8041541	74180.42%
median	51.007	38336.407	Median Increase (times)	751.5910953	75159.11%

*/


/***********************
 the strange case of 1 row window frames.
************************/

select count(*) over (order by id rows between current row and current row) from test_numeric;

/* Results in miliseconds

Patched:
27.779
41.351
43.434
48.595
60.952

Unpatched:
48.353
44.417
53.545
44.822
38.380
*/




/***********************
 cast float to numeric
************************/

select sum(float_4::numeric) over (order by id rows between current row and 10 following) from test_float;

/* Results in miliseconds

Patched:
182.801
149.210
159.477
157.277
169.348

Unpatched:
472.680
491.553
476.515
481.021
476.519

*/
inverse_transition_functions_5b0f391_2014-01-22.patch.gzapplication/x-gzip; name=inverse_transition_functions_5b0f391_2014-01-22.patch.gzDownload
#127David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#122)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Tue, Jan 21, 2014 at 3:20 AM, Florian Pflug <fgp@phlo.org> wrote:

On Jan20, 2014, at 08:42 , David Rowley <dgrowleyml@gmail.com> wrote:

On Mon, Jan 20, 2014 at 2:45 PM, Florian Pflug <fgp@phlo.org> wrote:
* I've also renamed INVFUNC to INVSFUNC. That's a pretty invasive

change, and

it's the last commit, so if you object to that, then you can merge up

to

eafa72330f23f7c970019156fcc26b18dd55be27 instead of
de3d9148be9732c4870b76af96c309eaf1d613d7.

Seems like sfunc really should be tfunc then we could have invtfunc. I'd

probably

understand this better if I knew what the 's' was for in sfunc. I've not

applied

this just yet. Do you have a reason why you think it's better?

My issue with just "invfunc" is mainly that it's too generic - it doesn't
tell
you what it's supposed to be the inverse of.

I've always assumed that 's' in 'sfunc' and 'stype' stands for 'state',
and that
the naming is inspired by control theory, where the function which acts on
the
state space is often called S.

Ok, that makes more sense now and it seems like a reasonable idea. I'm not
not quite sure yet as when someone said upthread that these "negative
transition functions" as I was calling them at the time should really be
called "inverse transition functions", I then posted that I was going to
call the create aggregate option "invfunc" which nobody seemed to object
to. I just don't want to go and change that now. It is very possible this
will come up again when the committer is looking at the patch. It would be
a waste if it ended up back at invfunc after we changed it to invsfunc.

Regards

David Rowley

#128David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#124)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Wed, Jan 22, 2014 at 12:46 AM, Florian Pflug <fgp@phlo.org> wrote:

On Jan21, 2014, at 10:53 , David Rowley <dgrowleyml@gmail.com> wrote:

It came to me that it might be possible to implement inverse transitions
for floating point aggregates by just detecting if precision has been
lost during forward transitions.

I've written the test to do this as:

IF state.value + value = state.value AND value <> 0 THEN
newstate.precision_lost := true;
newstate.value := state.value;
ELSE
newstate.precision_lost := false;
newstate.value := state.value + value;
END IF;

The inverse transition function checks the precision_lost and if it's

true it

returns NULL. The core code is now implemented (thanks to Florian) to
re-aggregate when NULL is returned from the inverse transition function.

That's not sufficient, I fear. You can lose all significant digits of the
value
and still have precision_lost = false afterwards. Try summing over 1e16,
1.01.
"SELECT 1e16::float8 + 1.01::float8 = 1e16::float8" returns FALSE, yet
"SELECT 1e16::float8 + 1.01::float8 - 1e16::float8" returns "2" where
"1.01"
would have been correct. That's still too much precision loss.

I'm quite certain that the general idea has merit, but the actual
invertibility condition is going to be more involved. If you want to play
with
this, I think the first step has to be to find a set of guarantees that
SUM(float) is supposed to meet. Currently, SUM(float) guarantees that if
the
summands all have the same sign, the error is bound by C * N, where C is
some
(machine-specific?) constant (about 1e-15 or so), and N is the number of
input
rows. Or at least so I think from looking at SUMs over floats in descending
order, which I guess is the worst case. You could, for example, try to see
if
you can find a invertibility conditions which guarantees the same, but
allows
C to be larger. That would put a bound on the number of digits lost by the
new
SUM(float) compared to the old one.

I guess if the case is that normal (non-window) sum(float) results are
undefined unless you add an order by to the aggregate then I guess there is
no possible logic to put in for inverse transitions that will make them
behave the same as an undefined behaviour.

I don't have high hopes for this getting int 9.4, though.

Yeah I'm going to drop it. I need to focus on documents and performance
testing.

If it seems sound enough, then I may implement it in C to see how much
overhead it adds to forward aggregation for floating point types, but

even

if it did add too much overhead to forward aggregation it might be worth
allowing aggregates to have 2 forward transition functions and if the 2nd
one exists then it could be used in windowing functions where the frame
does not have "unbounded following".

I don't think adding yet another type of aggregation function is the
solution here.

Why not?

I thought, if in the cases where we need to burden the forward transition
functions with extra code to make inverse transitions possible, then why
not have an extra transition function so that it does not slow down normal
aggregation?

My testing of sum(numeric) last night does not show any slow down by that
extra dscale tracking code that was added, but if it did then I'd be
thinking seriously about 2 forward transition functions to get around the
problem. I don't understand what would be wrong with that. The core code
could just make use of the 2nd function if it existed and inverse
transitions were thought to be required. i.e in a window context and does
not have UNBOUNDED PRECEDING.

Regards

David Rowley

#129Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#128)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan23, 2014, at 01:17 , David Rowley <dgrowleyml@gmail.com> wrote:

On Wed, Jan 22, 2014 at 12:46 AM, Florian Pflug <fgp@phlo.org> wrote:

If you want to play with
this, I think the first step has to be to find a set of guarantees that
SUM(float) is supposed to meet. Currently, SUM(float) guarantees that if the
summands all have the same sign, the error is bound by C * N, where C is some
(machine-specific?) constant (about 1e-15 or so), and N is the number of input
rows. Or at least so I think from looking at SUMs over floats in descending
order, which I guess is the worst case. You could, for example, try to see if
you can find a invertibility conditions which guarantees the same, but allows
C to be larger. That would put a bound on the number of digits lost by the new
SUM(float) compared to the old one.

I guess if the case is that normal (non-window) sum(float) results are undefined
unless you add an order by to the aggregate then I guess there is no possible
logic to put in for inverse transitions that will make them behave the same as
an undefined behaviour.

Actually, if sum(float) was really undefined, it'd be *very* easy to provide an
inverse transition function - just make it a no-op. Heck, you could then even
make the forward transition function a no-op, since the very definition of
"undefined behaviour" is "result can be anything, including setting your house
on fire". The point is, it's *not* undefined - it's just imprecise. And the
imprecision can even be quantified, it just depends on the number of
input rows (the equal-sign requirement is mostly there to make "imprecision"
equivalent to "relative error").

If it seems sound enough, then I may implement it in C to see how much
overhead it adds to forward aggregation for floating point types, but even
if it did add too much overhead to forward aggregation it might be worth
allowing aggregates to have 2 forward transition functions and if the 2nd
one exists then it could be used in windowing functions where the frame
does not have "unbounded following".

I don't think adding yet another type of aggregation function is the
solution here.

Why not?

I thought, if in the cases where we need to burden the forward transition
functions with extra code to make inverse transitions possible, then why
not have an extra transition function so that it does not slow down normal
aggregation?

My testing of sum(numeric) last night does not show any slow down by that
extra dscale tracking code that was added, but if it did then I'd be thinking
seriously about 2 forward transition functions to get around the problem.
I don't understand what would be wrong with that. The core code could just
make use of the 2nd function if it existed and inverse transitions were
thought to be required. i.e in a window context and does not
have UNBOUNDED PRECEDING.

First, every additional function increases the maintenance burden, and at
some point the expected benefit simply isn't worth it. IMHO at least.

Secondly, you'd really need a second aggregate definition - usually, the
non-invertible aggregate will get away with a much smaller state representation.
Aggregates with state type "internal" could hack their way around that by
simply not using the additional parts of their state structure, but e.g.
plpgsql aggregates would still incur overhead due to repeated copying of
a unnecessary large state value. If it's possible to represent the
forward-only but not the invertible state state with a copy-by-value type,
that overhead could easily be a large part of the total overhead you paid
for having an inverse transition function.

And lastly, we could achieve much of the benefit of a second transition
function by simply telling the forward transition function whether to
expect inverse transition function calls. We already expose things like
the aggregation memory context to C-language transition functions, so the
basic mechanism is already there. In fact, I pondered whether to do this -
but then figured I'd leave it to however needs that facility first. Though
it wouldn't be much code - basically, setting a flag in the WindowAggState,
and exporting a function to be used by aggregates to read it, similar
to what AggCheckCallContext does.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#130Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#127)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan23, 2014, at 01:07 , David Rowley <dgrowleyml@gmail.com> wrote:

On Tue, Jan 21, 2014 at 3:20 AM, Florian Pflug <fgp@phlo.org> wrote:

On Jan20, 2014, at 08:42 , David Rowley <dgrowleyml@gmail.com> wrote:

On Mon, Jan 20, 2014 at 2:45 PM, Florian Pflug <fgp@phlo.org> wrote:
* I've also renamed INVFUNC to INVSFUNC. That's a pretty invasive change, and
it's the last commit, so if you object to that, then you can merge up to
eafa72330f23f7c970019156fcc26b18dd55be27 instead of
de3d9148be9732c4870b76af96c309eaf1d613d7.

Seems like sfunc really should be tfunc then we could have invtfunc. I'd probably
understand this better if I knew what the 's' was for in sfunc. I've not applied
this just yet. Do you have a reason why you think it's better?

My issue with just "invfunc" is mainly that it's too generic - it doesn't tell
you what it's supposed to be the inverse of.

I've always assumed that 's' in 'sfunc' and 'stype' stands for 'state', and that
the naming is inspired by control theory, where the function which acts on the
state space is often called S.

Ok, that makes more sense now and it seems like a reasonable idea. I'm not not quite
sure yet as when someone said upthread that these "negative transition functions" as
I was calling them at the time should really be called "inverse transition functions",
I then posted that I was going to call the create aggregate option "invfunc" which
nobody seemed to object to. I just don't want to go and change that now. It is very
possible this will come up again when the committer is looking at the patch. It would
be a waste if it ended up back at invfunc after we changed it to invsfunc.

Since we already settled on "inverse transition function", I kinda doubt that
calling the parameter invsfunc is going to meet a lot of resistance. But we can put
that off a little longer still...

I've pushed a few additional things to https://github.com/fgp/postgres/tree/invtrans.

* I update the CREATE AGGREGATE documentation, trying to include the description of
the various modes of inverse transition functions into the paragraphs which already
explained about STRICT for transition functions and such.

* I've also updated the list of window functions to include a list of those
aggregates which potentially need to restart the computation, i.e. MIN/MAX and
the like.

* I've changed nodeWindowAgg.c to use per-aggregate aggregation contexts for the
invertible aggregates. Without that, the aggregate context is potentially never
reset, because that previously required *all* the aggregates to restart at the
same time. That would be OK if we were sure not to leak unbounded amounts of
stuff stores in that context, but unfortunately we sometimes do. For example,
whenever a strict, invertible aggregate ends up with only NULL inputs, we
re-initialize the aggregation, which leaks the old state value. We could
pfree() that of course, but that state value might reference other stuff that
we don't know about and thus cannot free. Separating the aggregation contexts
is the only solution I came up with, so I did that.

* I've also tweaked an if to flag aggregates as invertible only if the frame head
can actually move, i.e. if the frame start clause is something other than
UNBOUNDED PRECEEDING. Since the choice of whether to use a private aggregation
context is driven by that flag, that also makes the above apply only to aggregates
were the inverse transition function is actually used.

I hope to find some time tomorrow or so to complete my pass through the documentation -
what's still missing as an explanation of the EXPLAIN VERBOSE ANALYZE field and maybe
some cleanup of xaggr.sgml.

Do you have any additional things pending?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#131Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: David Rowley (#126)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 22 January 2014 09:54, David Rowley <dgrowleyml@gmail.com> wrote:

I've performed some more benchmarks on this patch tonight. The results and
full recreation scripts are attached along with the patch it was tested
against.

I noticed that the rate of changes to this patch has dropped off,
which I took as sign that it is ready for review, so I started doing
that.

My first thought was wow, this is a big patch!

I think it should probably be broken up. It might be overly ambitious
to try to get all of this committed during this commitfest, and in any
case, I suspect that the committer would probably choose to commit it
in stages. Perhaps something like:

Patch 1
- Basic support for inverse transition functions, CREATE AGGREGATE
support and doc updates. This should include test cases to validate
that the underlying executor changes are correct, by defining custom
aggregates such as sum(int) and array_agg() using inverse transition
functions.

Patch 2
- Add built-in inverse transition functions for count, sum(int), and friends.

Patch 3, 4...
- Other related groups of built-in aggregates. By this point, it
should be a fairly mechanical process.

Splitting it up this way now should help to focus on getting patch 1
correct, without being distracted by all the other aggregates that may
or may not usefully be made to have inverse transition functions. I
think the value of the feature has been proved, and it is good to see
that it can be applied to so many aggregates, but let's not try to do
it all at once.

Regarding what would be in patch 1, I've only given it a cursory look,
but I did immediately notice a problem with the nodeWindowAgg.c
changes --- for aggregates with non-strict transition functions,
something equivalent to the not null counting code is needed to make
it work correctly with FILTER. For example, the following query gives
the wrong results with this patch:

SELECT array_agg(i) FILTER (WHERE i%3=0)
OVER (ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
FROM generate_series(1,10) g(i);

Perhaps it should just always count the non-skipped values added after
removal of filtered and null values. Then you might be able to
simplify things by getting rid of ignore_nulls from
WindowStatePerAggData and replacing notnullcount with a more general
valueCount to track the number of values actually added to transValue.

I haven't read through nodeWindowAgg.c in any detail yet (I think it
will take me a while to fully get my head round that code) but I think
it needs some more test cases to verify that the various corner cases
are covered.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#132Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#131)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan24, 2014, at 08:47 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

I think it should probably be broken up. It might be overly ambitious
to try to get all of this committed during this commitfest, and in any
case, I suspect that the committer would probably choose to commit it
in stages. Perhaps something like:

Patch 1
- Basic support for inverse transition functions, CREATE AGGREGATE
support and doc updates. This should include test cases to validate
that the underlying executor changes are correct, by defining custom
aggregates such as sum(int) and array_agg() using inverse transition
functions.

Patch 2
- Add built-in inverse transition functions for count, sum(int), and friends.

Patch 3, 4...
- Other related groups of built-in aggregates. By this point, it
should be a fairly mechanical process.

Splitting it up this way now should help to focus on getting patch 1
correct, without being distracted by all the other aggregates that may
or may not usefully be made to have inverse transition functions. I
think the value of the feature has been proved, and it is good to see
that it can be applied to so many aggregates, but let's not try to do
it all at once.

Working on that now, will post individual patches later today.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#133Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#131)
5 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan24, 2014, at 08:47 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

My first thought was wow, this is a big patch!

I think it should probably be broken up. It might be overly ambitious
to try to get all of this committed during this commitfest, and in any
case, I suspect that the committer would probably choose to commit it
in stages. Perhaps something like:

<snipped>

Splitting it up this way now should help to focus on getting patch 1
correct, without being distracted by all the other aggregates that may
or may not usefully be made to have inverse transition functions. I
think the value of the feature has been proved, and it is good to see
that it can be applied to so many aggregates, but let's not try to do
it all at once.

I've now split this up into

invtrans_base: Basic machinery plus tests with SQL-language aggregates
invtrans_arith: COUNT(),SUM(),AVG(),STDDEV() and the like
invtrans_minmax: MIN(),MAX(),BOOL_AND(),BOOL_OR()
invtrans_collecting: ARRAY_AGG(), STRING_AGG()
invtrans_docs: Documentation

Each of these corresponds to one of the invtrans_* branches in my
github repo at https://github.com/fgp/postgres

Attached are the latest versions of these patches.

Regarding what would be in patch 1, I've only given it a cursory look,
but I did immediately notice a problem with the nodeWindowAgg.c
changes --- for aggregates with non-strict transition functions,
something equivalent to the not null counting code is needed to make
it work correctly with FILTER. For example, the following query gives
the wrong results with this patch:

SELECT array_agg(i) FILTER (WHERE i%3=0)
OVER (ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
FROM generate_series(1,10) g(i);

Perhaps it should just always count the non-skipped values added after
removal of filtered and null values. Then you might be able to
simplify things by getting rid of ignore_nulls from
WindowStatePerAggData and replacing notnullcount with a more general
valueCount to track the number of values actually added to transValue.

Ugh, very true. Though I don't see how to get rid of ignore_nulls, unless
we rewrite the filter clause to

(filter_clause) AND arg1 IS NOT NULL AND ... argN IS NOT NULL

for strict aggregates, which would probably be much slower and wouldn't
be right if any of the args call volatile functions.

But I've generalized notnullcount to transValueCount as you suggested,
and got rid of noTransValue, since that's now equivalent to
transValueCount == 0 && transValueIsNull.

string_agg() was also severely broken - it needs to track the number of
non-NULL strings within the state, since it skips rows with a NULL
string, but not with a NULL delimiter, which means we can't mark it
strict to make nodeWindowAgg.c take care of that.

I haven't read through nodeWindowAgg.c in any detail yet (I think it
will take me a while to fully get my head round that code) but I think
it needs some more test cases to verify that the various corner cases
are covered.

Yeah, I hadn't really gotten around to doing that yet :-(

The base patch now contains tests of the various strict and non-strict
cases, both in the filtered and non-filtered cases.

The invtrans_arith patch contains David's original tests, minus the one
with the logging inverse transition function, since that's tested by the
base patch already. It doesn't test sum(interval) though, I think (though
maybe I missed it, it's quite late already...). I think it maybe should -
not sure if there are any gotchas with SUM(interval) and inverse
transition functions.

The invtrans_collecting patch contains tests of the NULL behaviour of
string_agg() and array_agg() - I hope I've covered all the cases.

The invtrans_minmax patch doesn't contain any patches yet - David, could
you provide some for these functions, and also for bool_and and bool_or?
We don't need to test every datatype, but a few would be nice.

I've also run the regression tests of invtrans_arith and
invtrans_collecting against invtrans_base (i.e, without the actual
inverse transition functions) and they still succeed, meaning the
inverse transition functions don't change the results.

Doing the same against HEAD instead of invtrans_base yields a few
diffs, but all of them look OK - the stuff the explicitly tests
inverse transition functions via those "logging" aggregations that
simply log all calls to both the forward and inverse transition
function in a long string obviously yield different results if
inverse transition functions aren't used.

best regards,
Florian Pflug

Attachments:

invtrans_docs_0cb944.patchapplication/octet-stream; name=invtrans_docs_0cb944.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2230c93..44d547f 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 376,381 ****
--- 376,387 ----
        <entry>Transition function</entry>
       </row>
       <row>
+       <entry><structfield>agginvtransfn</structfield></entry>
+       <entry><type>regproc</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>Inverse transition function</entry>
+      </row>
+      <row>
        <entry><structfield>aggfinalfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c0a75de..51c493d 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT xmlagg(x) FROM (SELECT x FROM tes
*** 12885,12890 ****
--- 12885,13012 ----
     <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
     Other frame specifications can be used to obtain other effects.
    </para>
+   
+   <para>
+     Depending on the aggregate function, aggregating over frames starting
+     at a row relative to the current row can be drastically less efficient
+     than aggregating over frames aligned to the start of the partition. The
+     frame starts at a row relative to the current row if <literal>ORDER
+     BY</literal> is used together with any frame start clause other than
+     <literal>UNBOUNDED PRECEDING</literal> (which is the default). Then,
+     aggregates without a suitable <quote>inverse transition function
+     </quote> (see <xref linkend="SQL-CREATEAGGREGATE"> for details) will be
+     computed for each frame from scratch, instead of re-using the previous
+     frame's result, causing <emphasis>quadratic growth</emphasis> of the
+     execution time as the number of rows per partition increases. The table
+     <xref linkend="functions-aggregate-indframe"> list the built-in aggregate
+     functions affected by this. Note that quadratic growth is only a problem
+     if partitions contain many rows - for partitions with only a few rows,
+     even inefficient aggregates are unlikely to cause problems.
+   </para>
+ 
+   <table id="functions-aggregate-indframe">
+    <title>
+      Aggregate Function Behaviour for frames not starting at
+      <literal>UNBOUNDED PRECEDING</literal>.
+    </title>
+ 
+    <tgroup cols="3">
+      
+     <thead>
+      <row>
+       <entry>Aggregate Function</entry>
+       <entry>Input Type</entry>
+       <entry>Computed From Scratch</entry>
+      </row>
+     </thead>
+     
+     <tbody>
+       
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>float4</type>
+        or
+        <type>float8</type>
+       </entry>
+       <entry>always, to avoid error accumulation</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>numeric</type>
+       </entry>
+       <entry>if the maximum number of decimal digits within the inputs changes</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>min</function>
+       </entry>
+       <entry>
+        any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were minimal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>max</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were maximal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>bit_and</function>,<function>bit_or</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>string_agg</function>
+       </entry>
+       <entry>
+        <type>text</type> or
+        <type>bytea</type> or
+       </entry>
+       <entry>if the delimiter lengths vary</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>xmlagg</function>
+       </entry>
+       <entry>
+        <type>xml</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>json_agg</function>
+       </entry>
+       <entry>
+        <type>json</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
  
    <note>
     <para>
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index d15fcba..aaa09a7 100644
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
*************** CREATE AGGREGATE <replaceable class="par
*** 25,30 ****
--- 25,31 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 47,52 ****
--- 48,54 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 84,98 ****
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
!    a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
--- 86,103 ----
    </para>
  
    <para>
!    An aggregate function is made from one, two or three ordinary
     functions:
!    a (forward) state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
+    an optional inverse state transition function
+    <replaceable class="PARAMETER">invfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
+ <replaceable class="PARAMETER">invfunc</replaceable>( internal-state, data-values ) ---> internal-state-without-data-values
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 102,113 ****
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.  After all the rows have been processed,
!    the final function is invoked once to calculate the aggregate's return
!    value.  If there is no final function then the ending state value
!    is returned as-is.
    </para>
  
    <para>
--- 107,134 ----
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the forward state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.
!    If the aggregate is computed over a sliding frame, i.e. if it is used as a
!    <firstterm>window function</firstterm>, the inverse transition function is
!    used to undo the effect of a previous invocation of the forward transition
!    function once argument value(s) fall out of the sliding frame.
!    Conceptually, the forward transition functions thus adds some input
!    value(s) to the state, and the inverse transition functions removes them
!    again. Values are, if they are removed, always removed in the same order
!    they were added, without gaps. Whenever the inverse transition function is
!    invoked, it will thus receive the earliest added but not yet removed
!    argument value(s). If no inverse transition function is supplied, the
!    aggregate can still be used to aggregate over sliding frames, but with
!    reduced efficiency. <productname>PostgreSQL</productname> will then
!    recompute the whole aggregation whenever the start of the frame moves. To
!    calculate the aggregate's return value, the final function is invoked on
!    the ending state value. If there is no final function then ending state
!    value is returned as-is. Either way, the result is assumed to reflect the
!    aggregation of all values added but not yet removed from the state value.
!    Note that if the aggregate is used as a window function, the aggregation
!    may be continued after the final function has been called.
    </para>
  
    <para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 127,135 ****
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values.
!    This is handy for implementing aggregates like <function>max</function>.
!    Note that this behavior is only available when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
--- 148,164 ----
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values. Should inputs later need to be removed again, the
!    inverse transition function (if present) is used as long as some non-null
!    inputs remain part of the state value. In particular, even if the initial
!    state value is null, the inverse transition function might be used to remove
!    the first non-null input, even though that input was never passed to the
!    forward transition function, but instead just replaced the initial state!
!    The last non-null input, however, is not removed by invoking the inverse
!    transition function, but instead the state is simply reset to its initial
!    value. This is handy for implementing aggregates like <function>max</function>.
!    Note that turning the first non-null input into the initial state is only
!    possible when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
*************** CREATE AGGREGATE <replaceable class="PAR
*** 138,147 ****
    </para>
  
    <para>
!    If the state transition function is not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs
!    and null state values for itself.  This allows the aggregate
!    author to have full control over the aggregate's handling of null values.
    </para>
  
    <para>
--- 167,206 ----
    </para>
  
    <para>
!     If the forward transition function is not declared <quote>strict</quote>,
!     but the inverse transition function is, null input values are still not
!     passed to either the forward or the inverse transition function. The
!     system then behaves as it would for a strict forward transition function,
!     except that initial value null isn't handled specially, i.e. isn't
!     replaced by the first argument of the first non-null input. Instead the
!     forward transition function is simply invoked with a null state and
!     non-null argument values. So in effect, in this case the forward
!     transition function is being treated as non-strict in the state argument
!     but strict in all argument values.
!   </para> 
! 
!   <para>
!    If the state transition function is not <quote>strict</quote>, and either
!    no inverse was provided or is also not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs and null
!    state values for itself. The same goes for the inverse transition function.
!    This allows the aggregate author to have full control over the
!    aggregate's handling of null argument values.
!   </para>
!   
!   <para>
!     The inverse transition function can signal by returning null that it is
!     unable to remove a particular input value from a particular state.
!     <productname>PostgreSQL</productname> will then act as if no inverse
!     transition function had been supplied, i.e. it will recompute the whole
!     aggregation, starting with the first argument value that it would not have
!     removed. That allows aggregates like <function>max</function> to still
!     avoid redoing the whole aggregation in <emphasis>some</emphasis> cases,
!     without paying the overhead of tracking enough state to be able to avoid
!     them in <emphasis>all</emphasis> cases. This demands, however, that
!     null isn't used as a valid state value, except as the initial state. If
!     an aggregate provides an inverse transition function, it is therefore an
!     error for the forward transition function to return null.
    </para>
  
    <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 271,277 ****
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
--- 330,336 ----
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the (forward) state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
*************** SELECT col FROM tab ORDER BY col USING s
*** 281,287 ****
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value.
       </para>
  
       <para>
--- 340,348 ----
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value. Note that if an inverse
!       transition function is present, the forward transition function must
!       not return <literal>NULL</>
       </para>
  
       <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 294,299 ****
--- 355,384 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="PARAMETER">invfunc</replaceable></term>
+     <listitem>
+      <para>
+       The name of the inverse state transition function to be called for each
+       input row.  For a normal <replaceable class="PARAMETER">N</>-argument
+       aggregate function, the <replaceable class="PARAMETER">sfunc</>
+       must take <replaceable class="PARAMETER">N</>+1 arguments,
+       the first being of type <replaceable
+       class="PARAMETER">state_data_type</replaceable> and the rest
+       matching the declared input data type(s) of the aggregate.
+       The function must return a value of type <replaceable
+       class="PARAMETER">state_data_type</replaceable>. These are the same
+       demands placed on the forward transition function, meaning that the
+       signatures of the two functions must be identical. Their
+       <quote>strictness</quote> though may differ, but the only allowed
+       combination is a non-strict forward combined with a strict inverse
+       transition function. The inverse transition function may return
+       <literal>NULL</> to force the aggregation to be restarted from
+       scratch.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="PARAMETER">state_data_type</replaceable></term>
      <listitem>
       <para>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index e77ef12..397e398 100644
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 16,22 ****
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
--- 16,22 ----
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a forward state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
***************
*** 24,30 ****
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
--- 24,42 ----
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result. 
!    To enable efficient evaluation of an aggregate used as a window function
!    with a sliding frame (i.e. a frame that starts relative to the current row),
!    an aggregate can optionally provide an inverse state transition function.
!    The inverse transition function takes the the current state and the
!    aggregate's input value(s) for the <emphasis>earliest</emphasis> row passed
!    to the forward transition function, and returns a state equivalent to what
!    the current state had been had the forward transition function never been
!    invoked for that earliest row, only for all rows that followed it. Thus,
!    if an inverse transition function is provided, the rows that were part of
!    the previous row's frame but not of the current row's frame can simply be
!    removed from the state instead of having to redo the whole aggregation
!    over the new frame.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
*************** CREATE AGGREGATE avg (float8)
*** 132,137 ****
--- 144,188 ----
    </note>
  
    <para>
+    When providing an inverse transition function, care should be taken to
+    ensure that it doesn't introduce unexpected user-visible differences
+    between results obtained by reaggregating all inputs vs. using the inverse
+    transition function. An example for an aggregate where adding an inverse
+    transition function seems easy at first, yet were doing so would violate
+    this requirement is <function>sum</> over <type>float</> or
+    <type>double precision</>. A naive declaration of
+    <function>sum(<type>float</>)</function> could be
+    
+    <programlisting>
+    CREATE AGGREGATE unsafe_sum (float8)
+    (
+        stype = float8,
+        sfunc = float8pl,
+        invfunc = float8mi
+    );
+    </programlisting>
+    
+    This aggregate, howevery, can give wildly different results than it would
+    have without the inverse transition function. For example, consider
+    
+    <programlisting>
+    SELECT
+      unsafe_sum(x) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND
+                                                  1 FOLLOWING)
+    FROM (VALUES
+      (1, 1.0e20::float8),
+      (2, 1.0::float8)
+    ) AS v (n,x)
+    </programlisting>
+    
+    which returns 0 as it's second result, yet the expected answer is 1. The
+    reason for this is the limited precision of floating point types - adding
+    1 to 1e20 actually leaves the value unchanged, and so substracting 1e20
+    again yields 0, not 1. Note that this is a limitation of floating point
+    types in general and not a limitation of <productname>PostgreSQL</>.
+   </para>
+   
+   <para>
     Aggregate functions can use polymorphic
     state transition functions or final functions, so that the same functions
     can be used to implement multiple aggregates.
invtrans_arith_3283b8.patchapplication/octet-stream; name=invtrans_arith_3283b8.patchDownload
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..e62f2a3 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8inc(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 717,767 ----
  	}
  }
  
+ Datum
+ int8dec(PG_FUNCTION_ARGS)
+ {
+ 	/*
+ 	 * When int8 is pass-by-reference, we provide this special case to avoid
+ 	 * palloc overhead for COUNT(): when called as an inverse transition
+ 	 * aggregate, we know that the argument is modifiable local storage,
+ 	 * so just update it in-place. (If int8 is pass-by-value, then of course
+ 	 * this is useless as well as incorrect, so just ifdef it out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *arg = (int64 *) PG_GETARG_POINTER(0);
+ 		int64		result;
+ 
+ 		result = *arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && *arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		*arg = result;
+ 		PG_RETURN_POINTER(arg);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		/* Not called as an aggregate, so just do it the dumb way */
+ 		int64		arg = PG_GETARG_INT64(0);
+ 		int64		result;
+ 
+ 		result = arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		PG_RETURN_INT64(result);
+ 	}
+ }
+ 
+ 
  /*
   * These functions are exactly like int8inc but are used for aggregates that
   * count only non-null values.	Since the functions are declared strict,
*************** int8inc_any(PG_FUNCTION_ARGS)
*** 733,738 ****
--- 778,789 ----
  }
  
  Datum
+ int8inc_any_inv(PG_FUNCTION_ARGS)
+ {
+ 	return int8dec(fcinfo);
+ }
+ 
+ Datum
  int8inc_float8_float8(PG_FUNCTION_ARGS)
  {
  	return int8inc(fcinfo);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..345ae47 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_float4(PG_FUNCTION_ARGS)
*** 2479,2486 ****
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	bool		isNaN;			/* true if any processed number was NaN */
  	MemoryContext agg_context;	/* context we're calculating in */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
--- 2479,2490 ----
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	int			maxScale;		/* stores the maximum scale seen so far. */
! 	int64		maxScaleCount;  /* tracks the number of values we've
! 								 * seen with the maximum scale */
! 
  	MemoryContext agg_context;	/* context we're calculating in */
+ 	int64		NaNcount;		/* Count of NaN values that are aggregated */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
*************** makeNumericAggState(FunctionCallInfo fci
*** 2505,2510 ****
--- 2509,2518 ----
  	state = (NumericAggState *) palloc0(sizeof(NumericAggState));
  	state->calcSumX2 = calcSumX2;
  	state->agg_context = agg_context;
+ 	state->NaNcount = 0;
+ 
+ 	state->maxScale = 0;
+ 	state->maxScaleCount = 0;
  
  	MemoryContextSwitchTo(old_context);
  
*************** do_numeric_accum(NumericAggState *state,
*** 2522,2536 ****
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (state->isNaN || NUMERIC_IS_NAN(newval))
  	{
! 		state->isNaN = true;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
--- 2530,2558 ----
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (NUMERIC_IS_NAN(newval))
  	{
! 		state->NaNcount++;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
+ 	/*
+ 	 * Track the highest scale that we've seen as if we ever perform an inverse
+ 	 * transition and remove the last numeric with the highest scale that we've
+ 	 * seen then we can no longer perform inverse transitions without risking
+ 	 * having the wrong dscale in the result value.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 		state->maxScaleCount++;
+ 	else if (X.dscale > state->maxScale)
+ 	{
+ 		state->maxScale = X.dscale;
+ 		state->maxScaleCount = 1;
+ 	}
+ 
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2562,2567 ****
--- 2584,2676 ----
  }
  
  /*
+  * do_numeric_discard
+  * Attempts to remove a value from the aggregated state.
+  * If the value cannot be removed then the function will return false, the
+  * possible reasons for failing are described below.
+  *
+  * If we aggregate the values 1.01 and 2 then the result will be 3.01. If we
+  * are then asked to un-aggregate the 1.01 then we must reject this case as we
+  * won't be able to tell what the new aggregated value's dscale should be.
+  * We can't return 2.00 (dscale = 2) as we really should return just 2, but
+  * since we're not tracking any previous highest scales then we must just fail
+  * to perform the inverse transition and just return false.
+  *
+  * Values that are no longer aggregated should not be able to effect the dscale
+  * of the result of the values that *are* still aggregated.
+  *
+  * Note it may be better to track the number of times we've aggregated a
+  * numeric with each scale, then if we ever remove final highest scaled value
+  * then we can step the result's dscale down to the next highest value. This is
+  * perhaps slightly more work than we can afford to do here, but doing it this
+  * way would mean that we could always perform the inverse transition.
+  */
+ static bool
+ do_numeric_discard(NumericAggState *state, Numeric newval)
+ {
+ 	NumericVar	X;
+ 	NumericVar	X2;
+ 	MemoryContext old_context;
+ 
+ 	/* result is NaN if any processed number is NaN */
+ 	if (NUMERIC_IS_NAN(newval))
+ 	{
+ 		state->NaNcount--;
+ 		return true;
+ 	}
+ 
+ 	/* load processed number in short-lived context */
+ 	init_var_from_num(newval, &X);
+ 
+ 	/*
+ 	 * If we're asked to remove a numeric value that has a dscale the same as
+ 	 * the highest dscale that we've encountered so far, then we need to
+ 	 * update the count of the number of times that we've seen that dscale.
+ 	 * Once we get to the last value that has this maximum dscale we must
+ 	 * report that we've failed to perform the inverse transition.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 	{
+ 		/*
+ 		 * are we on the last value with maxScale?
+ 		 * if so we can't do any more inverse transitions.
+ 		 */
+ 		if (state->maxScaleCount == 1)
+ 			return false;
+ 
+ 		state->maxScaleCount--;
+ 	}
+ 
+ 	/* if we need X^2, calculate that in short-lived context */
+ 	if (state->calcSumX2)
+ 	{
+ 		init_var(&X2);
+ 		mul_var(&X, &X, &X2, X.dscale * 2);
+ 	}
+ 
+ 	/* The rest of this needs to work in the aggregate context */
+ 	old_context = MemoryContextSwitchTo(state->agg_context);
+ 
+ 	if (state->N-- > 0)
+ 	{
+ 		/* Subtract X from state */
+ 		sub_var(&(state->sumX), &X, &(state->sumX));
+ 
+ 		if (state->calcSumX2)
+ 			sub_var(&(state->sumX2), &X2, &(state->sumX2));
+ 	}
+ 	else
+ 	{
+ 		MemoryContextSwitchTo(old_context);
+ 		elog(ERROR, "cannot discard more values than were accumulated");
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 	return true;
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that require sumX2.
   */
  Datum
*************** numeric_accum(PG_FUNCTION_ARGS)
*** 2584,2589 ****
--- 2693,2716 ----
  }
  
  /*
+  * numeric_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ numeric_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* can we perform an inverse transition? if not return NULL. */
+ 	if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
  Datum
*************** numeric_avg_accum(PG_FUNCTION_ARGS)
*** 2606,2611 ****
--- 2733,2755 ----
  }
  
  /*
+  * numeric_avg_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict
+  */
+ Datum
+ numeric_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* can we perform an inverse transition? if not return NULL. */
+ 	if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
   * Integer data types all use Numeric accumulators to share code and
   * avoid risk of overflow.	For int2 and int4 inputs, Numeric accumulation
   * is overkill for the N and sum(X) values, but definitely not overkill
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2686,2691 ****
--- 2830,2908 ----
  	PG_RETURN_POINTER(state);
  }
  
+ 
+ /*
+  * int2_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int2_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 	Numeric		newval;
+ 
+ 	newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
+ 							 PG_GETARG_DATUM(1)));
+ 
+ 	/*
+ 	 * do_numeric_discard should never fail with numerics converted
+ 	 * from int types as the dscale should always be 0.
+ 	 */
+ 	if (!do_numeric_discard(state, newval))
+ 		elog(ERROR, "Unable to perform inverse transition on int type");
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * int4_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int4_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 	Numeric		newval;
+ 
+ 	newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
+ 							 PG_GETARG_DATUM(1)));
+ 
+ 	/*
+ 	 * do_numeric_discard should never fail with numerics converted
+ 	 * from int types as the dscale should always be 0.
+ 	 */
+ 	if (!do_numeric_discard(state, newval))
+ 		elog(ERROR, "Unable to perform inverse transition on int type");
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * int8_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int8_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 	Numeric		newval;
+ 
+ 	newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+ 							 PG_GETARG_DATUM(1)));
+ 
+ 	/*
+ 	 * do_numeric_discard should never fail with numerics converted
+ 	 * from int types as the dscale should always be 0.
+ 	 */
+ 	if (!do_numeric_discard(state, newval))
+ 		elog(ERROR, "Unable to perform inverse transition on int type");
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  /*
   * Transition function for int8 input when we don't need sumX2.
   */
*************** int8_avg_accum(PG_FUNCTION_ARGS)
*** 2713,2718 ****
--- 2930,2958 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * int8_avg_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int8_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 	Numeric		newval;
+ 
+ 	newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+ 		PG_GETARG_DATUM(1)));
+ 
+ 	/*
+ 	 * do_numeric_discard should never fail with numerics converted
+ 	 * from int types as the dscale should always be 0.
+ 	 */
+ 	if (!do_numeric_discard(state, newval))
+ 		elog(ERROR, "Unable to perform inverse transition on int type");
+ 
+ 	PG_RETURN_POINTER(state);
+ }
  
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
*************** numeric_avg(PG_FUNCTION_ARGS)
*** 2725,2731 ****
  	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
--- 2965,2971 ----
  	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
*************** numeric_sum(PG_FUNCTION_ARGS)
*** 2743,2749 ****
  	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
--- 2983,2989 ----
  	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
*************** numeric_stddev_internal(NumericAggState 
*** 2782,2788 ****
  
  	*is_null = false;
  
! 	if (state->isNaN)
  		return make_result(&const_nan);
  
  	init_var(&vN);
--- 3022,3028 ----
  
  	*is_null = false;
  
! 	if (state->NaNcount > 0)
  		return make_result(&const_nan);
  
  	init_var(&vN);
*************** int2_sum(PG_FUNCTION_ARGS)
*** 2978,2983 ****
--- 3218,3261 ----
  	}
  }
  
+ /*
+  * int2_sum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int2_sum_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		newval;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to avoid palloc overhead. If not, we need to return
+ 	 * the new value of the transition variable. (If int8 is pass-by-value,
+ 	 * then of course this is useless as well as incorrect, so just ifdef it
+ 	 * out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *oldsum = (int64 *) PG_GETARG_POINTER(0);
+ 
+ 		*oldsum = *oldsum - (int64) PG_GETARG_INT16(1);
+ 
+ 		PG_RETURN_POINTER(oldsum);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		int64		oldsum = PG_GETARG_INT64(0);
+ 
+ 		/* OK to do the subtraction. */
+ 		newval = oldsum - (int64) PG_GETARG_INT16(1);
+ 
+ 		PG_RETURN_INT64(newval);
+ 	}
+ }
+ 
  Datum
  int4_sum(PG_FUNCTION_ARGS)
  {
*************** int4_sum(PG_FUNCTION_ARGS)
*** 3028,3033 ****
--- 3306,3350 ----
  }
  
  /*
+  * int4_sum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int4_sum_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		newval;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to avoid palloc overhead. If not, we need to return
+ 	 * the new value of the transition variable. (If int8 is pass-by-value,
+ 	 * then of course this is useless as well as incorrect, so just ifdef it
+ 	 * out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *oldsum = (int64 *) PG_GETARG_POINTER(0);
+ 
+ 		*oldsum = *oldsum - (int64) PG_GETARG_INT32(1);
+ 
+ 		PG_RETURN_POINTER(oldsum);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		int64		oldsum = PG_GETARG_INT64(0);
+ 
+ 		/* OK to do the subtraction. */
+ 		newval = oldsum - (int64) PG_GETARG_INT32(1);
+ 
+ 		PG_RETURN_INT64(newval);
+ 	}
+ }
+ 
+ 
+ /*
   * Note: this function is obsolete, it's no longer used for SUM(int8).
   */
  Datum
*************** int2_avg_accum(PG_FUNCTION_ARGS)
*** 3106,3111 ****
--- 3423,3457 ----
  }
  
  Datum
+ int2_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int16		newval = PG_GETARG_INT16(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ 
+ Datum
  int4_avg_accum(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray;
*************** int4_avg_accum(PG_FUNCTION_ARGS)
*** 3134,3139 ****
--- 3480,3513 ----
  }
  
  Datum
+ int4_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int32		newval = PG_GETARG_INT32(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ Datum
  int8_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 4581862..fd6af59 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** interval_accum(PG_FUNCTION_ARGS)
*** 3073,3078 ****
--- 3073,3123 ----
  }
  
  Datum
+ interval_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Interval   *newval = PG_GETARG_INTERVAL_P(1);
+ 	Datum	   *transdatums;
+ 	int			ndatums;
+ 	Interval	sumX,
+ 				N;
+ 	Interval   *newsum;
+ 	ArrayType  *result;
+ 
+ 	deconstruct_array(transarray,
+ 					  INTERVALOID, sizeof(Interval), false, 'd',
+ 					  &transdatums, NULL, &ndatums);
+ 	if (ndatums != 2)
+ 		elog(ERROR, "expected 2-element interval array");
+ 
+ 	/*
+ 	 * XXX memcpy, instead of just extracting a pointer, to work around buggy
+ 	 * array code: it won't ensure proper alignment of Interval objects on
+ 	 * machines where double requires 8-byte alignment. That should be fixed,
+ 	 * but in the meantime...
+ 	 *
+ 	 * Note: must use DatumGetPointer here, not DatumGetIntervalP, else some
+ 	 * compilers optimize into double-aligned load/store anyway.
+ 	 */
+ 	memcpy((void *) &sumX, DatumGetPointer(transdatums[0]), sizeof(Interval));
+ 	memcpy((void *) &N, DatumGetPointer(transdatums[1]), sizeof(Interval));
+ 
+ 	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
+ 												   IntervalPGetDatum(&sumX),
+ 												 IntervalPGetDatum(newval)));
+ 	N.time -= 1;
+ 
+ 	transdatums[0] = IntervalPGetDatum(newsum);
+ 	transdatums[1] = IntervalPGetDatum(&N);
+ 
+ 	result = construct_array(transdatums, 2,
+ 							 INTERVALOID, sizeof(Interval), false, 'd');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ 
+ Datum
  interval_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 2fb3871..0e6bf89 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 103,125 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
--- 103,125 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		int8_avg_accum_inv		numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		int4_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		int2_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg_accum_inv	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum		interval_accum_inv		interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		int8_avg_accum_inv		numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum			int4_sum_inv			-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum			int2_sum_inv			-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl			-						-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl			-						-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl				cash_mi					-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl			interval_mi				-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_avg_accum_inv	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
*************** DATA(insert ( 2798	n 0 tidsmaller		-	-		
*** 166,221 ****
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
--- 166,221 ----
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		int8inc_any_inv	-		0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			int8dec			-		0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		int8_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		int4_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		int2_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_accum_inv	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		int8_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		int4_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		int2_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ad9774c..6ff5baa 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("truncate interval to specified un
*** 1309,1316 ****
--- 1309,1320 ----
  
  DATA(insert OID = 1219 (  int8inc		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8inc _null_ _null_ _null_ ));
  DESCR("increment");
+ DATA(insert OID = 3475 (  int8dec		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8dec _null_ _null_ _null_ ));
+ DESCR("decrement");
  DATA(insert OID = 2804 (  int8inc_any	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any _null_ _null_ _null_ ));
  DESCR("increment, ignores second argument");
+ DATA(insert OID = 3222 (  int8inc_any_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any_inv _null_ _null_ _null_ ));
+ DESCR("decrement, ignores second argument");
  DATA(insert OID = 1230 (  int8abs		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8abs _null_ _null_ _null_ ));
  
  DATA(insert OID = 1236 (  int8larger	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger _null_ _null_ _null_ ));
*************** DATA(insert OID = 1832 (  float8_stddev_
*** 2396,2411 ****
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1835 (  int4_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1836 (  int8_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
! DATA(insert OID = 2746 (  int8_avg_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
--- 2400,2427 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3233 (  numeric_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3234 (  numeric_avg_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1835 (  int4_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1836 (  int8_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
! DATA(insert OID = 2746 (  int8_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3235 (  int2_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3236 (  int4_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3237 (  int8_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3238 (  int8_avg_accum_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
*************** DATA(insert OID = 2596 (  numeric_stddev
*** 2418,2423 ****
--- 2434,2443 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1839 (  numeric_stddev_samp	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_stddev_samp _null_ _null_ _null_ ));
  DESCR("aggregate final function");
+ DATA(insert OID = 3239 (  int4_sum_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 23" _null_ _null_ _null_ _null_ int4_sum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3240 (  int2_sum_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 21" _null_ _null_ _null_ _null_ int2_sum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1840 (  int2_sum		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 20 "20 21" _null_ _null_ _null_ _null_ int2_sum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1841 (  int4_sum		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 20 "20 23" _null_ _null_ _null_ _null_ int4_sum _null_ _null_ _null_ ));
*************** DATA(insert OID = 1842 (  int8_sum		   P
*** 2426,2437 ****
--- 2446,2463 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 1843 (  interval_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3241 (  interval_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1844 (  interval_avg	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1186 "1187" _null_ _null_ _null_ _null_ interval_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1962 (  int2_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1963 (  int4_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3242 (  int2_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 3243 (  int4_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 1964 (  int8_avg		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1016" _null_ _null_ _null_ _null_ int8_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2805 (  int8inc_float8_float8		PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 20 "20 701 701" _null_ _null_ _null_ _null_ int8inc_float8_float8 _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..e2b1879 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum numeric_float8_no_overflow(
*** 999,1009 ****
--- 999,1015 ----
  extern Datum float4_numeric(PG_FUNCTION_ARGS);
  extern Datum numeric_float4(PG_FUNCTION_ARGS);
  extern Datum numeric_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int2_accum(PG_FUNCTION_ARGS);
  extern Datum int4_accum(PG_FUNCTION_ARGS);
  extern Datum int8_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int4_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int8_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int8_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg(PG_FUNCTION_ARGS);
  extern Datum numeric_sum(PG_FUNCTION_ARGS);
  extern Datum numeric_var_pop(PG_FUNCTION_ARGS);
*************** extern Datum numeric_var_samp(PG_FUNCTIO
*** 1011,1020 ****
--- 1017,1030 ----
  extern Datum numeric_stddev_pop(PG_FUNCTION_ARGS);
  extern Datum numeric_stddev_samp(PG_FUNCTION_ARGS);
  extern Datum int2_sum(PG_FUNCTION_ARGS);
+ extern Datum int2_sum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_sum(PG_FUNCTION_ARGS);
+ extern Datum int4_sum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_sum(PG_FUNCTION_ARGS);
  extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int4_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg(PG_FUNCTION_ARGS);
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  extern Datum hash_numeric(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..5078e4a 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8div(PG_FUNCTION_ARGS);
*** 74,80 ****
--- 74,82 ----
  extern Datum int8abs(PG_FUNCTION_ARGS);
  extern Datum int8mod(PG_FUNCTION_ARGS);
  extern Datum int8inc(PG_FUNCTION_ARGS);
+ extern Datum int8dec(PG_FUNCTION_ARGS);
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
+ extern Datum int8inc_any_inv(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..e852fbd 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum interval_mul(PG_FUNCTION_AR
*** 179,184 ****
--- 179,185 ----
  extern Datum mul_d_interval(PG_FUNCTION_ARGS);
  extern Datum interval_div(PG_FUNCTION_ARGS);
  extern Datum interval_accum(PG_FUNCTION_ARGS);
+ extern Datum interval_accum_inv(PG_FUNCTION_ARGS);
  extern Datum interval_avg(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_mi(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index df676ad..78e304d 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1278,1283 ****
--- 1278,1766 ----
  -- Test the arithmetic inverse transition functions
  --
  --
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 2.0000000000000000
+  2 | 2.5000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |    avg     
+ ---+------------
+  1 | @ 1.5 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+  i |  sum  
+ ---+-------
+  1 | $3.30
+  2 | $2.20
+  3 |      
+  4 |      
+ (4 rows)
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |   sum    
+ ---+----------
+  1 | @ 3 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 | 3.3
+  2 | 2.2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+  sum  
+ ------
+  6.01
+     5
+     3
+ (3 rows)
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     2
+  2 |     1
+  3 |     0
+  4 |     0
+ (4 rows)
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     4
+  2 |     3
+  3 |     2
+  4 |     1
+ (4 rows)
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   1
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   6
+  3 |   9
+  4 |   7
+ (4 rows)
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+          to_char          
+ --------------------------
+   100000000000000000000
+                       1.0
+ (2 rows)
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+  a |  b  | sum 
+ ---+-----+-----
+  1 |   1 |   1
+  2 |   2 |   3
+  3 | NaN | NaN
+  4 |   3 | NaN
+  5 |   4 |   7
+ (5 rows)
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 65c1005..21a7e0c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 465,470 ****
--- 465,611 ----
  --
  --
  
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
invtrans_base_548630.patchapplication/octet-stream; name=invtrans_base_548630.patchDownload
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..178bf88 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 56,61 ****
--- 56,62 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
*************** AggregateCreate(const char *aggName,
*** 68,78 ****
--- 69,81 ----
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
  	Oid			transfn;
+ 	Oid			invtransfn = InvalidOid; /* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	bool		transIsStrict;
  	Oid			rettype;
  	Oid			finaltype;
  	Oid			fnArgs[FUNC_MAX_ARGS];
*************** AggregateCreate(const char *aggName,
*** 234,241 ****
--- 237,298 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
+ 
+ 	/*
+ 	 * Remember if trans function is strict as we need to validate this
+ 	 * later if when we're dealing with the inverse transition function
+ 	 */
+ 	transIsStrict = proc->proisstrict;
+ 
  	ReleaseSysCache(tup);
  
+ 	/* handle invtransfn, if supplied */
+ 	if (agginvtransfnName)
+ 	{
+ 		/*
+ 		 * This must have the same number of arguments with the same types as
+ 		 * the transition function. We can just borrow the argument details
+ 		 * from the transition function and try to find a function with
+ 		 * the name of the inverse transition function and with a signature
+ 		 * that matches the transition function's.
+ 		 */
+ 		invtransfn = lookup_agg_function(agginvtransfnName,
+ 					nargs_transfn, fnArgs, InvalidOid, &rettype);
+ 
+ 		/*
+ 		 * Ensure the return type of the inverse transition function matches
+ 		 * the expected type.
+ 		 */
+ 		if (rettype != aggTransType)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("return type of inverse transition function %s is not %s",
+ 							NameListToString(agginvtransfnName),
+ 							format_type_be(aggTransType))));
+ 
+ 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+ 		proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 		/*
+ 		 * Allowing only the forward transition function to be strict would
+ 		 * require handling more special cases in advance_windowaggregate() and
+ 		 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 		 * that if the forward transition function is strict that the inverse
+ 		 * transition function is also strict.
+ 		 */
+ 		if (transIsStrict && !proc->proisstrict)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 						errmsg("aggregate functions inverse transition function must strict if the forward transition function is"
+ 						)));
+ 		}
+ 		ReleaseSysCache(tup);
+ 
+ 	}
+ 
  	/* handle finalfn, if supplied */
  	if (aggfinalfnName)
  	{
*************** AggregateCreate(const char *aggName,
*** 391,396 ****
--- 448,454 ----
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+ 	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
*************** AggregateCreate(const char *aggName,
*** 425,430 ****
--- 483,497 ----
  	referenced.objectSubId = 0;
  	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
+ 	/* Depends on inverse transition function, if any */
+ 	if (OidIsValid(invtransfn))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = invtransfn;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
  	{
*************** AggregateCreate(const char *aggName,
*** 447,453 ****
  }
  
  /*
!  * lookup_agg_function -- common code for finding both transfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
--- 514,521 ----
  }
  
  /*
!  * lookup_agg_function
!  * common code for finding both transfn, invtransfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..0cfaa75 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
*************** DefineAggregate(List *name, List *args, 
*** 60,65 ****
--- 60,66 ----
  	AclResult	aclresult;
  	char		aggKind = AGGKIND_NORMAL;
  	List	   *transfuncName = NIL;
+ 	List	   *invtransfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
*************** DefineAggregate(List *name, List *args, 
*** 112,117 ****
--- 113,120 ----
  			transfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
  			transfuncName = defGetQualifiedName(defel);
+ 		else if (pg_strcasecmp(defel->defname, "invfunc") == 0)
+ 			invtransfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
  			finalfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
*************** DefineAggregate(List *name, List *args, 
*** 283,288 ****
--- 286,292 ----
  						   parameterDefaults,
  						   variadicArgType,
  						   transfuncName,		/* step function name */
+ 						   invtransfuncName,	/* inverse trans function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0dba928..e86caaf 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_group_keys(PlanSta
*** 85,90 ****
--- 85,91 ----
  					 List *ancestors, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
+ static void show_windowagg_info(PlanState *planstate, ExplainState *es);
  static void show_tidbitmap_info(BitmapHeapScanState *planstate,
  								ExplainState *es);
  static void show_instrumentation_count(const char *qlabel, int which,
*************** ExplainNode(PlanState *planstate, List *
*** 1402,1407 ****
--- 1403,1412 ----
  		case T_Hash:
  			show_hash_info((HashState *) planstate, es);
  			break;
+ 		case T_WindowAgg:
+ 			if (es->verbose && planstate->instrument)
+ 				show_windowagg_info(planstate, es);
+ 			break;
  		default:
  			break;
  	}
*************** show_hash_info(HashState *hashstate, Exp
*** 1900,1905 ****
--- 1905,1925 ----
  	}
  }
  
+ static void
+ show_windowagg_info(PlanState *planstate, ExplainState *es)
+ {
+ 	WindowAggState *winaggstate = (WindowAggState *) planstate;
+ 	Instrumentation *inst = planstate->instrument;
+ 
+ 	if (inst->nloops > 0 && inst->ntuples > 0 && winaggstate->numaggs > 0)
+ 	{
+ 		double tperrow = winaggstate->aggfwdtrans /
+ 			(inst->nloops * inst->ntuples);
+ 
+ 		ExplainPropertyFloat("Transitions Per Row", tperrow, 1, es);
+ 	}
+ }
+ 
  /*
   * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
   */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..04f2ebe 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1679,1684 ****
--- 1679,1685 ----
  		Oid			transfn_oid,
  					finalfn_oid;
  		Expr	   *transfnexpr,
+ 				   *invtransfnexpr, /* needed but never used */
  				   *finalfnexpr;
  		Datum		textInitVal;
  		int			i;
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1798,1805 ****
--- 1799,1808 ----
  								aggref->aggtype,
  								aggref->inputcollid,
  								transfn_oid,
+ 								InvalidOid, /* invtrans is not needed here */
  								finalfn_oid,
  								&transfnexpr,
+ 								&invtransfnexpr,
  								&finalfnexpr);
  
  		/* set up infrastructure for calling the transfn and finalfn */
*************** ExecReScanAgg(AggState *node)
*** 2127,2168 ****
  }
  
  /*
-  * AggCheckCallContext - test if a SQL function is being called as an aggregate
-  *
-  * The transition and/or final functions of an aggregate may want to verify
-  * that they are being called as aggregates, rather than as plain SQL
-  * functions.  They should use this function to do so.	The return value
-  * is nonzero if being called as an aggregate, or zero if not.	(Specific
-  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
-  * values could conceivably appear in future.)
-  *
-  * If aggcontext isn't NULL, the function also stores at *aggcontext the
-  * identity of the memory context that aggregate transition values are
-  * being stored in.
-  */
- int
- AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
- {
- 	if (fcinfo->context && IsA(fcinfo->context, AggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_AGGREGATE;
- 	}
- 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_WINDOW;
- 	}
- 
- 	/* this is just to prevent "uninitialized variable" warnings */
- 	if (aggcontext)
- 		*aggcontext = NULL;
- 	return 0;
- }
- 
- /*
   * AggGetAggref - allow an aggregate support function to get its Aggref
   *
   * If the function is being called as an aggregate support function,
--- 2130,2135 ----
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..c88c718 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** typedef struct WindowStatePerFuncData
*** 102,119 ****
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transfer functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transfer functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/*
  	 * initial value from pg_aggregate entry
  	 */
--- 102,126 ----
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transition functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
+ 	Oid			invtransfn_oid; /* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transition functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
+ 	FmgrInfo	invtransfn;
  	FmgrInfo	finalfn;
  
+ 	/* Aggregate properties */
+ 	bool		use_invtransfn;			/* whether to use the invtransfn */
+ 	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+ 	bool		ignore_nulls;			/* whether we skip NULL inputs */
+ 
  	/*
  	 * initial value from pg_aggregate entry
  	 */
*************** typedef struct WindowStatePerAggData
*** 140,149 ****
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	Datum		transValue;		/* current transition value */
! 	bool		transValueIsNull;
  
! 	bool		noTransValue;	/* true if transValue not set yet */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
--- 147,159 ----
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	MemoryContext	aggcontext;			/* context for transValue */
! 	int64			transValueCount;	/* Number of aggregates values*/
! 	Datum			transValue;			/* current transition value */
! 	bool			transValueIsNull;
  
! 	/* Data local to eval_windowaggregates() */
! 	bool			restart;			/* tmp marker that agg needs restart */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
*************** static void initialize_windowaggregate(W
*** 152,157 ****
--- 162,170 ----
  static void advance_windowaggregate(WindowAggState *winstate,
  						WindowStatePerFunc perfuncstate,
  						WindowStatePerAgg peraggstate);
+ static bool retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate);
  static void finalize_windowaggregate(WindowAggState *winstate,
  						 WindowStatePerFunc perfuncstate,
  						 WindowStatePerAgg peraggstate,
*************** static bool are_peers(WindowAggState *wi
*** 181,186 ****
--- 194,243 ----
  static bool window_gettupleslot(WindowObject winobj, int64 pos,
  					TupleTableSlot *slot);
  
+ /*
+  * AggCheckCallContext - test if a SQL function is being called as an aggregate
+  *
+  * The transition and/or final functions of an aggregate may want to verify
+  * that they are being called as aggregates, rather than as plain SQL
+  * functions.  They should use this function to do so.	The return value
+  * is nonzero if being called as an aggregate, or zero if not.	(Specific
+  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+  * values could conceivably appear in future.)
+  *
+  * If aggcontext isn't NULL, the function also stores at *aggcontext the
+  * identity of the memory context that aggregate transition values are
+  * being stored in.
+  *
+  * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+  * is private.
+  */
+ int
+ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggState))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ 		return AGG_CONTEXT_AGGREGATE;
+ 	}
+ 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ 	{
+ 		if (aggcontext)
+ 		{
+ 			/* Must lookup per-aggregate context */
+ 			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+ 			int				aggno = winstate->calledaggno;
+ 			Assert(0 <= aggno && aggno < winstate->numaggs);
+ 			*aggcontext = winstate->peragg[aggno].aggcontext;
+ 		}
+ 		return AGG_CONTEXT_WINDOW;
+ 	}
+ 
+ 	/* this is just to prevent "uninitialized variable" warnings */
+ 	if (aggcontext)
+ 		*aggcontext = NULL;
+ 	return 0;
+ }
  
  /*
   * initialize_windowaggregate
*************** initialize_windowaggregate(WindowAggStat
*** 193,210 ****
  {
  	MemoryContext oldContext;
  
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = peraggstate->initValue;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->noTransValue = peraggstate->initValueIsNull;
  	peraggstate->resultValueIsNull = true;
  }
  
--- 250,275 ----
  {
  	MemoryContext oldContext;
  
+ 	/* If we're using a private aggcontext, we may reset it here. But if the
+ 	 * context is shared, we don't know which other aggregates may still need
+ 	 * it, so we must leave it to the caller to reset at an appropriate time
+ 	 */
+ 	if (!peraggstate->aggcontext_is_shared)
+ 		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+ 
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = (Datum) 0;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
+ 	peraggstate->transValueCount = 0;
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->resultValue = (Datum) 0;
  	peraggstate->resultValueIsNull = true;
  }
  
*************** advance_windowaggregate(WindowAggState *
*** 254,265 ****
  		i++;
  	}
  
! 	if (peraggstate->transfn.fn_strict)
  	{
- 		/*
- 		 * For a strict transfn, nothing happens when there's a NULL input; we
- 		 * just keep the prior transValue.
- 		 */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
--- 319,327 ----
  		i++;
  	}
  
! 	/* Skip NULL inputs for aggregates which desire that. */
! 	if (peraggstate->ignore_nulls)
  	{
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
*************** advance_windowaggregate(WindowAggState *
*** 268,290 ****
  				return;
  			}
  		}
! 		if (peraggstate->noTransValue)
  		{
  			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
  			 * already checked that the agg's input type is binary-compatible
  			 * with its transtype, so straight copy here is OK.)
  			 *
  			 * We must copy the datum into aggcontext if it is pass-by-ref. We
  			 * do not need to pfree the old transValue, since it's NULL.
  			 */
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->noTransValue = false;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
--- 330,357 ----
  				return;
  			}
  		}
! 	}
! 	else
! 		Assert(!peraggstate->transfn.fn_strict);
! 
! 	if (peraggstate->transfn.fn_strict) {
! 		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
  		{
  			/*
! 			 * For strict transfer functions with initial value NULL we use
! 			 * the first non-NULL input as the initial state. (We
  			 * already checked that the agg's input type is binary-compatible
  			 * with its transtype, so straight copy here is OK.)
  			 *
  			 * We must copy the datum into aggcontext if it is pass-by-ref. We
  			 * do not need to pfree the old transValue, since it's NULL.
  			 */
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->transValueCount = 1;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
*************** advance_windowaggregate(WindowAggState *
*** 294,308 ****
  			 * Don't call a strict function with NULL inputs.  Note it is
  			 * possible to get here despite the above tests, if the transfn is
  			 * strict *and* returned a NULL on a prior cycle. If that happens
! 			 * we will propagate the NULL all the way to the end.
  			 */
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  	}
  
  	/*
  	 * OK to call the transition function
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
--- 361,389 ----
  			 * Don't call a strict function with NULL inputs.  Note it is
  			 * possible to get here despite the above tests, if the transfn is
  			 * strict *and* returned a NULL on a prior cycle. If that happens
! 			 * we will propagate the NULL all the way to the end. That can only
! 			 * happen if there's no inverse transition function, though, since
! 			 * we disallow transitions back to NULL if there is one below.
  			 */
  			MemoryContextSwitchTo(oldContext);
+ 			Assert(peraggstate->invtransfn_oid == InvalidOid);
  			return;
  		}
  	}
  
  	/*
+ 	 * We must track the number of inputs that we add to transValue, since
+ 	 * to remove the last input, retreat_windowaggregate() musn't call the
+ 	 * inverse transition function, but simply reset transValue back to its
+ 	 * initial value.
+ 	 */
+ 	Assert(peraggstate->transValueCount >= 0);
+ 	peraggstate->transValueCount++;
+ 
+ 	/*
  	 * OK to call the transition function
+ 	 * Transfer functions with an inverse MUST not return NULL, see
+ 	 * retreat_windowaggregate()
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
*************** advance_windowaggregate(WindowAggState *
*** 310,316 ****
--- 391,405 ----
  							 (void *) winstate, NULL);
  	fcinfo->arg[0] = peraggstate->transValue;
  	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
  	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("transition function with an inverse returned NULL")));
+ 	}
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
*************** advance_windowaggregate(WindowAggState *
*** 322,328 ****
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
--- 411,417 ----
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
*************** advance_windowaggregate(WindowAggState *
*** 337,342 ****
--- 426,574 ----
  }
  
  /*
+  * retreat_windowaggregate
+  * removes tuples from aggregation.
+  * The calling function must ensure that each aggregate has
+  * a valid inverse transition function.
+  */
+ static bool
+ retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate)
+ {
+ 	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
+ 	int			numArguments = perfuncstate->numArguments;
+ 	FunctionCallInfoData fcinfodata;
+ 	FunctionCallInfo fcinfo = &fcinfodata;
+ 	Datum		newVal;
+ 	ListCell   *arg;
+ 	int			i;
+ 	MemoryContext oldContext;
+ 	ExprContext *econtext = winstate->tmpcontext;
+ 	ExprState  *filter = wfuncstate->aggfilter;
+ 
+ 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ 
+ 	/* Skip anything FILTERed out */
+ 	if (filter)
+ 	{
+ 		bool		isnull;
+ 		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ 
+ 		if (isnull || !DatumGetBool(res))
+ 		{
+ 			MemoryContextSwitchTo(oldContext);
+ 			return true;
+ 		}
+ 	}
+ 
+ 	/* We start from 1, since the 0th arg will be the transition value */
+ 	i = 1;
+ 	foreach(arg, wfuncstate->args)
+ 	{
+ 		ExprState  *argstate = (ExprState *) lfirst(arg);
+ 
+ 		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
+ 									  &fcinfo->argnull[i], NULL);
+ 		i++;
+ 	}
+ 
+ 	/* Skip inputs containing NULLS for aggregates that require this */
+ 	if (peraggstate->ignore_nulls)
+ 	{
+ 		for (i = 1; i <= numArguments; i++)
+ 		{
+ 			if (fcinfo->argnull[i])
+ 			{
+ 				MemoryContextSwitchTo(oldContext);
+ 				return true;
+ 			}
+ 		}
+ 	}
+ 	else
+ 		Assert(!peraggstate->invtransfn.fn_strict);
+ 
+ 	/* There should still an added but not yet removed value */
+ 	Assert(peraggstate->transValueCount >= 1);
+ 
+ 	/*
+ 	 * We mustn't use the inverse transition function to remove the last
+ 	 * input. Doing so would yield a non-NULL state, whereas we should be
+ 	 * in the initial state afterwards which may very well be NULL. So
+ 	 * instead, we simply re-initialize the aggregation in this case.
+ 	 */
+ 	if (peraggstate->transValueCount == 1)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		initialize_windowaggregate(winstate,
+ 								&winstate->perfunc[peraggstate->wfuncno],
+ 								peraggstate);
+ 		return true;
+ 	}
+ 
+ 	/*
+ 	 * Perform the inverse transition.
+ 	 *
+ 	 * For pairs of forward and inverse transition functions, the state may
+ 	 * never be NULL, except in the ignore_nulls case, and then only until
+ 	 * until we see the first non-NULL input during which time should never
+ 	 * attempt to invoke the inverse transition function. Excluding NULL
+ 	 * as a possible state value allows us to make it mean "sorry, can't
+ 	 * do an inverse transition in this case" when returned by the inverse
+ 	 * transition function. In that case, we report the failure to the
+ 	 * caller.
+ 	 */
+ 	if (peraggstate->transValueIsNull)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		elog(ERROR, "transition value is NULL during inverse transition");
+ 	}
+ 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
+ 							 numArguments + 1,
+ 							 perfuncstate->winCollation,
+ 							 (void *) winstate, NULL);
+ 	fcinfo->arg[0] = peraggstate->transValue;
+ 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
+ 	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (fcinfo->isnull)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		return false;
+ 	}
+ 
+ 	/* Update number of added but not yet removed values */
+ 	peraggstate->transValueCount--;
+ 
+ 	/*
+ 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
+ 	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
+ 	 * first input, we don't need to do anything.
+ 	 */
+ 	if (!peraggstate->transtypeByVal &&
+ 		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
+ 	{
+ 		if (!fcinfo->isnull)
+ 		{
+ 			MemoryContextSwitchTo(peraggstate->aggcontext);
+ 			newVal = datumCopy(newVal,
+ 							   peraggstate->transtypeByVal,
+ 							   peraggstate->transtypeLen);
+ 		}
+ 		if (!peraggstate->transValueIsNull)
+ 			pfree(DatumGetPointer(peraggstate->transValue));
+ 	}
+ 
+ 	MemoryContextSwitchTo(oldContext);
+ 	peraggstate->transValue = newVal;
+ 	peraggstate->transValueIsNull = fcinfo->isnull;
+ 
+ 	return true;
+ }
+ 
+ 
+ /*
   * finalize_windowaggregate
   * parallel to finalize_aggregate in nodeAgg.c
   */
*************** finalize_windowaggregate(WindowAggState 
*** 370,376 ****
--- 602,610 ----
  		}
  		else
  		{
+ 			winstate->calledaggno = perfuncstate->aggno;
  			*result = FunctionCallInvoke(&fcinfo);
+ 			winstate->calledaggno = -1;
  			*isnull = fcinfo.isnull;
  		}
  	}
*************** finalize_windowaggregate(WindowAggState 
*** 392,397 ****
--- 626,632 ----
  	MemoryContextSwitchTo(oldContext);
  }
  
+ 
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
*************** eval_windowaggregates(WindowAggState *wi
*** 406,417 ****
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs;
! 	int			i;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
--- 641,658 ----
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs,
! 				numaggs_restart = 0,
! 				i;
! 	int64		pos,
! 				aggregatedupto_nonrestarted;
! 	bool		is_first = (winstate->currentpos == 0),
! 				ok;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
! 	TupleTableSlot *temp_slot = winstate->temp_slot_1;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
*************** eval_windowaggregates(WindowAggState *wi
*** 420,426 ****
  	/* final output execution is in ps_ExprContext */
  	econtext = winstate->ss.ps.ps_ExprContext;
  	agg_winobj = winstate->agg_winobj;
- 	agg_row_slot = winstate->agg_row_slot;
  
  	/*
  	 * Currently, we support only a subset of the SQL-standard window framing
--- 661,666 ----
*************** eval_windowaggregates(WindowAggState *wi
*** 438,446 ****
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * For other frame start rules, we discard the aggregate state and re-run
! 	 * the aggregates whenever the frame head row moves.  We can still
! 	 * optimize as above whenever successive rows share the same frame head.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
--- 678,697 ----
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * We can still optimize as above whenever successive rows share the same
! 	 * frame head, but if the frame head moves beyond the aggregated base point
! 	 * we use the aggregate function's inverse transition function. This
! 	 * removes the tuple from aggregation and restores the aggregate's current
! 	 * state to what it would be if the removed row had never been aggregated
! 	 * in the first place. Inverse transition functions may optionally return
! 	 * NULL, this indicates that the function was unable to remove the tuple
! 	 * from aggregation, when this happens we must perform the aggregation all
! 	 * over again for all tuples in the new frame boundary.
! 	 *
! 	 * If the aggregate function does not have a inverse transition function
! 	 * and the frame head moves beyond the aggregated position then we must
! 	 * discard the aggregated state and re-aggregate similar to how we would
! 	 * have to if the inverse transition function had returned NULL.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
*************** eval_windowaggregates(WindowAggState *wi
*** 452,529 ****
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
- 	 *
- 	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
- 	 * some aggregates like SUM and COUNT we could avoid that by implementing
- 	 * a "negative transition function" that would be called for each row as
- 	 * it exits the frame.	We'd have to think about avoiding recalculation of
- 	 * volatile arguments of aggregate functions, too.
  	 */
  
  	/*
  	 * First, update the frame head position.
  	 */
! 	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
  
  	/*
! 	 * Initialize aggregates on first call for partition, or if the frame head
! 	 * position moved since last time.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos != winstate->aggregatedbase)
  	{
- 		/*
- 		 * Discard transient aggregate values
- 		 */
- 		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
- 
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  
  		/*
! 		 * If we created a mark pointer for aggregates, keep it pushed up to
! 		 * frame head, so that tuplestore can discard unnecessary rows.
  		 */
! 		if (agg_winobj->markptr >= 0)
! 			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
  
  		/*
! 		 * Initialize for loop below
  		 */
! 		ExecClearTuple(agg_row_slot);
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 		winstate->aggregatedupto = winstate->frameheadpos;
  	}
  
  	/*
! 	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
! 	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
! 	 * have to recalculate when the frame head moves or currentpos has
! 	 * advanced past the place we'd aggregated up to.  Check for these cases
! 	 * and if so, reuse the saved result values.
  	 */
! 	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
! 		for (i = 0; i < numaggs; i++)
  		{
- 			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
- 		return;
  	}
  
  	/*
  	 * Advance until we reach a row not in frame (or end of partition).
  	 *
  	 * Note the loop invariant: agg_row_slot is either empty or holds the row
--- 703,880 ----
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
  	 */
  
  	/*
  	 * First, update the frame head position.
+ 	 *
+ 	 * The frame head should never move backwards, and the code below wouldn't
+ 	 * cope if it did, so for safety we complain if it does.
  	 */
! 	update_frameheadpos(agg_winobj, temp_slot);
! 	if (winstate->frameheadpos < winstate->aggregatedbase)
! 		elog(ERROR, "frame moved backwards unexpectedly");
  
  	/*
! 	 * If the frame didn't change compared to the previous row, we can re-use
! 	 * the cached result. Since we don't know the current frame's end yet, we
! 	 * cannot check that the obvious way. But we can exploit that if the frame
! 	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
! 	 * row lies within the previous row's frame, the two frame's ends must
! 	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
! 	 * so we don't need to check for that explicitly here.
  	 */
! 	if (winstate->aggregatedbase == winstate->frameheadpos &&
! 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
! 		}
! 		return;
! 	}
! 
! 	/* Initialize restart flags.
! 	 *
! 	 * We restart the aggregation
! 	 *   - if we're processing the first row in the partition, or
! 	 *   - if we the frame's head moved and we cannot use an inverse
! 	 *     transition function, or
! 	 *   - if the new frame doesn't overlap the old one
! 	 *
! 	 * Note that we don't strictly need to restart in the last case, but
! 	 * if we're going to remove *all* rows from the aggregation anyway, a
! 	 * restart surely is faster.
! 	 */
! 	for (i = 0; i < numaggs; i++)
! 	{
! 		peraggstate = &winstate->peragg[i];
! 		if (is_first ||
! 			(winstate->aggregatedbase < winstate->frameheadpos &&
! 			!peraggstate->use_invtransfn) ||
! 			(winstate->aggregatedupto <= winstate->frameheadpos))
! 		{
! 			peraggstate->restart = true;
! 			numaggs_restart++;
  		}
+ 		else
+ 			peraggstate->restart = false;
+ 	}
  
+ 	/*
+ 	 * Attempt to update aggregatedbase to match the frame's head by
+ 	 * removing those inputs from the aggregations which fell off the top
+ 	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+ 	 * return false, in which case we restart that aggregate below.
+ 	 *
+ 	 * We track the number of aggregates which require a restart, and
+ 	 * exit as soon as there are no more retreatable aggregates left.
+ 	 */
+ 	for(pos = winstate->aggregatedbase;
+ 		pos < winstate->frameheadpos && numaggs_restart < numaggs;
+ 		++pos)
+ 	{
  		/*
! 		 * Fetch the tuple where the current aggregation started from.
! 		 * This should never fail as we should have been here before.
  		 */
! 		if (!window_gettupleslot(agg_winobj, pos, temp_slot))
! 			elog(ERROR, "Unable to find tuple in tuplestore");
! 
! 		/* Set tuple context for evaluation of aggregate arguments */
! 		winstate->tmpcontext->ecxt_outertuple = temp_slot;
  
  		/*
! 		 * Perform the inverse transition for each aggregate function in
! 		 * the window, unless it has already been marked as needing a
! 		 * restart.
  		 */
! 		for (i = 0; i < numaggs; i++)
! 		{
! 			peraggstate = &winstate->peragg[i];
! 			if (peraggstate->restart)
! 				continue;
! 
! 			wfuncno = peraggstate->wfuncno;
! 			ok = retreat_windowaggregate(winstate,
! 										 &winstate->perfunc[wfuncno],
! 										 peraggstate);
! 			if (!ok)
! 			{
! 				/* Inverse transition function has failed, must restart */
! 				peraggstate->restart = true;
! 				numaggs_restart++;
! 			}
! 		}
! 
! 		/* Reset per-input-tuple context and tuple after each tuple */
! 		ResetExprContext(winstate->tmpcontext);
! 		ExecClearTuple(temp_slot);
  	}
  
  	/*
! 	 * Then restart the aggregates which require it.
! 	 *
! 	 * We assume that aggregates using the shared context always restart
! 	 * if *any* aggregate restarts, and we may thus cleanup the shared
! 	 * aggcontext if that is the case. The private aggcontexts are reset
! 	 * by initialize_windowaggregate() if their owning aggregate restarts,
! 	 * otherwise we just pfree() the cached result.
  	 */
! 	if (numaggs_restart > 0)
! 		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < numaggs; i++)
  	{
! 		peraggstate = &winstate->peragg[i];
! 		/* Aggregates using the shared ctx must restart if *any* agg does */
! 		Assert(!peraggstate->aggcontext_is_shared ||
! 			   !numaggs_restart || peraggstate->restart);
! 		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
! 			!peraggstate->resulttypeByVal)
! 		{
! 			pfree(DatumGetPointer(peraggstate->resultValue));
! 			peraggstate->resultValue = (Datum) 0;
! 			peraggstate->resultValueIsNull = true;
! 		}
! 		else if (peraggstate->restart)
  		{
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  	}
  
  	/*
+ 	 * After this, non-restarted aggregated contain the rows between
+ 	 * aggregatedbase and aggregatedupto_nonrestarted, and restarted aggregates
+ 	 * the rows between aggregatedbase and aggregatedupto, which is to say none,
+ 	 * and aggregatedbase matches frameheadpos. If we modify we must clear
+ 	 * agg_row_slot, see the loop invariant below.
+ 	 */
+ 	winstate->aggregatedbase = winstate->frameheadpos;
+ 	aggregatedupto_nonrestarted = winstate->aggregatedupto;
+ 	if (numaggs_restart > 0 &&
+ 		winstate->aggregatedupto != winstate->frameheadpos)
+ 	{
+ 		winstate->aggregatedupto = winstate->frameheadpos;
+ 		ExecClearTuple(agg_row_slot);
+ 	}
+ 
+ 	/*
+ 	 * If we created a mark pointer for aggregates, keep it pushed up to
+ 	 * frame head, so that tuplestore can discard unnecessary rows.
+ 	 */
+ 	if (agg_winobj->markptr >= 0)
+ 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+ 
+ 	/*
  	 * Advance until we reach a row not in frame (or end of partition).
  	 *
  	 * Note the loop invariant: agg_row_slot is either empty or holds the row
*************** eval_windowaggregates(WindowAggState *wi
*** 551,556 ****
--- 902,912 ----
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
+ 			/* Non-restarted aggregates skip until aggregatedupto_previous*/
+ 			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+ 				!peraggstate->restart)
+ 				continue;
+ 
  			wfuncno = peraggstate->wfuncno;
  			advance_windowaggregate(winstate,
  									&winstate->perfunc[wfuncno],
*************** eval_windowaggregates(WindowAggState *wi
*** 565,570 ****
--- 921,937 ----
  		ExecClearTuple(agg_row_slot);
  	}
  
+ 	/* The frame's end is not supposed to move backwards, ever */
+ 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+ 
+ 	/* Update statistics */
+ 	if (numaggs_restart > 0)
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - winstate->frameheadpos);
+ 	else
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - aggregatedupto_nonrestarted);
+ 
  	/*
  	 * finalize aggregates and fill result/isnull fields.
  	 */
*************** eval_windowaggregates(WindowAggState *wi
*** 589,616 ****
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal)
  		{
! 			/*
! 			 * clear old resultValue in order not to leak memory.  (Note: the
! 			 * new result can't possibly be the same datum as old resultValue,
! 			 * because we never passed it to the trans function.)
! 			 */
! 			if (!peraggstate->resultValueIsNull)
! 				pfree(DatumGetPointer(peraggstate->resultValue));
! 
! 			/*
! 			 * If pass-by-ref, copy it into our aggregate context.
! 			 */
! 			if (!*isnull)
! 			{
! 				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
! 				peraggstate->resultValue =
! 					datumCopy(*result,
! 							  peraggstate->resulttypeByVal,
! 							  peraggstate->resulttypeLen);
! 				MemoryContextSwitchTo(oldContext);
! 			}
  		}
  		else
  		{
--- 956,969 ----
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal && !*isnull)
  		{
! 			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
! 			peraggstate->resultValue =
! 				datumCopy(*result,
! 						  peraggstate->resulttypeByVal,
! 						  peraggstate->resulttypeLen);
! 			MemoryContextSwitchTo(oldContext);
  		}
  		else
  		{
*************** eval_windowfunction(WindowAggState *wins
*** 651,656 ****
--- 1004,1010 ----
  	/* Just in case, make all the regular argument slots be null */
  	memset(fcinfo.argnull, true, perfuncstate->numArguments);
  
+ 	winstate->calledaggno = -1;
  	*result = FunctionCallInvoke(&fcinfo);
  	*isnull = fcinfo.isnull;
  
*************** spool_tuples(WindowAggState *winstate, i
*** 794,800 ****
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kluge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
--- 1148,1154 ----
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kludge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
*************** release_partition(WindowAggState *winsta
*** 869,875 ****
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
--- 1223,1232 ----
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < winstate->numaggs; ++i)
! 		if (!winstate->peragg[i].aggcontext_is_shared)
! 			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1419,1425 ****
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno;
  	ListCell   *l;
  
  	/* check for unsupported flags */
--- 1776,1783 ----
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno,
! 				numaggs_invtrans;
  	ListCell   *l;
  
  	/* check for unsupported flags */
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1450,1457 ****
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived context for aggregate trans values etc */
! 	winstate->aggcontext =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
--- 1808,1817 ----
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived contexts for aggregate trans values etc
! 	 * Note that invertible aggregates use their own private context
! 	 */
! 	winstate->aggcontext_shared =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1535,1540 ****
--- 1895,1901 ----
  
  	wfuncno = -1;
  	aggno = -1;
+ 	numaggs_invtrans = 0;
  	foreach(l, winstate->funcs)
  	{
  		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1603,1608 ****
--- 1964,1971 ----
  			peraggstate = &winstate->peragg[aggno];
  			initialize_peragg(winstate, wfunc, peraggstate);
  			peraggstate->wfuncno = wfuncno;
+ 			if (peraggstate->use_invtransfn)
+ 				numaggs_invtrans++;
  		}
  		else
  		{
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1618,1623 ****
--- 1981,1987 ----
  	/* Update numfuncs, numaggs to match number of unique functions found */
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
+ 	winstate->numaggs_invtrans = numaggs_invtrans;
  
  	/* Set up WindowObject for aggregates, if needed */
  	if (winstate->numaggs > 0)
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1646,1651 ****
--- 2010,2021 ----
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	/* initialize temporary data */
+ 	winstate->calledaggno = -1;
+ 
+ 	/* initialize statistics */
+ 	winstate->aggfwdtrans = 0;
+ 
  	return winstate;
  }
  
*************** void
*** 1657,1668 ****
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
  
  	release_partition(node);
  
- 	pfree(node->perfunc);
- 	pfree(node->peragg);
- 
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
--- 2027,2036 ----
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
+ 	int			i;
  
  	release_partition(node);
  
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
*************** ExecEndWindowAgg(WindowAggState *node)
*** 1677,1683 ****
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
--- 2045,2057 ----
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext_shared);
! 	for(i = 0; i < node->numaggs; i++)
! 		if (!node->peragg[i].aggcontext_is_shared)
! 			MemoryContextDelete(node->peragg[i].aggcontext);
! 
! 	pfree(node->perfunc);
! 	pfree(node->peragg);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
*************** initialize_peragg(WindowAggState *winsta
*** 1735,1742 ****
--- 2109,2118 ----
  	Oid			aggtranstype;
  	AclResult	aclresult;
  	Oid			transfn_oid,
+ 				invtransfn_oid,
  				finalfn_oid;
  	Expr	   *transfnexpr,
+ 			   *invtransfnexpr,
  			   *finalfnexpr;
  	Datum		textInitVal;
  	int			i;
*************** initialize_peragg(WindowAggState *winsta
*** 1762,1767 ****
--- 2138,2144 ----
  	 */
  
  	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+ 	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
  	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
  	/* Check that aggregate owner has permission to call component fns */
*************** initialize_peragg(WindowAggState *winsta
*** 1783,1788 ****
--- 2160,2176 ----
  			aclcheck_error(aclresult, ACL_KIND_PROC,
  						   get_func_name(transfn_oid));
  		InvokeFunctionExecuteHook(transfn_oid);
+ 
+ 		if (OidIsValid(invtransfn_oid))
+ 		{
+ 			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+ 										 ACL_EXECUTE);
+ 			if (aclresult != ACLCHECK_OK)
+ 				aclcheck_error(aclresult, ACL_KIND_PROC,
+ 				get_func_name(invtransfn_oid));
+ 			InvokeFunctionExecuteHook(invtransfn_oid);
+ 		}
+ 
  		if (OidIsValid(finalfn_oid))
  		{
  			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
*************** initialize_peragg(WindowAggState *winsta
*** 1810,1822 ****
--- 2198,2218 ----
  							wfunc->wintype,
  							wfunc->inputcollid,
  							transfn_oid,
+ 							invtransfn_oid,
  							finalfn_oid,
  							&transfnexpr,
+ 							&invtransfnexpr,
  							&finalfnexpr);
  
  	fmgr_info(transfn_oid, &peraggstate->transfn);
  	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+ 		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+ 	}
+ 
  	if (OidIsValid(finalfn_oid))
  	{
  		fmgr_info(finalfn_oid, &peraggstate->finalfn);
*************** initialize_peragg(WindowAggState *winsta
*** 1860,1867 ****
--- 2256,2341 ----
  							wfunc->winfnoid)));
  	}
  
+ 	/*
+ 	 * Allowing only the forward transition function to be strict would
+ 	 * require handling more special cases in advance_windowaggregate() and
+ 	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 	 * that if the forward transition function is strict that the inverse
+ 	 * transition function is also strict. This should have been checked at
+ 	 * the aggregate function's definition time, but it's better to be safe...
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		peraggstate->transfn.fn_strict && !peraggstate->invtransfn.fn_strict)
+ 	{
+ 		ereport(ERROR,
+ 			(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 			errmsg("aggregate functions inverse transition function must strict if the forward transition function is")));
+ 	}
+ 
  	ReleaseSysCache(aggTuple);
  
+ 	/*
+ 	 * We ignore NULLs if either of the transition function is strict, i.e
+ 	 * if in that case neither of the functions will ever see NULL inputs.
+ 	 * If the forward transition function is non-strict, and the initial value
+ 	 * is NULL, it will receive a NULL state upon the first non-NULL input
+ 	 * though.
+ 	 */
+ 	peraggstate->ignore_nulls = ((OidIsValid(transfn_oid) &&
+ 								  peraggstate->transfn.fn_strict) ||
+ 								 (OidIsValid(invtransfn_oid) &&
+ 								  peraggstate->invtransfn.fn_strict));
+ 
+ 	/*
+ 	 * We can use the inverse transition function only if the aggregate's
+ 	 * arguments don't contain calls to volatile functions. Otherwise,
+ 	 * the difference between restarting and not restarting the aggregation
+ 	 * would be user-visible. Note that this check also covers the case where
+ 	 * the FILTER's WHERE clause contains a volatile function. If the frame
+ 	 * head cannot move, we won't ever need the inverse transition function,
+ 	 * so we also mark as "don't use" in that case.
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		!contain_volatile_functions((Node *) wfunc) &&
+ 		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+ 	{
+ 		peraggstate->use_invtransfn = true;
+ 	}
+ 	else
+ 	{
+ 		peraggstate->use_invtransfn = false;
+ 	}
+ 
+ 	/*
+ 	 * Invertible aggregates use their own aggcontext.
+ 	 *
+ 	 * This is necessary because they might all restart at different times,
+ 	 * so we might never be able to reset the shared context otherwise. We
+ 	 * can't make it the aggregate's responsibility to clean up after
+ 	 * themselves, because strict aggregates must be restarted whenever we
+ 	 * remove their last non-NULL input, which the aggregate won't be aware
+ 	 * is happening. Also, just pfree()ing the transValue upon restarting
+ 	 * wouldn't help, since we'd miss any indirectly referenced data. We
+ 	 * could, in theory, declare that aggregates with a state type other then
+ 	 * "internal" musn't allocate anything in the aggcontext themselves, that
+ 	 * non-strict aggregates with state type internal must clean up after
+ 	 * themselves when their inverse transfer function returns NULL, and then
+ 	 * only use private aggcontexts for strict aggregates with state type
+ 	 * internal. But that'd be a rather grotty set of requirements.
+ 	 */
+ 	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+ 	if (!peraggstate->aggcontext_is_shared)
+ 	{
+ 		peraggstate->aggcontext =
+ 			AllocSetContextCreate(CurrentMemoryContext,
+ 								  "WindowAgg_AggregatePrivate",
+ 								  ALLOCSET_DEFAULT_MINSIZE,
+ 								  ALLOCSET_DEFAULT_INITSIZE,
+ 								  ALLOCSET_DEFAULT_MAXSIZE);
+ 	}
+ 	else
+ 		peraggstate->aggcontext = winstate->aggcontext_shared;
+ 
  	return peraggstate;
  }
  
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..91bea45 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1203,1210 ****
--- 1203,1212 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr)
  {
  	Param	   *argp;
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1249,1254 ****
--- 1251,1269 ----
  	fexpr->funcvariadic = agg_variadic;
  	*transfnexpr = (Expr *) fexpr;
  
+ 
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+ 												agg_result_type,
+ 												args,
+ 												InvalidOid,
+ 												agg_input_collation,
+ 												COERCE_EXPLICIT_CALL);
+ 	}
+ 	else
+ 		*invtransfnexpr	= NULL;
+ 
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
  	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3862f05..6b28572 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11510,11515 ****
--- 11510,11516 ----
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
+ 	int			i_agginvtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
  	int			i_hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11518,11523 ****
--- 11519,11525 ----
  	int			i_agginitval;
  	int			i_convertok;
  	const char *aggtransfn;
+ 	const char *agginvtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
  	bool		hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11542,11548 ****
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
--- 11544,11550 ----
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11625,11630 ****
--- 11627,11633 ----
  	res = ExecuteSqlQueryForSingleRow(fout, query->data);
  
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
+ 	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
  	i_hypothetical = PQfnumber(res, "hypothetical");
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11634,11639 ****
--- 11637,11643 ----
  	i_convertok = PQfnumber(res, "convertok");
  
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ 	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11704,11709 ****
--- 11708,11719 ----
  		appendStringLiteralAH(details, agginitval, fout);
  	}
  
+ 	if (strcmp(agginvtransfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    INVFUNC = %s",
+ 			agginvtransfn);
+ 	}
+ 
  	if (strcmp(aggfinalfn, "-") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    FINALFUNC = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 96f08d3..2fb3871 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 46,51 ****
--- 46,52 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		agginvtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 69,85 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_agginvtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,276 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 103,278 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	-	json_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
*************** extern Oid AggregateCreate(const char *a
*** 288,293 ****
--- 290,296 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 455c089..c2bde24 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** extern void **find_rendezvous_variable(c
*** 644,651 ****
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly with nodeAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
--- 644,653 ----
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
!  * which is in execute/nodeWindowAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly neither nodeAgg
!  * nor nodeWindowAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..ff558c6 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WindowAggState
*** 1738,1743 ****
--- 1738,1744 ----
  	List	   *funcs;			/* all WindowFunc nodes in targetlist */
  	int			numfuncs;		/* total number of window functions */
  	int			numaggs;		/* number that are plain aggregates */
+ 	int			numaggs_invtrans;	/* number that are invertible aggregates */
  
  	WindowStatePerFunc perfunc; /* per-window-function information */
  	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
*************** typedef struct WindowAggState
*** 1762,1768 ****
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext;	/* context for each aggregate data */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
--- 1763,1769 ----
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext_shared;	/* shared context for agg states */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
*************** typedef struct WindowAggState
*** 1780,1789 ****
--- 1781,1796 ----
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
  
+ 	/* temporary data */
+ 	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+ 
  	/* temporary slots for tuples fetched back from tuplestore */
  	TupleTableSlot *agg_row_slot;
  	TupleTableSlot *temp_slot_1;
  	TupleTableSlot *temp_slot_2;
+ 
+ 	/* Statistics */
+ 	double	aggfwdtrans;	/* number of forward transitions */
  } WindowAggState;
  
  /* ----------------
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void build_aggregate_fnexprs(Oid 
*** 39,46 ****
--- 39,48 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr);
  
  #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..0def229 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select least_agg(variadic array[q1,q2]) 
*** 1580,1582 ****
--- 1580,1606 ----
   -4567890123456789
  (1 row)
  
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict AND NOT pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..6e2723f 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** alter aggregate my_rank(VARIADIC "any" O
*** 90,92 ****
--- 90,129 ----
   public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
  (2 rows)
  
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi
+ );
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_n
+ );
+ ERROR:  aggregate functions inverse transition function must strict if the forward transition function is
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = intminus
+ );
+ ERROR:  function intminus(double precision, double precision) does not exist
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_int
+ );
+ ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..df676ad 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT nth_value_def(ten) OVER (PARTITIO
*** 1071,1073 ****
--- 1071,1290 ----
               1 |   3 |    3
  (10 rows)
  
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict
+ );
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict
+ );
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+ ----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+  1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+  1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+  1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+  1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+  1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+  2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+  3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+ (9 rows)
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+ ----------+--------------+-------------------+-------------+------------------
+  1,1:NULL | +NULL        | I+NULL            |             | I
+  1,2:-    | +NULL        | I+NULL            |             | I
+  1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+  1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+  1,5:-    |              | I                 |             | I
+  1,6:-    |              | I                 |             | I
+  2,1:-    |              | I                 |             | I
+  2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+  3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+ (9 rows)
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invfunc = sum_int_randrestart_invsfunc
+ );
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+  eq1 | eq2 | eq3 
+ -----+-----+-----
+  t   | t   | t
+ (1 row)
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..284ea76 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** drop view aggordview1;
*** 590,592 ****
--- 590,609 ----
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict AND NOT pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..b7fd4a3 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** alter aggregate my_rank(VARIADIC "any" O
*** 101,103 ****
--- 101,144 ----
    rename to test_rank;
  
  \da test_*
+ 
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi
+ );
+ 
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_n
+ );
+ 
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = intminus
+ );
+ 
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_int
+ );
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..65c1005 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** SELECT nth_value_def(n := 2, val := ten)
*** 284,286 ****
--- 284,478 ----
  
  SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
    FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+ 
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ 
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict
+ );
+ 
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ 
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict
+ );
+ 
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ 
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invfunc = sum_int_randrestart_invsfunc
+ );
+ 
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ 
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
invtrans_collecting_e4cbd9.patchapplication/octet-stream; name=invtrans_collecting_e4cbd9.patchDownload
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index c62e3fb..16174dd 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** create_singleton_array(FunctionCallInfo 
*** 471,477 ****
  
  
  /*
!  * ARRAY_AGG aggregate function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
--- 471,477 ----
  
  
  /*
!  * ARRAY_AGG aggregate transition function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 508,513 ****
--- 508,537 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * ARRAY_AGG aggregate inverse transition function
+  */
+ Datum
+ array_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since NULLs
+ 	 * need to be removed just like any other value.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "array_agg_invtransfn called with NULL state");
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "array_agg_invtransfn called in non-aggregate context");
+ 	
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	shiftArrayResult(state, 1);
+ 	
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 311d0c2..b30b775 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** accumArrayResult(ArrayBuildState *astate
*** 4587,4592 ****
--- 4587,4593 ----
  		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
  		astate->mcontext = arr_context;
  		astate->alen = 64;		/* arbitrary starting array size */
+ 		astate->offset = 0;
  		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
  		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
  		astate->nelems = 0;
*************** accumArrayResult(ArrayBuildState *astate
*** 4600,4606 ****
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/* enlarge dvalues[]/dnulls[] if needed */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
--- 4601,4612 ----
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/*
! 		 * If the buffers are filled completely (offset must be zero then),
! 		 * we double their size. If they aren't, but the values extend to the
! 		 * end of the buffers, we reclaim wasted space at the beginning by
! 		 * moving the values to the front of the buffers.
! 		 */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
*************** accumArrayResult(ArrayBuildState *astate
*** 4609,4614 ****
--- 4615,4629 ----
  			astate->dnulls = (bool *)
  				repalloc(astate->dnulls, astate->alen * sizeof(bool));
  		}
+ 		else if (astate->offset + astate->nelems >= astate->alen)
+ 		{
+ 			memmove(astate->dvalues, astate->dvalues + astate->offset,
+ 					astate->alen * sizeof(Datum));
+ 			memmove(astate->dnulls, astate->dnulls + astate->offset,
+ 					astate->alen * sizeof(bool));
+ 			astate->offset = 0;
+ 		}
+ 		Assert(astate->offset + astate->nelems < astate->alen);
  	}
  
  	/*
*************** accumArrayResult(ArrayBuildState *astate
*** 4627,4634 ****
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->nelems] = dvalue;
! 	astate->dnulls[astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
--- 4642,4649 ----
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->offset + astate->nelems] = dvalue;
! 	astate->dnulls[astate->offset + astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
*************** accumArrayResult(ArrayBuildState *astate
*** 4637,4642 ****
--- 4652,4698 ----
  }
  
  /*
+  * shiftArrayResult - shift leading Datums out of an array result
+  *
+  *	astate is working state
+  *	count is the number of leading Datums to shift out
+  *
+  * If count is equal to or larger than the number of relements, the array
+  * result is empty afterwards. If astate is NULL, nothing is done.
+  */
+ void
+ shiftArrayResult(ArrayBuildState *astate, int count)
+ {
+ 	int		i;
+ 	
+ 	if (astate == NULL)
+ 		return;
+ 	
+ 	/* Limit shift count to number of elements for safety */
+ 	count = Min(count, astate->nelems);
+ 	
+ 	/* For pass-by-ref types, free values we shift out */
+ 	if (!astate->typbyval) {
+ 		for(i = astate->offset; i < astate->offset + count; ++i) {
+ 			if (astate->dnulls[i])
+ 				continue;
+ 
+ 			pfree(DatumGetPointer(astate->dvalues[i]));
+ 			
+ 			/* For cleanliness' sake */
+ 			astate->dnulls[i] = false;
+ 			astate->dvalues[i] = 0;
+ 		}
+ 		
+ 	}
+ 	
+ 	/* Update state */
+ 	astate->nelems -= count;
+ 	astate->offset += count;
+ }
+ 
+ 
+ /*
   * makeArrayResult - produce 1-D final result of accumArrayResult
   *
   *	astate is working state (not NULL)
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4679,4686 ****
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues,
! 								astate->dnulls,
  								ndims,
  								dims,
  								lbs,
--- 4735,4742 ----
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues + astate->offset,
! 								astate->dnulls + astate->offset,
  								ndims,
  								dims,
  								lbs,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..463acb7 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** typedef struct
*** 50,55 ****
--- 50,63 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct StringAggState
+ {
+ 	StringInfoData 	string; 	/* Contents */
+ 	int 			offset;		/* Offset into stringinfo's data */
+ 	int 			delimLen;	/* Delim length, -1 initially, -2 if multiple */
+ 	int64			notNullCount;/* Number of non-NULL inputs */  
+ } StringAggState;
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
*************** static void appendStringInfoText(StringI
*** 78,84 ****
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
--- 86,95 ----
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo);
! static void prepareAppendStringAggState(StringAggState *state,
! 										int delimLen, int valueLen);
! static bool removeFromStringAggState(StringAggState *state, int valueLen);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
*************** static void text_format_string_conversio
*** 92,97 ****
--- 103,226 ----
  static void text_format_append_string(StringInfo buf, const char *str,
  						  int flags, int width);
  
+ /*****************************************************************************
+  *	 SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA)			 *
+  *****************************************************************************/
+ 
+ /*
+  * subroutine to initialize state
+  */
+ static StringAggState*
+ makeStringAggState(FunctionCallInfo fcinfo)
+ {
+ 	StringAggState*	state;
+ 	MemoryContext aggcontext;
+ 	MemoryContext oldcontext;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 	}
+ 
+ 	/*
+ 	 * Create state in aggregate context.  It'll stay there across subsequent
+ 	 * calls.
+ 	 */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = (StringAggState *) palloc(sizeof(StringAggState));
+ 	initStringInfo(&state->string);
+ 	state->offset = 0;
+ 	state->delimLen = -1;
+ 	state->notNullCount = 0;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * Prepare state for appending a value and a delimiter with specified lengths.
+  * pass -1 for delimLen if no delimiter will be added
+  */
+ void
+ prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen)
+ {
+ 	/*
+ 	 * Reclaim wasted space
+ 	 *
+ 	 * We move the contents to the left if the current contents fit into the
+ 	 * wasted space, i.e. if we waste more than we store. The limit is
+ 	 * somewhat arbitrary, but it's the smallest one that allows
+ 	 * memcpy to be used, because the source and destination don't overlap.
+ 	 * Note that we must check for <, not <=, because we include the trailing
+ 	 * '\0' in the copy.
+ 	 */
+ 	if (state->string.len - state->offset < state->offset)
+ 	{
+ 		state->string.len -= state->offset;
+ 		memcpy(state->string.data, state->string.data + state->offset,
+ 			   state->string.len + 1);
+ 		state->offset = 0;
+ 	}
+ 	
+ 	/*
+ 	 * Enlarge StringInfo
+ 	 *
+ 	 * Not strictly necessary, but avoids potentially resizing twice when
+ 	 * the actual append... calls are done by the caller
+ 	 */
+ 	enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen);
+ 	
+ 	
+ 	/* Track delimiter length */
+ 	if (delimLen == -1)
+ 		{} /* Not specified, don't update */
+ 	else if (state->delimLen == -1)
+ 		state->delimLen = delimLen;
+ 	else if (state->delimLen != delimLen)
+ 		state->delimLen = -2;
+ }
+ 
+ /*
+  * Remove value with given length and the delimiter that follows
+  *
+  * Returns false if removal was impossible because delimiters varied
+  */
+ bool
+ removeFromStringAggState(StringAggState *state, int valueLen)
+ {
+ 	/* Remove the string */
+ 	state->offset += valueLen;
+ 
+ 	/*
+ 	 * Remove delimiter if necessary.
+ 	 *
+ 	 * The delimiter we need to remove isn't the delimiter we were passed, but
+ 	 * rather the delimiter passed when adding the input *after* this one. We
+ 	 * thus need the delimiter length to be all the same to be able to proceed.
+ 	 * If we're removing the last string, there will be no delimiter following
+ 	 * it. In that case, we may reset delimLen to its initial value.
+ 	 */
+ 	if (state->delimLen == -2)
+ 		return false;
+ 	if (state->offset < state->string.len)
+ 	{
+ 		Assert(state->delimLen >= 0);
+ 		state->offset += state->delimLen;
+ 	}
+ 	else
+ 		state->delimLen = -1;
+ 
+ 	/* Don't crash if we're ever asked to remove more than was added */
+ 	if (state->offset > state->string.len)
+ 	{
+ 		state->offset = state->string.len;
+ 		elog(ERROR, "tried to remove more data than was aggregated");
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /*****************************************************************************
   *	 CONVERSION ROUTINES EXPORTED FOR USE BY C CODE							 *
*************** byteasend(PG_FUNCTION_ARGS)
*** 408,435 ****
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		bytea	   *value = PG_GETARG_BYTEA_PP(1);
  
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 		{
! 			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim));
! 		}
  
! 		appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value));
  	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
--- 537,589 ----
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	bytea		   *value,
! 				   *delim;
! 	int				valueLen,
! 					delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
  
! 	value = PG_GETARG_BYTEA_PP(1);
! 	valueLen = VARSIZE_ANY_EXHDR(value);
! 	state->notNullCount++;
  
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
! 	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_BYTEA_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
! 	}
! 	else
! 	{
! 		/* Delimiter is NULL, treat as zero-length string */
! 		delim = NULL;
! 		delimLen = 0;
  	}
  
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, valueLen);
+ 	if (delim)
+ 		appendBinaryStringInfo(&state->string, VARDATA_ANY(delim),
+ 							   VARSIZE_ANY_EXHDR(delim));
+ 	appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen);
+ 
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 438,459 ****
  }
  
  Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
  	{
  		bytea	   *result;
  
! 		result = (bytea *) palloc(state->len + VARHDRSZ);
! 		SET_VARSIZE(result, state->len + VARHDRSZ);
! 		memcpy(VARDATA(result), state->data, state->len);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
--- 592,661 ----
  }
  
  Datum
+ bytea_string_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	int				valueLen;
+ 	StringAggState *state;
+ 	
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since it
+ 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "string_agg_invtransfn called with NULL state");
+ 	else if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
+ 	
+ 	state = (StringAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* We append nothing if the string is NULL, so skip here as well */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_POINTER(state);
+ 
+ 	/* No need to de-toast value, need only the length */
+ 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
+ 	Assert(state->notNullCount >= 1);
+ 	state->notNullCount--;
+ 
+ 	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
+ 	 * The transition type for string_agg() is declared to be "internal",
+ 	 * which is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	if (removeFromStringAggState(state, valueLen))
+ 		PG_RETURN_POINTER(state);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
  	{
+ 		int			resultLen = state->string.len  - state->offset;
  		bytea	   *result;
  
! 		result = (bytea *) palloc(resultLen + VARHDRSZ);
! 		SET_VARSIZE(result, resultLen + VARHDRSZ);
! 		memcpy(VARDATA(result), state->string.data + state->offset, resultLen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3733,3801 ****
   * the associated value.
   */
  
! /* subroutine to initialize state */
! static StringInfo
! makeStringAggState(FunctionCallInfo fcinfo)
  {
! 	StringInfo	state;
! 	MemoryContext aggcontext;
! 	MemoryContext oldcontext;
  
! 	if (!AggCheckCallContext(fcinfo, &aggcontext))
  	{
! 		/* cannot be called directly because of internal-type argument */
! 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
  	}
  
  	/*
! 	 * Create state in aggregate context.  It'll stay there across subsequent
! 	 * calls.
  	 */
! 	oldcontext = MemoryContextSwitchTo(aggcontext);
! 	state = makeStringInfo();
! 	MemoryContextSwitchTo(oldcontext);
! 
! 	return state;
  }
  
  Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 			appendStringInfoText(state, PG_GETARG_TEXT_PP(2));	/* delimiter */
  
! 		appendStringInfoText(state, PG_GETARG_TEXT_PP(1));		/* value */
! 	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len));
  	else
  		PG_RETURN_NULL();
  }
--- 3935,4054 ----
   * the associated value.
   */
  
! Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	text		   *value,
! 				   *delim;
! 	int				delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
! 
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
! 
! 	value = PG_GETARG_TEXT_PP(1);
! 	state->notNullCount++;
! 
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
  	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_TEXT_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
  	}
+ 	else
+ 	{
+ 		/* Delimiter is NULL, treat as zero-length string */
+ 		delim = NULL;
+ 		delimLen = 0;
+ 	}
+ 
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value));
+ 	if (delim)
+ 		appendStringInfoText(&state->string, delim);
+ 	appendStringInfoText(&state->string, value);
  
  	/*
! 	 * The transition type for string_agg() is declared to be "internal",
! 	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
! string_agg_invtransfn(PG_FUNCTION_ARGS)
  {
! 	int				valueLen;
! 	StringAggState *state;
! 	
! 	/*
! 	 * Shouldn't happen, but we cannot mark this function strict, since it
! 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
! 	 * Must also prevent direct calls because of the "interal" argument
! 	 */
! 	if (PG_ARGISNULL(0))
! 		elog(ERROR, "string_agg_invtransfn called with NULL state");
! 	else if (!AggCheckCallContext(fcinfo, NULL))
! 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
  
! 	state = (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* We append nothing if the string is NULL, so skip here as well */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* No need to de-toast value, need only the length */
! 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
! 	Assert(state->notNullCount >= 1);
! 	state->notNullCount--;
  
  	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	if (removeFromStringAggState(state, valueLen))
! 		PG_RETURN_POINTER(state);
! 	else
! 		PG_RETURN_NULL();
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset,
! 												  state->string.len - state->offset));
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 2fb3871..5c9488f 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-	-					0	
*** 250,262 ****
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn	-	json_agg_finalfn	0	2281	0	_null_ ));
--- 250,262 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_invtransfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_invtransfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_invtransfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn	-	json_agg_finalfn	0	2281	0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ad9774c..1c2d8bb 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 875,880 ****
--- 875,882 ----
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3180 (  array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2463,2474 ****
--- 2465,2480 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3546 (  string_agg_invtransfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3538 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into a string");
  DATA(insert OID = 3543 (  bytea_string_agg_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3547 (  bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3544 (  bytea_string_agg_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..9a6fc39 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 83,88 ****
--- 83,89 ----
  	Datum	   *dvalues;		/* array of accumulated Datums */
  	bool	   *dnulls;			/* array of is-null flags for Datums */
  	int			alen;			/* allocated length of above arrays */
+ 	int			offset;			/* offset of first element in above arrays */
  	int			nelems;			/* number of valid entries in above arrays */
  	Oid			element_type;	/* data type of the Datums */
  	int16		typlen;			/* needed info about datatype */
*************** extern ArrayBuildState *accumArrayResult
*** 255,260 ****
--- 256,262 ----
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
  				 MemoryContext rcontext);
+ extern void shiftArrayResult(ArrayBuildState *astate, int count);
  extern Datum makeArrayResult(ArrayBuildState *astate,
  				MemoryContext rcontext);
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
*************** extern ArrayType *create_singleton_array
*** 290,295 ****
--- 292,298 ----
  					   int ndims);
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
  
  /*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..7d8e749 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 812,820 ****
--- 812,822 ----
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
  extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS);
  
  extern Datum text_concat(PG_FUNCTION_ARGS);
  extern Datum text_concat_ws(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index df676ad..81a4b74 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1288,1290 ****
--- 1288,1334 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
+      row     | str | str_del | str_vardel |      bin       |      bin_del       |      bin_vardel      |       ary        
+ -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------
+  1:1,0100,-  | -   | -       | -          | -              | -                  | -                    | 
+  2:-,0200,2  | 1   | 1       | 1          | \x0100         | \x0100             | \x0100               | {NULL}
+  3:3,----,3  | 1   | 1       | 1          | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2}
+  4:-,0400,4  | 13  | 1,3     | 13         | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2,3}
+  5:5,----,-  | 3   | 3       | 3          | \x02000400     | \x0200000400       | \x0200000400         | {2,3,4}
+  6:6,0600,6  | 35  | 3,5     | 3||5       | \x0400         | \x0400             | \x0400               | {3,4,NULL}
+  7:7,0700,-  | 56  | 5,6     | 56         | \x04000600     | \x0400000600       | \x04000600           | {4,NULL,6}
+  8:8,0800,8  | 567 | 5,6,7   | 56|7       | \x06000700     | \x0600000700       | \x0600000700         | {NULL,6,NULL}
+  9:-,----,-  | 678 | 6,7,8   | 6|7||8     | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8}
+  10:-,----,- | 78  | 7,8     | 7||8       | \x07000800     | \x0700000800       | \x070000000800       | {NULL,8,NULL}
+  11:-,----,- | 8   | 8       | 8          | \x0800         | \x0800             | \x0800               | {8,NULL,NULL}
+  12:-,----,- | -   | -       | -          | -              | -                  | -                    | {NULL,NULL,NULL}
+ (12 rows)
+ 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 65c1005..d7c3f27 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 476,478 ****
--- 476,507 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ 
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
invtrans_minmax_54201d.patchapplication/octet-stream; name=invtrans_minmax_54201d.patchDownload
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 311d0c2..dd07f1a 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_larger(PG_FUNCTION_ARGS)
*** 4714,4719 ****
--- 4714,4737 ----
  }
  
  Datum
+ array_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) > 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ 
+ Datum
  array_smaller(PG_FUNCTION_ARGS)
  {
  	ArrayType  *v1,
*************** array_smaller(PG_FUNCTION_ARGS)
*** 4728,4733 ****
--- 4746,4767 ----
  	PG_RETURN_ARRAYTYPE_P(result);
  }
  
+ Datum
+ array_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) < 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
  
  typedef struct generate_subscripts_fctx
  {
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index d419b4a..52488cd 100644
*** a/src/backend/utils/adt/bool.c
--- b/src/backend/utils/adt/bool.c
*************** boolge(PG_FUNCTION_ARGS)
*** 285,290 ****
--- 285,292 ----
  
  /* function for standard EVERY aggregate implementation conforming to SQL 2003.
   * must be strict. It is also named bool_and for homogeneity.
+  * Note: this is no longer used for the bool_and() and every() aggregate
+  * functions.
   */
  Datum
  booland_statefunc(PG_FUNCTION_ARGS)
*************** booland_statefunc(PG_FUNCTION_ARGS)
*** 294,302 ****
--- 296,396 ----
  
  /* function for standard ANY/SOME aggregate conforming to SQL 2003.
   * must be strict. The name of the aggregate is bool_or. See the doc.
+  * Note: this is no longer used for the bool_or aggregate function.
   */
  Datum
  boolor_statefunc(PG_FUNCTION_ARGS)
  {
  	PG_RETURN_BOOL(PG_GETARG_BOOL(0) || PG_GETARG_BOOL(1));
  }
+ 
+ typedef struct BoolAggState
+ {
+ 	int64 aggcount; /* number of values aggregated */
+ 	int64 aggtrue; /* number of values aggregated which are true */
+ } BoolAggState;
+ 
+ static BoolAggState *
+ makeBoolAggState(FunctionCallInfo fcinfo)
+ {
+ 	BoolAggState *state;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 	state = (BoolAggState *) palloc(sizeof(BoolAggState));
+ 	state->aggcount = 0;
+ 	state->aggtrue = 0;
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 
+ 	return state;
+ }
+ 
+ Datum
+ bool_accum(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* Create the state data when we see the first non-null input. */
+ 	if (state == NULL)
+ 		state = makeBoolAggState(fcinfo);
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount++;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue++;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /* bool_accum_inv
+  * removes a bool from aggregation
+  * Note that this function should be declared as strict
+  */
+ Datum
+ bool_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state = (BoolAggState *) PG_GETARG_POINTER(0);
+ 	bool value = PG_GETARG_BOOL(1);
+ 
+ 	state->aggcount--;
+ 	if (value)
+ 		state->aggtrue--;
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_alltrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if all values are true */
+ 	PG_RETURN_BOOL(state->aggcount == state->aggtrue);
+ }
+ 
+ Datum
+ bool_anytrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if any value is true */
+ 	PG_RETURN_BOOL(state->aggtrue > 0);
+ }
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 0158758..cba17a3 100644
*** a/src/backend/utils/adt/cash.c
--- b/src/backend/utils/adt/cash.c
*************** cashlarger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,896 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 > c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cashsmaller()
   * Return smaller of two cash values.
   */
*************** cashsmaller(PG_FUNCTION_ARGS)
*** 892,897 ****
--- 906,925 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 < c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cash_words()
   * This converts a int4 as well but to a representation using words
   * Obviously way North American centric - sorry
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 06cc0cd..0cca0b0 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** date_larger(PG_FUNCTION_ARGS)
*** 396,401 ****
--- 396,415 ----
  }
  
  Datum
+ date_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 > dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  date_smaller(PG_FUNCTION_ARGS)
  {
  	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
*************** date_smaller(PG_FUNCTION_ARGS)
*** 404,409 ****
--- 418,437 ----
  	PG_RETURN_DATEADT((dateVal1 < dateVal2) ? dateVal1 : dateVal2);
  }
  
+ Datum
+ date_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 < dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* Compute difference between two dates in days.
   */
  Datum
*************** time_larger(PG_FUNCTION_ARGS)
*** 1463,1468 ****
--- 1491,1510 ----
  }
  
  Datum
+ time_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 > time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  time_smaller(PG_FUNCTION_ARGS)
  {
  	TimeADT		time1 = PG_GETARG_TIMEADT(0);
*************** time_smaller(PG_FUNCTION_ARGS)
*** 1471,1476 ****
--- 1513,1532 ----
  	PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
  }
  
+ Datum
+ time_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 < time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* overlaps_time() --- implements the SQL OVERLAPS operator.
   *
   * Algorithm is per SQL spec.  This is much harder than you'd think
*************** timetz_larger(PG_FUNCTION_ARGS)
*** 2262,2267 ****
--- 2318,2337 ----
  }
  
  Datum
+ timetz_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) > 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  timetz_smaller(PG_FUNCTION_ARGS)
  {
  	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
*************** timetz_smaller(PG_FUNCTION_ARGS)
*** 2275,2280 ****
--- 2345,2364 ----
  	PG_RETURN_TIMETZADT_P(result);
  }
  
+ Datum
+ timetz_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) < 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* timetz_pl_interval()
   * Add interval to timetz.
   */
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 83c3878..32035e0 100644
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
*************** enum_smaller(PG_FUNCTION_ARGS)
*** 273,278 ****
--- 273,292 ----
  }
  
  Datum
+ enum_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) < 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_larger(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
*************** enum_larger(PG_FUNCTION_ARGS)
*** 282,287 ****
--- 296,315 ----
  }
  
  Datum
+ enum_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) > 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_cmp(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index e2a0812..527eff6 100644
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4larger(PG_FUNCTION_ARGS)
*** 626,631 ****
--- 626,647 ----
  }
  
  Datum
+ float4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float4smaller(PG_FUNCTION_ARGS)
  {
  	float4		arg1 = PG_GETARG_FLOAT4(0);
*************** float4smaller(PG_FUNCTION_ARGS)
*** 639,644 ****
--- 655,676 ----
  	PG_RETURN_FLOAT4(result);
  }
  
+ Datum
+ float4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  /*
   *		======================
   *		FLOAT8 BASE OPERATIONS
*************** float8larger(PG_FUNCTION_ARGS)
*** 693,698 ****
--- 725,746 ----
  }
  
  Datum
+ float8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float8smaller(PG_FUNCTION_ARGS)
  {
  	float8		arg1 = PG_GETARG_FLOAT8(0);
*************** float8smaller(PG_FUNCTION_ARGS)
*** 706,711 ****
--- 754,774 ----
  	PG_RETURN_FLOAT8(result);
  }
  
+ Datum
+ float8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *		====================
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 669355e..fe9d736 100644
*** a/src/backend/utils/adt/int.c
--- b/src/backend/utils/adt/int.c
*************** int2larger(PG_FUNCTION_ARGS)
*** 1185,1190 ****
--- 1185,1204 ----
  }
  
  Datum
+ int2larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int2smaller(PG_FUNCTION_ARGS)
  {
  	int16		arg1 = PG_GETARG_INT16(0);
*************** int2smaller(PG_FUNCTION_ARGS)
*** 1194,1199 ****
--- 1208,1227 ----
  }
  
  Datum
+ int2smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4larger(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4larger(PG_FUNCTION_ARGS)
*** 1203,1208 ****
--- 1231,1250 ----
  }
  
  Datum
+ int4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4smaller(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4smaller(PG_FUNCTION_ARGS)
*** 1211,1216 ****
--- 1253,1272 ----
  	PG_RETURN_INT32((arg1 < arg2) ? arg1 : arg2);
  }
  
+ Datum
+ int4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /*
   * Bit-pushing operators
   *
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..820be68 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8larger(PG_FUNCTION_ARGS)
*** 752,757 ****
--- 752,771 ----
  }
  
  Datum
+ int8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int8smaller(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
*************** int8smaller(PG_FUNCTION_ARGS)
*** 764,769 ****
--- 778,797 ----
  }
  
  Datum
+ int8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int84pl(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..4749152 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_smaller(PG_FUNCTION_ARGS)
*** 1834,1839 ****
--- 1834,1854 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) < 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * numeric_larger() -
*************** numeric_larger(PG_FUNCTION_ARGS)
*** 1856,1861 ****
--- 1871,1892 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) > 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /* ----------------------------------------------------------------------
   *
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 8945ef4..745449a 100644
*** a/src/backend/utils/adt/oid.c
--- b/src/backend/utils/adt/oid.c
*************** oidlarger(PG_FUNCTION_ARGS)
*** 397,402 ****
--- 397,416 ----
  }
  
  Datum
+ oidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidsmaller(PG_FUNCTION_ARGS)
  {
  	Oid			arg1 = PG_GETARG_OID(0);
*************** oidsmaller(PG_FUNCTION_ARGS)
*** 406,411 ****
--- 420,439 ----
  }
  
  Datum
+ oidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidvectoreq(PG_FUNCTION_ARGS)
  {
  	int32		cmp = DatumGetInt32(btoidvectorcmp(fcinfo));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 6e2bbdc..57fcf80 100644
*** a/src/backend/utils/adt/tid.c
--- b/src/backend/utils/adt/tid.c
*************** tidlarger(PG_FUNCTION_ARGS)
*** 237,242 ****
--- 237,256 ----
  }
  
  Datum
+ tidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) > 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  tidsmaller(PG_FUNCTION_ARGS)
  {
  	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
*************** tidsmaller(PG_FUNCTION_ARGS)
*** 245,250 ****
--- 259,277 ----
  	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
  }
  
+ Datum
+ tidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) < 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *	Functions to get latest tid of a specified tuple.
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 4581862..5b03c9f 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** timestamp_smaller(PG_FUNCTION_ARGS)
*** 2393,2398 ****
--- 2393,2412 ----
  }
  
  Datum
+ timestamp_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) < 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  timestamp_larger(PG_FUNCTION_ARGS)
  {
  	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
*************** timestamp_larger(PG_FUNCTION_ARGS)
*** 2406,2411 ****
--- 2420,2438 ----
  	PG_RETURN_TIMESTAMP(result);
  }
  
+ Datum
+ timestamp_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) > 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
  
  Datum
  timestamp_mi(PG_FUNCTION_ARGS)
*************** interval_smaller(PG_FUNCTION_ARGS)
*** 2849,2854 ****
--- 2876,2895 ----
  }
  
  Datum
+ interval_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) < 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_larger(PG_FUNCTION_ARGS)
  {
  	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
*************** interval_larger(PG_FUNCTION_ARGS)
*** 2863,2868 ****
--- 2904,2923 ----
  }
  
  Datum
+ interval_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) > 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_pl(PG_FUNCTION_ARGS)
  {
  	Interval   *span1 = PG_GETARG_INTERVAL_P(0);
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 502ca44..536ca66 100644
*** a/src/backend/utils/adt/varchar.c
--- b/src/backend/utils/adt/varchar.c
*************** bpchar_larger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,905 ----
  }
  
  Datum
+ bpchar_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp > 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  bpchar_smaller(PG_FUNCTION_ARGS)
  {
  	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
*************** bpchar_smaller(PG_FUNCTION_ARGS)
*** 894,899 ****
--- 917,945 ----
  	PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2);
  }
  
+ Datum
+ bpchar_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp < 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /*
   * bpchar needs a specialized hash function because we want to ignore
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..6b640cb 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** text_larger(PG_FUNCTION_ARGS)
*** 1697,1702 ****
--- 1697,1716 ----
  }
  
  Datum
+ text_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  text_smaller(PG_FUNCTION_ARGS)
  {
  	text	   *arg1 = PG_GETARG_TEXT_PP(0);
*************** text_smaller(PG_FUNCTION_ARGS)
*** 1708,1713 ****
--- 1722,1740 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ Datum
+ text_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * The following operators support character-by-character comparison
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 2fb3871..39f991e 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2113	n 0 interval_pl		-	-	
*** 122,169 ****
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
--- 122,169 ----
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger			int8larger_inv			-		413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger			int4larger_inv			-		521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger			int2larger_inv			-		520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger			oidlarger_inv			-		610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger		float4larger_inv		-		623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger		float8larger_inv		-		674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger			int4larger_inv			-		563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger			date_larger_inv			-		1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger			time_larger_inv			-		1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger		timetz_larger_inv		-		1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger			cashlarger_inv			-		903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	timestamp_larger_inv	-		2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	timestamptz_larger_inv	-		1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger		interval_larger_inv		-		1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger			text_larger_inv			-		666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger		numeric_larger_inv		-		1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger		array_larger_inv		-		1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger		bpchar_larger_inv		-		1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger			tidlarger_inv			-		2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger			enum_larger_inv			-		3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller			int8smaller_inv			-		412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller			int4smaller_inv			-		97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller			int2smaller_inv			-		95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller			oidsmaller_inv			-		609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller		float4smaller_inv		-		622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller		float8smaller_inv		-		672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller			int4smaller_inv			-		562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller		date_smaller_inv		-		1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller		time_smaller_inv		-		1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller		timetz_smaller_inv		-		1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller			cashsmaller_inv			-		902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	timestamp_smaller_inv	-		2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller timestamptz_smaller_inv	-		1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	interval_smaller_inv	-		1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller		text_smaller_inv		-		664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller		numeric_smaller_inv		-		1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller		array_smaller_inv		-		1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller		bpchar_smaller_inv		-		1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller			tidsmaller_inv			-		2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller		enum_smaller_inv		-		3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
*************** DATA(insert ( 2828	n 0 float8_regr_accum
*** 232,240 ****
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
--- 232,240 ----
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
! DATA(insert ( 2518	n 0 bool_accum		bool_accum_inv		bool_anytrue	59	2281		16	_null_ ));
! DATA(insert ( 2519	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ad9774c..386a219 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("larger of two");
*** 367,372 ****
--- 367,377 ----
  DATA(insert OID = 211 (  float4smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3200 (  float4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3201 (  float4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 212 (  int4um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "23" _null_ _null_ _null_ _null_ int4um _null_ _null_ _null_ ));
  DATA(insert OID = 213 (  int2um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "21" _null_ _null_ _null_ _null_ int2um _null_ _null_ _null_ ));
  
*************** DATA(insert OID = 223 (  float8larger	  
*** 386,391 ****
--- 391,400 ----
  DESCR("larger of two");
  DATA(insert OID = 224 (  float8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3202 (  float8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3203 (  float8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 225 (  lseg_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "601" _null_ _null_ _null_ _null_	lseg_center _null_ _null_ _null_ ));
  DATA(insert OID = 226 (  path_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "602" _null_ _null_ _null_ _null_	path_center _null_ _null_ _null_ ));
*************** DATA(insert OID = 458 (  text_larger	   
*** 711,716 ****
--- 720,729 ----
  DESCR("larger of two");
  DATA(insert OID = 459 (  text_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3204 (  text_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3205 (  text_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 460 (  int8in			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "2275" _null_ _null_ _null_ _null_ int8in _null_ _null_ _null_ ));
  DESCR("I/O");
*************** DATA(insert OID = 515 (  array_larger	  
*** 859,864 ****
--- 872,881 ----
  DESCR("larger of two");
  DATA(insert OID = 516 (  array_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3206 (  array_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3207 (  array_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1191 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 23 "2277 23 16" _null_ _null_ _null_ _null_ generate_subscripts _null_ _null_ _null_ ));
  DESCR("array subscripts generator");
  DATA(insert OID = 1192 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 23 "2277 23" _null_ _null_ _null_ _null_ generate_subscripts_nodir _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 913,918 ****
--- 930,945 ----
  DATA(insert OID = 771 (  int2smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3208 (  int4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3209 (  int4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3210 (  int2larger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3211 (  int2smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 774 (  gistgettuple	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_	gistgettuple _null_ _null_ _null_ ));
  DESCR("gist(internal)");
  DATA(insert OID = 638 (  gistgetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	gistgetbitmap _null_ _null_ _null_ ));
*************** DATA(insert OID =  898 (  cashlarger	   
*** 1003,1008 ****
--- 1030,1039 ----
  DESCR("larger of two");
  DATA(insert OID =  899 (  cashsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID =  3212 (  cashlarger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID =  3213 (  cashsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID =  919 (  flt8_mul_cash    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "701 790" _null_ _null_ _null_ _null_	flt8_mul_cash _null_ _null_ _null_ ));
  DATA(insert OID =  935 (  cash_words	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "790" _null_ _null_ _null_ _null_	cash_words _null_ _null_ _null_ ));
  DESCR("output money amount as words");
*************** DATA(insert OID = 1063 (  bpchar_larger 
*** 1157,1162 ****
--- 1188,1197 ----
  DESCR("larger of two");
  DATA(insert OID = 1064 (  bpchar_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3214 (  bpchar_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3215 (  bpchar_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1078 (  bpcharcmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1042 1042" _null_ _null_ _null_ _null_ bpcharcmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1080 (  hashbpchar	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "1042" _null_ _null_ _null_ _null_	hashbpchar _null_ _null_ _null_ ));
*************** DATA(insert OID = 1138 (  date_larger	  
*** 1191,1196 ****
--- 1226,1235 ----
  DESCR("larger of two");
  DATA(insert OID = 1139 (  date_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3216 (  date_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3217 (  date_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1140 (  date_mi		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1082 1082" _null_ _null_ _null_ _null_ date_mi _null_ _null_ _null_ ));
  DATA(insert OID = 1141 (  date_pli		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_pli _null_ _null_ _null_ ));
  DATA(insert OID = 1142 (  date_mii		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_mii _null_ _null_ _null_ ));
*************** DATA(insert OID = 1195 (  timestamptz_sm
*** 1281,1290 ****
--- 1320,1339 ----
  DESCR("smaller of two");
  DATA(insert OID = 1196 (  timestamptz_larger  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 3218 (  timestamptz_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3219 (  timestamptz_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1197 (  interval_smaller	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  DATA(insert OID = 1198 (  interval_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 3220 (  interval_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3221 (  interval_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1199 (  age				PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_	timestamptz_age _null_ _null_ _null_ ));
  DESCR("date difference preserving months and years");
  
*************** DESCR("larger of two");
*** 1318,1323 ****
--- 1367,1378 ----
  DATA(insert OID = 1237 (  int8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3223 (  int8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3224 (  int8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1238 (  texticregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexeq _null_ _null_ _null_ ));
  DATA(insert OID = 1239 (  texticregexne    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexne _null_ _null_ _null_ ));
  DATA(insert OID = 1240 (  nameicregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "19 25" _null_ _null_ _null_ _null_ nameicregexeq _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1374,1379 ****
--- 1429,1439 ----
  DATA(insert OID = 2796 ( tidsmaller		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3225 ( tidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3226 ( tidsmaller_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1296 (  timedate_pl	   PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1114 "1083 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
  DATA(insert OID = 1297 (  datetimetz_pl    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1082 1266" _null_ _null_ _null_ _null_ datetimetz_timestamptz _null_ _null_ _null_ ));
  DATA(insert OID = 1298 (  timetzdate_pl    PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1184 "1266 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1515,1520 ****
--- 1575,1590 ----
  DATA(insert OID = 1380 (  timetz_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3227 (  time_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3228 (  time_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3229 (  timetz_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3230 (  timetz_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1381 (  char_length	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "25" _null_ _null_ _null_ _null_ textlen _null_ _null_ _null_ ));
  DESCR("character length");
  
*************** DATA(insert OID = 1766 ( numeric_smaller
*** 2277,2282 ****
--- 2347,2357 ----
  DESCR("smaller of two");
  DATA(insert OID = 1767 ( numeric_larger			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 3231 ( numeric_smaller_inv		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3232 ( numeric_larger_inv			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1769 ( numeric_cmp			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1700 1700" _null_ _null_ _null_ _null_ numeric_cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1771 ( numeric_uminus			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ numeric_uminus _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 2801,2806 ****
--- 2876,2886 ----
  DATA(insert OID = 1966 (  oidsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3244 (  oidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3245 (  oidsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 timestamp_transform f f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
  DESCR("adjust timestamptz precision");
  DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 time_transform f f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
*************** DATA(insert OID = 2035 (  timestamp_smal
*** 2862,2867 ****
--- 2942,2953 ----
  DESCR("smaller of two");
  DATA(insert OID = 2036 (  timestamp_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 3246 (  timestamp_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3247 (  timestamp_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 2037 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 1266 "25 1266" _null_ _null_ _null_ _null_ timetz_zone _null_ _null_ _null_ ));
  DESCR("adjust time with time zone to new zone");
  DATA(insert OID = 2038 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1186 1266" _null_ _null_ _null_ _null_	timetz_izone _null_ _null_ _null_ ));
*************** DESCR("non-persistent series generator")
*** 3853,3863 ****
  DATA(insert OID = 939  (  generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t s 3 0 1184 "1184 1184 1186" _null_ _null_ _null_ _null_ generate_series_timestamptz _null_ _null_ _null_ ));
  DESCR("non-persistent series generator");
  
- /* boolean aggregates */
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
! DESCR("aggregate transition function");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
! DESCR("aggregate transition function");
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
--- 3939,3950 ----
  DATA(insert OID = 939  (  generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t s 3 0 1184 "1184 1184 1186" _null_ _null_ _null_ _null_ generate_series_timestamptz _null_ _null_ _null_ ));
  DESCR("non-persistent series generator");
  
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
! DESCR("true if both inputs are true");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
! DESCR("true if any inputs are true");
! 
! /* boolean aggregates */
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
*************** DATA(insert OID = 2518 ( bool_or					   
*** 3865,3871 ****
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
! 
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("bitwise-and smallint aggregate");
--- 3952,3965 ----
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
! DATA(insert OID = 8033 ( bool_accum			   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum _null_ _null_ _null_ ));
! DESCR("aggregate transition function");
! DATA(insert OID = 3248 ( bool_accum_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum_inv _null_ _null_ _null_ ));
! DESCR("inverse aggregate transition function");
! DATA(insert OID = 8035 ( bool_alltrue			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_alltrue _null_ _null_ _null_ ));
! DESCR("aggregate final function");
! DATA(insert OID = 8036 ( bool_anytrue			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_anytrue _null_ _null_ _null_ ));
! DESCR("aggregate final function");
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("bitwise-and smallint aggregate");
*************** DATA(insert OID = 3524 (  enum_smaller	P
*** 4205,4210 ****
--- 4299,4309 ----
  DESCR("smaller of two");
  DATA(insert OID = 3525 (  enum_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 3249 (  enum_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3250 (  enum_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 3526 (  max			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("maximum value of all enum input values");
  DATA(insert OID = 3527 (  min			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..43e4973 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern Datum array_upper(PG_FUNCTION_ARG
*** 206,212 ****
--- 206,214 ----
  extern Datum array_length(PG_FUNCTION_ARGS);
  extern Datum array_cardinality(PG_FUNCTION_ARGS);
  extern Datum array_larger(PG_FUNCTION_ARGS);
+ extern Datum array_larger_inv(PG_FUNCTION_ARGS);
  extern Datum array_smaller(PG_FUNCTION_ARGS);
+ extern Datum array_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
  extern Datum array_fill(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..c97c910 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum boolle(PG_FUNCTION_ARGS);
*** 121,126 ****
--- 121,130 ----
  extern Datum boolge(PG_FUNCTION_ARGS);
  extern Datum booland_statefunc(PG_FUNCTION_ARGS);
  extern Datum boolor_statefunc(PG_FUNCTION_ARGS);
+ extern Datum bool_accum(PG_FUNCTION_ARGS);
+ extern Datum bool_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum bool_alltrue(PG_FUNCTION_ARGS);
+ extern Datum bool_anytrue(PG_FUNCTION_ARGS);
  extern bool parse_bool(const char *value, bool *result);
  extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
  
*************** extern Datum enum_ge(PG_FUNCTION_ARGS);
*** 167,173 ****
--- 171,179 ----
  extern Datum enum_gt(PG_FUNCTION_ARGS);
  extern Datum enum_cmp(PG_FUNCTION_ARGS);
  extern Datum enum_smaller(PG_FUNCTION_ARGS);
+ extern Datum enum_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum enum_larger(PG_FUNCTION_ARGS);
+ extern Datum enum_larger_inv(PG_FUNCTION_ARGS);
  extern Datum enum_first(PG_FUNCTION_ARGS);
  extern Datum enum_last(PG_FUNCTION_ARGS);
  extern Datum enum_range_bounds(PG_FUNCTION_ARGS);
*************** extern Datum int42div(PG_FUNCTION_ARGS);
*** 241,249 ****
--- 247,259 ----
  extern Datum int4mod(PG_FUNCTION_ARGS);
  extern Datum int2mod(PG_FUNCTION_ARGS);
  extern Datum int2larger(PG_FUNCTION_ARGS);
+ extern Datum int2larger_inv(PG_FUNCTION_ARGS);
  extern Datum int2smaller(PG_FUNCTION_ARGS);
+ extern Datum int2smaller_inv(PG_FUNCTION_ARGS);
  extern Datum int4larger(PG_FUNCTION_ARGS);
+ extern Datum int4larger_inv(PG_FUNCTION_ARGS);
  extern Datum int4smaller(PG_FUNCTION_ARGS);
+ extern Datum int4smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int4and(PG_FUNCTION_ARGS);
  extern Datum int4or(PG_FUNCTION_ARGS);
*************** extern Datum float4abs(PG_FUNCTION_ARGS)
*** 347,358 ****
--- 357,372 ----
  extern Datum float4um(PG_FUNCTION_ARGS);
  extern Datum float4up(PG_FUNCTION_ARGS);
  extern Datum float4larger(PG_FUNCTION_ARGS);
+ extern Datum float4larger_inv(PG_FUNCTION_ARGS);
  extern Datum float4smaller(PG_FUNCTION_ARGS);
+ extern Datum float4smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float8abs(PG_FUNCTION_ARGS);
  extern Datum float8um(PG_FUNCTION_ARGS);
  extern Datum float8up(PG_FUNCTION_ARGS);
  extern Datum float8larger(PG_FUNCTION_ARGS);
+ extern Datum float8larger_inv(PG_FUNCTION_ARGS);
  extern Datum float8smaller(PG_FUNCTION_ARGS);
+ extern Datum float8smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float4pl(PG_FUNCTION_ARGS);
  extern Datum float4mi(PG_FUNCTION_ARGS);
  extern Datum float4mul(PG_FUNCTION_ARGS);
*************** extern Datum oidle(PG_FUNCTION_ARGS);
*** 501,507 ****
--- 515,523 ----
  extern Datum oidge(PG_FUNCTION_ARGS);
  extern Datum oidgt(PG_FUNCTION_ARGS);
  extern Datum oidlarger(PG_FUNCTION_ARGS);
+ extern Datum oidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum oidsmaller(PG_FUNCTION_ARGS);
+ extern Datum oidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum oidvectorin(PG_FUNCTION_ARGS);
  extern Datum oidvectorout(PG_FUNCTION_ARGS);
  extern Datum oidvectorrecv(PG_FUNCTION_ARGS);
*************** extern Datum tidgt(PG_FUNCTION_ARGS);
*** 707,713 ****
--- 723,731 ----
  extern Datum tidge(PG_FUNCTION_ARGS);
  extern Datum bttidcmp(PG_FUNCTION_ARGS);
  extern Datum tidlarger(PG_FUNCTION_ARGS);
+ extern Datum tidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum tidsmaller(PG_FUNCTION_ARGS);
+ extern Datum tidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum currtid_byreloid(PG_FUNCTION_ARGS);
  extern Datum currtid_byrelname(PG_FUNCTION_ARGS);
  
*************** extern Datum bpchargt(PG_FUNCTION_ARGS);
*** 730,736 ****
--- 748,756 ----
  extern Datum bpcharge(PG_FUNCTION_ARGS);
  extern Datum bpcharcmp(PG_FUNCTION_ARGS);
  extern Datum bpchar_larger(PG_FUNCTION_ARGS);
+ extern Datum bpchar_larger_inv(PG_FUNCTION_ARGS);
  extern Datum bpchar_smaller(PG_FUNCTION_ARGS);
+ extern Datum bpchar_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum bpcharlen(PG_FUNCTION_ARGS);
  extern Datum bpcharoctetlen(PG_FUNCTION_ARGS);
  extern Datum hashbpchar(PG_FUNCTION_ARGS);
*************** extern Datum text_le(PG_FUNCTION_ARGS);
*** 770,776 ****
--- 790,798 ----
  extern Datum text_gt(PG_FUNCTION_ARGS);
  extern Datum text_ge(PG_FUNCTION_ARGS);
  extern Datum text_larger(PG_FUNCTION_ARGS);
+ extern Datum text_larger_inv(PG_FUNCTION_ARGS);
  extern Datum text_smaller(PG_FUNCTION_ARGS);
+ extern Datum text_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum text_pattern_lt(PG_FUNCTION_ARGS);
  extern Datum text_pattern_le(PG_FUNCTION_ARGS);
  extern Datum text_pattern_gt(PG_FUNCTION_ARGS);
*************** extern Datum numeric_div_trunc(PG_FUNCTI
*** 980,986 ****
--- 1002,1010 ----
  extern Datum numeric_mod(PG_FUNCTION_ARGS);
  extern Datum numeric_inc(PG_FUNCTION_ARGS);
  extern Datum numeric_smaller(PG_FUNCTION_ARGS);
+ extern Datum numeric_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_larger(PG_FUNCTION_ARGS);
+ extern Datum numeric_larger_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_fac(PG_FUNCTION_ARGS);
  extern Datum numeric_sqrt(PG_FUNCTION_ARGS);
  extern Datum numeric_exp(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/cash.h b/src/include/utils/cash.h
index 3a491f9..4e184cc 100644
*** a/src/include/utils/cash.h
--- b/src/include/utils/cash.h
*************** extern Datum int2_mul_cash(PG_FUNCTION_A
*** 60,66 ****
--- 60,68 ----
  extern Datum cash_div_int2(PG_FUNCTION_ARGS);
  
  extern Datum cashlarger(PG_FUNCTION_ARGS);
+ extern Datum cashlarger_inv(PG_FUNCTION_ARGS);
  extern Datum cashsmaller(PG_FUNCTION_ARGS);
+ extern Datum cashsmaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum cash_words(PG_FUNCTION_ARGS);
  
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 622aa19..9f459ac 100644
*** a/src/include/utils/date.h
--- b/src/include/utils/date.h
*************** extern Datum date_cmp(PG_FUNCTION_ARGS);
*** 108,114 ****
--- 108,116 ----
  extern Datum date_sortsupport(PG_FUNCTION_ARGS);
  extern Datum date_finite(PG_FUNCTION_ARGS);
  extern Datum date_larger(PG_FUNCTION_ARGS);
+ extern Datum date_larger_inv(PG_FUNCTION_ARGS);
  extern Datum date_smaller(PG_FUNCTION_ARGS);
+ extern Datum date_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum date_mi(PG_FUNCTION_ARGS);
  extern Datum date_pli(PG_FUNCTION_ARGS);
  extern Datum date_mii(PG_FUNCTION_ARGS);
*************** extern Datum time_cmp(PG_FUNCTION_ARGS);
*** 168,174 ****
--- 170,178 ----
  extern Datum time_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_time(PG_FUNCTION_ARGS);
  extern Datum time_larger(PG_FUNCTION_ARGS);
+ extern Datum time_larger_inv(PG_FUNCTION_ARGS);
  extern Datum time_smaller(PG_FUNCTION_ARGS);
+ extern Datum time_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum time_mi_time(PG_FUNCTION_ARGS);
  extern Datum timestamp_time(PG_FUNCTION_ARGS);
  extern Datum timestamptz_time(PG_FUNCTION_ARGS);
*************** extern Datum timetz_cmp(PG_FUNCTION_ARGS
*** 195,201 ****
--- 199,207 ----
  extern Datum timetz_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_timetz(PG_FUNCTION_ARGS);
  extern Datum timetz_larger(PG_FUNCTION_ARGS);
+ extern Datum timetz_larger_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_smaller(PG_FUNCTION_ARGS);
+ extern Datum timetz_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_time(PG_FUNCTION_ARGS);
  extern Datum time_timetz(PG_FUNCTION_ARGS);
  extern Datum timestamptz_timetz(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..d102ccb 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8inc(PG_FUNCTION_ARGS);
*** 77,83 ****
--- 77,85 ----
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
+ extern Datum int8larger_inv(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
+ extern Datum int8smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int8and(PG_FUNCTION_ARGS);
  extern Datum int8or(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..85c0283 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum timestamp_cmp(PG_FUNCTION_A
*** 111,117 ****
--- 111,119 ----
  extern Datum timestamp_sortsupport(PG_FUNCTION_ARGS);
  extern Datum timestamp_hash(PG_FUNCTION_ARGS);
  extern Datum timestamp_smaller(PG_FUNCTION_ARGS);
+ extern Datum timestamp_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timestamp_larger(PG_FUNCTION_ARGS);
+ extern Datum timestamp_larger_inv(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS);
  extern Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS);
*************** extern Datum interval_finite(PG_FUNCTION
*** 147,153 ****
--- 149,157 ----
  extern Datum interval_cmp(PG_FUNCTION_ARGS);
  extern Datum interval_hash(PG_FUNCTION_ARGS);
  extern Datum interval_smaller(PG_FUNCTION_ARGS);
+ extern Datum interval_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum interval_larger(PG_FUNCTION_ARGS);
+ extern Datum interval_larger_inv(PG_FUNCTION_ARGS);
  extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
  extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
  extern Datum interval_justify_days(PG_FUNCTION_ARGS);
#134David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#129)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, Jan 23, 2014 at 1:57 PM, Florian Pflug <fgp@phlo.org> wrote:

On Jan23, 2014, at 01:17 , David Rowley <dgrowleyml@gmail.com> wrote:

On Wed, Jan 22, 2014 at 12:46 AM, Florian Pflug <fgp@phlo.org> wrote:

If you want to play with
this, I think the first step has to be to find a set of guarantees that
SUM(float) is supposed to meet. Currently, SUM(float) guarantees that

if the

summands all have the same sign, the error is bound by C * N, where C

is some

(machine-specific?) constant (about 1e-15 or so), and N is the number

of input

rows. Or at least so I think from looking at SUMs over floats in

descending

order, which I guess is the worst case. You could, for example, try to

see if

you can find a invertibility conditions which guarantees the same, but

allows

C to be larger. That would put a bound on the number of digits lost by

the new

SUM(float) compared to the old one.

I guess if the case is that normal (non-window) sum(float) results are

undefined

unless you add an order by to the aggregate then I guess there is no

possible

logic to put in for inverse transitions that will make them behave the

same as

an undefined behaviour.

Actually, if sum(float) was really undefined, it'd be *very* easy to
provide an
inverse transition function - just make it a no-op. Heck, you could then
even
make the forward transition function a no-op, since the very definition of
"undefined behaviour" is "result can be anything, including setting your
house
on fire". The point is, it's *not* undefined - it's just imprecise. And the
imprecision can even be quantified, it just depends on the number of
input rows (the equal-sign requirement is mostly there to make
"imprecision"
equivalent to "relative error").

My apologies, I meant to use the term nondeterministic rather than
undefined. There's really not any need that I can see to turn things silly
here.

My point was more that since sum(float) can give different results if it
used an index scan rather than a seq scan, trying to get the inverse
transition to match something that gives varying results sounds like a
tricky task to take on.

If it seems sound enough, then I may implement it in C to see how much
overhead it adds to forward aggregation for floating point types, but

even

if it did add too much overhead to forward aggregation it might be

worth

allowing aggregates to have 2 forward transition functions and if the

2nd

one exists then it could be used in windowing functions where the

frame

does not have "unbounded following".

I don't think adding yet another type of aggregation function is the
solution here.

Why not?

I thought, if in the cases where we need to burden the forward transition
functions with extra code to make inverse transitions possible, then why
not have an extra transition function so that it does not slow down

normal

aggregation?

My testing of sum(numeric) last night does not show any slow down by that
extra dscale tracking code that was added, but if it did then I'd be

thinking

seriously about 2 forward transition functions to get around the problem.
I don't understand what would be wrong with that. The core code could

just

make use of the 2nd function if it existed and inverse transitions were
thought to be required. i.e in a window context and does not
have UNBOUNDED PRECEDING.

First, every additional function increases the maintenance burden, and at
some point the expected benefit simply isn't worth it. IMHO at least.

There's little point in arguing for this as we've managed to narrow any
forward transition over head to background noise so far, my point more was
if there was no other way to maintain performance and have an inverse
transition function that this may be a solution to that problem.

Secondly, you'd really need a second aggregate definition - usually, the
non-invertible aggregate will get away with a much smaller state
representation.
Aggregates with state type "internal" could hack their way around that by
simply not using the additional parts of their state structure, but e.g.
plpgsql aggregates would still incur overhead due to repeated copying of
a unnecessary large state value. If it's possible to represent the
forward-only but not the invertible state state with a copy-by-value type,
that overhead could easily be a large part of the total overhead you paid
for having an inverse transition function.

I had imagined they keep the same state type and don't use any extra
variables that are defined for the forward transition function that is
invertible.

And lastly, we could achieve much of the benefit of a second transition
function by simply telling the forward transition function whether to
expect inverse transition function calls. We already expose things like
the aggregation memory context to C-language transition functions, so the
basic mechanism is already there. In fact, I pondered whether to do this -
but then figured I'd leave it to however needs that facility first. Though
it wouldn't be much code - basically, setting a flag in the WindowAggState,
and exporting a function to be used by aggregates to read it, similar
to what AggCheckCallContext does.

Sounds like a good idea, but what would the solution aggregate transition
functions that are not written in C?

Show quoted text

best regards,
Florian Pflug

#135Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#134)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

David Rowley <dgrowleyml@gmail.com> writes:

My point was more that since sum(float) can give different results if it
used an index scan rather than a seq scan, trying to get the inverse
transition to match something that gives varying results sounds like a
tricky task to take on.

This is just a variant of the same excuse we heard before. The question
is not whether sum(float8) can give bad results; the question is whether
we are going to break applications that are using it carefully (ie with
an appropriate ORDER BY) and expecting to get good results.

Secondly, you'd really need a second aggregate definition - usually, the
non-invertible aggregate will get away with a much smaller state
representation.

Yeah. I think the idea of multiple transition functions in a single
aggregate definition is pretty broken to start with, but the likelihood
that they couldn't share aggregate state types puts it completely beyond
sanity. We're not going to start inventing "stype2" etc.

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

#136Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#134)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan25, 2014, at 09:50 , David Rowley <dgrowleyml@gmail.com> wrote:

On Thu, Jan 23, 2014 at 1:57 PM, Florian Pflug <fgp@phlo.org> wrote:

On Jan23, 2014, at 01:17 , David Rowley <dgrowleyml@gmail.com> wrote:

On Wed, Jan 22, 2014 at 12:46 AM, Florian Pflug <fgp@phlo.org> wrote:

If you want to play with
this, I think the first step has to be to find a set of guarantees that
SUM(float) is supposed to meet. Currently, SUM(float) guarantees that if the
summands all have the same sign, the error is bound by C * N, where C is some
(machine-specific?) constant (about 1e-15 or so), and N is the number of input
rows. Or at least so I think from looking at SUMs over floats in descending
order, which I guess is the worst case. You could, for example, try to see if
you can find a invertibility conditions which guarantees the same, but allows
C to be larger. That would put a bound on the number of digits lost by the new
SUM(float) compared to the old one.

I guess if the case is that normal (non-window) sum(float) results are undefined
unless you add an order by to the aggregate then I guess there is no possible
logic to put in for inverse transitions that will make them behave the same as
an undefined behaviour.

Actually, if sum(float) was really undefined, it'd be *very* easy to provide an
inverse transition function - just make it a no-op. Heck, you could then even
make the forward transition function a no-op, since the very definition of
"undefined behaviour" is "result can be anything, including setting your house
on fire". The point is, it's *not* undefined - it's just imprecise. And the
imprecision can even be quantified, it just depends on the number of
input rows (the equal-sign requirement is mostly there to make "imprecision"
equivalent to "relative error").

My apologies, I meant to use the term nondeterministic rather than undefined.
There's really not any need that I can see to turn things silly here.

I wasn't trying to be absurd, I was trying to get a point across.

My point was more that since sum(float) can give different results if it used
an index scan rather than a seq scan, trying to get the inverse transition to
match something that gives varying results sounds like a tricky task to take on.

You don't have to match it digit-by-digit! In that I fully agree with Kevin -
floats are *always* an approximation, and so is thus SUM(float). Summarization
order is BTW not the only source of non-determinism for SUM(float) - the exact
result can very between architectures, and for some architectures between
compilers. (Intel is one of these, due to their 80-bit extended precision format
that gets truncated to 64-bit when stored to main memory).

But in a large number of cases, they won't vary by *much*, which is *the* reason
why SUM(float) is *not* totally useless. And reasoning about SUM(float) which
ignores that by simply calling it "non-deterministic", "undefined" or whatever,
without any quantification of the possible error, has thus zero chance of
leading to interesting conclusions.

Secondly, you'd really need a second aggregate definition - usually, the
non-invertible aggregate will get away with a much smaller state representation.
Aggregates with state type "internal" could hack their way around that by
simply not using the additional parts of their state structure, but e.g.
plpgsql aggregates would still incur overhead due to repeated copying of
a unnecessary large state value. If it's possible to represent the
forward-only but not the invertible state state with a copy-by-value type,
that overhead could easily be a large part of the total overhead you paid
for having an inverse transition function.

I had imagined they keep the same state type and don't use any extra variables
that are defined for the forward transition function that is invertible.

Yeah, and the above explains that at least for non-C-language aggregates,
passing around that unnecessarily large state may very well prove to be the
source of a large part, if not almost all, of the overhead. So having just
a separate forward transition function will buy you almost nothing or some
cases.

I just tried this. I defined two aggregates mymax(int4) and myfastmax(int4),
both with just a forward transition function, both SQL-language functions.
mymax uses a composite type for the state containing an int4 field holding
the current maximum, and a dummy int8 field. myfastmax uses a plain int4
for the state, holding the current maximum. Both forward transition
functions essentially do

case when current_max > v then current_max else v end

On my laptop, computing the maximum of 1e6 rows takes about 4.5 seconds with
myfastmax and 7.8 seconds with mymax. If make mymax's transition function
increment the dummy field on every transition, the time increases from 7.8
to 8.2 seconds. So here, using a composite type for the state accounts for
about 3.3 seconds, or 40%, of the total runtime of 9 seconds, whereas the
increment costs about 0.4 seconds or 5% of the total runtime.

And lastly, we could achieve much of the benefit of a second transition
function by simply telling the forward transition function whether to
expect inverse transition function calls. We already expose things like
the aggregation memory context to C-language transition functions, so the
basic mechanism is already there. In fact, I pondered whether to do this -
but then figured I'd leave it to however needs that facility first. Though
it wouldn't be much code - basically, setting a flag in the WindowAggState,
and exporting a function to be used by aggregates to read it, similar
to what AggCheckCallContext does.

Sounds like a good idea, but what would the solution aggregate transition
functions that are not written in C?

See above - non-C aggregates are exactly the ones that benefit least from
a specialized non-invertible forward transition function, because the have
to serialize and deserialize the state type on every transition...

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#137David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#133)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sat, Jan 25, 2014 at 3:21 PM, Florian Pflug <fgp@phlo.org> wrote:

On Jan24, 2014, at 08:47 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
The invtrans_minmax patch doesn't contain any patches yet - David, could
you provide some for these functions, and also for bool_and and bool_or?
We don't need to test every datatype, but a few would be nice.

I've added a few regression tests for min, min and bool_or and bool_and.
I've pushed these up to here:

https://github.com/david-rowley/postgres/commits/invtrans_minmax

I did wonder if you'd want to see uses of FILTER in there as I'm thinking
that should really be covered by the core patch and the tests here really
should just be checking the inverse transition functions for min and max.

As for the data types tested, I ended just adding tests for int and text
for min and max. Let me know if you think that more should be tested.

As for bool_or and bool_and. I didn't think there was much extra that would
need tested after I added 1 test. It's pretty simple code and adding
anything extra seems like it would be testing something else.

Regards

David Rowley

#138Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#137)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan26, 2014, at 00:24 , David Rowley <dgrowleyml@gmail.com> wrote:

On Sat, Jan 25, 2014 at 3:21 PM, Florian Pflug <fgp@phlo.org> wrote:
On Jan24, 2014, at 08:47 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
The invtrans_minmax patch doesn't contain any patches yet - David, could
you provide some for these functions, and also for bool_and and bool_or?
We don't need to test every datatype, but a few would be nice.

I've added a few regression tests for min, min and bool_or and bool_and.
I've pushed these up to here:

https://github.com/david-rowley/postgres/commits/invtrans_minmax

OK, I've pushed this to github. I haven't produced new patches yet - I
think it's probably better to wait for a few things to pile up, to make
this less of a moving target. But ultimately, this is up to Dean - Dean,
I'll do whatever makes your life as a reviewer easier.

I did wonder if you'd want to see uses of FILTER in there as I'm thinking
that should really be covered by the core patch and the tests here really
should just be checking the inverse transition functions for min and max.

I don't mind the FILTER - when this gets committed, the distinction between
what was in which patch goes away anyway. What I did realize when merging
this is that we currently don't tests the case of a window which lies
fully after the current row (i.e. BETWEEN N FOLLOWING AND m FOLLOWING). We
do test BETWEEN n PRECEDING AND m PRECEDING, because the array_agg and
string_agg tests do that. So maybe some of the MIN/MAX or arithmetic tests
could exercise that case?

As for the data types tested, I ended just adding tests for int and text
for min and max. Let me know if you think that more should be tested.

That's sufficient for the regression tests, I think.

As for bool_or and bool_and. I didn't think there was much extra that would
need tested after I added 1 test. It's pretty simple code and adding anything
extra seems like it would be testing something else.

Sounds fine to me.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#139Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#133)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 25 January 2014 02:21, Florian Pflug <fgp@phlo.org> wrote:

I've now split this up into

invtrans_base: Basic machinery plus tests with SQL-language aggregates
invtrans_arith: COUNT(),SUM(),AVG(),STDDEV() and the like
invtrans_minmax: MIN(),MAX(),BOOL_AND(),BOOL_OR()
invtrans_collecting: ARRAY_AGG(), STRING_AGG()
invtrans_docs: Documentation

Thanks. That makes it a bit easier to review, and hopefully easier to commit.

The thing that I'm currently trying to get my head round is the
null/not null, strict/not strict handling in this patch, which I find
a bit odd, particularly when transfn and inv_transfn have different
strictness settings:

strict transfn vs non-strict inv_transfn
----------------------------------------

This case is explicitly forbidden, both in CREATE AGGREGATE and in the
executor. To me, that seems overly restrictive --- if transfn is
strict, then you know for sure that no NULL values are added to the
aggregate state, so surely it doesn't matter whether or not
inv_transfn is strict. It will never be asked to remove NULL values.

I think there are definite use-cases where a user might want to use a
pre-existing function as the inverse transition function, so it seems
harsh to force them to wrap it in a strict function in this case.

AFAICS, the code in advance/retreat_windowaggregate should just work
if those checks are removed.

non-strict transfn vs strict inv_transfn
----------------------------------------

At first this seems as though it must be an error --- the forwards
transition function allows NULL values into the aggregate state, and
the inverse transition function is strict, so it cannot remove them.

But actually what the patch is doing in this case is treating the
forwards transition function as partially strict --- it won't be
passed NULL values (at least in a window context), but it may be
passed a NULL state in order to build the initial state when the first
non-NULL value arrives.

It looks like this behaviour is intended to support aggregates that
ignore NULL values, but cannot actually be made to have a strict
transition function. I think typically this is because the aggregate's
initial state is NULL and it's state type differs from the type of the
values being aggregated, and so can't be automatically created from
the first non-NULL value.

That all seems quite ugly though, because now you have a transition
function that is not strict, which is passed NULL values when used in
a normal aggregate context, and not passed NULL values when used in a
window context (whether sliding or not), except for the NULL state for
the first non-NULL value.

I'm not sure if there is a better way to do it though. If we disallow
this case, these aggregates would have to use non-strict functions for
both the forward and inverse transition functions, which means they
would have to implement their own non-NULL value counting.
Alternatively, allowing strict transition functions for these
aggregates would require that we provide some other way to initialise
the state from the first non-NULL input, such as a new initfn.

Thoughts?

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#140Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#139)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan27, 2014, at 23:28 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

strict transfn vs non-strict inv_transfn
----------------------------------------

This case is explicitly forbidden, both in CREATE AGGREGATE and in the
executor. To me, that seems overly restrictive --- if transfn is
strict, then you know for sure that no NULL values are added to the
aggregate state, so surely it doesn't matter whether or not
inv_transfn is strict. It will never be asked to remove NULL values.

I think there are definite use-cases where a user might want to use a
pre-existing function as the inverse transition function, so it seems
harsh to force them to wrap it in a strict function in this case.

AFAICS, the code in advance/retreat_windowaggregate should just work
if those checks are removed.

True. It didn't use to in earlier version of the patch because the advance
and retreat functions looked at the strict settings directly, but now that
there's an ignore_nulls flag in the executor node, the only requirement
should be that ignore_nulls is set if either of the transition functions
is strict.

I'm not sure that the likelihood of someone wanting to combine a strict
forward with a non-strict inverse function is very hight, but neither

non-strict transfn vs strict inv_transfn
----------------------------------------

At first this seems as though it must be an error --- the forwards
transition function allows NULL values into the aggregate state, and
the inverse transition function is strict, so it cannot remove them.

But actually what the patch is doing in this case is treating the
forwards transition function as partially strict --- it won't be
passed NULL values (at least in a window context), but it may be
passed a NULL state in order to build the initial state when the first
non-NULL value arrives.

Exactly.

It looks like this behaviour is intended to support aggregates that
ignore NULL values, but cannot actually be made to have a strict
transition function. I think typically this is because the aggregate's
initial state is NULL and it's state type differs from the type of the
values being aggregated, and so can't be automatically created from
the first non-NULL value.

Yes. I added this because the alternative would haven been to count
non-NULL values in most of the existing SUM() aggregates.

That all seems quite ugly though, because now you have a transition
function that is not strict, which is passed NULL values when used in
a normal aggregate context, and not passed NULL values when used in a
window context (whether sliding or not), except for the NULL state for
the first non-NULL value.

Ugh, true. Clearly, nodeAgg.c needs to follow what nodeWindowAgg.c does
and skip NULL inputs if the aggregate has a strict inverse transition
function. That fact that we never actually *call* the inverse doesn't
matter. Will fix that once we decided on the various strictness issues.

I'm not sure if there is a better way to do it though. If we disallow
this case, these aggregates would have to use non-strict functions for
both the forward and inverse transition functions, which means they
would have to implement their own non-NULL value counting.
Alternatively, allowing strict transition functions for these
aggregates would require that we provide some other way to initialise
the state from the first non-NULL input, such as a new initfn.

I'm not sure an initfn would really help. It would allow us to return
to the initial requirement that the strict settings match - but you
already deem the check that the forward transition function can only
be strict if the inverse is also overly harsh, so would that really be
an improvement? It's also cost us an additional pg_proc entry per aggregate.

Another idea would be to have an explicit nulls=ignore|process option
for CREATE AGGREGATE. If nulls=process and either of the transition
functions are strict, we could either error out, or simply do what
normal functions calls do and pretend they return NULL for NULL inputs.
Not sure how the rule that forward transition functions may not return
NULL if there's an inverse transition function would fit in if we do
the latter, though.

The question is - is it worth it the effort to add that flag?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#141Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#140)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 28 January 2014 20:16, Florian Pflug <fgp@phlo.org> wrote:

On Jan27, 2014, at 23:28 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

strict transfn vs non-strict inv_transfn
----------------------------------------

This case is explicitly forbidden, both in CREATE AGGREGATE and in the
executor. To me, that seems overly restrictive --- if transfn is
strict, then you know for sure that no NULL values are added to the
aggregate state, so surely it doesn't matter whether or not
inv_transfn is strict. It will never be asked to remove NULL values.

I think there are definite use-cases where a user might want to use a
pre-existing function as the inverse transition function, so it seems
harsh to force them to wrap it in a strict function in this case.

AFAICS, the code in advance/retreat_windowaggregate should just work
if those checks are removed.

True. It didn't use to in earlier version of the patch because the advance
and retreat functions looked at the strict settings directly, but now that
there's an ignore_nulls flag in the executor node, the only requirement
should be that ignore_nulls is set if either of the transition functions
is strict.

I'm not sure that the likelihood of someone wanting to combine a strict
forward with a non-strict inverse function is very hight, but neither

Me neither, but the checks to forbid it aren't adding anything, and I
think it's best to make it as flexible as possible.

non-strict transfn vs strict inv_transfn
----------------------------------------

At first this seems as though it must be an error --- the forwards
transition function allows NULL values into the aggregate state, and
the inverse transition function is strict, so it cannot remove them.

But actually what the patch is doing in this case is treating the
forwards transition function as partially strict --- it won't be
passed NULL values (at least in a window context), but it may be
passed a NULL state in order to build the initial state when the first
non-NULL value arrives.

Exactly.

It looks like this behaviour is intended to support aggregates that
ignore NULL values, but cannot actually be made to have a strict
transition function. I think typically this is because the aggregate's
initial state is NULL and it's state type differs from the type of the
values being aggregated, and so can't be automatically created from
the first non-NULL value.

Yes. I added this because the alternative would haven been to count
non-NULL values in most of the existing SUM() aggregates.

That all seems quite ugly though, because now you have a transition
function that is not strict, which is passed NULL values when used in
a normal aggregate context, and not passed NULL values when used in a
window context (whether sliding or not), except for the NULL state for
the first non-NULL value.

Ugh, true. Clearly, nodeAgg.c needs to follow what nodeWindowAgg.c does
and skip NULL inputs if the aggregate has a strict inverse transition
function. That fact that we never actually *call* the inverse doesn't
matter. Will fix that once we decided on the various strictness issues.

Yuk!

I'm not sure if there is a better way to do it though. If we disallow
this case, these aggregates would have to use non-strict functions for
both the forward and inverse transition functions, which means they
would have to implement their own non-NULL value counting.
Alternatively, allowing strict transition functions for these
aggregates would require that we provide some other way to initialise
the state from the first non-NULL input, such as a new initfn.

I'm not sure an initfn would really help. It would allow us to return
to the initial requirement that the strict settings match - but you
already deem the check that the forward transition function can only
be strict if the inverse is also overly harsh, so would that really be
an improvement? It's also cost us an additional pg_proc entry per aggregate.

Another idea would be to have an explicit nulls=ignore|process option
for CREATE AGGREGATE. If nulls=process and either of the transition
functions are strict, we could either error out, or simply do what
normal functions calls do and pretend they return NULL for NULL inputs.
Not sure how the rule that forward transition functions may not return
NULL if there's an inverse transition function would fit in if we do
the latter, though.

The question is - is it worth it the effort to add that flag?

Yeah, I thought about a flag too. I think that could work quite nicely.

Basically where I'm coming from is trying to make this as flexible as
possible, without pre-judging the kinds of aggregates that users may
want to add.

One use-case I had in mind upthread was suppose you wanted to write a
custom version of array_agg that only collected non-NULL values. With
such a flag, that would be trivial, but with the current patch you'd
have to (count-intuitively) wrap the inverse transition function in a
strict function.

Another use-case I can imagine is suppose you wanted a custom version
of sum() that returned NULL if any of the input values were NULL. If
you knew that most of your data was non-NULL, you might still wish to
benefit from an inverse transition function. Right now the patch won't
allow that because of the error in advance_windowaggregate(), but
possibly that could be relaxed by forcing a restart in that case. If
I've understood correctly that should give similar to current
performance if NULLs are present, and enhanced performance as the
window moved over non-NULL data.

In that second case, it would also be nice if you could simply re-use
the existing sum forward and inverse transition functions, with a
different null-handling flag.

Also, in cases where the forwards transition function cannot be made
strict (e.g., it's state type is internal) and there is no inverse
transition function, there might be a small performance gain to be had
from not calling the transition function with NULL values, rather than
have it ignore them.

So I think an ignore-nulls flag would have real benefits, as well as
being a cleaner design than relying on a strict inverse transition
function.

What do others think?

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#142Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#141)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan29, 2014, at 09:59 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 28 January 2014 20:16, Florian Pflug <fgp@phlo.org> wrote:

On Jan27, 2014, at 23:28 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

This case is explicitly forbidden, both in CREATE AGGREGATE and in the
executor. To me, that seems overly restrictive --- if transfn is
strict, then you know for sure that no NULL values are added to the
aggregate state, so surely it doesn't matter whether or not
inv_transfn is strict. It will never be asked to remove NULL values.

I think there are definite use-cases where a user might want to use a
pre-existing function as the inverse transition function, so it seems
harsh to force them to wrap it in a strict function in this case.

I'm not sure that the likelihood of someone wanting to combine a strict
forward with a non-strict inverse function is very hight, but neither

Me neither, but the checks to forbid it aren't adding anything, and I
think it's best to make it as flexible as possible.

Ok.

Another idea would be to have an explicit nulls=ignore|process option
for CREATE AGGREGATE. If nulls=process and either of the transition
functions are strict, we could either error out, or simply do what
normal functions calls do and pretend they return NULL for NULL inputs.
Not sure how the rule that forward transition functions may not return
NULL if there's an inverse transition function would fit in if we do
the latter, though.

The question is - is it worth it the effort to add that flag?

One use-case I had in mind upthread was suppose you wanted to write a
custom version of array_agg that only collected non-NULL values. With
such a flag, that would be trivial, but with the current patch you'd
have to (count-intuitively) wrap the inverse transition function in a
strict function.

I'd be more convinced by that if doing so was actually possible for
non-superusers. But it isn't, because the aggregate uses "internal" as
it's state type and it's thus entirely up to the user to not crash the
database by mixing transfer and final functions with incompatible
state data. Plus, instead of creating a custom aggregate, one can just
use a FILTER clause to get rid of the NULL values.

Another use-case I can imagine is suppose you wanted a custom version
of sum() that returned NULL if any of the input values were NULL. If
you knew that most of your data was non-NULL, you might still wish to
benefit from an inverse transition function. Right now the patch won't
allow that because of the error in advance_windowaggregate(), but
possibly that could be relaxed by forcing a restart in that case.

That's not really true - that patch only forbids that if you insist on
representing the state "i have seen a NULL input" with a NULL state value.
But if you instead just count the number of NULLS in your transition
functions, all you need to do is to have your final function return NULL
if that count is not zero.

If I've understood correctly that should give similar to current
performance if NULLs are present, and enhanced performance as the
window moved over non-NULL data.

Exactly - and this makes defining a NULL-sensitive SUM() this way
rather silly - a simple counter has very nearly zero overhead, and avoids
all rescans.

In that second case, it would also be nice if you could simply re-use
the existing sum forward and inverse transition functions, with a
different null-handling flag.

Even if we had a nulls=process|ignore flag, SUM's transition functions
would still need to take that use-case into account explicitly to make
this work - at the very least, the forward transition function would
need to return NULL if the input is NULL instead of just skipping it as
it does now. But that would leads to completely unnecessary rescans, so
what we actually ought to do then is to make it track whether there have
been NULL inputs and make the finalfunc return NULL in this case. Which
would border on ironic, since the whole raison d'etre for this is to
*avoid* spreading NULL-counting logic around...

Plus, to make this actually useable, we'd have to document this, and tell
people how to define such a SUM aggregate. But I don't want to go there -
we really mustn't encourage people to mix-and-match built-in aggregates
with state type "internal", since whether they work or crash depends
on factors outside the control of the user.

And finally, you can get that behaviour quite easily by doing

CASE WHEN bool_and(input IS NOT NULL) whatever_agg(input) ELSE NULL END

So I think an ignore-nulls flag would have real benefits, as well as
being a cleaner design than relying on a strict inverse transition
function.

The more I think about this, the less convinced I am. In fact, I'm
currently leaning towards just forbidding non-strict forward transition
function with strict inverses, and adding non-NULL counters to the
aggregates that then require them. It's really only the SUM() aggregates
that are affected by this, I think.

For an argument in favour of that, look at how we handle *non*-NULL
initial states. For these, even aggregates with nulls=ignore would
require some tracking, because otherwise they'd simply return the
initial state if the input contained no non-NULL value. Which quite
probably isn't what you'd want - you'd usually want to return NULL
in this case (as all the built-in aggregates do).

Currently, all built-in aggregates with non-NULL initial states compute
an expected value of some kind (average, std. deviation, regressions),
and thus need to count the number of inputs anyway. But that's just
coincidence - if it wasn't for state type "internal", having SUM start
from 0 instead of NULL would be entirely reasonable.

So if we have a nulls=ignore flag, we ought to have an nullif=only_nulls
flag too to cover all the ground. But at that point, just making this
the transition function's responsibility seems more sensible.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#143Florian Pflug
fgp@phlo.org
In reply to: Florian Pflug (#142)
4 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Jan29, 2014, at 13:45 , Florian Pflug <fgp@phlo.org> wrote:

In fact, I'm
currently leaning towards just forbidding non-strict forward transition
function with strict inverses, and adding non-NULL counters to the
aggregates that then require them. It's really only the SUM() aggregates
that are affected by this, I think.

I finally got around to doing that, and the results aren't too bad. The
attached patches required that the strictness settings of the forward and
reverse transition functions agree, and employ exactly the same NULL-skipping
logic we always had.

The only aggregates seriously affected by that change were SUM(int2) and
SUM(int4).

The SUM, AVG and STDDEV aggregates which use NumericAggState where
already mostly prepared for this - all they required were a few adjustments
to correctly handle the last non-NULL, non-NaN input being removed, and a few
additional PG_ARGISNULL calls for the inverse transition functions since they're
now non-strict. I've also modified them to unconditionally allocate the state
at the first call, instead upon seeing the first non-NULL input, but that isn't
strictly required. But without that, the state can have three classes of values -
SQL-NULL, NULL pointer and valid pointer, and that's just confusing...

SUM(int2) and SUM(int4) now simply use the same transition functions as
AVG(int2) and AVG(int4), which use an int8 array to track the sum of the inputs
and the number of inputs, plus a new final function int2int4_sum(). Previously,
they used a single int8 as their state type.

Since I was touching the code anyway, I removed some unnecessary inverse
transition functions - namely, int8_avg_accum_inv and numeric_avg_accum_inv. These
are completely identical to their non-avg cousins - the only difference between
the corresponding forward transition functions is whether they request computation
of sumX2 (i.e. the sum of squares of the inputs) or not.

I haven't yet updated the docs - it'll do that if and when there's consensus
about whether this is the way to go or not.

best regards,
Florian Pflug

Attachments:

invtrans_strictstrict_arith_164226.patchapplication/octet-stream; name=invtrans_strictstrict_arith_164226.patchDownload
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..e62f2a3 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8inc(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 717,767 ----
  	}
  }
  
+ Datum
+ int8dec(PG_FUNCTION_ARGS)
+ {
+ 	/*
+ 	 * When int8 is pass-by-reference, we provide this special case to avoid
+ 	 * palloc overhead for COUNT(): when called as an inverse transition
+ 	 * aggregate, we know that the argument is modifiable local storage,
+ 	 * so just update it in-place. (If int8 is pass-by-value, then of course
+ 	 * this is useless as well as incorrect, so just ifdef it out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *arg = (int64 *) PG_GETARG_POINTER(0);
+ 		int64		result;
+ 
+ 		result = *arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && *arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		*arg = result;
+ 		PG_RETURN_POINTER(arg);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		/* Not called as an aggregate, so just do it the dumb way */
+ 		int64		arg = PG_GETARG_INT64(0);
+ 		int64		result;
+ 
+ 		result = arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		PG_RETURN_INT64(result);
+ 	}
+ }
+ 
+ 
  /*
   * These functions are exactly like int8inc but are used for aggregates that
   * count only non-null values.	Since the functions are declared strict,
*************** int8inc_any(PG_FUNCTION_ARGS)
*** 733,738 ****
--- 778,789 ----
  }
  
  Datum
+ int8inc_any_inv(PG_FUNCTION_ARGS)
+ {
+ 	return int8dec(fcinfo);
+ }
+ 
+ Datum
  int8inc_float8_float8(PG_FUNCTION_ARGS)
  {
  	return int8inc(fcinfo);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..53e7624 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_float4(PG_FUNCTION_ARGS)
*** 2479,2486 ****
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	bool		isNaN;			/* true if any processed number was NaN */
  	MemoryContext agg_context;	/* context we're calculating in */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
--- 2479,2490 ----
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	int			maxScale;		/* stores the maximum scale seen so far. */
! 	int64		maxScaleCount;  /* tracks the number of values we've
! 								 * seen with the maximum scale */
! 
  	MemoryContext agg_context;	/* context we're calculating in */
+ 	int64		NaNcount;		/* Count of NaN values that are aggregated */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
*************** makeNumericAggState(FunctionCallInfo fci
*** 2505,2510 ****
--- 2509,2518 ----
  	state = (NumericAggState *) palloc0(sizeof(NumericAggState));
  	state->calcSumX2 = calcSumX2;
  	state->agg_context = agg_context;
+ 	state->NaNcount = 0;
+ 
+ 	state->maxScale = 0;
+ 	state->maxScaleCount = 0;
  
  	MemoryContextSwitchTo(old_context);
  
*************** do_numeric_accum(NumericAggState *state,
*** 2522,2536 ****
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (state->isNaN || NUMERIC_IS_NAN(newval))
  	{
! 		state->isNaN = true;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
--- 2530,2558 ----
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (NUMERIC_IS_NAN(newval))
  	{
! 		state->NaNcount++;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
+ 	/*
+ 	 * Track the highest scale that we've seen as if we ever perform an inverse
+ 	 * transition and remove the last numeric with the highest scale that we've
+ 	 * seen then we can no longer perform inverse transitions without risking
+ 	 * having the wrong dscale in the result value.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 		state->maxScaleCount++;
+ 	else if (X.dscale > state->maxScale)
+ 	{
+ 		state->maxScale = X.dscale;
+ 		state->maxScaleCount = 1;
+ 	}
+ 
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2541,2553 ****
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N++ > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
  	}
  	else
  	{
--- 2563,2577 ----
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
+ 
+ 		state->N++;
  	}
  	else
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2556,2567 ****
--- 2580,2692 ----
  
  		if (state->calcSumX2)
  			set_var_from_var(&X2, &(state->sumX2));
+ 
+ 		state->N = 1;
  	}
  
  	MemoryContextSwitchTo(old_context);
  }
  
  /*
+  * do_numeric_discard
+  * Attempts to remove a value from the aggregated state.
+  * If the value cannot be removed then the function will return false, the
+  * possible reasons for failing are described below.
+  *
+  * If we aggregate the values 1.01 and 2 then the result will be 3.01. If we
+  * are then asked to un-aggregate the 1.01 then we must reject this case as we
+  * won't be able to tell what the new aggregated value's dscale should be.
+  * We can't return 2.00 (dscale = 2) as we really should return just 2, but
+  * since we're not tracking any previous highest scales then we must just fail
+  * to perform the inverse transition and just return false.
+  *
+  * Values that are no longer aggregated should not be able to effect the dscale
+  * of the result of the values that *are* still aggregated.
+  *
+  * Note it may be better to track the number of times we've aggregated a
+  * numeric with each scale, then if we ever remove final highest scaled value
+  * then we can step the result's dscale down to the next highest value. This is
+  * perhaps slightly more work than we can afford to do here, but doing it this
+  * way would mean that we could always perform the inverse transition.
+  */
+ static bool
+ do_numeric_discard(NumericAggState *state, Numeric newval)
+ {
+ 	NumericVar	X;
+ 	NumericVar	X2;
+ 	MemoryContext old_context;
+ 
+ 	/* result is NaN if any processed number is NaN */
+ 	if (NUMERIC_IS_NAN(newval))
+ 	{
+ 		state->NaNcount--;
+ 		return true;
+ 	}
+ 
+ 	/* load processed number in short-lived context */
+ 	init_var_from_num(newval, &X);
+ 
+ 	/*
+ 	 * state->sumX's dscale matches the maximum dscale of any of the inputs
+ 	 * Removing the last input with that dscale would require us to recompute
+ 	 * the maximum dscale of the *remaining* inputs, which we cannot do unless
+ 	 * no more non-NaN inputs remain at all. So we report a failure instead,
+ 	 * and force the aggregation to be redone from scratch.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 	{
+ 		if (state->maxScaleCount > 1)
+ 		{
+ 			/* Some remaining inputs have same dscale */
+ 			--state->maxScaleCount;
+ 		}
+ 		else if (state->N == 1)
+ 		{
+ 			/* No remaining non-NaN inputs at all */
+ 			state->maxScale = 0;
+ 			state->maxScaleCount = 0;
+ 		}
+ 		else
+ 		{
+ 			/* No remaining inputs have same dscale */
+ 			return false;
+ 		}
+ 	}
+ 
+ 	/* if we need X^2, calculate that in short-lived context */
+ 	if (state->calcSumX2)
+ 	{
+ 		init_var(&X2);
+ 		mul_var(&X, &X, &X2, X.dscale * 2);
+ 	}
+ 
+ 	/* The rest of this needs to work in the aggregate context */
+ 	old_context = MemoryContextSwitchTo(state->agg_context);
+ 
+ 	if (state->N > 1)
+ 	{
+ 		sub_var(&(state->sumX), &X, &(state->sumX));
+ 		if (state->calcSumX2)
+ 			sub_var(&(state->sumX2), &X2, &(state->sumX2));
+ 		state->N--;
+ 	}
+ 	else if (state->N == 1)
+ 	{
+ 		/* Sums will be reset by next call to do_numeric_accum */
+ 		state->N = 0;
+ 	}
+ 	else
+ 	{
+ 		MemoryContextSwitchTo(old_context);
+ 		elog(ERROR, "cannot discard more values than were accumulated");
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 	return true;
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that require sumX2.
   */
  Datum
*************** numeric_accum(PG_FUNCTION_ARGS)
*** 2571,2588 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
--- 2696,2734 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, true);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * Generic inverse transition function for numeric aggregates
+  */
+ Datum
+ numeric_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL)
+ 		elog(ERROR, "numeric_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		/* Can we perform an inverse transition? if not return NULL. */
+ 		if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 			PG_RETURN_NULL();
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
+ 
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
*************** numeric_avg_accum(PG_FUNCTION_ARGS)
*** 2593,2606 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, false);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
- 	}
  
  	PG_RETURN_POINTER(state);
  }
--- 2739,2750 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, false);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  
  	PG_RETURN_POINTER(state);
  }
*************** int2_accum(PG_FUNCTION_ARGS)
*** 2621,2637 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2765,2780 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int4_accum(PG_FUNCTION_ARGS)
*** 2645,2661 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2788,2803 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2669,2686 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
! 		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
--- 2811,2913 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
+ 		do_numeric_accum(state, newval);
+ 	}
  
! 	PG_RETURN_POINTER(state);
! }
  
! 
! /*
!  * int2_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int2_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int4_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int4_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int8_accum_inv
!  * aggregate inverse transition function.
!  * This function must be declared as strict.
!  */
! Datum
! int8_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
  	}
  
  	PG_RETURN_POINTER(state);
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2688,2693 ****
--- 2915,2921 ----
  
  /*
   * Transition function for int8 input when we don't need sumX2.
+  * For the inverse, we use int8_accum_inv.
   */
  Datum
  int8_avg_accum(PG_FUNCTION_ARGS)
*************** int8_avg_accum(PG_FUNCTION_ARGS)
*** 2696,2719 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, false);
- 
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
- 
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
--- 2924,2945 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, false);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
*************** numeric_avg(PG_FUNCTION_ARGS)
*** 2722,2731 ****
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
--- 2948,2958 ----
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
*************** numeric_sum(PG_FUNCTION_ARGS)
*** 2740,2749 ****
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
--- 2967,2978 ----
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)
! 		/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
*************** numeric_stddev_internal(NumericAggState 
*** 2774,2780 ****
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL)
  	{
  		*is_null = true;
  		return NULL;
--- 3003,3009 ----
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
  	{
  		*is_null = true;
  		return NULL;
*************** numeric_stddev_internal(NumericAggState 
*** 2782,2788 ****
  
  	*is_null = false;
  
! 	if (state->isNaN)
  		return make_result(&const_nan);
  
  	init_var(&vN);
--- 3011,3017 ----
  
  	*is_null = false;
  
! 	if (state->NaNcount > 0)
  		return make_result(&const_nan);
  
  	init_var(&vN);
*************** numeric_stddev_pop(PG_FUNCTION_ARGS)
*** 2913,2932 ****
  }
  
  /*
!  * SUM transition functions for integer datatypes.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   *
!  * Because SQL defines the SUM() of no values to be NULL, not zero,
!  * the initial condition of the transition data value needs to be NULL. This
!  * means we can't rely on ExecAgg to automatically insert the first non-null
!  * data value into the transition data: it doesn't know how to do the type
!  * conversion.	The upshot is that these routines have to be marked non-strict
!  * and handle substitution of the first non-null input themselves.
   */
  
  Datum
--- 3142,3155 ----
  }
  
  /*
!  * Obsolete SUM transition functions for integer datatypes.
   *
!  * These were used to implement SUM aggregates before inverse transition
!  * functions were added. For inverse transitions, we need to know the number
!  * of summands to be able to return NULL whenenver the number of non-NULL
!  * inputs becomes zero. We therefore now use the intX_avg_accum and
!  * intX_avg_accum_inv transition functions, which use int8[] is their
!  * transition type to be able to count the number of inputs.
   */
  
  Datum
*************** int8_sum(PG_FUNCTION_ARGS)
*** 3065,3074 ****
  										NumericGetDatum(oldsum), newval));
  }
  
- 
  /*
!  * Routines for avg(int2) and avg(int4).  The transition datatype
!  * is a two-element int8 array, holding count and sum.
   */
  
  typedef struct Int8TransTypeData
--- 3288,3302 ----
  										NumericGetDatum(oldsum), newval));
  }
  
  /*
!  * Routines for sum(int2), sum(int4), avg(int2) and avg(int4).  The transition
!  * datatype is a two-element int8 array, holding count and sum.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   */
  
  typedef struct Int8TransTypeData
*************** int2_avg_accum(PG_FUNCTION_ARGS)
*** 3106,3111 ****
--- 3334,3368 ----
  }
  
  Datum
+ int2_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int16		newval = PG_GETARG_INT16(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ 
+ Datum
  int4_avg_accum(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray;
*************** int4_avg_accum(PG_FUNCTION_ARGS)
*** 3134,3139 ****
--- 3391,3442 ----
  }
  
  Datum
+ int4_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int32		newval = PG_GETARG_INT32(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ Datum
+ int2int4_sum(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Int8TransTypeData *transdata;
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 
+ 	/* SQL defines SUM of no values to be NULL */
+ 	if (transdata->count == 0)
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_DATUM(Int64GetDatumFast(transdata->sum));
+ }
+ 
+ Datum
  int8_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 4581862..fd6af59 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** interval_accum(PG_FUNCTION_ARGS)
*** 3073,3078 ****
--- 3073,3123 ----
  }
  
  Datum
+ interval_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Interval   *newval = PG_GETARG_INTERVAL_P(1);
+ 	Datum	   *transdatums;
+ 	int			ndatums;
+ 	Interval	sumX,
+ 				N;
+ 	Interval   *newsum;
+ 	ArrayType  *result;
+ 
+ 	deconstruct_array(transarray,
+ 					  INTERVALOID, sizeof(Interval), false, 'd',
+ 					  &transdatums, NULL, &ndatums);
+ 	if (ndatums != 2)
+ 		elog(ERROR, "expected 2-element interval array");
+ 
+ 	/*
+ 	 * XXX memcpy, instead of just extracting a pointer, to work around buggy
+ 	 * array code: it won't ensure proper alignment of Interval objects on
+ 	 * machines where double requires 8-byte alignment. That should be fixed,
+ 	 * but in the meantime...
+ 	 *
+ 	 * Note: must use DatumGetPointer here, not DatumGetIntervalP, else some
+ 	 * compilers optimize into double-aligned load/store anyway.
+ 	 */
+ 	memcpy((void *) &sumX, DatumGetPointer(transdatums[0]), sizeof(Interval));
+ 	memcpy((void *) &N, DatumGetPointer(transdatums[1]), sizeof(Interval));
+ 
+ 	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
+ 												   IntervalPGetDatum(&sumX),
+ 												 IntervalPGetDatum(newval)));
+ 	N.time -= 1;
+ 
+ 	transdatums[0] = IntervalPGetDatum(newsum);
+ 	transdatums[1] = IntervalPGetDatum(&N);
+ 
+ 	result = construct_array(transdatums, 2,
+ 							 INTERVALOID, sizeof(Interval), false, 'd');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ 
+ Datum
  interval_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 2fb3871..5bd8542 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 103,125 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
--- 103,125 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		int8_accum_inv			numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		int4_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		int2_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_accum_inv		numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum		interval_accum_inv		interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		int8_accum_inv			numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_avg_accum		int4_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2109	n 0 int2_avg_accum		int2_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2110	n 0 float4pl			-						-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl			-						-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl				cash_mi					-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl			interval_mi				-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_accum_inv		numeric_sum		0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
*************** DATA(insert ( 2798	n 0 tidsmaller		-	-		
*** 166,221 ****
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
--- 166,221 ----
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		int8inc_any_inv	-		0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			int8dec			-		0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		int8_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		int4_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		int2_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_accum_inv	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		int8_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		int4_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		int2_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ad9774c..f66d82d 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("truncate interval to specified un
*** 1309,1316 ****
--- 1309,1320 ----
  
  DATA(insert OID = 1219 (  int8inc		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8inc _null_ _null_ _null_ ));
  DESCR("increment");
+ DATA(insert OID = 3475 (  int8dec		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8dec _null_ _null_ _null_ ));
+ DESCR("decrement");
  DATA(insert OID = 2804 (  int8inc_any	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any _null_ _null_ _null_ ));
  DESCR("increment, ignores second argument");
+ DATA(insert OID = 3222 (  int8inc_any_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any_inv _null_ _null_ _null_ ));
+ DESCR("decrement, ignores second argument");
  DATA(insert OID = 1230 (  int8abs		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8abs _null_ _null_ _null_ ));
  
  DATA(insert OID = 1236 (  int8larger	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger _null_ _null_ _null_ ));
*************** DATA(insert OID = 1832 (  float8_stddev_
*** 2396,2401 ****
--- 2400,2407 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3233 (  numeric_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
*************** DATA(insert OID = 1835 (  int4_accum	   
*** 2404,2411 ****
  DESCR("aggregate transition function");
  DATA(insert OID = 1836 (  int8_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
! DATA(insert OID = 2746 (  int8_avg_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
--- 2410,2423 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 1836 (  int8_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
! DATA(insert OID = 2746 (  int8_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3235 (  int2_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3236 (  int4_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3237 (  int8_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
*************** DATA(insert OID = 1842 (  int8_sum		   P
*** 2426,2439 ****
--- 2438,2459 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 1843 (  interval_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3241 (  interval_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1844 (  interval_avg	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1186 "1187" _null_ _null_ _null_ _null_ interval_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1962 (  int2_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1963 (  int4_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3242 (  int2_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 3243 (  int4_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 1964 (  int8_avg		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1016" _null_ _null_ _null_ _null_ int8_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
+ DATA(insert OID = 3239 (  int2int4_sum	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "1016" _null_ _null_ _null_ _null_ int2int4_sum _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  DATA(insert OID = 2805 (  int8inc_float8_float8		PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 20 "20 701 701" _null_ _null_ _null_ _null_ int8inc_float8_float8 _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2806 (  float8_regr_accum			PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 1022 "1022 701 701" _null_ _null_ _null_ _null_ float8_regr_accum _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..9e44693 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum numeric_float8_no_overflow(
*** 999,1008 ****
--- 999,1012 ----
  extern Datum float4_numeric(PG_FUNCTION_ARGS);
  extern Datum numeric_float4(PG_FUNCTION_ARGS);
  extern Datum numeric_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg_accum(PG_FUNCTION_ARGS);
  extern Datum int2_accum(PG_FUNCTION_ARGS);
  extern Datum int4_accum(PG_FUNCTION_ARGS);
  extern Datum int8_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int4_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int8_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg_accum(PG_FUNCTION_ARGS);
  extern Datum numeric_avg(PG_FUNCTION_ARGS);
  extern Datum numeric_sum(PG_FUNCTION_ARGS);
*************** extern Datum int2_sum(PG_FUNCTION_ARGS);
*** 1014,1020 ****
--- 1018,1027 ----
  extern Datum int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_sum(PG_FUNCTION_ARGS);
  extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int4_avg_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int2int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_avg(PG_FUNCTION_ARGS);
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  extern Datum hash_numeric(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..5078e4a 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8div(PG_FUNCTION_ARGS);
*** 74,80 ****
--- 74,82 ----
  extern Datum int8abs(PG_FUNCTION_ARGS);
  extern Datum int8mod(PG_FUNCTION_ARGS);
  extern Datum int8inc(PG_FUNCTION_ARGS);
+ extern Datum int8dec(PG_FUNCTION_ARGS);
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
+ extern Datum int8inc_any_inv(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..e852fbd 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum interval_mul(PG_FUNCTION_AR
*** 179,184 ****
--- 179,185 ----
  extern Datum mul_d_interval(PG_FUNCTION_ARGS);
  extern Datum interval_div(PG_FUNCTION_ARGS);
  extern Datum interval_accum(PG_FUNCTION_ARGS);
+ extern Datum interval_accum_inv(PG_FUNCTION_ARGS);
  extern Datum interval_avg(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_mi(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index aa29471..1d28a81 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1295,1300 ****
--- 1295,1783 ----
  -- Test the arithmetic inverse transition functions
  --
  --
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 2.0000000000000000
+  2 | 2.5000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |    avg     
+ ---+------------
+  1 | @ 1.5 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+  i |  sum  
+ ---+-------
+  1 | $3.30
+  2 | $2.20
+  3 |      
+  4 |      
+ (4 rows)
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |   sum    
+ ---+----------
+  1 | @ 3 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 | 3.3
+  2 | 2.2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+  sum  
+ ------
+  6.01
+     5
+     3
+ (3 rows)
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     2
+  2 |     1
+  3 |     0
+  4 |     0
+ (4 rows)
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     4
+  2 |     3
+  3 |     2
+  4 |     1
+ (4 rows)
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   1
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   6
+  3 |   9
+  4 |   7
+ (4 rows)
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+          to_char          
+ --------------------------
+   100000000000000000000
+                       1.0
+ (2 rows)
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+  a |  b  | sum 
+ ---+-----+-----
+  1 |   1 |   1
+  2 |   2 |   3
+  3 | NaN | NaN
+  4 |   3 | NaN
+  5 |   4 |   7
+ (5 rows)
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 676fa45..e2af490 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 476,481 ****
--- 476,622 ----
  --
  --
  
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
invtrans_strictstrict_base_84870a.patchapplication/octet-stream; name=invtrans_strictstrict_base_84870a.patchDownload
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..538a5e1 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 56,61 ****
--- 56,62 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
*************** AggregateCreate(const char *aggName,
*** 68,78 ****
--- 69,81 ----
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
  	Oid			transfn;
+ 	Oid			invtransfn = InvalidOid; /* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	bool		transIsStrict;
  	Oid			rettype;
  	Oid			finaltype;
  	Oid			fnArgs[FUNC_MAX_ARGS];
*************** AggregateCreate(const char *aggName,
*** 234,241 ****
--- 237,297 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
+ 
+ 	/*
+ 	 * Remember if trans function is strict as we need to validate this
+ 	 * later if when we're dealing with the inverse transition function
+ 	 */
+ 	transIsStrict = proc->proisstrict;
+ 
  	ReleaseSysCache(tup);
  
+ 	/* handle invtransfn, if supplied */
+ 	if (agginvtransfnName)
+ 	{
+ 		/*
+ 		 * This must have the same number of arguments with the same types as
+ 		 * the transition function. We can just borrow the argument details
+ 		 * from the transition function and try to find a function with
+ 		 * the name of the inverse transition function and with a signature
+ 		 * that matches the transition function's.
+ 		 */
+ 		invtransfn = lookup_agg_function(agginvtransfnName,
+ 					nargs_transfn, fnArgs, InvalidOid, &rettype);
+ 
+ 		/*
+ 		 * Ensure the return type of the inverse transition function matches
+ 		 * the expected type.
+ 		 */
+ 		if (rettype != aggTransType)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("return type of inverse transition function %s is not %s",
+ 							NameListToString(agginvtransfnName),
+ 							format_type_be(aggTransType))));
+ 
+ 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+ 		proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 		/*
+ 		 * We force the strictness settings of the forward and inverse
+ 		 * transition functions to agree. This allows places which only need
+ 		 * forward transitions to not look at the inverse transition function
+ 		 * at all.
+ 		 */
+ 		if (transIsStrict != proc->proisstrict)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 						errmsg("stricness of forward and reverse transition functions must match"
+ 						)));
+ 		}
+ 		ReleaseSysCache(tup);
+ 
+ 	}
+ 
  	/* handle finalfn, if supplied */
  	if (aggfinalfnName)
  	{
*************** AggregateCreate(const char *aggName,
*** 391,396 ****
--- 447,453 ----
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+ 	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
*************** AggregateCreate(const char *aggName,
*** 425,430 ****
--- 482,496 ----
  	referenced.objectSubId = 0;
  	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
+ 	/* Depends on inverse transition function, if any */
+ 	if (OidIsValid(invtransfn))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = invtransfn;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
  	{
*************** AggregateCreate(const char *aggName,
*** 447,453 ****
  }
  
  /*
!  * lookup_agg_function -- common code for finding both transfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
--- 513,520 ----
  }
  
  /*
!  * lookup_agg_function
!  * common code for finding both transfn, invtransfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..0cfaa75 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
*************** DefineAggregate(List *name, List *args, 
*** 60,65 ****
--- 60,66 ----
  	AclResult	aclresult;
  	char		aggKind = AGGKIND_NORMAL;
  	List	   *transfuncName = NIL;
+ 	List	   *invtransfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
*************** DefineAggregate(List *name, List *args, 
*** 112,117 ****
--- 113,120 ----
  			transfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
  			transfuncName = defGetQualifiedName(defel);
+ 		else if (pg_strcasecmp(defel->defname, "invfunc") == 0)
+ 			invtransfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
  			finalfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
*************** DefineAggregate(List *name, List *args, 
*** 283,288 ****
--- 286,292 ----
  						   parameterDefaults,
  						   variadicArgType,
  						   transfuncName,		/* step function name */
+ 						   invtransfuncName,	/* inverse trans function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0dba928..e86caaf 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_group_keys(PlanSta
*** 85,90 ****
--- 85,91 ----
  					 List *ancestors, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
+ static void show_windowagg_info(PlanState *planstate, ExplainState *es);
  static void show_tidbitmap_info(BitmapHeapScanState *planstate,
  								ExplainState *es);
  static void show_instrumentation_count(const char *qlabel, int which,
*************** ExplainNode(PlanState *planstate, List *
*** 1402,1407 ****
--- 1403,1412 ----
  		case T_Hash:
  			show_hash_info((HashState *) planstate, es);
  			break;
+ 		case T_WindowAgg:
+ 			if (es->verbose && planstate->instrument)
+ 				show_windowagg_info(planstate, es);
+ 			break;
  		default:
  			break;
  	}
*************** show_hash_info(HashState *hashstate, Exp
*** 1900,1905 ****
--- 1905,1925 ----
  	}
  }
  
+ static void
+ show_windowagg_info(PlanState *planstate, ExplainState *es)
+ {
+ 	WindowAggState *winaggstate = (WindowAggState *) planstate;
+ 	Instrumentation *inst = planstate->instrument;
+ 
+ 	if (inst->nloops > 0 && inst->ntuples > 0 && winaggstate->numaggs > 0)
+ 	{
+ 		double tperrow = winaggstate->aggfwdtrans /
+ 			(inst->nloops * inst->ntuples);
+ 
+ 		ExplainPropertyFloat("Transitions Per Row", tperrow, 1, es);
+ 	}
+ }
+ 
  /*
   * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
   */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..04f2ebe 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1679,1684 ****
--- 1679,1685 ----
  		Oid			transfn_oid,
  					finalfn_oid;
  		Expr	   *transfnexpr,
+ 				   *invtransfnexpr, /* needed but never used */
  				   *finalfnexpr;
  		Datum		textInitVal;
  		int			i;
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1798,1805 ****
--- 1799,1808 ----
  								aggref->aggtype,
  								aggref->inputcollid,
  								transfn_oid,
+ 								InvalidOid, /* invtrans is not needed here */
  								finalfn_oid,
  								&transfnexpr,
+ 								&invtransfnexpr,
  								&finalfnexpr);
  
  		/* set up infrastructure for calling the transfn and finalfn */
*************** ExecReScanAgg(AggState *node)
*** 2127,2168 ****
  }
  
  /*
-  * AggCheckCallContext - test if a SQL function is being called as an aggregate
-  *
-  * The transition and/or final functions of an aggregate may want to verify
-  * that they are being called as aggregates, rather than as plain SQL
-  * functions.  They should use this function to do so.	The return value
-  * is nonzero if being called as an aggregate, or zero if not.	(Specific
-  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
-  * values could conceivably appear in future.)
-  *
-  * If aggcontext isn't NULL, the function also stores at *aggcontext the
-  * identity of the memory context that aggregate transition values are
-  * being stored in.
-  */
- int
- AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
- {
- 	if (fcinfo->context && IsA(fcinfo->context, AggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_AGGREGATE;
- 	}
- 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_WINDOW;
- 	}
- 
- 	/* this is just to prevent "uninitialized variable" warnings */
- 	if (aggcontext)
- 		*aggcontext = NULL;
- 	return 0;
- }
- 
- /*
   * AggGetAggref - allow an aggregate support function to get its Aggref
   *
   * If the function is being called as an aggregate support function,
--- 2130,2135 ----
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..4bc3565 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** typedef struct WindowStatePerFuncData
*** 102,119 ****
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transfer functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transfer functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/*
  	 * initial value from pg_aggregate entry
  	 */
--- 102,125 ----
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transition functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
+ 	Oid			invtransfn_oid; /* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transition functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
+ 	FmgrInfo	invtransfn;
  	FmgrInfo	finalfn;
  
+ 	/* Aggregate properties */
+ 	bool		use_invtransfn;			/* whether to use the invtransfn */
+ 	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+ 
  	/*
  	 * initial value from pg_aggregate entry
  	 */
*************** typedef struct WindowStatePerAggData
*** 140,149 ****
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	Datum		transValue;		/* current transition value */
! 	bool		transValueIsNull;
  
! 	bool		noTransValue;	/* true if transValue not set yet */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
--- 146,158 ----
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	MemoryContext	aggcontext;			/* context for transValue */
! 	int64			transValueCount;	/* Number of aggregates values*/
! 	Datum			transValue;			/* current transition value */
! 	bool			transValueIsNull;
  
! 	/* Data local to eval_windowaggregates() */
! 	bool			restart;			/* tmp marker that agg needs restart */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
*************** static void initialize_windowaggregate(W
*** 152,157 ****
--- 161,169 ----
  static void advance_windowaggregate(WindowAggState *winstate,
  						WindowStatePerFunc perfuncstate,
  						WindowStatePerAgg peraggstate);
+ static bool retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate);
  static void finalize_windowaggregate(WindowAggState *winstate,
  						 WindowStatePerFunc perfuncstate,
  						 WindowStatePerAgg peraggstate,
*************** static bool are_peers(WindowAggState *wi
*** 181,186 ****
--- 193,242 ----
  static bool window_gettupleslot(WindowObject winobj, int64 pos,
  					TupleTableSlot *slot);
  
+ /*
+  * AggCheckCallContext - test if a SQL function is being called as an aggregate
+  *
+  * The transition and/or final functions of an aggregate may want to verify
+  * that they are being called as aggregates, rather than as plain SQL
+  * functions.  They should use this function to do so.	The return value
+  * is nonzero if being called as an aggregate, or zero if not.	(Specific
+  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+  * values could conceivably appear in future.)
+  *
+  * If aggcontext isn't NULL, the function also stores at *aggcontext the
+  * identity of the memory context that aggregate transition values are
+  * being stored in.
+  *
+  * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+  * is private.
+  */
+ int
+ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggState))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ 		return AGG_CONTEXT_AGGREGATE;
+ 	}
+ 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ 	{
+ 		if (aggcontext)
+ 		{
+ 			/* Must lookup per-aggregate context */
+ 			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+ 			int				aggno = winstate->calledaggno;
+ 			Assert(0 <= aggno && aggno < winstate->numaggs);
+ 			*aggcontext = winstate->peragg[aggno].aggcontext;
+ 		}
+ 		return AGG_CONTEXT_WINDOW;
+ 	}
+ 
+ 	/* this is just to prevent "uninitialized variable" warnings */
+ 	if (aggcontext)
+ 		*aggcontext = NULL;
+ 	return 0;
+ }
  
  /*
   * initialize_windowaggregate
*************** initialize_windowaggregate(WindowAggStat
*** 193,210 ****
  {
  	MemoryContext oldContext;
  
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = peraggstate->initValue;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->noTransValue = peraggstate->initValueIsNull;
  	peraggstate->resultValueIsNull = true;
  }
  
--- 249,274 ----
  {
  	MemoryContext oldContext;
  
+ 	/* If we're using a private aggcontext, we may reset it here. But if the
+ 	 * context is shared, we don't know which other aggregates may still need
+ 	 * it, so we must leave it to the caller to reset at an appropriate time
+ 	 */
+ 	if (!peraggstate->aggcontext_is_shared)
+ 		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+ 
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = (Datum) 0;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
+ 	peraggstate->transValueCount = 0;
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->resultValue = (Datum) 0;
  	peraggstate->resultValueIsNull = true;
  }
  
*************** advance_windowaggregate(WindowAggState *
*** 254,265 ****
  		i++;
  	}
  
  	if (peraggstate->transfn.fn_strict)
  	{
- 		/*
- 		 * For a strict transfn, nothing happens when there's a NULL input; we
- 		 * just keep the prior transValue.
- 		 */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
--- 318,326 ----
  		i++;
  	}
  
+ 	/* Skip NULL inputs for aggregates which desire that. */
  	if (peraggstate->transfn.fn_strict)
  	{
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
*************** advance_windowaggregate(WindowAggState *
*** 268,290 ****
  				return;
  			}
  		}
! 		if (peraggstate->noTransValue)
  		{
  			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
  			 * already checked that the agg's input type is binary-compatible
  			 * with its transtype, so straight copy here is OK.)
  			 *
  			 * We must copy the datum into aggcontext if it is pass-by-ref. We
  			 * do not need to pfree the old transValue, since it's NULL.
  			 */
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->noTransValue = false;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
--- 329,354 ----
  				return;
  			}
  		}
! 	}
! 
! 	if (peraggstate->transfn.fn_strict) {
! 		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
  		{
  			/*
! 			 * For strict transfer functions with initial value NULL we use
! 			 * the first non-NULL input as the initial state. (We
  			 * already checked that the agg's input type is binary-compatible
  			 * with its transtype, so straight copy here is OK.)
  			 *
  			 * We must copy the datum into aggcontext if it is pass-by-ref. We
  			 * do not need to pfree the old transValue, since it's NULL.
  			 */
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->transValueCount = 1;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
*************** advance_windowaggregate(WindowAggState *
*** 294,308 ****
  			 * Don't call a strict function with NULL inputs.  Note it is
  			 * possible to get here despite the above tests, if the transfn is
  			 * strict *and* returned a NULL on a prior cycle. If that happens
! 			 * we will propagate the NULL all the way to the end.
  			 */
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  	}
  
  	/*
  	 * OK to call the transition function
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
--- 358,386 ----
  			 * Don't call a strict function with NULL inputs.  Note it is
  			 * possible to get here despite the above tests, if the transfn is
  			 * strict *and* returned a NULL on a prior cycle. If that happens
! 			 * we will propagate the NULL all the way to the end. That can only
! 			 * happen if there's no inverse transition function, though, since
! 			 * we disallow transitions back to NULL if there is one below.
  			 */
  			MemoryContextSwitchTo(oldContext);
+ 			Assert(peraggstate->invtransfn_oid == InvalidOid);
  			return;
  		}
  	}
  
  	/*
+ 	 * We must track the number of inputs that we add to transValue, since
+ 	 * to remove the last input, retreat_windowaggregate() musn't call the
+ 	 * inverse transition function, but simply reset transValue back to its
+ 	 * initial value.
+ 	 */
+ 	Assert(peraggstate->transValueCount >= 0);
+ 	peraggstate->transValueCount++;
+ 
+ 	/*
  	 * OK to call the transition function
+ 	 * Transfer functions with an inverse MUST not return NULL, see
+ 	 * retreat_windowaggregate()
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
*************** advance_windowaggregate(WindowAggState *
*** 310,316 ****
--- 388,402 ----
  							 (void *) winstate, NULL);
  	fcinfo->arg[0] = peraggstate->transValue;
  	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
  	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("transition function with an inverse returned NULL")));
+ 	}
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
*************** advance_windowaggregate(WindowAggState *
*** 322,328 ****
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
--- 408,552 ----
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
! 			newVal = datumCopy(newVal,
! 							   peraggstate->transtypeByVal,
! 							   peraggstate->transtypeLen);
! 		}
! 		if (!peraggstate->transValueIsNull)
! 			pfree(DatumGetPointer(peraggstate->transValue));
! 	}
! 
! 	MemoryContextSwitchTo(oldContext);
! 	peraggstate->transValue = newVal;
! 	peraggstate->transValueIsNull = fcinfo->isnull;
! }
! 
! /*
!  * retreat_windowaggregate
!  * removes tuples from aggregation.
!  * The calling function must ensure that each aggregate has
!  * a valid inverse transition function.
!  */
! static bool
! retreat_windowaggregate(WindowAggState *winstate,
! 						WindowStatePerFunc perfuncstate,
! 						WindowStatePerAgg peraggstate)
! {
! 	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
! 	int			numArguments = perfuncstate->numArguments;
! 	FunctionCallInfoData fcinfodata;
! 	FunctionCallInfo fcinfo = &fcinfodata;
! 	Datum		newVal;
! 	ListCell   *arg;
! 	int			i;
! 	MemoryContext oldContext;
! 	ExprContext *econtext = winstate->tmpcontext;
! 	ExprState  *filter = wfuncstate->aggfilter;
! 
! 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
! 
! 	/* Skip anything FILTERed out */
! 	if (filter)
! 	{
! 		bool		isnull;
! 		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
! 
! 		if (isnull || !DatumGetBool(res))
! 		{
! 			MemoryContextSwitchTo(oldContext);
! 			return true;
! 		}
! 	}
! 
! 	/* We start from 1, since the 0th arg will be the transition value */
! 	i = 1;
! 	foreach(arg, wfuncstate->args)
! 	{
! 		ExprState  *argstate = (ExprState *) lfirst(arg);
! 
! 		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
! 									  &fcinfo->argnull[i], NULL);
! 		i++;
! 	}
! 
! 	/* Skip inputs containing NULLS for aggregates that require this */
! 	if (peraggstate->invtransfn.fn_strict)
! 	{
! 		for (i = 1; i <= numArguments; i++)
! 		{
! 			if (fcinfo->argnull[i])
! 			{
! 				MemoryContextSwitchTo(oldContext);
! 				return true;
! 			}
! 		}
! 	}
! 
! 	/* There should still an added but not yet removed value */
! 	Assert(peraggstate->transValueCount >= 1);
! 
! 	/*
! 	 * We mustn't use the inverse transition function to remove the last
! 	 * input. Doing so would yield a non-NULL state, whereas we should be
! 	 * in the initial state afterwards which may very well be NULL. So
! 	 * instead, we simply re-initialize the aggregation in this case.
! 	 */
! 	if (peraggstate->transValueCount == 1)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		initialize_windowaggregate(winstate,
! 								&winstate->perfunc[peraggstate->wfuncno],
! 								peraggstate);
! 		return true;
! 	}
! 
! 	/*
! 	 * Perform the inverse transition.
! 	 *
! 	 * For pairs of forward and inverse transition functions, the state may
! 	 * never be NULL, except in the ignore_nulls case, and then only until
! 	 * until we see the first non-NULL input during which time should never
! 	 * attempt to invoke the inverse transition function. Excluding NULL
! 	 * as a possible state value allows us to make it mean "sorry, can't
! 	 * do an inverse transition in this case" when returned by the inverse
! 	 * transition function. In that case, we report the failure to the
! 	 * caller.
! 	 */
! 	if (peraggstate->transValueIsNull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		elog(ERROR, "transition value is NULL during inverse transition");
! 	}
! 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
! 							 numArguments + 1,
! 							 perfuncstate->winCollation,
! 							 (void *) winstate, NULL);
! 	fcinfo->arg[0] = peraggstate->transValue;
! 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
! 	winstate->calledaggno = perfuncstate->aggno;
! 	newVal = FunctionCallInvoke(fcinfo);
! 	winstate->calledaggno = -1;
! 	if (fcinfo->isnull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		return false;
! 	}
! 
! 	/* Update number of added but not yet removed values */
! 	peraggstate->transValueCount--;
! 
! 	/*
! 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
! 	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
! 	 * first input, we don't need to do anything.
! 	 */
! 	if (!peraggstate->transtypeByVal &&
! 		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
! 	{
! 		if (!fcinfo->isnull)
! 		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
*************** advance_windowaggregate(WindowAggState *
*** 334,341 ****
--- 558,568 ----
  	MemoryContextSwitchTo(oldContext);
  	peraggstate->transValue = newVal;
  	peraggstate->transValueIsNull = fcinfo->isnull;
+ 
+ 	return true;
  }
  
+ 
  /*
   * finalize_windowaggregate
   * parallel to finalize_aggregate in nodeAgg.c
*************** finalize_windowaggregate(WindowAggState 
*** 370,376 ****
--- 597,605 ----
  		}
  		else
  		{
+ 			winstate->calledaggno = perfuncstate->aggno;
  			*result = FunctionCallInvoke(&fcinfo);
+ 			winstate->calledaggno = -1;
  			*isnull = fcinfo.isnull;
  		}
  	}
*************** finalize_windowaggregate(WindowAggState 
*** 392,397 ****
--- 621,627 ----
  	MemoryContextSwitchTo(oldContext);
  }
  
+ 
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
*************** eval_windowaggregates(WindowAggState *wi
*** 406,417 ****
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs;
! 	int			i;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
--- 636,653 ----
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs,
! 				numaggs_restart = 0,
! 				i;
! 	int64		pos,
! 				aggregatedupto_nonrestarted;
! 	bool		is_first = (winstate->currentpos == 0),
! 				ok;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
! 	TupleTableSlot *temp_slot = winstate->temp_slot_1;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
*************** eval_windowaggregates(WindowAggState *wi
*** 420,426 ****
  	/* final output execution is in ps_ExprContext */
  	econtext = winstate->ss.ps.ps_ExprContext;
  	agg_winobj = winstate->agg_winobj;
- 	agg_row_slot = winstate->agg_row_slot;
  
  	/*
  	 * Currently, we support only a subset of the SQL-standard window framing
--- 656,661 ----
*************** eval_windowaggregates(WindowAggState *wi
*** 438,446 ****
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * For other frame start rules, we discard the aggregate state and re-run
! 	 * the aggregates whenever the frame head row moves.  We can still
! 	 * optimize as above whenever successive rows share the same frame head.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
--- 673,692 ----
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * We can still optimize as above whenever successive rows share the same
! 	 * frame head, but if the frame head moves beyond the aggregated base point
! 	 * we use the aggregate function's inverse transition function. This
! 	 * removes the tuple from aggregation and restores the aggregate's current
! 	 * state to what it would be if the removed row had never been aggregated
! 	 * in the first place. Inverse transition functions may optionally return
! 	 * NULL, this indicates that the function was unable to remove the tuple
! 	 * from aggregation, when this happens we must perform the aggregation all
! 	 * over again for all tuples in the new frame boundary.
! 	 *
! 	 * If the aggregate function does not have a inverse transition function
! 	 * and the frame head moves beyond the aggregated position then we must
! 	 * discard the aggregated state and re-aggregate similar to how we would
! 	 * have to if the inverse transition function had returned NULL.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
*************** eval_windowaggregates(WindowAggState *wi
*** 452,529 ****
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
- 	 *
- 	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
- 	 * some aggregates like SUM and COUNT we could avoid that by implementing
- 	 * a "negative transition function" that would be called for each row as
- 	 * it exits the frame.	We'd have to think about avoiding recalculation of
- 	 * volatile arguments of aggregate functions, too.
  	 */
  
  	/*
  	 * First, update the frame head position.
  	 */
! 	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
  
  	/*
! 	 * Initialize aggregates on first call for partition, or if the frame head
! 	 * position moved since last time.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos != winstate->aggregatedbase)
  	{
- 		/*
- 		 * Discard transient aggregate values
- 		 */
- 		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
- 
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  
  		/*
! 		 * If we created a mark pointer for aggregates, keep it pushed up to
! 		 * frame head, so that tuplestore can discard unnecessary rows.
  		 */
! 		if (agg_winobj->markptr >= 0)
! 			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
  
  		/*
! 		 * Initialize for loop below
  		 */
! 		ExecClearTuple(agg_row_slot);
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 		winstate->aggregatedupto = winstate->frameheadpos;
  	}
  
  	/*
! 	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
! 	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
! 	 * have to recalculate when the frame head moves or currentpos has
! 	 * advanced past the place we'd aggregated up to.  Check for these cases
! 	 * and if so, reuse the saved result values.
  	 */
! 	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
! 		for (i = 0; i < numaggs; i++)
  		{
- 			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
- 		return;
  	}
  
  	/*
  	 * Advance until we reach a row not in frame (or end of partition).
  	 *
  	 * Note the loop invariant: agg_row_slot is either empty or holds the row
--- 698,875 ----
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
  	 */
  
  	/*
  	 * First, update the frame head position.
+ 	 *
+ 	 * The frame head should never move backwards, and the code below wouldn't
+ 	 * cope if it did, so for safety we complain if it does.
  	 */
! 	update_frameheadpos(agg_winobj, temp_slot);
! 	if (winstate->frameheadpos < winstate->aggregatedbase)
! 		elog(ERROR, "frame moved backwards unexpectedly");
  
  	/*
! 	 * If the frame didn't change compared to the previous row, we can re-use
! 	 * the cached result. Since we don't know the current frame's end yet, we
! 	 * cannot check that the obvious way. But we can exploit that if the frame
! 	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
! 	 * row lies within the previous row's frame, the two frame's ends must
! 	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
! 	 * so we don't need to check for that explicitly here.
  	 */
! 	if (winstate->aggregatedbase == winstate->frameheadpos &&
! 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
+ 		return;
+ 	}
  
+ 	/* Initialize restart flags.
+ 	 *
+ 	 * We restart the aggregation
+ 	 *   - if we're processing the first row in the partition, or
+ 	 *   - if we the frame's head moved and we cannot use an inverse
+ 	 *     transition function, or
+ 	 *   - if the new frame doesn't overlap the old one
+ 	 *
+ 	 * Note that we don't strictly need to restart in the last case, but
+ 	 * if we're going to remove *all* rows from the aggregation anyway, a
+ 	 * restart surely is faster.
+ 	 */
+ 	for (i = 0; i < numaggs; i++)
+ 	{
+ 		peraggstate = &winstate->peragg[i];
+ 		if (is_first ||
+ 			(winstate->aggregatedbase < winstate->frameheadpos &&
+ 			!peraggstate->use_invtransfn) ||
+ 			(winstate->aggregatedupto <= winstate->frameheadpos))
+ 		{
+ 			peraggstate->restart = true;
+ 			numaggs_restart++;
+ 		}
+ 		else
+ 			peraggstate->restart = false;
+ 	}
+ 
+ 	/*
+ 	 * Attempt to update aggregatedbase to match the frame's head by
+ 	 * removing those inputs from the aggregations which fell off the top
+ 	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+ 	 * return false, in which case we restart that aggregate below.
+ 	 *
+ 	 * We track the number of aggregates which require a restart, and
+ 	 * exit as soon as there are no more retreatable aggregates left.
+ 	 */
+ 	for(pos = winstate->aggregatedbase;
+ 		pos < winstate->frameheadpos && numaggs_restart < numaggs;
+ 		++pos)
+ 	{
  		/*
! 		 * Fetch the tuple where the current aggregation started from.
! 		 * This should never fail as we should have been here before.
  		 */
! 		if (!window_gettupleslot(agg_winobj, pos, temp_slot))
! 			elog(ERROR, "Unable to find tuple in tuplestore");
! 
! 		/* Set tuple context for evaluation of aggregate arguments */
! 		winstate->tmpcontext->ecxt_outertuple = temp_slot;
  
  		/*
! 		 * Perform the inverse transition for each aggregate function in
! 		 * the window, unless it has already been marked as needing a
! 		 * restart.
  		 */
! 		for (i = 0; i < numaggs; i++)
! 		{
! 			peraggstate = &winstate->peragg[i];
! 			if (peraggstate->restart)
! 				continue;
! 
! 			wfuncno = peraggstate->wfuncno;
! 			ok = retreat_windowaggregate(winstate,
! 										 &winstate->perfunc[wfuncno],
! 										 peraggstate);
! 			if (!ok)
! 			{
! 				/* Inverse transition function has failed, must restart */
! 				peraggstate->restart = true;
! 				numaggs_restart++;
! 			}
! 		}
! 
! 		/* Reset per-input-tuple context and tuple after each tuple */
! 		ResetExprContext(winstate->tmpcontext);
! 		ExecClearTuple(temp_slot);
  	}
  
  	/*
! 	 * Then restart the aggregates which require it.
! 	 *
! 	 * We assume that aggregates using the shared context always restart
! 	 * if *any* aggregate restarts, and we may thus cleanup the shared
! 	 * aggcontext if that is the case. The private aggcontexts are reset
! 	 * by initialize_windowaggregate() if their owning aggregate restarts,
! 	 * otherwise we just pfree() the cached result.
  	 */
! 	if (numaggs_restart > 0)
! 		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < numaggs; i++)
  	{
! 		peraggstate = &winstate->peragg[i];
! 		/* Aggregates using the shared ctx must restart if *any* agg does */
! 		Assert(!peraggstate->aggcontext_is_shared ||
! 			   !numaggs_restart || peraggstate->restart);
! 		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
! 			!peraggstate->resulttypeByVal)
! 		{
! 			pfree(DatumGetPointer(peraggstate->resultValue));
! 			peraggstate->resultValue = (Datum) 0;
! 			peraggstate->resultValueIsNull = true;
! 		}
! 		else if (peraggstate->restart)
  		{
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  	}
  
  	/*
+ 	 * After this, non-restarted aggregated contain the rows between
+ 	 * aggregatedbase and aggregatedupto_nonrestarted, and restarted aggregates
+ 	 * the rows between aggregatedbase and aggregatedupto, which is to say none,
+ 	 * and aggregatedbase matches frameheadpos. If we modify we must clear
+ 	 * agg_row_slot, see the loop invariant below.
+ 	 */
+ 	winstate->aggregatedbase = winstate->frameheadpos;
+ 	aggregatedupto_nonrestarted = winstate->aggregatedupto;
+ 	if (numaggs_restart > 0 &&
+ 		winstate->aggregatedupto != winstate->frameheadpos)
+ 	{
+ 		winstate->aggregatedupto = winstate->frameheadpos;
+ 		ExecClearTuple(agg_row_slot);
+ 	}
+ 
+ 	/*
+ 	 * If we created a mark pointer for aggregates, keep it pushed up to
+ 	 * frame head, so that tuplestore can discard unnecessary rows.
+ 	 */
+ 	if (agg_winobj->markptr >= 0)
+ 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+ 
+ 	/*
  	 * Advance until we reach a row not in frame (or end of partition).
  	 *
  	 * Note the loop invariant: agg_row_slot is either empty or holds the row
*************** eval_windowaggregates(WindowAggState *wi
*** 551,556 ****
--- 897,907 ----
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
+ 			/* Non-restarted aggregates skip until aggregatedupto_previous*/
+ 			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+ 				!peraggstate->restart)
+ 				continue;
+ 
  			wfuncno = peraggstate->wfuncno;
  			advance_windowaggregate(winstate,
  									&winstate->perfunc[wfuncno],
*************** eval_windowaggregates(WindowAggState *wi
*** 565,570 ****
--- 916,932 ----
  		ExecClearTuple(agg_row_slot);
  	}
  
+ 	/* The frame's end is not supposed to move backwards, ever */
+ 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+ 
+ 	/* Update statistics */
+ 	if (numaggs_restart > 0)
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - winstate->frameheadpos);
+ 	else
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - aggregatedupto_nonrestarted);
+ 
  	/*
  	 * finalize aggregates and fill result/isnull fields.
  	 */
*************** eval_windowaggregates(WindowAggState *wi
*** 589,616 ****
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal)
  		{
! 			/*
! 			 * clear old resultValue in order not to leak memory.  (Note: the
! 			 * new result can't possibly be the same datum as old resultValue,
! 			 * because we never passed it to the trans function.)
! 			 */
! 			if (!peraggstate->resultValueIsNull)
! 				pfree(DatumGetPointer(peraggstate->resultValue));
! 
! 			/*
! 			 * If pass-by-ref, copy it into our aggregate context.
! 			 */
! 			if (!*isnull)
! 			{
! 				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
! 				peraggstate->resultValue =
! 					datumCopy(*result,
! 							  peraggstate->resulttypeByVal,
! 							  peraggstate->resulttypeLen);
! 				MemoryContextSwitchTo(oldContext);
! 			}
  		}
  		else
  		{
--- 951,964 ----
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal && !*isnull)
  		{
! 			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
! 			peraggstate->resultValue =
! 				datumCopy(*result,
! 						  peraggstate->resulttypeByVal,
! 						  peraggstate->resulttypeLen);
! 			MemoryContextSwitchTo(oldContext);
  		}
  		else
  		{
*************** eval_windowfunction(WindowAggState *wins
*** 651,656 ****
--- 999,1005 ----
  	/* Just in case, make all the regular argument slots be null */
  	memset(fcinfo.argnull, true, perfuncstate->numArguments);
  
+ 	winstate->calledaggno = -1;
  	*result = FunctionCallInvoke(&fcinfo);
  	*isnull = fcinfo.isnull;
  
*************** spool_tuples(WindowAggState *winstate, i
*** 794,800 ****
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kluge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
--- 1143,1149 ----
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kludge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
*************** release_partition(WindowAggState *winsta
*** 869,875 ****
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
--- 1218,1227 ----
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < winstate->numaggs; ++i)
! 		if (!winstate->peragg[i].aggcontext_is_shared)
! 			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1419,1425 ****
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno;
  	ListCell   *l;
  
  	/* check for unsupported flags */
--- 1771,1778 ----
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno,
! 				numaggs_invtrans;
  	ListCell   *l;
  
  	/* check for unsupported flags */
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1450,1457 ****
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived context for aggregate trans values etc */
! 	winstate->aggcontext =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
--- 1803,1812 ----
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived contexts for aggregate trans values etc
! 	 * Note that invertible aggregates use their own private context
! 	 */
! 	winstate->aggcontext_shared =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1535,1540 ****
--- 1890,1896 ----
  
  	wfuncno = -1;
  	aggno = -1;
+ 	numaggs_invtrans = 0;
  	foreach(l, winstate->funcs)
  	{
  		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1603,1608 ****
--- 1959,1966 ----
  			peraggstate = &winstate->peragg[aggno];
  			initialize_peragg(winstate, wfunc, peraggstate);
  			peraggstate->wfuncno = wfuncno;
+ 			if (peraggstate->use_invtransfn)
+ 				numaggs_invtrans++;
  		}
  		else
  		{
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1618,1623 ****
--- 1976,1982 ----
  	/* Update numfuncs, numaggs to match number of unique functions found */
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
+ 	winstate->numaggs_invtrans = numaggs_invtrans;
  
  	/* Set up WindowObject for aggregates, if needed */
  	if (winstate->numaggs > 0)
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1646,1651 ****
--- 2005,2016 ----
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	/* initialize temporary data */
+ 	winstate->calledaggno = -1;
+ 
+ 	/* initialize statistics */
+ 	winstate->aggfwdtrans = 0;
+ 
  	return winstate;
  }
  
*************** void
*** 1657,1668 ****
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
  
  	release_partition(node);
  
- 	pfree(node->perfunc);
- 	pfree(node->peragg);
- 
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
--- 2022,2031 ----
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
+ 	int			i;
  
  	release_partition(node);
  
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
*************** ExecEndWindowAgg(WindowAggState *node)
*** 1677,1683 ****
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
--- 2040,2052 ----
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext_shared);
! 	for(i = 0; i < node->numaggs; i++)
! 		if (!node->peragg[i].aggcontext_is_shared)
! 			MemoryContextDelete(node->peragg[i].aggcontext);
! 
! 	pfree(node->perfunc);
! 	pfree(node->peragg);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
*************** initialize_peragg(WindowAggState *winsta
*** 1735,1742 ****
--- 2104,2113 ----
  	Oid			aggtranstype;
  	AclResult	aclresult;
  	Oid			transfn_oid,
+ 				invtransfn_oid,
  				finalfn_oid;
  	Expr	   *transfnexpr,
+ 			   *invtransfnexpr,
  			   *finalfnexpr;
  	Datum		textInitVal;
  	int			i;
*************** initialize_peragg(WindowAggState *winsta
*** 1762,1767 ****
--- 2133,2139 ----
  	 */
  
  	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+ 	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
  	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
  	/* Check that aggregate owner has permission to call component fns */
*************** initialize_peragg(WindowAggState *winsta
*** 1783,1788 ****
--- 2155,2171 ----
  			aclcheck_error(aclresult, ACL_KIND_PROC,
  						   get_func_name(transfn_oid));
  		InvokeFunctionExecuteHook(transfn_oid);
+ 
+ 		if (OidIsValid(invtransfn_oid))
+ 		{
+ 			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+ 										 ACL_EXECUTE);
+ 			if (aclresult != ACLCHECK_OK)
+ 				aclcheck_error(aclresult, ACL_KIND_PROC,
+ 				get_func_name(invtransfn_oid));
+ 			InvokeFunctionExecuteHook(invtransfn_oid);
+ 		}
+ 
  		if (OidIsValid(finalfn_oid))
  		{
  			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
*************** initialize_peragg(WindowAggState *winsta
*** 1810,1822 ****
--- 2193,2213 ----
  							wfunc->wintype,
  							wfunc->inputcollid,
  							transfn_oid,
+ 							invtransfn_oid,
  							finalfn_oid,
  							&transfnexpr,
+ 							&invtransfnexpr,
  							&finalfnexpr);
  
  	fmgr_info(transfn_oid, &peraggstate->transfn);
  	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+ 		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+ 	}
+ 
  	if (OidIsValid(finalfn_oid))
  	{
  		fmgr_info(finalfn_oid, &peraggstate->finalfn);
*************** initialize_peragg(WindowAggState *winsta
*** 1860,1867 ****
--- 2251,2324 ----
  							wfunc->winfnoid)));
  	}
  
+ 	/*
+ 	 * Allowing only the forward transition function to be strict would
+ 	 * require handling more special cases in advance_windowaggregate() and
+ 	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 	 * that if the forward transition function is strict that the inverse
+ 	 * transition function is also strict. This should have been checked at
+ 	 * the aggregate function's definition time, but it's better to be safe...
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 				errmsg("stricness of forward and reverse transition functions must match")));
+ 	}
+ 
  	ReleaseSysCache(aggTuple);
  
+ 	/*
+ 	 * We can use the inverse transition function only if the aggregate's
+ 	 * arguments don't contain calls to volatile functions. Otherwise,
+ 	 * the difference between restarting and not restarting the aggregation
+ 	 * would be user-visible. Note that this check also covers the case where
+ 	 * the FILTER's WHERE clause contains a volatile function. If the frame
+ 	 * head cannot move, we won't ever need the inverse transition function,
+ 	 * so we also mark as "don't use" in that case.
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		!contain_volatile_functions((Node *) wfunc) &&
+ 		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+ 	{
+ 		peraggstate->use_invtransfn = true;
+ 	}
+ 	else
+ 	{
+ 		peraggstate->use_invtransfn = false;
+ 	}
+ 
+ 	/*
+ 	 * Invertible aggregates use their own aggcontext.
+ 	 *
+ 	 * This is necessary because they might all restart at different times,
+ 	 * so we might never be able to reset the shared context otherwise. We
+ 	 * can't make it the aggregate's responsibility to clean up after
+ 	 * themselves, because strict aggregates must be restarted whenever we
+ 	 * remove their last non-NULL input, which the aggregate won't be aware
+ 	 * is happening. Also, just pfree()ing the transValue upon restarting
+ 	 * wouldn't help, since we'd miss any indirectly referenced data. We
+ 	 * could, in theory, declare that aggregates with a state type other then
+ 	 * "internal" musn't allocate anything in the aggcontext themselves, that
+ 	 * non-strict aggregates with state type internal must clean up after
+ 	 * themselves when their inverse transfer function returns NULL, and then
+ 	 * only use private aggcontexts for strict aggregates with state type
+ 	 * internal. But that'd be a rather grotty set of requirements.
+ 	 */
+ 	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+ 	if (!peraggstate->aggcontext_is_shared)
+ 	{
+ 		peraggstate->aggcontext =
+ 			AllocSetContextCreate(CurrentMemoryContext,
+ 								  "WindowAgg_AggregatePrivate",
+ 								  ALLOCSET_DEFAULT_MINSIZE,
+ 								  ALLOCSET_DEFAULT_INITSIZE,
+ 								  ALLOCSET_DEFAULT_MAXSIZE);
+ 	}
+ 	else
+ 		peraggstate->aggcontext = winstate->aggcontext_shared;
+ 
  	return peraggstate;
  }
  
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..91bea45 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1203,1210 ****
--- 1203,1212 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr)
  {
  	Param	   *argp;
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1249,1254 ****
--- 1251,1269 ----
  	fexpr->funcvariadic = agg_variadic;
  	*transfnexpr = (Expr *) fexpr;
  
+ 
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+ 												agg_result_type,
+ 												args,
+ 												InvalidOid,
+ 												agg_input_collation,
+ 												COERCE_EXPLICIT_CALL);
+ 	}
+ 	else
+ 		*invtransfnexpr	= NULL;
+ 
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
  	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3862f05..6b28572 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11510,11515 ****
--- 11510,11516 ----
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
+ 	int			i_agginvtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
  	int			i_hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11518,11523 ****
--- 11519,11525 ----
  	int			i_agginitval;
  	int			i_convertok;
  	const char *aggtransfn;
+ 	const char *agginvtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
  	bool		hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11542,11548 ****
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
--- 11544,11550 ----
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11625,11630 ****
--- 11627,11633 ----
  	res = ExecuteSqlQueryForSingleRow(fout, query->data);
  
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
+ 	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
  	i_hypothetical = PQfnumber(res, "hypothetical");
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11634,11639 ****
--- 11637,11643 ----
  	i_convertok = PQfnumber(res, "convertok");
  
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ 	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11704,11709 ****
--- 11708,11719 ----
  		appendStringLiteralAH(details, agginitval, fout);
  	}
  
+ 	if (strcmp(agginvtransfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    INVFUNC = %s",
+ 			agginvtransfn);
+ 	}
+ 
  	if (strcmp(aggfinalfn, "-") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    FINALFUNC = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 96f08d3..2fb3871 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 46,51 ****
--- 46,52 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		agginvtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 69,85 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_agginvtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,276 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 103,278 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	-	json_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
*************** extern Oid AggregateCreate(const char *a
*** 288,293 ****
--- 290,296 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 455c089..c2bde24 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** extern void **find_rendezvous_variable(c
*** 644,651 ****
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly with nodeAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
--- 644,653 ----
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
!  * which is in execute/nodeWindowAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly neither nodeAgg
!  * nor nodeWindowAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..ff558c6 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WindowAggState
*** 1738,1743 ****
--- 1738,1744 ----
  	List	   *funcs;			/* all WindowFunc nodes in targetlist */
  	int			numfuncs;		/* total number of window functions */
  	int			numaggs;		/* number that are plain aggregates */
+ 	int			numaggs_invtrans;	/* number that are invertible aggregates */
  
  	WindowStatePerFunc perfunc; /* per-window-function information */
  	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
*************** typedef struct WindowAggState
*** 1762,1768 ****
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext;	/* context for each aggregate data */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
--- 1763,1769 ----
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext_shared;	/* shared context for agg states */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
*************** typedef struct WindowAggState
*** 1780,1789 ****
--- 1781,1796 ----
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
  
+ 	/* temporary data */
+ 	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+ 
  	/* temporary slots for tuples fetched back from tuplestore */
  	TupleTableSlot *agg_row_slot;
  	TupleTableSlot *temp_slot_1;
  	TupleTableSlot *temp_slot_2;
+ 
+ 	/* Statistics */
+ 	double	aggfwdtrans;	/* number of forward transitions */
  } WindowAggState;
  
  /* ----------------
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void build_aggregate_fnexprs(Oid 
*** 39,46 ****
--- 39,48 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr);
  
  #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..0def229 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select least_agg(variadic array[q1,q2]) 
*** 1580,1582 ****
--- 1580,1606 ----
   -4567890123456789
  (1 row)
  
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict AND NOT pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..d54f390 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** alter aggregate my_rank(VARIADIC "any" O
*** 90,92 ****
--- 90,129 ----
   public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
  (2 rows)
  
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi
+ );
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_n
+ );
+ ERROR:  stricness of forward and reverse transition functions must match
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = intminus
+ );
+ ERROR:  function intminus(double precision, double precision) does not exist
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_int
+ );
+ ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..aa29471 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT nth_value_def(ten) OVER (PARTITIO
*** 1071,1073 ****
--- 1071,1307 ----
               1 |   3 |    3
  (10 rows)
  
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict
+ );
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict
+ );
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+ ----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+  1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+  1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+  1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+  1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+  1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+  2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+  3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+ (9 rows)
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+ ----------+--------------+-------------------+-------------+------------------
+  1,1:NULL | +NULL        | I+NULL            |             | I
+  1,2:-    | +NULL        | I+NULL            |             | I
+  1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+  1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+  1,5:-    |              | I                 |             | I
+  1,6:-    |              | I                 |             | I
+  2,1:-    |              | I                 |             | I
+  2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+  3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+ (9 rows)
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+  logging_agg_strict 
+ --------------------
+  a
+  b
+  c
+ (3 rows)
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invfunc = sum_int_randrestart_invsfunc
+ );
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+  eq1 | eq2 | eq3 
+ -----+-----+-----
+  t   | t   | t
+ (1 row)
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..284ea76 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** drop view aggordview1;
*** 590,592 ****
--- 590,609 ----
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict AND NOT pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..b7fd4a3 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** alter aggregate my_rank(VARIADIC "any" O
*** 101,103 ****
--- 101,144 ----
    rename to test_rank;
  
  \da test_*
+ 
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi
+ );
+ 
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_n
+ );
+ 
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = intminus
+ );
+ 
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_int
+ );
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..676fa45 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** SELECT nth_value_def(n := 2, val := ten)
*** 284,286 ****
--- 284,489 ----
  
  SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
    FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+ 
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ 
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict
+ );
+ 
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ 
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict
+ );
+ 
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ 
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invfunc = sum_int_randrestart_invsfunc
+ );
+ 
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ 
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
invtrans_strictstrict_collecting_711b2f.patchapplication/octet-stream; name=invtrans_strictstrict_collecting_711b2f.patchDownload
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index c62e3fb..5a3a31d 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** create_singleton_array(FunctionCallInfo 
*** 471,477 ****
  
  
  /*
!  * ARRAY_AGG aggregate function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
--- 471,477 ----
  
  
  /*
!  * ARRAY_AGG aggregate transition function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 508,513 ****
--- 508,537 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * ARRAY_AGG aggregate inverse transition function
+  */
+ Datum
+ array_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since NULLs
+ 	 * need to be removed just like any other value.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "array_agg_invtransfn called with NULL state");
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "array_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	shiftArrayResult(state, 1);
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 311d0c2..828e72d 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** accumArrayResult(ArrayBuildState *astate
*** 4587,4592 ****
--- 4587,4593 ----
  		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
  		astate->mcontext = arr_context;
  		astate->alen = 64;		/* arbitrary starting array size */
+ 		astate->offset = 0;
  		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
  		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
  		astate->nelems = 0;
*************** accumArrayResult(ArrayBuildState *astate
*** 4600,4606 ****
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/* enlarge dvalues[]/dnulls[] if needed */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
--- 4601,4612 ----
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/*
! 		 * If the buffers are filled completely (offset must be zero then),
! 		 * we double their size. If they aren't, but the values extend to the
! 		 * end of the buffers, we reclaim wasted space at the beginning by
! 		 * moving the values to the front of the buffers.
! 		 */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
*************** accumArrayResult(ArrayBuildState *astate
*** 4609,4614 ****
--- 4615,4629 ----
  			astate->dnulls = (bool *)
  				repalloc(astate->dnulls, astate->alen * sizeof(bool));
  		}
+ 		else if (astate->offset + astate->nelems >= astate->alen)
+ 		{
+ 			memmove(astate->dvalues, astate->dvalues + astate->offset,
+ 					astate->alen * sizeof(Datum));
+ 			memmove(astate->dnulls, astate->dnulls + astate->offset,
+ 					astate->alen * sizeof(bool));
+ 			astate->offset = 0;
+ 		}
+ 		Assert(astate->offset + astate->nelems < astate->alen);
  	}
  
  	/*
*************** accumArrayResult(ArrayBuildState *astate
*** 4627,4634 ****
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->nelems] = dvalue;
! 	astate->dnulls[astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
--- 4642,4649 ----
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->offset + astate->nelems] = dvalue;
! 	astate->dnulls[astate->offset + astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
*************** accumArrayResult(ArrayBuildState *astate
*** 4637,4642 ****
--- 4652,4698 ----
  }
  
  /*
+  * shiftArrayResult - shift leading Datums out of an array result
+  *
+  *	astate is working state
+  *	count is the number of leading Datums to shift out
+  *
+  * If count is equal to or larger than the number of relements, the array
+  * result is empty afterwards. If astate is NULL, nothing is done.
+  */
+ void
+ shiftArrayResult(ArrayBuildState *astate, int count)
+ {
+ 	int		i;
+ 
+ 	if (astate == NULL)
+ 		return;
+ 
+ 	/* Limit shift count to number of elements for safety */
+ 	count = Min(count, astate->nelems);
+ 
+ 	/* For pass-by-ref types, free values we shift out */
+ 	if (!astate->typbyval) {
+ 		for(i = astate->offset; i < astate->offset + count; ++i) {
+ 			if (astate->dnulls[i])
+ 				continue;
+ 
+ 			pfree(DatumGetPointer(astate->dvalues[i]));
+ 
+ 			/* For cleanliness' sake */
+ 			astate->dnulls[i] = false;
+ 			astate->dvalues[i] = 0;
+ 		}
+ 
+ 	}
+ 
+ 	/* Update state */
+ 	astate->nelems -= count;
+ 	astate->offset += count;
+ }
+ 
+ 
+ /*
   * makeArrayResult - produce 1-D final result of accumArrayResult
   *
   *	astate is working state (not NULL)
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4679,4686 ****
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues,
! 								astate->dnulls,
  								ndims,
  								dims,
  								lbs,
--- 4735,4742 ----
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues + astate->offset,
! 								astate->dnulls + astate->offset,
  								ndims,
  								dims,
  								lbs,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..768ee49 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** typedef struct
*** 50,55 ****
--- 50,63 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct StringAggState
+ {
+ 	StringInfoData 	string; 	/* Contents */
+ 	int 			offset;		/* Offset into stringinfo's data */
+ 	int 			delimLen;	/* Delim length, -1 initially, -2 if multiple */
+ 	int64			notNullCount;/* Number of non-NULL inputs */
+ } StringAggState;
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
*************** static void appendStringInfoText(StringI
*** 78,84 ****
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
--- 86,95 ----
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo);
! static void prepareAppendStringAggState(StringAggState *state,
! 										int delimLen, int valueLen);
! static bool removeFromStringAggState(StringAggState *state, int valueLen);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
*************** static void text_format_string_conversio
*** 92,97 ****
--- 103,226 ----
  static void text_format_append_string(StringInfo buf, const char *str,
  						  int flags, int width);
  
+ /*****************************************************************************
+  *	 SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA)			 *
+  *****************************************************************************/
+ 
+ /*
+  * subroutine to initialize state
+  */
+ static StringAggState*
+ makeStringAggState(FunctionCallInfo fcinfo)
+ {
+ 	StringAggState*	state;
+ 	MemoryContext aggcontext;
+ 	MemoryContext oldcontext;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 	}
+ 
+ 	/*
+ 	 * Create state in aggregate context.  It'll stay there across subsequent
+ 	 * calls.
+ 	 */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = (StringAggState *) palloc(sizeof(StringAggState));
+ 	initStringInfo(&state->string);
+ 	state->offset = 0;
+ 	state->delimLen = -1;
+ 	state->notNullCount = 0;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * Prepare state for appending a value and a delimiter with specified lengths.
+  * pass -1 for delimLen if no delimiter will be added
+  */
+ void
+ prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen)
+ {
+ 	/*
+ 	 * Reclaim wasted space
+ 	 *
+ 	 * We move the contents to the left if the current contents fit into the
+ 	 * wasted space, i.e. if we waste more than we store. The limit is
+ 	 * somewhat arbitrary, but it's the smallest one that allows
+ 	 * memcpy to be used, because the source and destination don't overlap.
+ 	 * Note that we must check for <, not <=, because we include the trailing
+ 	 * '\0' in the copy.
+ 	 */
+ 	if (state->string.len - state->offset < state->offset)
+ 	{
+ 		state->string.len -= state->offset;
+ 		memcpy(state->string.data, state->string.data + state->offset,
+ 			   state->string.len + 1);
+ 		state->offset = 0;
+ 	}
+ 
+ 	/*
+ 	 * Enlarge StringInfo
+ 	 *
+ 	 * Not strictly necessary, but avoids potentially resizing twice when
+ 	 * the actual append... calls are done by the caller
+ 	 */
+ 	enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen);
+ 
+ 
+ 	/* Track delimiter length */
+ 	if (delimLen == -1)
+ 		{} /* Not specified, don't update */
+ 	else if (state->delimLen == -1)
+ 		state->delimLen = delimLen;
+ 	else if (state->delimLen != delimLen)
+ 		state->delimLen = -2;
+ }
+ 
+ /*
+  * Remove value with given length and the delimiter that follows
+  *
+  * Returns false if removal was impossible because delimiters varied
+  */
+ bool
+ removeFromStringAggState(StringAggState *state, int valueLen)
+ {
+ 	/* Remove the string */
+ 	state->offset += valueLen;
+ 
+ 	/*
+ 	 * Remove delimiter if necessary.
+ 	 *
+ 	 * The delimiter we need to remove isn't the delimiter we were passed, but
+ 	 * rather the delimiter passed when adding the input *after* this one. We
+ 	 * thus need the delimiter length to be all the same to be able to proceed.
+ 	 * If we're removing the last string, there will be no delimiter following
+ 	 * it. In that case, we may reset delimLen to its initial value.
+ 	 */
+ 	if (state->delimLen == -2)
+ 		return false;
+ 	if (state->offset < state->string.len)
+ 	{
+ 		Assert(state->delimLen >= 0);
+ 		state->offset += state->delimLen;
+ 	}
+ 	else
+ 		state->delimLen = -1;
+ 
+ 	/* Don't crash if we're ever asked to remove more than was added */
+ 	if (state->offset > state->string.len)
+ 	{
+ 		state->offset = state->string.len;
+ 		elog(ERROR, "tried to remove more data than was aggregated");
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /*****************************************************************************
   *	 CONVERSION ROUTINES EXPORTED FOR USE BY C CODE							 *
*************** byteasend(PG_FUNCTION_ARGS)
*** 408,435 ****
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		bytea	   *value = PG_GETARG_BYTEA_PP(1);
  
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 		{
! 			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim));
! 		}
  
! 		appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value));
  	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
--- 537,589 ----
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	bytea		   *value,
! 				   *delim;
! 	int				valueLen,
! 					delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
  
! 	value = PG_GETARG_BYTEA_PP(1);
! 	valueLen = VARSIZE_ANY_EXHDR(value);
! 	state->notNullCount++;
  
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
! 	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_BYTEA_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
! 	}
! 	else
! 	{
! 		/* Delimiter is NULL, treat as zero-length string */
! 		delim = NULL;
! 		delimLen = 0;
  	}
  
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, valueLen);
+ 	if (delim)
+ 		appendBinaryStringInfo(&state->string, VARDATA_ANY(delim),
+ 							   VARSIZE_ANY_EXHDR(delim));
+ 	appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen);
+ 
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 438,459 ****
  }
  
  Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
  	{
  		bytea	   *result;
  
! 		result = (bytea *) palloc(state->len + VARHDRSZ);
! 		SET_VARSIZE(result, state->len + VARHDRSZ);
! 		memcpy(VARDATA(result), state->data, state->len);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
--- 592,661 ----
  }
  
  Datum
+ bytea_string_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	int				valueLen;
+ 	StringAggState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since it
+ 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "string_agg_invtransfn called with NULL state");
+ 	else if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (StringAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* We append nothing if the string is NULL, so skip here as well */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_POINTER(state);
+ 
+ 	/* No need to de-toast value, need only the length */
+ 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
+ 	Assert(state->notNullCount >= 1);
+ 	state->notNullCount--;
+ 
+ 	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
+ 	 * The transition type for string_agg() is declared to be "internal",
+ 	 * which is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	if (removeFromStringAggState(state, valueLen))
+ 		PG_RETURN_POINTER(state);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
  	{
+ 		int			resultLen = state->string.len  - state->offset;
  		bytea	   *result;
  
! 		result = (bytea *) palloc(resultLen + VARHDRSZ);
! 		SET_VARSIZE(result, resultLen + VARHDRSZ);
! 		memcpy(VARDATA(result), state->string.data + state->offset, resultLen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3733,3801 ****
   * the associated value.
   */
  
! /* subroutine to initialize state */
! static StringInfo
! makeStringAggState(FunctionCallInfo fcinfo)
  {
! 	StringInfo	state;
! 	MemoryContext aggcontext;
! 	MemoryContext oldcontext;
  
! 	if (!AggCheckCallContext(fcinfo, &aggcontext))
  	{
! 		/* cannot be called directly because of internal-type argument */
! 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
  	}
  
  	/*
! 	 * Create state in aggregate context.  It'll stay there across subsequent
! 	 * calls.
  	 */
! 	oldcontext = MemoryContextSwitchTo(aggcontext);
! 	state = makeStringInfo();
! 	MemoryContextSwitchTo(oldcontext);
! 
! 	return state;
  }
  
  Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 			appendStringInfoText(state, PG_GETARG_TEXT_PP(2));	/* delimiter */
  
! 		appendStringInfoText(state, PG_GETARG_TEXT_PP(1));		/* value */
! 	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len));
  	else
  		PG_RETURN_NULL();
  }
--- 3935,4054 ----
   * the associated value.
   */
  
! Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	text		   *value,
! 				   *delim;
! 	int				delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
! 
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
! 
! 	value = PG_GETARG_TEXT_PP(1);
! 	state->notNullCount++;
! 
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
  	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_TEXT_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
  	}
+ 	else
+ 	{
+ 		/* Delimiter is NULL, treat as zero-length string */
+ 		delim = NULL;
+ 		delimLen = 0;
+ 	}
+ 
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value));
+ 	if (delim)
+ 		appendStringInfoText(&state->string, delim);
+ 	appendStringInfoText(&state->string, value);
  
  	/*
! 	 * The transition type for string_agg() is declared to be "internal",
! 	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
! string_agg_invtransfn(PG_FUNCTION_ARGS)
  {
! 	int				valueLen;
! 	StringAggState *state;
  
! 	/*
! 	 * Shouldn't happen, but we cannot mark this function strict, since it
! 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
! 	 * Must also prevent direct calls because of the "interal" argument
! 	 */
! 	if (PG_ARGISNULL(0))
! 		elog(ERROR, "string_agg_invtransfn called with NULL state");
! 	else if (!AggCheckCallContext(fcinfo, NULL))
! 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
  
! 	state = (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* We append nothing if the string is NULL, so skip here as well */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* No need to de-toast value, need only the length */
! 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
! 	Assert(state->notNullCount >= 1);
! 	state->notNullCount--;
  
  	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	if (removeFromStringAggState(state, valueLen))
! 		PG_RETURN_POINTER(state);
! 	else
! 		PG_RETURN_NULL();
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset,
! 												  state->string.len - state->offset));
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 2fb3871..5c9488f 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-	-					0	
*** 250,262 ****
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn	-	json_agg_finalfn	0	2281	0	_null_ ));
--- 250,262 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_invtransfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_invtransfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_invtransfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn	-	json_agg_finalfn	0	2281	0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ad9774c..1c2d8bb 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 875,880 ****
--- 875,882 ----
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3180 (  array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2463,2474 ****
--- 2465,2480 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3546 (  string_agg_invtransfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3538 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into a string");
  DATA(insert OID = 3543 (  bytea_string_agg_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3547 (  bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3544 (  bytea_string_agg_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..9a6fc39 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 83,88 ****
--- 83,89 ----
  	Datum	   *dvalues;		/* array of accumulated Datums */
  	bool	   *dnulls;			/* array of is-null flags for Datums */
  	int			alen;			/* allocated length of above arrays */
+ 	int			offset;			/* offset of first element in above arrays */
  	int			nelems;			/* number of valid entries in above arrays */
  	Oid			element_type;	/* data type of the Datums */
  	int16		typlen;			/* needed info about datatype */
*************** extern ArrayBuildState *accumArrayResult
*** 255,260 ****
--- 256,262 ----
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
  				 MemoryContext rcontext);
+ extern void shiftArrayResult(ArrayBuildState *astate, int count);
  extern Datum makeArrayResult(ArrayBuildState *astate,
  				MemoryContext rcontext);
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
*************** extern ArrayType *create_singleton_array
*** 290,295 ****
--- 292,298 ----
  					   int ndims);
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
  
  /*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..7d8e749 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 812,820 ****
--- 812,822 ----
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
  extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS);
  
  extern Datum text_concat(PG_FUNCTION_ARGS);
  extern Datum text_concat_ws(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index aa29471..02f2918 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1305,1307 ****
--- 1305,1351 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
+      row     | str | str_del | str_vardel |      bin       |      bin_del       |      bin_vardel      |       ary        
+ -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------
+  1:1,0100,-  | -   | -       | -          | -              | -                  | -                    | 
+  2:-,0200,2  | 1   | 1       | 1          | \x0100         | \x0100             | \x0100               | {NULL}
+  3:3,----,3  | 1   | 1       | 1          | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2}
+  4:-,0400,4  | 13  | 1,3     | 13         | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2,3}
+  5:5,----,-  | 3   | 3       | 3          | \x02000400     | \x0200000400       | \x0200000400         | {2,3,4}
+  6:6,0600,6  | 35  | 3,5     | 3||5       | \x0400         | \x0400             | \x0400               | {3,4,NULL}
+  7:7,0700,-  | 56  | 5,6     | 56         | \x04000600     | \x0400000600       | \x04000600           | {4,NULL,6}
+  8:8,0800,8  | 567 | 5,6,7   | 56|7       | \x06000700     | \x0600000700       | \x0600000700         | {NULL,6,NULL}
+  9:-,----,-  | 678 | 6,7,8   | 6|7||8     | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8}
+  10:-,----,- | 78  | 7,8     | 7||8       | \x07000800     | \x0700000800       | \x070000000800       | {NULL,8,NULL}
+  11:-,----,- | 8   | 8       | 8          | \x0800         | \x0800             | \x0800               | {8,NULL,NULL}
+  12:-,----,- | -   | -       | -          | -              | -                  | -                    | {NULL,NULL,NULL}
+ (12 rows)
+ 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 676fa45..dd7f02e 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 487,489 ****
--- 487,518 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ 
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
invtrans_strictstrict_minmax_857501e.patchapplication/octet-stream; name=invtrans_strictstrict_minmax_857501e.patchDownload
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 311d0c2..dd07f1a 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_larger(PG_FUNCTION_ARGS)
*** 4714,4719 ****
--- 4714,4737 ----
  }
  
  Datum
+ array_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) > 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ 
+ Datum
  array_smaller(PG_FUNCTION_ARGS)
  {
  	ArrayType  *v1,
*************** array_smaller(PG_FUNCTION_ARGS)
*** 4728,4733 ****
--- 4746,4767 ----
  	PG_RETURN_ARRAYTYPE_P(result);
  }
  
+ Datum
+ array_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) < 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
  
  typedef struct generate_subscripts_fctx
  {
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index d419b4a..593460d 100644
*** a/src/backend/utils/adt/bool.c
--- b/src/backend/utils/adt/bool.c
*************** boolge(PG_FUNCTION_ARGS)
*** 285,290 ****
--- 285,292 ----
  
  /* function for standard EVERY aggregate implementation conforming to SQL 2003.
   * must be strict. It is also named bool_and for homogeneity.
+  * Note: this is no longer used for the bool_and() and every() aggregate
+  * functions.
   */
  Datum
  booland_statefunc(PG_FUNCTION_ARGS)
*************** booland_statefunc(PG_FUNCTION_ARGS)
*** 294,302 ****
--- 296,400 ----
  
  /* function for standard ANY/SOME aggregate conforming to SQL 2003.
   * must be strict. The name of the aggregate is bool_or. See the doc.
+  * Note: this is no longer used for the bool_or aggregate function.
   */
  Datum
  boolor_statefunc(PG_FUNCTION_ARGS)
  {
  	PG_RETURN_BOOL(PG_GETARG_BOOL(0) || PG_GETARG_BOOL(1));
  }
+ 
+ typedef struct BoolAggState
+ {
+ 	int64 aggcount; /* number of values aggregated */
+ 	int64 aggtrue; /* number of values aggregated which are true */
+ } BoolAggState;
+ 
+ static BoolAggState *
+ makeBoolAggState(FunctionCallInfo fcinfo)
+ {
+ 	BoolAggState *state;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 	state = (BoolAggState *) palloc(sizeof(BoolAggState));
+ 	state->aggcount = 0;
+ 	state->aggtrue = 0;
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 
+ 	return state;
+ }
+ 
+ Datum
+ bool_accum(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* Create the state data on first call */
+ 	if (state == NULL)
+ 		state = makeBoolAggState(fcinfo);
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount++;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue++;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* bool_accum should have created the state data */
+ 	if (state == NULL)
+ 		elog(ERROR, "bool_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount--;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue--;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_alltrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if all values are true */
+ 	PG_RETURN_BOOL(state->aggcount == state->aggtrue);
+ }
+ 
+ Datum
+ bool_anytrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if any value is true */
+ 	PG_RETURN_BOOL(state->aggtrue > 0);
+ }
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 0158758..cba17a3 100644
*** a/src/backend/utils/adt/cash.c
--- b/src/backend/utils/adt/cash.c
*************** cashlarger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,896 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 > c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cashsmaller()
   * Return smaller of two cash values.
   */
*************** cashsmaller(PG_FUNCTION_ARGS)
*** 892,897 ****
--- 906,925 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 < c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cash_words()
   * This converts a int4 as well but to a representation using words
   * Obviously way North American centric - sorry
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 06cc0cd..0cca0b0 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** date_larger(PG_FUNCTION_ARGS)
*** 396,401 ****
--- 396,415 ----
  }
  
  Datum
+ date_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 > dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  date_smaller(PG_FUNCTION_ARGS)
  {
  	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
*************** date_smaller(PG_FUNCTION_ARGS)
*** 404,409 ****
--- 418,437 ----
  	PG_RETURN_DATEADT((dateVal1 < dateVal2) ? dateVal1 : dateVal2);
  }
  
+ Datum
+ date_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 < dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* Compute difference between two dates in days.
   */
  Datum
*************** time_larger(PG_FUNCTION_ARGS)
*** 1463,1468 ****
--- 1491,1510 ----
  }
  
  Datum
+ time_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 > time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  time_smaller(PG_FUNCTION_ARGS)
  {
  	TimeADT		time1 = PG_GETARG_TIMEADT(0);
*************** time_smaller(PG_FUNCTION_ARGS)
*** 1471,1476 ****
--- 1513,1532 ----
  	PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
  }
  
+ Datum
+ time_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 < time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* overlaps_time() --- implements the SQL OVERLAPS operator.
   *
   * Algorithm is per SQL spec.  This is much harder than you'd think
*************** timetz_larger(PG_FUNCTION_ARGS)
*** 2262,2267 ****
--- 2318,2337 ----
  }
  
  Datum
+ timetz_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) > 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  timetz_smaller(PG_FUNCTION_ARGS)
  {
  	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
*************** timetz_smaller(PG_FUNCTION_ARGS)
*** 2275,2280 ****
--- 2345,2364 ----
  	PG_RETURN_TIMETZADT_P(result);
  }
  
+ Datum
+ timetz_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) < 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* timetz_pl_interval()
   * Add interval to timetz.
   */
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 83c3878..32035e0 100644
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
*************** enum_smaller(PG_FUNCTION_ARGS)
*** 273,278 ****
--- 273,292 ----
  }
  
  Datum
+ enum_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) < 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_larger(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
*************** enum_larger(PG_FUNCTION_ARGS)
*** 282,287 ****
--- 296,315 ----
  }
  
  Datum
+ enum_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) > 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_cmp(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index e2a0812..527eff6 100644
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4larger(PG_FUNCTION_ARGS)
*** 626,631 ****
--- 626,647 ----
  }
  
  Datum
+ float4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float4smaller(PG_FUNCTION_ARGS)
  {
  	float4		arg1 = PG_GETARG_FLOAT4(0);
*************** float4smaller(PG_FUNCTION_ARGS)
*** 639,644 ****
--- 655,676 ----
  	PG_RETURN_FLOAT4(result);
  }
  
+ Datum
+ float4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  /*
   *		======================
   *		FLOAT8 BASE OPERATIONS
*************** float8larger(PG_FUNCTION_ARGS)
*** 693,698 ****
--- 725,746 ----
  }
  
  Datum
+ float8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float8smaller(PG_FUNCTION_ARGS)
  {
  	float8		arg1 = PG_GETARG_FLOAT8(0);
*************** float8smaller(PG_FUNCTION_ARGS)
*** 706,711 ****
--- 754,774 ----
  	PG_RETURN_FLOAT8(result);
  }
  
+ Datum
+ float8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *		====================
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 669355e..fe9d736 100644
*** a/src/backend/utils/adt/int.c
--- b/src/backend/utils/adt/int.c
*************** int2larger(PG_FUNCTION_ARGS)
*** 1185,1190 ****
--- 1185,1204 ----
  }
  
  Datum
+ int2larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int2smaller(PG_FUNCTION_ARGS)
  {
  	int16		arg1 = PG_GETARG_INT16(0);
*************** int2smaller(PG_FUNCTION_ARGS)
*** 1194,1199 ****
--- 1208,1227 ----
  }
  
  Datum
+ int2smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4larger(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4larger(PG_FUNCTION_ARGS)
*** 1203,1208 ****
--- 1231,1250 ----
  }
  
  Datum
+ int4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4smaller(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4smaller(PG_FUNCTION_ARGS)
*** 1211,1216 ****
--- 1253,1272 ----
  	PG_RETURN_INT32((arg1 < arg2) ? arg1 : arg2);
  }
  
+ Datum
+ int4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /*
   * Bit-pushing operators
   *
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..820be68 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8larger(PG_FUNCTION_ARGS)
*** 752,757 ****
--- 752,771 ----
  }
  
  Datum
+ int8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int8smaller(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
*************** int8smaller(PG_FUNCTION_ARGS)
*** 764,769 ****
--- 778,797 ----
  }
  
  Datum
+ int8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int84pl(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..4749152 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_smaller(PG_FUNCTION_ARGS)
*** 1834,1839 ****
--- 1834,1854 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) < 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * numeric_larger() -
*************** numeric_larger(PG_FUNCTION_ARGS)
*** 1856,1861 ****
--- 1871,1892 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) > 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /* ----------------------------------------------------------------------
   *
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 8945ef4..745449a 100644
*** a/src/backend/utils/adt/oid.c
--- b/src/backend/utils/adt/oid.c
*************** oidlarger(PG_FUNCTION_ARGS)
*** 397,402 ****
--- 397,416 ----
  }
  
  Datum
+ oidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidsmaller(PG_FUNCTION_ARGS)
  {
  	Oid			arg1 = PG_GETARG_OID(0);
*************** oidsmaller(PG_FUNCTION_ARGS)
*** 406,411 ****
--- 420,439 ----
  }
  
  Datum
+ oidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidvectoreq(PG_FUNCTION_ARGS)
  {
  	int32		cmp = DatumGetInt32(btoidvectorcmp(fcinfo));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 6e2bbdc..57fcf80 100644
*** a/src/backend/utils/adt/tid.c
--- b/src/backend/utils/adt/tid.c
*************** tidlarger(PG_FUNCTION_ARGS)
*** 237,242 ****
--- 237,256 ----
  }
  
  Datum
+ tidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) > 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  tidsmaller(PG_FUNCTION_ARGS)
  {
  	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
*************** tidsmaller(PG_FUNCTION_ARGS)
*** 245,250 ****
--- 259,277 ----
  	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
  }
  
+ Datum
+ tidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) < 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *	Functions to get latest tid of a specified tuple.
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 4581862..5b03c9f 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** timestamp_smaller(PG_FUNCTION_ARGS)
*** 2393,2398 ****
--- 2393,2412 ----
  }
  
  Datum
+ timestamp_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) < 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  timestamp_larger(PG_FUNCTION_ARGS)
  {
  	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
*************** timestamp_larger(PG_FUNCTION_ARGS)
*** 2406,2411 ****
--- 2420,2438 ----
  	PG_RETURN_TIMESTAMP(result);
  }
  
+ Datum
+ timestamp_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) > 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
  
  Datum
  timestamp_mi(PG_FUNCTION_ARGS)
*************** interval_smaller(PG_FUNCTION_ARGS)
*** 2849,2854 ****
--- 2876,2895 ----
  }
  
  Datum
+ interval_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) < 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_larger(PG_FUNCTION_ARGS)
  {
  	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
*************** interval_larger(PG_FUNCTION_ARGS)
*** 2863,2868 ****
--- 2904,2923 ----
  }
  
  Datum
+ interval_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) > 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_pl(PG_FUNCTION_ARGS)
  {
  	Interval   *span1 = PG_GETARG_INTERVAL_P(0);
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 502ca44..536ca66 100644
*** a/src/backend/utils/adt/varchar.c
--- b/src/backend/utils/adt/varchar.c
*************** bpchar_larger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,905 ----
  }
  
  Datum
+ bpchar_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp > 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  bpchar_smaller(PG_FUNCTION_ARGS)
  {
  	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
*************** bpchar_smaller(PG_FUNCTION_ARGS)
*** 894,899 ****
--- 917,945 ----
  	PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2);
  }
  
+ Datum
+ bpchar_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp < 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /*
   * bpchar needs a specialized hash function because we want to ignore
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..6b640cb 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** text_larger(PG_FUNCTION_ARGS)
*** 1697,1702 ****
--- 1697,1716 ----
  }
  
  Datum
+ text_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  text_smaller(PG_FUNCTION_ARGS)
  {
  	text	   *arg1 = PG_GETARG_TEXT_PP(0);
*************** text_smaller(PG_FUNCTION_ARGS)
*** 1708,1713 ****
--- 1722,1740 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ Datum
+ text_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * The following operators support character-by-character comparison
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 2fb3871..39f991e 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2113	n 0 interval_pl		-	-	
*** 122,169 ****
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
--- 122,169 ----
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger			int8larger_inv			-		413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger			int4larger_inv			-		521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger			int2larger_inv			-		520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger			oidlarger_inv			-		610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger		float4larger_inv		-		623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger		float8larger_inv		-		674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger			int4larger_inv			-		563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger			date_larger_inv			-		1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger			time_larger_inv			-		1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger		timetz_larger_inv		-		1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger			cashlarger_inv			-		903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	timestamp_larger_inv	-		2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	timestamptz_larger_inv	-		1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger		interval_larger_inv		-		1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger			text_larger_inv			-		666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger		numeric_larger_inv		-		1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger		array_larger_inv		-		1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger		bpchar_larger_inv		-		1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger			tidlarger_inv			-		2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger			enum_larger_inv			-		3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller			int8smaller_inv			-		412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller			int4smaller_inv			-		97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller			int2smaller_inv			-		95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller			oidsmaller_inv			-		609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller		float4smaller_inv		-		622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller		float8smaller_inv		-		672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller			int4smaller_inv			-		562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller		date_smaller_inv		-		1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller		time_smaller_inv		-		1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller		timetz_smaller_inv		-		1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller			cashsmaller_inv			-		902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	timestamp_smaller_inv	-		2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller timestamptz_smaller_inv	-		1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	interval_smaller_inv	-		1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller		text_smaller_inv		-		664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller		numeric_smaller_inv		-		1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller		array_smaller_inv		-		1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller		bpchar_smaller_inv		-		1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller			tidsmaller_inv			-		2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller		enum_smaller_inv		-		3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
*************** DATA(insert ( 2828	n 0 float8_regr_accum
*** 232,240 ****
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
--- 232,240 ----
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
! DATA(insert ( 2518	n 0 bool_accum		bool_accum_inv		bool_anytrue	59	2281		16	_null_ ));
! DATA(insert ( 2519	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ad9774c..c96a968 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("larger of two");
*** 367,372 ****
--- 367,377 ----
  DATA(insert OID = 211 (  float4smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3200 (  float4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3201 (  float4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 212 (  int4um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "23" _null_ _null_ _null_ _null_ int4um _null_ _null_ _null_ ));
  DATA(insert OID = 213 (  int2um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "21" _null_ _null_ _null_ _null_ int2um _null_ _null_ _null_ ));
  
*************** DATA(insert OID = 223 (  float8larger	  
*** 386,391 ****
--- 391,400 ----
  DESCR("larger of two");
  DATA(insert OID = 224 (  float8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3202 (  float8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3203 (  float8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 225 (  lseg_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "601" _null_ _null_ _null_ _null_	lseg_center _null_ _null_ _null_ ));
  DATA(insert OID = 226 (  path_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "602" _null_ _null_ _null_ _null_	path_center _null_ _null_ _null_ ));
*************** DATA(insert OID = 458 (  text_larger	   
*** 711,716 ****
--- 720,729 ----
  DESCR("larger of two");
  DATA(insert OID = 459 (  text_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3204 (  text_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3205 (  text_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 460 (  int8in			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "2275" _null_ _null_ _null_ _null_ int8in _null_ _null_ _null_ ));
  DESCR("I/O");
*************** DATA(insert OID = 515 (  array_larger	  
*** 859,864 ****
--- 872,881 ----
  DESCR("larger of two");
  DATA(insert OID = 516 (  array_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3206 (  array_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3207 (  array_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1191 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 23 "2277 23 16" _null_ _null_ _null_ _null_ generate_subscripts _null_ _null_ _null_ ));
  DESCR("array subscripts generator");
  DATA(insert OID = 1192 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 23 "2277 23" _null_ _null_ _null_ _null_ generate_subscripts_nodir _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 913,918 ****
--- 930,945 ----
  DATA(insert OID = 771 (  int2smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3208 (  int4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3209 (  int4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3210 (  int2larger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3211 (  int2smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 774 (  gistgettuple	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_	gistgettuple _null_ _null_ _null_ ));
  DESCR("gist(internal)");
  DATA(insert OID = 638 (  gistgetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	gistgetbitmap _null_ _null_ _null_ ));
*************** DATA(insert OID =  898 (  cashlarger	   
*** 1003,1008 ****
--- 1030,1039 ----
  DESCR("larger of two");
  DATA(insert OID =  899 (  cashsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID =  3212 (  cashlarger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID =  3213 (  cashsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID =  919 (  flt8_mul_cash    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "701 790" _null_ _null_ _null_ _null_	flt8_mul_cash _null_ _null_ _null_ ));
  DATA(insert OID =  935 (  cash_words	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "790" _null_ _null_ _null_ _null_	cash_words _null_ _null_ _null_ ));
  DESCR("output money amount as words");
*************** DATA(insert OID = 1063 (  bpchar_larger 
*** 1157,1162 ****
--- 1188,1197 ----
  DESCR("larger of two");
  DATA(insert OID = 1064 (  bpchar_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3214 (  bpchar_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3215 (  bpchar_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1078 (  bpcharcmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1042 1042" _null_ _null_ _null_ _null_ bpcharcmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1080 (  hashbpchar	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "1042" _null_ _null_ _null_ _null_	hashbpchar _null_ _null_ _null_ ));
*************** DATA(insert OID = 1138 (  date_larger	  
*** 1191,1196 ****
--- 1226,1235 ----
  DESCR("larger of two");
  DATA(insert OID = 1139 (  date_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3216 (  date_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3217 (  date_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1140 (  date_mi		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1082 1082" _null_ _null_ _null_ _null_ date_mi _null_ _null_ _null_ ));
  DATA(insert OID = 1141 (  date_pli		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_pli _null_ _null_ _null_ ));
  DATA(insert OID = 1142 (  date_mii		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_mii _null_ _null_ _null_ ));
*************** DATA(insert OID = 1195 (  timestamptz_sm
*** 1281,1290 ****
--- 1320,1339 ----
  DESCR("smaller of two");
  DATA(insert OID = 1196 (  timestamptz_larger  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 3218 (  timestamptz_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3219 (  timestamptz_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1197 (  interval_smaller	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  DATA(insert OID = 1198 (  interval_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 3220 (  interval_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3221 (  interval_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1199 (  age				PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_	timestamptz_age _null_ _null_ _null_ ));
  DESCR("date difference preserving months and years");
  
*************** DESCR("larger of two");
*** 1318,1323 ****
--- 1367,1378 ----
  DATA(insert OID = 1237 (  int8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3223 (  int8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3224 (  int8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1238 (  texticregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexeq _null_ _null_ _null_ ));
  DATA(insert OID = 1239 (  texticregexne    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexne _null_ _null_ _null_ ));
  DATA(insert OID = 1240 (  nameicregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "19 25" _null_ _null_ _null_ _null_ nameicregexeq _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1374,1379 ****
--- 1429,1439 ----
  DATA(insert OID = 2796 ( tidsmaller		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3225 ( tidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3226 ( tidsmaller_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1296 (  timedate_pl	   PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1114 "1083 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
  DATA(insert OID = 1297 (  datetimetz_pl    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1082 1266" _null_ _null_ _null_ _null_ datetimetz_timestamptz _null_ _null_ _null_ ));
  DATA(insert OID = 1298 (  timetzdate_pl    PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1184 "1266 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1515,1520 ****
--- 1575,1590 ----
  DATA(insert OID = 1380 (  timetz_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3227 (  time_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3228 (  time_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3229 (  timetz_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3230 (  timetz_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1381 (  char_length	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "25" _null_ _null_ _null_ _null_ textlen _null_ _null_ _null_ ));
  DESCR("character length");
  
*************** DATA(insert OID = 1766 ( numeric_smaller
*** 2277,2282 ****
--- 2347,2357 ----
  DESCR("smaller of two");
  DATA(insert OID = 1767 ( numeric_larger			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 3231 ( numeric_smaller_inv		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3232 ( numeric_larger_inv			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1769 ( numeric_cmp			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1700 1700" _null_ _null_ _null_ _null_ numeric_cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1771 ( numeric_uminus			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ numeric_uminus _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 2801,2806 ****
--- 2876,2886 ----
  DATA(insert OID = 1966 (  oidsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3244 (  oidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3245 (  oidsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 timestamp_transform f f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
  DESCR("adjust timestamptz precision");
  DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 time_transform f f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
*************** DATA(insert OID = 2035 (  timestamp_smal
*** 2862,2867 ****
--- 2942,2953 ----
  DESCR("smaller of two");
  DATA(insert OID = 2036 (  timestamp_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 3246 (  timestamp_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3247 (  timestamp_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 2037 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 1266 "25 1266" _null_ _null_ _null_ _null_ timetz_zone _null_ _null_ _null_ ));
  DESCR("adjust time with time zone to new zone");
  DATA(insert OID = 2038 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1186 1266" _null_ _null_ _null_ _null_	timetz_izone _null_ _null_ _null_ ));
*************** DESCR("non-persistent series generator")
*** 3853,3863 ****
  DATA(insert OID = 939  (  generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t s 3 0 1184 "1184 1184 1186" _null_ _null_ _null_ _null_ generate_series_timestamptz _null_ _null_ _null_ ));
  DESCR("non-persistent series generator");
  
- /* boolean aggregates */
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
! DESCR("aggregate transition function");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
! DESCR("aggregate transition function");
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
--- 3939,3950 ----
  DATA(insert OID = 939  (  generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t s 3 0 1184 "1184 1184 1186" _null_ _null_ _null_ _null_ generate_series_timestamptz _null_ _null_ _null_ ));
  DESCR("non-persistent series generator");
  
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
! DESCR("true if both inputs are true");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
! DESCR("true if any inputs are true");
! 
! /* boolean aggregates */
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
*************** DATA(insert OID = 2518 ( bool_or					   
*** 3865,3871 ****
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
! 
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("bitwise-and smallint aggregate");
--- 3952,3965 ----
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
! DATA(insert OID = 8033 ( bool_accum			   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum _null_ _null_ _null_ ));
! DESCR("aggregate transition function");
! DATA(insert OID = 3248 ( bool_accum_inv		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum_inv _null_ _null_ _null_ ));
! DESCR("inverse aggregate transition function");
! DATA(insert OID = 8035 ( bool_alltrue			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_alltrue _null_ _null_ _null_ ));
! DESCR("aggregate final function");
! DATA(insert OID = 8036 ( bool_anytrue			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_anytrue _null_ _null_ _null_ ));
! DESCR("aggregate final function");
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("bitwise-and smallint aggregate");
*************** DATA(insert OID = 3524 (  enum_smaller	P
*** 4205,4210 ****
--- 4299,4309 ----
  DESCR("smaller of two");
  DATA(insert OID = 3525 (  enum_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 3249 (  enum_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3250 (  enum_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 3526 (  max			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("maximum value of all enum input values");
  DATA(insert OID = 3527 (  min			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..43e4973 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern Datum array_upper(PG_FUNCTION_ARG
*** 206,212 ****
--- 206,214 ----
  extern Datum array_length(PG_FUNCTION_ARGS);
  extern Datum array_cardinality(PG_FUNCTION_ARGS);
  extern Datum array_larger(PG_FUNCTION_ARGS);
+ extern Datum array_larger_inv(PG_FUNCTION_ARGS);
  extern Datum array_smaller(PG_FUNCTION_ARGS);
+ extern Datum array_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
  extern Datum array_fill(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..c97c910 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum boolle(PG_FUNCTION_ARGS);
*** 121,126 ****
--- 121,130 ----
  extern Datum boolge(PG_FUNCTION_ARGS);
  extern Datum booland_statefunc(PG_FUNCTION_ARGS);
  extern Datum boolor_statefunc(PG_FUNCTION_ARGS);
+ extern Datum bool_accum(PG_FUNCTION_ARGS);
+ extern Datum bool_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum bool_alltrue(PG_FUNCTION_ARGS);
+ extern Datum bool_anytrue(PG_FUNCTION_ARGS);
  extern bool parse_bool(const char *value, bool *result);
  extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
  
*************** extern Datum enum_ge(PG_FUNCTION_ARGS);
*** 167,173 ****
--- 171,179 ----
  extern Datum enum_gt(PG_FUNCTION_ARGS);
  extern Datum enum_cmp(PG_FUNCTION_ARGS);
  extern Datum enum_smaller(PG_FUNCTION_ARGS);
+ extern Datum enum_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum enum_larger(PG_FUNCTION_ARGS);
+ extern Datum enum_larger_inv(PG_FUNCTION_ARGS);
  extern Datum enum_first(PG_FUNCTION_ARGS);
  extern Datum enum_last(PG_FUNCTION_ARGS);
  extern Datum enum_range_bounds(PG_FUNCTION_ARGS);
*************** extern Datum int42div(PG_FUNCTION_ARGS);
*** 241,249 ****
--- 247,259 ----
  extern Datum int4mod(PG_FUNCTION_ARGS);
  extern Datum int2mod(PG_FUNCTION_ARGS);
  extern Datum int2larger(PG_FUNCTION_ARGS);
+ extern Datum int2larger_inv(PG_FUNCTION_ARGS);
  extern Datum int2smaller(PG_FUNCTION_ARGS);
+ extern Datum int2smaller_inv(PG_FUNCTION_ARGS);
  extern Datum int4larger(PG_FUNCTION_ARGS);
+ extern Datum int4larger_inv(PG_FUNCTION_ARGS);
  extern Datum int4smaller(PG_FUNCTION_ARGS);
+ extern Datum int4smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int4and(PG_FUNCTION_ARGS);
  extern Datum int4or(PG_FUNCTION_ARGS);
*************** extern Datum float4abs(PG_FUNCTION_ARGS)
*** 347,358 ****
--- 357,372 ----
  extern Datum float4um(PG_FUNCTION_ARGS);
  extern Datum float4up(PG_FUNCTION_ARGS);
  extern Datum float4larger(PG_FUNCTION_ARGS);
+ extern Datum float4larger_inv(PG_FUNCTION_ARGS);
  extern Datum float4smaller(PG_FUNCTION_ARGS);
+ extern Datum float4smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float8abs(PG_FUNCTION_ARGS);
  extern Datum float8um(PG_FUNCTION_ARGS);
  extern Datum float8up(PG_FUNCTION_ARGS);
  extern Datum float8larger(PG_FUNCTION_ARGS);
+ extern Datum float8larger_inv(PG_FUNCTION_ARGS);
  extern Datum float8smaller(PG_FUNCTION_ARGS);
+ extern Datum float8smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float4pl(PG_FUNCTION_ARGS);
  extern Datum float4mi(PG_FUNCTION_ARGS);
  extern Datum float4mul(PG_FUNCTION_ARGS);
*************** extern Datum oidle(PG_FUNCTION_ARGS);
*** 501,507 ****
--- 515,523 ----
  extern Datum oidge(PG_FUNCTION_ARGS);
  extern Datum oidgt(PG_FUNCTION_ARGS);
  extern Datum oidlarger(PG_FUNCTION_ARGS);
+ extern Datum oidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum oidsmaller(PG_FUNCTION_ARGS);
+ extern Datum oidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum oidvectorin(PG_FUNCTION_ARGS);
  extern Datum oidvectorout(PG_FUNCTION_ARGS);
  extern Datum oidvectorrecv(PG_FUNCTION_ARGS);
*************** extern Datum tidgt(PG_FUNCTION_ARGS);
*** 707,713 ****
--- 723,731 ----
  extern Datum tidge(PG_FUNCTION_ARGS);
  extern Datum bttidcmp(PG_FUNCTION_ARGS);
  extern Datum tidlarger(PG_FUNCTION_ARGS);
+ extern Datum tidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum tidsmaller(PG_FUNCTION_ARGS);
+ extern Datum tidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum currtid_byreloid(PG_FUNCTION_ARGS);
  extern Datum currtid_byrelname(PG_FUNCTION_ARGS);
  
*************** extern Datum bpchargt(PG_FUNCTION_ARGS);
*** 730,736 ****
--- 748,756 ----
  extern Datum bpcharge(PG_FUNCTION_ARGS);
  extern Datum bpcharcmp(PG_FUNCTION_ARGS);
  extern Datum bpchar_larger(PG_FUNCTION_ARGS);
+ extern Datum bpchar_larger_inv(PG_FUNCTION_ARGS);
  extern Datum bpchar_smaller(PG_FUNCTION_ARGS);
+ extern Datum bpchar_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum bpcharlen(PG_FUNCTION_ARGS);
  extern Datum bpcharoctetlen(PG_FUNCTION_ARGS);
  extern Datum hashbpchar(PG_FUNCTION_ARGS);
*************** extern Datum text_le(PG_FUNCTION_ARGS);
*** 770,776 ****
--- 790,798 ----
  extern Datum text_gt(PG_FUNCTION_ARGS);
  extern Datum text_ge(PG_FUNCTION_ARGS);
  extern Datum text_larger(PG_FUNCTION_ARGS);
+ extern Datum text_larger_inv(PG_FUNCTION_ARGS);
  extern Datum text_smaller(PG_FUNCTION_ARGS);
+ extern Datum text_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum text_pattern_lt(PG_FUNCTION_ARGS);
  extern Datum text_pattern_le(PG_FUNCTION_ARGS);
  extern Datum text_pattern_gt(PG_FUNCTION_ARGS);
*************** extern Datum numeric_div_trunc(PG_FUNCTI
*** 980,986 ****
--- 1002,1010 ----
  extern Datum numeric_mod(PG_FUNCTION_ARGS);
  extern Datum numeric_inc(PG_FUNCTION_ARGS);
  extern Datum numeric_smaller(PG_FUNCTION_ARGS);
+ extern Datum numeric_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_larger(PG_FUNCTION_ARGS);
+ extern Datum numeric_larger_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_fac(PG_FUNCTION_ARGS);
  extern Datum numeric_sqrt(PG_FUNCTION_ARGS);
  extern Datum numeric_exp(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/cash.h b/src/include/utils/cash.h
index 3a491f9..4e184cc 100644
*** a/src/include/utils/cash.h
--- b/src/include/utils/cash.h
*************** extern Datum int2_mul_cash(PG_FUNCTION_A
*** 60,66 ****
--- 60,68 ----
  extern Datum cash_div_int2(PG_FUNCTION_ARGS);
  
  extern Datum cashlarger(PG_FUNCTION_ARGS);
+ extern Datum cashlarger_inv(PG_FUNCTION_ARGS);
  extern Datum cashsmaller(PG_FUNCTION_ARGS);
+ extern Datum cashsmaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum cash_words(PG_FUNCTION_ARGS);
  
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 622aa19..9f459ac 100644
*** a/src/include/utils/date.h
--- b/src/include/utils/date.h
*************** extern Datum date_cmp(PG_FUNCTION_ARGS);
*** 108,114 ****
--- 108,116 ----
  extern Datum date_sortsupport(PG_FUNCTION_ARGS);
  extern Datum date_finite(PG_FUNCTION_ARGS);
  extern Datum date_larger(PG_FUNCTION_ARGS);
+ extern Datum date_larger_inv(PG_FUNCTION_ARGS);
  extern Datum date_smaller(PG_FUNCTION_ARGS);
+ extern Datum date_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum date_mi(PG_FUNCTION_ARGS);
  extern Datum date_pli(PG_FUNCTION_ARGS);
  extern Datum date_mii(PG_FUNCTION_ARGS);
*************** extern Datum time_cmp(PG_FUNCTION_ARGS);
*** 168,174 ****
--- 170,178 ----
  extern Datum time_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_time(PG_FUNCTION_ARGS);
  extern Datum time_larger(PG_FUNCTION_ARGS);
+ extern Datum time_larger_inv(PG_FUNCTION_ARGS);
  extern Datum time_smaller(PG_FUNCTION_ARGS);
+ extern Datum time_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum time_mi_time(PG_FUNCTION_ARGS);
  extern Datum timestamp_time(PG_FUNCTION_ARGS);
  extern Datum timestamptz_time(PG_FUNCTION_ARGS);
*************** extern Datum timetz_cmp(PG_FUNCTION_ARGS
*** 195,201 ****
--- 199,207 ----
  extern Datum timetz_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_timetz(PG_FUNCTION_ARGS);
  extern Datum timetz_larger(PG_FUNCTION_ARGS);
+ extern Datum timetz_larger_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_smaller(PG_FUNCTION_ARGS);
+ extern Datum timetz_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_time(PG_FUNCTION_ARGS);
  extern Datum time_timetz(PG_FUNCTION_ARGS);
  extern Datum timestamptz_timetz(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..d102ccb 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8inc(PG_FUNCTION_ARGS);
*** 77,83 ****
--- 77,85 ----
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
+ extern Datum int8larger_inv(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
+ extern Datum int8smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int8and(PG_FUNCTION_ARGS);
  extern Datum int8or(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..85c0283 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum timestamp_cmp(PG_FUNCTION_A
*** 111,117 ****
--- 111,119 ----
  extern Datum timestamp_sortsupport(PG_FUNCTION_ARGS);
  extern Datum timestamp_hash(PG_FUNCTION_ARGS);
  extern Datum timestamp_smaller(PG_FUNCTION_ARGS);
+ extern Datum timestamp_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timestamp_larger(PG_FUNCTION_ARGS);
+ extern Datum timestamp_larger_inv(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS);
  extern Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS);
*************** extern Datum interval_finite(PG_FUNCTION
*** 147,153 ****
--- 149,157 ----
  extern Datum interval_cmp(PG_FUNCTION_ARGS);
  extern Datum interval_hash(PG_FUNCTION_ARGS);
  extern Datum interval_smaller(PG_FUNCTION_ARGS);
+ extern Datum interval_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum interval_larger(PG_FUNCTION_ARGS);
+ extern Datum interval_larger_inv(PG_FUNCTION_ARGS);
  extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
  extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
  extern Datum interval_justify_days(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index aa29471..862da92 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1300,1305 ****
--- 1300,1410 ----
  -- Test the MIN, MAX and boolean inverse transition functions
  --
  --
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  40 |  30
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  20 |  20
+     |  40 |  40
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  30 |  30
+  40 |     |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |  20 |  10
+     |  10 |  10
+  10 |  10 |  10
+ (5 rows)
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |     |  10
+     |     |  10
+  10 |     |  10
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  |  p  | max | min 
+ ----+-----+-----+-----
+  ad | 100 | ae  | ab
+  ab | 100 | ae  | ab
+  ae | 100 | ae  | ae
+  ad | 200 | ad  | aa
+  aa | 200 | aa  | aa
+ (5 rows)
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  b | bool_and | bool_or 
+ ---+----------+---------
+  t | t        | t
+  t | f        | t
+  f | f        | f
+  f | f        | t
+  t | t        | t
+ (5 rows)
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 676fa45..8224b97 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 482,487 ****
--- 482,523 ----
  --
  --
  
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
#144Florian Pflug
fgp@phlo.org
In reply to: Florian Pflug (#143)
5 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Feb20, 2014, at 02:48 , Florian Pflug <fgp@phlo.org> wrote:

On Jan29, 2014, at 13:45 , Florian Pflug <fgp@phlo.org> wrote:

In fact, I'm
currently leaning towards just forbidding non-strict forward transition
function with strict inverses, and adding non-NULL counters to the
aggregates that then require them. It's really only the SUM() aggregates
that are affected by this, I think.

I finally got around to doing that, and the results aren't too bad. The
attached patches required that the strictness settings of the forward and
reverse transition functions agree, and employ exactly the same NULL-skipping
logic we always had.

The only aggregates seriously affected by that change were SUM(int2) and
SUM(int4).

The SUM, AVG and STDDEV aggregates which use NumericAggState where
already mostly prepared for this - all they required were a few adjustments
to correctly handle the last non-NULL, non-NaN input being removed, and a few
additional PG_ARGISNULL calls for the inverse transition functions since they're
now non-strict. I've also modified them to unconditionally allocate the state
at the first call, instead upon seeing the first non-NULL input, but that isn't
strictly required. But without that, the state can have three classes of values -
SQL-NULL, NULL pointer and valid pointer, and that's just confusing...

SUM(int2) and SUM(int4) now simply use the same transition functions as
AVG(int2) and AVG(int4), which use an int8 array to track the sum of the inputs
and the number of inputs, plus a new final function int2int4_sum(). Previously,
they used a single int8 as their state type.

Since I was touching the code anyway, I removed some unnecessary inverse
transition functions - namely, int8_avg_accum_inv and numeric_avg_accum_inv. These
are completely identical to their non-avg cousins - the only difference between
the corresponding forward transition functions is whether they request computation
of sumX2 (i.e. the sum of squares of the inputs) or not.

I haven't yet updated the docs - it'll do that if and when there's consensus
about whether this is the way to go or not.

I realized only after posting this that the patches no longer apply to current HEAD.

Attached are rebased patches.

best regards,
Florian Pflug

Attachments:

invtrans_strictstrict_arith_3dd64e.patchapplication/octet-stream; name=invtrans_strictstrict_arith_3dd64e.patch; x-unix-mode=0644Download
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..e62f2a3 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8inc(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 717,767 ----
  	}
  }
  
+ Datum
+ int8dec(PG_FUNCTION_ARGS)
+ {
+ 	/*
+ 	 * When int8 is pass-by-reference, we provide this special case to avoid
+ 	 * palloc overhead for COUNT(): when called as an inverse transition
+ 	 * aggregate, we know that the argument is modifiable local storage,
+ 	 * so just update it in-place. (If int8 is pass-by-value, then of course
+ 	 * this is useless as well as incorrect, so just ifdef it out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *arg = (int64 *) PG_GETARG_POINTER(0);
+ 		int64		result;
+ 
+ 		result = *arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && *arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		*arg = result;
+ 		PG_RETURN_POINTER(arg);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		/* Not called as an aggregate, so just do it the dumb way */
+ 		int64		arg = PG_GETARG_INT64(0);
+ 		int64		result;
+ 
+ 		result = arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		PG_RETURN_INT64(result);
+ 	}
+ }
+ 
+ 
  /*
   * These functions are exactly like int8inc but are used for aggregates that
   * count only non-null values.	Since the functions are declared strict,
*************** int8inc_any(PG_FUNCTION_ARGS)
*** 733,738 ****
--- 778,789 ----
  }
  
  Datum
+ int8inc_any_inv(PG_FUNCTION_ARGS)
+ {
+ 	return int8dec(fcinfo);
+ }
+ 
+ Datum
  int8inc_float8_float8(PG_FUNCTION_ARGS)
  {
  	return int8inc(fcinfo);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..53e7624 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_float4(PG_FUNCTION_ARGS)
*** 2479,2486 ****
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	bool		isNaN;			/* true if any processed number was NaN */
  	MemoryContext agg_context;	/* context we're calculating in */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
--- 2479,2490 ----
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	int			maxScale;		/* stores the maximum scale seen so far. */
! 	int64		maxScaleCount;  /* tracks the number of values we've
! 								 * seen with the maximum scale */
! 
  	MemoryContext agg_context;	/* context we're calculating in */
+ 	int64		NaNcount;		/* Count of NaN values that are aggregated */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
*************** makeNumericAggState(FunctionCallInfo fci
*** 2505,2510 ****
--- 2509,2518 ----
  	state = (NumericAggState *) palloc0(sizeof(NumericAggState));
  	state->calcSumX2 = calcSumX2;
  	state->agg_context = agg_context;
+ 	state->NaNcount = 0;
+ 
+ 	state->maxScale = 0;
+ 	state->maxScaleCount = 0;
  
  	MemoryContextSwitchTo(old_context);
  
*************** do_numeric_accum(NumericAggState *state,
*** 2522,2536 ****
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (state->isNaN || NUMERIC_IS_NAN(newval))
  	{
! 		state->isNaN = true;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
--- 2530,2558 ----
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (NUMERIC_IS_NAN(newval))
  	{
! 		state->NaNcount++;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
+ 	/*
+ 	 * Track the highest scale that we've seen as if we ever perform an inverse
+ 	 * transition and remove the last numeric with the highest scale that we've
+ 	 * seen then we can no longer perform inverse transitions without risking
+ 	 * having the wrong dscale in the result value.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 		state->maxScaleCount++;
+ 	else if (X.dscale > state->maxScale)
+ 	{
+ 		state->maxScale = X.dscale;
+ 		state->maxScaleCount = 1;
+ 	}
+ 
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2541,2553 ****
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N++ > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
  	}
  	else
  	{
--- 2563,2577 ----
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
+ 
+ 		state->N++;
  	}
  	else
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2556,2567 ****
--- 2580,2692 ----
  
  		if (state->calcSumX2)
  			set_var_from_var(&X2, &(state->sumX2));
+ 
+ 		state->N = 1;
  	}
  
  	MemoryContextSwitchTo(old_context);
  }
  
  /*
+  * do_numeric_discard
+  * Attempts to remove a value from the aggregated state.
+  * If the value cannot be removed then the function will return false, the
+  * possible reasons for failing are described below.
+  *
+  * If we aggregate the values 1.01 and 2 then the result will be 3.01. If we
+  * are then asked to un-aggregate the 1.01 then we must reject this case as we
+  * won't be able to tell what the new aggregated value's dscale should be.
+  * We can't return 2.00 (dscale = 2) as we really should return just 2, but
+  * since we're not tracking any previous highest scales then we must just fail
+  * to perform the inverse transition and just return false.
+  *
+  * Values that are no longer aggregated should not be able to effect the dscale
+  * of the result of the values that *are* still aggregated.
+  *
+  * Note it may be better to track the number of times we've aggregated a
+  * numeric with each scale, then if we ever remove final highest scaled value
+  * then we can step the result's dscale down to the next highest value. This is
+  * perhaps slightly more work than we can afford to do here, but doing it this
+  * way would mean that we could always perform the inverse transition.
+  */
+ static bool
+ do_numeric_discard(NumericAggState *state, Numeric newval)
+ {
+ 	NumericVar	X;
+ 	NumericVar	X2;
+ 	MemoryContext old_context;
+ 
+ 	/* result is NaN if any processed number is NaN */
+ 	if (NUMERIC_IS_NAN(newval))
+ 	{
+ 		state->NaNcount--;
+ 		return true;
+ 	}
+ 
+ 	/* load processed number in short-lived context */
+ 	init_var_from_num(newval, &X);
+ 
+ 	/*
+ 	 * state->sumX's dscale matches the maximum dscale of any of the inputs
+ 	 * Removing the last input with that dscale would require us to recompute
+ 	 * the maximum dscale of the *remaining* inputs, which we cannot do unless
+ 	 * no more non-NaN inputs remain at all. So we report a failure instead,
+ 	 * and force the aggregation to be redone from scratch.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 	{
+ 		if (state->maxScaleCount > 1)
+ 		{
+ 			/* Some remaining inputs have same dscale */
+ 			--state->maxScaleCount;
+ 		}
+ 		else if (state->N == 1)
+ 		{
+ 			/* No remaining non-NaN inputs at all */
+ 			state->maxScale = 0;
+ 			state->maxScaleCount = 0;
+ 		}
+ 		else
+ 		{
+ 			/* No remaining inputs have same dscale */
+ 			return false;
+ 		}
+ 	}
+ 
+ 	/* if we need X^2, calculate that in short-lived context */
+ 	if (state->calcSumX2)
+ 	{
+ 		init_var(&X2);
+ 		mul_var(&X, &X, &X2, X.dscale * 2);
+ 	}
+ 
+ 	/* The rest of this needs to work in the aggregate context */
+ 	old_context = MemoryContextSwitchTo(state->agg_context);
+ 
+ 	if (state->N > 1)
+ 	{
+ 		sub_var(&(state->sumX), &X, &(state->sumX));
+ 		if (state->calcSumX2)
+ 			sub_var(&(state->sumX2), &X2, &(state->sumX2));
+ 		state->N--;
+ 	}
+ 	else if (state->N == 1)
+ 	{
+ 		/* Sums will be reset by next call to do_numeric_accum */
+ 		state->N = 0;
+ 	}
+ 	else
+ 	{
+ 		MemoryContextSwitchTo(old_context);
+ 		elog(ERROR, "cannot discard more values than were accumulated");
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 	return true;
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that require sumX2.
   */
  Datum
*************** numeric_accum(PG_FUNCTION_ARGS)
*** 2571,2588 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
--- 2696,2734 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, true);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * Generic inverse transition function for numeric aggregates
+  */
+ Datum
+ numeric_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL)
+ 		elog(ERROR, "numeric_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		/* Can we perform an inverse transition? if not return NULL. */
+ 		if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 			PG_RETURN_NULL();
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
+ 
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
*************** numeric_avg_accum(PG_FUNCTION_ARGS)
*** 2593,2606 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, false);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
- 	}
  
  	PG_RETURN_POINTER(state);
  }
--- 2739,2750 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, false);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  
  	PG_RETURN_POINTER(state);
  }
*************** int2_accum(PG_FUNCTION_ARGS)
*** 2621,2637 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2765,2780 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int4_accum(PG_FUNCTION_ARGS)
*** 2645,2661 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2788,2803 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2669,2686 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
! 		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
--- 2811,2913 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
+ 		do_numeric_accum(state, newval);
+ 	}
  
! 	PG_RETURN_POINTER(state);
! }
  
! 
! /*
!  * int2_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int2_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int4_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int4_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int8_accum_inv
!  * aggregate inverse transition function.
!  * This function must be declared as strict.
!  */
! Datum
! int8_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
  	}
  
  	PG_RETURN_POINTER(state);
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2688,2693 ****
--- 2915,2921 ----
  
  /*
   * Transition function for int8 input when we don't need sumX2.
+  * For the inverse, we use int8_accum_inv.
   */
  Datum
  int8_avg_accum(PG_FUNCTION_ARGS)
*************** int8_avg_accum(PG_FUNCTION_ARGS)
*** 2696,2719 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, false);
- 
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
- 
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
--- 2924,2945 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, false);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
*************** numeric_avg(PG_FUNCTION_ARGS)
*** 2722,2731 ****
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
--- 2948,2958 ----
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
*************** numeric_sum(PG_FUNCTION_ARGS)
*** 2740,2749 ****
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
--- 2967,2978 ----
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)
! 		/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
*************** numeric_stddev_internal(NumericAggState 
*** 2774,2780 ****
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL)
  	{
  		*is_null = true;
  		return NULL;
--- 3003,3009 ----
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
  	{
  		*is_null = true;
  		return NULL;
*************** numeric_stddev_internal(NumericAggState 
*** 2782,2788 ****
  
  	*is_null = false;
  
! 	if (state->isNaN)
  		return make_result(&const_nan);
  
  	init_var(&vN);
--- 3011,3017 ----
  
  	*is_null = false;
  
! 	if (state->NaNcount > 0)
  		return make_result(&const_nan);
  
  	init_var(&vN);
*************** numeric_stddev_pop(PG_FUNCTION_ARGS)
*** 2913,2932 ****
  }
  
  /*
!  * SUM transition functions for integer datatypes.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   *
!  * Because SQL defines the SUM() of no values to be NULL, not zero,
!  * the initial condition of the transition data value needs to be NULL. This
!  * means we can't rely on ExecAgg to automatically insert the first non-null
!  * data value into the transition data: it doesn't know how to do the type
!  * conversion.	The upshot is that these routines have to be marked non-strict
!  * and handle substitution of the first non-null input themselves.
   */
  
  Datum
--- 3142,3155 ----
  }
  
  /*
!  * Obsolete SUM transition functions for integer datatypes.
   *
!  * These were used to implement SUM aggregates before inverse transition
!  * functions were added. For inverse transitions, we need to know the number
!  * of summands to be able to return NULL whenenver the number of non-NULL
!  * inputs becomes zero. We therefore now use the intX_avg_accum and
!  * intX_avg_accum_inv transition functions, which use int8[] is their
!  * transition type to be able to count the number of inputs.
   */
  
  Datum
*************** int8_sum(PG_FUNCTION_ARGS)
*** 3065,3074 ****
  										NumericGetDatum(oldsum), newval));
  }
  
- 
  /*
!  * Routines for avg(int2) and avg(int4).  The transition datatype
!  * is a two-element int8 array, holding count and sum.
   */
  
  typedef struct Int8TransTypeData
--- 3288,3302 ----
  										NumericGetDatum(oldsum), newval));
  }
  
  /*
!  * Routines for sum(int2), sum(int4), avg(int2) and avg(int4).  The transition
!  * datatype is a two-element int8 array, holding count and sum.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   */
  
  typedef struct Int8TransTypeData
*************** int2_avg_accum(PG_FUNCTION_ARGS)
*** 3106,3111 ****
--- 3334,3368 ----
  }
  
  Datum
+ int2_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int16		newval = PG_GETARG_INT16(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ 
+ Datum
  int4_avg_accum(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray;
*************** int4_avg_accum(PG_FUNCTION_ARGS)
*** 3134,3139 ****
--- 3391,3442 ----
  }
  
  Datum
+ int4_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int32		newval = PG_GETARG_INT32(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ Datum
+ int2int4_sum(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Int8TransTypeData *transdata;
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 
+ 	/* SQL defines SUM of no values to be NULL */
+ 	if (transdata->count == 0)
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_DATUM(Int64GetDatumFast(transdata->sum));
+ }
+ 
+ Datum
  int8_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index cf6982b..eb051ba 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** interval_accum(PG_FUNCTION_ARGS)
*** 3157,3162 ****
--- 3157,3207 ----
  }
  
  Datum
+ interval_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Interval   *newval = PG_GETARG_INTERVAL_P(1);
+ 	Datum	   *transdatums;
+ 	int			ndatums;
+ 	Interval	sumX,
+ 				N;
+ 	Interval   *newsum;
+ 	ArrayType  *result;
+ 
+ 	deconstruct_array(transarray,
+ 					  INTERVALOID, sizeof(Interval), false, 'd',
+ 					  &transdatums, NULL, &ndatums);
+ 	if (ndatums != 2)
+ 		elog(ERROR, "expected 2-element interval array");
+ 
+ 	/*
+ 	 * XXX memcpy, instead of just extracting a pointer, to work around buggy
+ 	 * array code: it won't ensure proper alignment of Interval objects on
+ 	 * machines where double requires 8-byte alignment. That should be fixed,
+ 	 * but in the meantime...
+ 	 *
+ 	 * Note: must use DatumGetPointer here, not DatumGetIntervalP, else some
+ 	 * compilers optimize into double-aligned load/store anyway.
+ 	 */
+ 	memcpy((void *) &sumX, DatumGetPointer(transdatums[0]), sizeof(Interval));
+ 	memcpy((void *) &N, DatumGetPointer(transdatums[1]), sizeof(Interval));
+ 
+ 	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
+ 												   IntervalPGetDatum(&sumX),
+ 												 IntervalPGetDatum(newval)));
+ 	N.time -= 1;
+ 
+ 	transdatums[0] = IntervalPGetDatum(newsum);
+ 	transdatums[1] = IntervalPGetDatum(&N);
+ 
+ 	result = construct_array(transdatums, 2,
+ 							 INTERVALOID, sizeof(Interval), false, 'd');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ 
+ Datum
  interval_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..874aeec 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 103,125 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
--- 103,125 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		int8_accum_inv			numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		int4_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		int2_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_accum_inv		numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum		interval_accum_inv		interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		int8_accum_inv			numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_avg_accum		int4_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2109	n 0 int2_avg_accum		int2_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2110	n 0 float4pl			-						-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl			-						-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl				cash_mi					-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl			interval_mi				-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_accum_inv		numeric_sum		0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
*************** DATA(insert ( 2798	n 0 tidsmaller		-	-		
*** 166,221 ****
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
--- 166,221 ----
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		int8inc_any_inv	-		0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			int8dec			-		0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		int8_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		int4_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		int2_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_accum_inv	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		int8_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		int4_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		int2_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 11c1e1a..d18a6e1 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("truncate interval to specified un
*** 1309,1316 ****
--- 1309,1320 ----
  
  DATA(insert OID = 1219 (  int8inc		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8inc _null_ _null_ _null_ ));
  DESCR("increment");
+ DATA(insert OID = 3210 (  int8dec		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8dec _null_ _null_ _null_ ));
+ DESCR("decrement");
  DATA(insert OID = 2804 (  int8inc_any	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any _null_ _null_ _null_ ));
  DESCR("increment, ignores second argument");
+ DATA(insert OID = 3211 (  int8inc_any_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any_inv _null_ _null_ _null_ ));
+ DESCR("decrement, ignores second argument");
  DATA(insert OID = 1230 (  int8abs		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8abs _null_ _null_ _null_ ));
  
  DATA(insert OID = 1236 (  int8larger	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger _null_ _null_ _null_ ));
*************** DATA(insert OID = 1832 (  float8_stddev_
*** 2396,2401 ****
--- 2400,2407 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3212 (  numeric_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
*************** DATA(insert OID = 1836 (  int8_accum	   
*** 2406,2411 ****
--- 2412,2423 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 2746 (  int8_avg_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3213 (  int2_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3214 (  int4_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3215 (  int8_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
*************** DATA(insert OID = 1842 (  int8_sum		   P
*** 2426,2439 ****
--- 2438,2459 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 1843 (  interval_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3216 (  interval_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1844 (  interval_avg	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1186 "1187" _null_ _null_ _null_ _null_ interval_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1962 (  int2_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1963 (  int4_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3217 (  int2_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 3218 (  int4_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 1964 (  int8_avg		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1016" _null_ _null_ _null_ _null_ int8_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
+ DATA(insert OID = 3219 (  int2int4_sum	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "1016" _null_ _null_ _null_ _null_ int2int4_sum _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  DATA(insert OID = 2805 (  int8inc_float8_float8		PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 20 "20 701 701" _null_ _null_ _null_ _null_ int8inc_float8_float8 _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2806 (  float8_regr_accum			PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 1022 "1022 701 701" _null_ _null_ _null_ _null_ float8_regr_accum _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..9e44693 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum numeric_float8_no_overflow(
*** 999,1008 ****
--- 999,1012 ----
  extern Datum float4_numeric(PG_FUNCTION_ARGS);
  extern Datum numeric_float4(PG_FUNCTION_ARGS);
  extern Datum numeric_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg_accum(PG_FUNCTION_ARGS);
  extern Datum int2_accum(PG_FUNCTION_ARGS);
  extern Datum int4_accum(PG_FUNCTION_ARGS);
  extern Datum int8_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int4_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int8_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg_accum(PG_FUNCTION_ARGS);
  extern Datum numeric_avg(PG_FUNCTION_ARGS);
  extern Datum numeric_sum(PG_FUNCTION_ARGS);
*************** extern Datum int2_sum(PG_FUNCTION_ARGS);
*** 1014,1020 ****
--- 1018,1027 ----
  extern Datum int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_sum(PG_FUNCTION_ARGS);
  extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int4_avg_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int2int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_avg(PG_FUNCTION_ARGS);
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  extern Datum hash_numeric(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..5078e4a 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8div(PG_FUNCTION_ARGS);
*** 74,80 ****
--- 74,82 ----
  extern Datum int8abs(PG_FUNCTION_ARGS);
  extern Datum int8mod(PG_FUNCTION_ARGS);
  extern Datum int8inc(PG_FUNCTION_ARGS);
+ extern Datum int8dec(PG_FUNCTION_ARGS);
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
+ extern Datum int8inc_any_inv(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..e852fbd 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum interval_mul(PG_FUNCTION_AR
*** 179,184 ****
--- 179,185 ----
  extern Datum mul_d_interval(PG_FUNCTION_ARGS);
  extern Datum interval_div(PG_FUNCTION_ARGS);
  extern Datum interval_accum(PG_FUNCTION_ARGS);
+ extern Datum interval_accum_inv(PG_FUNCTION_ARGS);
  extern Datum interval_avg(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_mi(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index aa29471..1d28a81 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1295,1300 ****
--- 1295,1783 ----
  -- Test the arithmetic inverse transition functions
  --
  --
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 2.0000000000000000
+  2 | 2.5000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |    avg     
+ ---+------------
+  1 | @ 1.5 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+  i |  sum  
+ ---+-------
+  1 | $3.30
+  2 | $2.20
+  3 |      
+  4 |      
+ (4 rows)
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |   sum    
+ ---+----------
+  1 | @ 3 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 | 3.3
+  2 | 2.2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+  sum  
+ ------
+  6.01
+     5
+     3
+ (3 rows)
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     2
+  2 |     1
+  3 |     0
+  4 |     0
+ (4 rows)
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     4
+  2 |     3
+  3 |     2
+  4 |     1
+ (4 rows)
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   1
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   6
+  3 |   9
+  4 |   7
+ (4 rows)
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+          to_char          
+ --------------------------
+   100000000000000000000
+                       1.0
+ (2 rows)
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+  a |  b  | sum 
+ ---+-----+-----
+  1 |   1 |   1
+  2 |   2 |   3
+  3 | NaN | NaN
+  4 |   3 | NaN
+  5 |   4 |   7
+ (5 rows)
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 676fa45..e2af490 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 476,481 ****
--- 476,622 ----
  --
  --
  
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
invtrans_strictstrict_base_70f89c6.patchapplication/octet-stream; name=invtrans_strictstrict_base_70f89c6.patch; x-unix-mode=0644Download
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..538a5e1 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 56,61 ****
--- 56,62 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
*************** AggregateCreate(const char *aggName,
*** 68,78 ****
--- 69,81 ----
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
  	Oid			transfn;
+ 	Oid			invtransfn = InvalidOid; /* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	bool		transIsStrict;
  	Oid			rettype;
  	Oid			finaltype;
  	Oid			fnArgs[FUNC_MAX_ARGS];
*************** AggregateCreate(const char *aggName,
*** 234,241 ****
--- 237,297 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
+ 
+ 	/*
+ 	 * Remember if trans function is strict as we need to validate this
+ 	 * later if when we're dealing with the inverse transition function
+ 	 */
+ 	transIsStrict = proc->proisstrict;
+ 
  	ReleaseSysCache(tup);
  
+ 	/* handle invtransfn, if supplied */
+ 	if (agginvtransfnName)
+ 	{
+ 		/*
+ 		 * This must have the same number of arguments with the same types as
+ 		 * the transition function. We can just borrow the argument details
+ 		 * from the transition function and try to find a function with
+ 		 * the name of the inverse transition function and with a signature
+ 		 * that matches the transition function's.
+ 		 */
+ 		invtransfn = lookup_agg_function(agginvtransfnName,
+ 					nargs_transfn, fnArgs, InvalidOid, &rettype);
+ 
+ 		/*
+ 		 * Ensure the return type of the inverse transition function matches
+ 		 * the expected type.
+ 		 */
+ 		if (rettype != aggTransType)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("return type of inverse transition function %s is not %s",
+ 							NameListToString(agginvtransfnName),
+ 							format_type_be(aggTransType))));
+ 
+ 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+ 		proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 		/*
+ 		 * We force the strictness settings of the forward and inverse
+ 		 * transition functions to agree. This allows places which only need
+ 		 * forward transitions to not look at the inverse transition function
+ 		 * at all.
+ 		 */
+ 		if (transIsStrict != proc->proisstrict)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 						errmsg("stricness of forward and reverse transition functions must match"
+ 						)));
+ 		}
+ 		ReleaseSysCache(tup);
+ 
+ 	}
+ 
  	/* handle finalfn, if supplied */
  	if (aggfinalfnName)
  	{
*************** AggregateCreate(const char *aggName,
*** 391,396 ****
--- 447,453 ----
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+ 	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
*************** AggregateCreate(const char *aggName,
*** 425,430 ****
--- 482,496 ----
  	referenced.objectSubId = 0;
  	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
+ 	/* Depends on inverse transition function, if any */
+ 	if (OidIsValid(invtransfn))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = invtransfn;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
  	{
*************** AggregateCreate(const char *aggName,
*** 447,453 ****
  }
  
  /*
!  * lookup_agg_function -- common code for finding both transfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
--- 513,520 ----
  }
  
  /*
!  * lookup_agg_function
!  * common code for finding both transfn, invtransfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..0cfaa75 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
*************** DefineAggregate(List *name, List *args, 
*** 60,65 ****
--- 60,66 ----
  	AclResult	aclresult;
  	char		aggKind = AGGKIND_NORMAL;
  	List	   *transfuncName = NIL;
+ 	List	   *invtransfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
*************** DefineAggregate(List *name, List *args, 
*** 112,117 ****
--- 113,120 ----
  			transfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
  			transfuncName = defGetQualifiedName(defel);
+ 		else if (pg_strcasecmp(defel->defname, "invfunc") == 0)
+ 			invtransfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
  			finalfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
*************** DefineAggregate(List *name, List *args, 
*** 283,288 ****
--- 286,292 ----
  						   parameterDefaults,
  						   variadicArgType,
  						   transfuncName,		/* step function name */
+ 						   invtransfuncName,	/* inverse trans function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 08f3167..beee43d 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_group_keys(PlanSta
*** 85,90 ****
--- 85,91 ----
  					 List *ancestors, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
+ static void show_windowagg_info(PlanState *planstate, ExplainState *es);
  static void show_tidbitmap_info(BitmapHeapScanState *planstate,
  								ExplainState *es);
  static void show_instrumentation_count(const char *qlabel, int which,
*************** ExplainNode(PlanState *planstate, List *
*** 1420,1425 ****
--- 1421,1430 ----
  		case T_Hash:
  			show_hash_info((HashState *) planstate, es);
  			break;
+ 		case T_WindowAgg:
+ 			if (es->verbose && planstate->instrument)
+ 				show_windowagg_info(planstate, es);
+ 			break;
  		default:
  			break;
  	}
*************** show_hash_info(HashState *hashstate, Exp
*** 1918,1923 ****
--- 1923,1943 ----
  	}
  }
  
+ static void
+ show_windowagg_info(PlanState *planstate, ExplainState *es)
+ {
+ 	WindowAggState *winaggstate = (WindowAggState *) planstate;
+ 	Instrumentation *inst = planstate->instrument;
+ 
+ 	if (inst->nloops > 0 && inst->ntuples > 0 && winaggstate->numaggs > 0)
+ 	{
+ 		double tperrow = winaggstate->aggfwdtrans /
+ 			(inst->nloops * inst->ntuples);
+ 
+ 		ExplainPropertyFloat("Transitions Per Row", tperrow, 1, es);
+ 	}
+ }
+ 
  /*
   * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
   */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..04f2ebe 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1679,1684 ****
--- 1679,1685 ----
  		Oid			transfn_oid,
  					finalfn_oid;
  		Expr	   *transfnexpr,
+ 				   *invtransfnexpr, /* needed but never used */
  				   *finalfnexpr;
  		Datum		textInitVal;
  		int			i;
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1798,1805 ****
--- 1799,1808 ----
  								aggref->aggtype,
  								aggref->inputcollid,
  								transfn_oid,
+ 								InvalidOid, /* invtrans is not needed here */
  								finalfn_oid,
  								&transfnexpr,
+ 								&invtransfnexpr,
  								&finalfnexpr);
  
  		/* set up infrastructure for calling the transfn and finalfn */
*************** ExecReScanAgg(AggState *node)
*** 2127,2168 ****
  }
  
  /*
-  * AggCheckCallContext - test if a SQL function is being called as an aggregate
-  *
-  * The transition and/or final functions of an aggregate may want to verify
-  * that they are being called as aggregates, rather than as plain SQL
-  * functions.  They should use this function to do so.	The return value
-  * is nonzero if being called as an aggregate, or zero if not.	(Specific
-  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
-  * values could conceivably appear in future.)
-  *
-  * If aggcontext isn't NULL, the function also stores at *aggcontext the
-  * identity of the memory context that aggregate transition values are
-  * being stored in.
-  */
- int
- AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
- {
- 	if (fcinfo->context && IsA(fcinfo->context, AggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_AGGREGATE;
- 	}
- 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_WINDOW;
- 	}
- 
- 	/* this is just to prevent "uninitialized variable" warnings */
- 	if (aggcontext)
- 		*aggcontext = NULL;
- 	return 0;
- }
- 
- /*
   * AggGetAggref - allow an aggregate support function to get its Aggref
   *
   * If the function is being called as an aggregate support function,
--- 2130,2135 ----
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..4bc3565 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** typedef struct WindowStatePerFuncData
*** 102,119 ****
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transfer functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transfer functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/*
  	 * initial value from pg_aggregate entry
  	 */
--- 102,125 ----
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transition functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
+ 	Oid			invtransfn_oid; /* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transition functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
+ 	FmgrInfo	invtransfn;
  	FmgrInfo	finalfn;
  
+ 	/* Aggregate properties */
+ 	bool		use_invtransfn;			/* whether to use the invtransfn */
+ 	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+ 
  	/*
  	 * initial value from pg_aggregate entry
  	 */
*************** typedef struct WindowStatePerAggData
*** 140,149 ****
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	Datum		transValue;		/* current transition value */
! 	bool		transValueIsNull;
  
! 	bool		noTransValue;	/* true if transValue not set yet */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
--- 146,158 ----
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	MemoryContext	aggcontext;			/* context for transValue */
! 	int64			transValueCount;	/* Number of aggregates values*/
! 	Datum			transValue;			/* current transition value */
! 	bool			transValueIsNull;
  
! 	/* Data local to eval_windowaggregates() */
! 	bool			restart;			/* tmp marker that agg needs restart */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
*************** static void initialize_windowaggregate(W
*** 152,157 ****
--- 161,169 ----
  static void advance_windowaggregate(WindowAggState *winstate,
  						WindowStatePerFunc perfuncstate,
  						WindowStatePerAgg peraggstate);
+ static bool retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate);
  static void finalize_windowaggregate(WindowAggState *winstate,
  						 WindowStatePerFunc perfuncstate,
  						 WindowStatePerAgg peraggstate,
*************** static bool are_peers(WindowAggState *wi
*** 181,186 ****
--- 193,242 ----
  static bool window_gettupleslot(WindowObject winobj, int64 pos,
  					TupleTableSlot *slot);
  
+ /*
+  * AggCheckCallContext - test if a SQL function is being called as an aggregate
+  *
+  * The transition and/or final functions of an aggregate may want to verify
+  * that they are being called as aggregates, rather than as plain SQL
+  * functions.  They should use this function to do so.	The return value
+  * is nonzero if being called as an aggregate, or zero if not.	(Specific
+  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+  * values could conceivably appear in future.)
+  *
+  * If aggcontext isn't NULL, the function also stores at *aggcontext the
+  * identity of the memory context that aggregate transition values are
+  * being stored in.
+  *
+  * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+  * is private.
+  */
+ int
+ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggState))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ 		return AGG_CONTEXT_AGGREGATE;
+ 	}
+ 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ 	{
+ 		if (aggcontext)
+ 		{
+ 			/* Must lookup per-aggregate context */
+ 			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+ 			int				aggno = winstate->calledaggno;
+ 			Assert(0 <= aggno && aggno < winstate->numaggs);
+ 			*aggcontext = winstate->peragg[aggno].aggcontext;
+ 		}
+ 		return AGG_CONTEXT_WINDOW;
+ 	}
+ 
+ 	/* this is just to prevent "uninitialized variable" warnings */
+ 	if (aggcontext)
+ 		*aggcontext = NULL;
+ 	return 0;
+ }
  
  /*
   * initialize_windowaggregate
*************** initialize_windowaggregate(WindowAggStat
*** 193,210 ****
  {
  	MemoryContext oldContext;
  
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = peraggstate->initValue;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->noTransValue = peraggstate->initValueIsNull;
  	peraggstate->resultValueIsNull = true;
  }
  
--- 249,274 ----
  {
  	MemoryContext oldContext;
  
+ 	/* If we're using a private aggcontext, we may reset it here. But if the
+ 	 * context is shared, we don't know which other aggregates may still need
+ 	 * it, so we must leave it to the caller to reset at an appropriate time
+ 	 */
+ 	if (!peraggstate->aggcontext_is_shared)
+ 		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+ 
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = (Datum) 0;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
+ 	peraggstate->transValueCount = 0;
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->resultValue = (Datum) 0;
  	peraggstate->resultValueIsNull = true;
  }
  
*************** advance_windowaggregate(WindowAggState *
*** 254,265 ****
  		i++;
  	}
  
  	if (peraggstate->transfn.fn_strict)
  	{
- 		/*
- 		 * For a strict transfn, nothing happens when there's a NULL input; we
- 		 * just keep the prior transValue.
- 		 */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
--- 318,326 ----
  		i++;
  	}
  
+ 	/* Skip NULL inputs for aggregates which desire that. */
  	if (peraggstate->transfn.fn_strict)
  	{
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
*************** advance_windowaggregate(WindowAggState *
*** 268,290 ****
  				return;
  			}
  		}
! 		if (peraggstate->noTransValue)
  		{
  			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
  			 * already checked that the agg's input type is binary-compatible
  			 * with its transtype, so straight copy here is OK.)
  			 *
  			 * We must copy the datum into aggcontext if it is pass-by-ref. We
  			 * do not need to pfree the old transValue, since it's NULL.
  			 */
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->noTransValue = false;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
--- 329,354 ----
  				return;
  			}
  		}
! 	}
! 
! 	if (peraggstate->transfn.fn_strict) {
! 		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
  		{
  			/*
! 			 * For strict transfer functions with initial value NULL we use
! 			 * the first non-NULL input as the initial state. (We
  			 * already checked that the agg's input type is binary-compatible
  			 * with its transtype, so straight copy here is OK.)
  			 *
  			 * We must copy the datum into aggcontext if it is pass-by-ref. We
  			 * do not need to pfree the old transValue, since it's NULL.
  			 */
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->transValueCount = 1;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
*************** advance_windowaggregate(WindowAggState *
*** 294,308 ****
  			 * Don't call a strict function with NULL inputs.  Note it is
  			 * possible to get here despite the above tests, if the transfn is
  			 * strict *and* returned a NULL on a prior cycle. If that happens
! 			 * we will propagate the NULL all the way to the end.
  			 */
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  	}
  
  	/*
  	 * OK to call the transition function
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
--- 358,386 ----
  			 * Don't call a strict function with NULL inputs.  Note it is
  			 * possible to get here despite the above tests, if the transfn is
  			 * strict *and* returned a NULL on a prior cycle. If that happens
! 			 * we will propagate the NULL all the way to the end. That can only
! 			 * happen if there's no inverse transition function, though, since
! 			 * we disallow transitions back to NULL if there is one below.
  			 */
  			MemoryContextSwitchTo(oldContext);
+ 			Assert(peraggstate->invtransfn_oid == InvalidOid);
  			return;
  		}
  	}
  
  	/*
+ 	 * We must track the number of inputs that we add to transValue, since
+ 	 * to remove the last input, retreat_windowaggregate() musn't call the
+ 	 * inverse transition function, but simply reset transValue back to its
+ 	 * initial value.
+ 	 */
+ 	Assert(peraggstate->transValueCount >= 0);
+ 	peraggstate->transValueCount++;
+ 
+ 	/*
  	 * OK to call the transition function
+ 	 * Transfer functions with an inverse MUST not return NULL, see
+ 	 * retreat_windowaggregate()
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
*************** advance_windowaggregate(WindowAggState *
*** 310,316 ****
--- 388,402 ----
  							 (void *) winstate, NULL);
  	fcinfo->arg[0] = peraggstate->transValue;
  	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
  	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("transition function with an inverse returned NULL")));
+ 	}
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
*************** advance_windowaggregate(WindowAggState *
*** 322,328 ****
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
--- 408,552 ----
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
! 			newVal = datumCopy(newVal,
! 							   peraggstate->transtypeByVal,
! 							   peraggstate->transtypeLen);
! 		}
! 		if (!peraggstate->transValueIsNull)
! 			pfree(DatumGetPointer(peraggstate->transValue));
! 	}
! 
! 	MemoryContextSwitchTo(oldContext);
! 	peraggstate->transValue = newVal;
! 	peraggstate->transValueIsNull = fcinfo->isnull;
! }
! 
! /*
!  * retreat_windowaggregate
!  * removes tuples from aggregation.
!  * The calling function must ensure that each aggregate has
!  * a valid inverse transition function.
!  */
! static bool
! retreat_windowaggregate(WindowAggState *winstate,
! 						WindowStatePerFunc perfuncstate,
! 						WindowStatePerAgg peraggstate)
! {
! 	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
! 	int			numArguments = perfuncstate->numArguments;
! 	FunctionCallInfoData fcinfodata;
! 	FunctionCallInfo fcinfo = &fcinfodata;
! 	Datum		newVal;
! 	ListCell   *arg;
! 	int			i;
! 	MemoryContext oldContext;
! 	ExprContext *econtext = winstate->tmpcontext;
! 	ExprState  *filter = wfuncstate->aggfilter;
! 
! 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
! 
! 	/* Skip anything FILTERed out */
! 	if (filter)
! 	{
! 		bool		isnull;
! 		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
! 
! 		if (isnull || !DatumGetBool(res))
! 		{
! 			MemoryContextSwitchTo(oldContext);
! 			return true;
! 		}
! 	}
! 
! 	/* We start from 1, since the 0th arg will be the transition value */
! 	i = 1;
! 	foreach(arg, wfuncstate->args)
! 	{
! 		ExprState  *argstate = (ExprState *) lfirst(arg);
! 
! 		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
! 									  &fcinfo->argnull[i], NULL);
! 		i++;
! 	}
! 
! 	/* Skip inputs containing NULLS for aggregates that require this */
! 	if (peraggstate->invtransfn.fn_strict)
! 	{
! 		for (i = 1; i <= numArguments; i++)
! 		{
! 			if (fcinfo->argnull[i])
! 			{
! 				MemoryContextSwitchTo(oldContext);
! 				return true;
! 			}
! 		}
! 	}
! 
! 	/* There should still an added but not yet removed value */
! 	Assert(peraggstate->transValueCount >= 1);
! 
! 	/*
! 	 * We mustn't use the inverse transition function to remove the last
! 	 * input. Doing so would yield a non-NULL state, whereas we should be
! 	 * in the initial state afterwards which may very well be NULL. So
! 	 * instead, we simply re-initialize the aggregation in this case.
! 	 */
! 	if (peraggstate->transValueCount == 1)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		initialize_windowaggregate(winstate,
! 								&winstate->perfunc[peraggstate->wfuncno],
! 								peraggstate);
! 		return true;
! 	}
! 
! 	/*
! 	 * Perform the inverse transition.
! 	 *
! 	 * For pairs of forward and inverse transition functions, the state may
! 	 * never be NULL, except in the ignore_nulls case, and then only until
! 	 * until we see the first non-NULL input during which time should never
! 	 * attempt to invoke the inverse transition function. Excluding NULL
! 	 * as a possible state value allows us to make it mean "sorry, can't
! 	 * do an inverse transition in this case" when returned by the inverse
! 	 * transition function. In that case, we report the failure to the
! 	 * caller.
! 	 */
! 	if (peraggstate->transValueIsNull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		elog(ERROR, "transition value is NULL during inverse transition");
! 	}
! 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
! 							 numArguments + 1,
! 							 perfuncstate->winCollation,
! 							 (void *) winstate, NULL);
! 	fcinfo->arg[0] = peraggstate->transValue;
! 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
! 	winstate->calledaggno = perfuncstate->aggno;
! 	newVal = FunctionCallInvoke(fcinfo);
! 	winstate->calledaggno = -1;
! 	if (fcinfo->isnull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		return false;
! 	}
! 
! 	/* Update number of added but not yet removed values */
! 	peraggstate->transValueCount--;
! 
! 	/*
! 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
! 	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
! 	 * first input, we don't need to do anything.
! 	 */
! 	if (!peraggstate->transtypeByVal &&
! 		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
! 	{
! 		if (!fcinfo->isnull)
! 		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
*************** advance_windowaggregate(WindowAggState *
*** 334,341 ****
--- 558,568 ----
  	MemoryContextSwitchTo(oldContext);
  	peraggstate->transValue = newVal;
  	peraggstate->transValueIsNull = fcinfo->isnull;
+ 
+ 	return true;
  }
  
+ 
  /*
   * finalize_windowaggregate
   * parallel to finalize_aggregate in nodeAgg.c
*************** finalize_windowaggregate(WindowAggState 
*** 370,376 ****
--- 597,605 ----
  		}
  		else
  		{
+ 			winstate->calledaggno = perfuncstate->aggno;
  			*result = FunctionCallInvoke(&fcinfo);
+ 			winstate->calledaggno = -1;
  			*isnull = fcinfo.isnull;
  		}
  	}
*************** finalize_windowaggregate(WindowAggState 
*** 392,397 ****
--- 621,627 ----
  	MemoryContextSwitchTo(oldContext);
  }
  
+ 
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
*************** eval_windowaggregates(WindowAggState *wi
*** 406,417 ****
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs;
! 	int			i;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
--- 636,653 ----
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs,
! 				numaggs_restart = 0,
! 				i;
! 	int64		pos,
! 				aggregatedupto_nonrestarted;
! 	bool		is_first = (winstate->currentpos == 0),
! 				ok;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
! 	TupleTableSlot *temp_slot = winstate->temp_slot_1;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
*************** eval_windowaggregates(WindowAggState *wi
*** 420,426 ****
  	/* final output execution is in ps_ExprContext */
  	econtext = winstate->ss.ps.ps_ExprContext;
  	agg_winobj = winstate->agg_winobj;
- 	agg_row_slot = winstate->agg_row_slot;
  
  	/*
  	 * Currently, we support only a subset of the SQL-standard window framing
--- 656,661 ----
*************** eval_windowaggregates(WindowAggState *wi
*** 438,446 ****
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * For other frame start rules, we discard the aggregate state and re-run
! 	 * the aggregates whenever the frame head row moves.  We can still
! 	 * optimize as above whenever successive rows share the same frame head.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
--- 673,692 ----
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * We can still optimize as above whenever successive rows share the same
! 	 * frame head, but if the frame head moves beyond the aggregated base point
! 	 * we use the aggregate function's inverse transition function. This
! 	 * removes the tuple from aggregation and restores the aggregate's current
! 	 * state to what it would be if the removed row had never been aggregated
! 	 * in the first place. Inverse transition functions may optionally return
! 	 * NULL, this indicates that the function was unable to remove the tuple
! 	 * from aggregation, when this happens we must perform the aggregation all
! 	 * over again for all tuples in the new frame boundary.
! 	 *
! 	 * If the aggregate function does not have a inverse transition function
! 	 * and the frame head moves beyond the aggregated position then we must
! 	 * discard the aggregated state and re-aggregate similar to how we would
! 	 * have to if the inverse transition function had returned NULL.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
*************** eval_windowaggregates(WindowAggState *wi
*** 452,529 ****
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
- 	 *
- 	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
- 	 * some aggregates like SUM and COUNT we could avoid that by implementing
- 	 * a "negative transition function" that would be called for each row as
- 	 * it exits the frame.	We'd have to think about avoiding recalculation of
- 	 * volatile arguments of aggregate functions, too.
  	 */
  
  	/*
  	 * First, update the frame head position.
  	 */
! 	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
  
  	/*
! 	 * Initialize aggregates on first call for partition, or if the frame head
! 	 * position moved since last time.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos != winstate->aggregatedbase)
  	{
- 		/*
- 		 * Discard transient aggregate values
- 		 */
- 		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
- 
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  
  		/*
! 		 * If we created a mark pointer for aggregates, keep it pushed up to
! 		 * frame head, so that tuplestore can discard unnecessary rows.
  		 */
! 		if (agg_winobj->markptr >= 0)
! 			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
  
  		/*
! 		 * Initialize for loop below
  		 */
! 		ExecClearTuple(agg_row_slot);
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 		winstate->aggregatedupto = winstate->frameheadpos;
  	}
  
  	/*
! 	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
! 	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
! 	 * have to recalculate when the frame head moves or currentpos has
! 	 * advanced past the place we'd aggregated up to.  Check for these cases
! 	 * and if so, reuse the saved result values.
  	 */
! 	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
! 		for (i = 0; i < numaggs; i++)
  		{
- 			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
- 		return;
  	}
  
  	/*
  	 * Advance until we reach a row not in frame (or end of partition).
  	 *
  	 * Note the loop invariant: agg_row_slot is either empty or holds the row
--- 698,875 ----
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
  	 */
  
  	/*
  	 * First, update the frame head position.
+ 	 *
+ 	 * The frame head should never move backwards, and the code below wouldn't
+ 	 * cope if it did, so for safety we complain if it does.
  	 */
! 	update_frameheadpos(agg_winobj, temp_slot);
! 	if (winstate->frameheadpos < winstate->aggregatedbase)
! 		elog(ERROR, "frame moved backwards unexpectedly");
  
  	/*
! 	 * If the frame didn't change compared to the previous row, we can re-use
! 	 * the cached result. Since we don't know the current frame's end yet, we
! 	 * cannot check that the obvious way. But we can exploit that if the frame
! 	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
! 	 * row lies within the previous row's frame, the two frame's ends must
! 	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
! 	 * so we don't need to check for that explicitly here.
  	 */
! 	if (winstate->aggregatedbase == winstate->frameheadpos &&
! 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
+ 		return;
+ 	}
  
+ 	/* Initialize restart flags.
+ 	 *
+ 	 * We restart the aggregation
+ 	 *   - if we're processing the first row in the partition, or
+ 	 *   - if we the frame's head moved and we cannot use an inverse
+ 	 *     transition function, or
+ 	 *   - if the new frame doesn't overlap the old one
+ 	 *
+ 	 * Note that we don't strictly need to restart in the last case, but
+ 	 * if we're going to remove *all* rows from the aggregation anyway, a
+ 	 * restart surely is faster.
+ 	 */
+ 	for (i = 0; i < numaggs; i++)
+ 	{
+ 		peraggstate = &winstate->peragg[i];
+ 		if (is_first ||
+ 			(winstate->aggregatedbase < winstate->frameheadpos &&
+ 			!peraggstate->use_invtransfn) ||
+ 			(winstate->aggregatedupto <= winstate->frameheadpos))
+ 		{
+ 			peraggstate->restart = true;
+ 			numaggs_restart++;
+ 		}
+ 		else
+ 			peraggstate->restart = false;
+ 	}
+ 
+ 	/*
+ 	 * Attempt to update aggregatedbase to match the frame's head by
+ 	 * removing those inputs from the aggregations which fell off the top
+ 	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+ 	 * return false, in which case we restart that aggregate below.
+ 	 *
+ 	 * We track the number of aggregates which require a restart, and
+ 	 * exit as soon as there are no more retreatable aggregates left.
+ 	 */
+ 	for(pos = winstate->aggregatedbase;
+ 		pos < winstate->frameheadpos && numaggs_restart < numaggs;
+ 		++pos)
+ 	{
  		/*
! 		 * Fetch the tuple where the current aggregation started from.
! 		 * This should never fail as we should have been here before.
  		 */
! 		if (!window_gettupleslot(agg_winobj, pos, temp_slot))
! 			elog(ERROR, "Unable to find tuple in tuplestore");
! 
! 		/* Set tuple context for evaluation of aggregate arguments */
! 		winstate->tmpcontext->ecxt_outertuple = temp_slot;
  
  		/*
! 		 * Perform the inverse transition for each aggregate function in
! 		 * the window, unless it has already been marked as needing a
! 		 * restart.
  		 */
! 		for (i = 0; i < numaggs; i++)
! 		{
! 			peraggstate = &winstate->peragg[i];
! 			if (peraggstate->restart)
! 				continue;
! 
! 			wfuncno = peraggstate->wfuncno;
! 			ok = retreat_windowaggregate(winstate,
! 										 &winstate->perfunc[wfuncno],
! 										 peraggstate);
! 			if (!ok)
! 			{
! 				/* Inverse transition function has failed, must restart */
! 				peraggstate->restart = true;
! 				numaggs_restart++;
! 			}
! 		}
! 
! 		/* Reset per-input-tuple context and tuple after each tuple */
! 		ResetExprContext(winstate->tmpcontext);
! 		ExecClearTuple(temp_slot);
  	}
  
  	/*
! 	 * Then restart the aggregates which require it.
! 	 *
! 	 * We assume that aggregates using the shared context always restart
! 	 * if *any* aggregate restarts, and we may thus cleanup the shared
! 	 * aggcontext if that is the case. The private aggcontexts are reset
! 	 * by initialize_windowaggregate() if their owning aggregate restarts,
! 	 * otherwise we just pfree() the cached result.
  	 */
! 	if (numaggs_restart > 0)
! 		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < numaggs; i++)
  	{
! 		peraggstate = &winstate->peragg[i];
! 		/* Aggregates using the shared ctx must restart if *any* agg does */
! 		Assert(!peraggstate->aggcontext_is_shared ||
! 			   !numaggs_restart || peraggstate->restart);
! 		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
! 			!peraggstate->resulttypeByVal)
! 		{
! 			pfree(DatumGetPointer(peraggstate->resultValue));
! 			peraggstate->resultValue = (Datum) 0;
! 			peraggstate->resultValueIsNull = true;
! 		}
! 		else if (peraggstate->restart)
  		{
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  	}
  
  	/*
+ 	 * After this, non-restarted aggregated contain the rows between
+ 	 * aggregatedbase and aggregatedupto_nonrestarted, and restarted aggregates
+ 	 * the rows between aggregatedbase and aggregatedupto, which is to say none,
+ 	 * and aggregatedbase matches frameheadpos. If we modify we must clear
+ 	 * agg_row_slot, see the loop invariant below.
+ 	 */
+ 	winstate->aggregatedbase = winstate->frameheadpos;
+ 	aggregatedupto_nonrestarted = winstate->aggregatedupto;
+ 	if (numaggs_restart > 0 &&
+ 		winstate->aggregatedupto != winstate->frameheadpos)
+ 	{
+ 		winstate->aggregatedupto = winstate->frameheadpos;
+ 		ExecClearTuple(agg_row_slot);
+ 	}
+ 
+ 	/*
+ 	 * If we created a mark pointer for aggregates, keep it pushed up to
+ 	 * frame head, so that tuplestore can discard unnecessary rows.
+ 	 */
+ 	if (agg_winobj->markptr >= 0)
+ 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+ 
+ 	/*
  	 * Advance until we reach a row not in frame (or end of partition).
  	 *
  	 * Note the loop invariant: agg_row_slot is either empty or holds the row
*************** eval_windowaggregates(WindowAggState *wi
*** 551,556 ****
--- 897,907 ----
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
+ 			/* Non-restarted aggregates skip until aggregatedupto_previous*/
+ 			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+ 				!peraggstate->restart)
+ 				continue;
+ 
  			wfuncno = peraggstate->wfuncno;
  			advance_windowaggregate(winstate,
  									&winstate->perfunc[wfuncno],
*************** eval_windowaggregates(WindowAggState *wi
*** 565,570 ****
--- 916,932 ----
  		ExecClearTuple(agg_row_slot);
  	}
  
+ 	/* The frame's end is not supposed to move backwards, ever */
+ 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+ 
+ 	/* Update statistics */
+ 	if (numaggs_restart > 0)
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - winstate->frameheadpos);
+ 	else
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - aggregatedupto_nonrestarted);
+ 
  	/*
  	 * finalize aggregates and fill result/isnull fields.
  	 */
*************** eval_windowaggregates(WindowAggState *wi
*** 589,616 ****
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal)
  		{
! 			/*
! 			 * clear old resultValue in order not to leak memory.  (Note: the
! 			 * new result can't possibly be the same datum as old resultValue,
! 			 * because we never passed it to the trans function.)
! 			 */
! 			if (!peraggstate->resultValueIsNull)
! 				pfree(DatumGetPointer(peraggstate->resultValue));
! 
! 			/*
! 			 * If pass-by-ref, copy it into our aggregate context.
! 			 */
! 			if (!*isnull)
! 			{
! 				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
! 				peraggstate->resultValue =
! 					datumCopy(*result,
! 							  peraggstate->resulttypeByVal,
! 							  peraggstate->resulttypeLen);
! 				MemoryContextSwitchTo(oldContext);
! 			}
  		}
  		else
  		{
--- 951,964 ----
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal && !*isnull)
  		{
! 			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
! 			peraggstate->resultValue =
! 				datumCopy(*result,
! 						  peraggstate->resulttypeByVal,
! 						  peraggstate->resulttypeLen);
! 			MemoryContextSwitchTo(oldContext);
  		}
  		else
  		{
*************** eval_windowfunction(WindowAggState *wins
*** 651,656 ****
--- 999,1005 ----
  	/* Just in case, make all the regular argument slots be null */
  	memset(fcinfo.argnull, true, perfuncstate->numArguments);
  
+ 	winstate->calledaggno = -1;
  	*result = FunctionCallInvoke(&fcinfo);
  	*isnull = fcinfo.isnull;
  
*************** spool_tuples(WindowAggState *winstate, i
*** 794,800 ****
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kluge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
--- 1143,1149 ----
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kludge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
*************** release_partition(WindowAggState *winsta
*** 869,875 ****
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
--- 1218,1227 ----
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < winstate->numaggs; ++i)
! 		if (!winstate->peragg[i].aggcontext_is_shared)
! 			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1419,1425 ****
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno;
  	ListCell   *l;
  
  	/* check for unsupported flags */
--- 1771,1778 ----
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno,
! 				numaggs_invtrans;
  	ListCell   *l;
  
  	/* check for unsupported flags */
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1450,1457 ****
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived context for aggregate trans values etc */
! 	winstate->aggcontext =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
--- 1803,1812 ----
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived contexts for aggregate trans values etc
! 	 * Note that invertible aggregates use their own private context
! 	 */
! 	winstate->aggcontext_shared =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1535,1540 ****
--- 1890,1896 ----
  
  	wfuncno = -1;
  	aggno = -1;
+ 	numaggs_invtrans = 0;
  	foreach(l, winstate->funcs)
  	{
  		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1603,1608 ****
--- 1959,1966 ----
  			peraggstate = &winstate->peragg[aggno];
  			initialize_peragg(winstate, wfunc, peraggstate);
  			peraggstate->wfuncno = wfuncno;
+ 			if (peraggstate->use_invtransfn)
+ 				numaggs_invtrans++;
  		}
  		else
  		{
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1618,1623 ****
--- 1976,1982 ----
  	/* Update numfuncs, numaggs to match number of unique functions found */
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
+ 	winstate->numaggs_invtrans = numaggs_invtrans;
  
  	/* Set up WindowObject for aggregates, if needed */
  	if (winstate->numaggs > 0)
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1646,1651 ****
--- 2005,2016 ----
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	/* initialize temporary data */
+ 	winstate->calledaggno = -1;
+ 
+ 	/* initialize statistics */
+ 	winstate->aggfwdtrans = 0;
+ 
  	return winstate;
  }
  
*************** void
*** 1657,1668 ****
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
  
  	release_partition(node);
  
- 	pfree(node->perfunc);
- 	pfree(node->peragg);
- 
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
--- 2022,2031 ----
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
+ 	int			i;
  
  	release_partition(node);
  
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
*************** ExecEndWindowAgg(WindowAggState *node)
*** 1677,1683 ****
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
--- 2040,2052 ----
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext_shared);
! 	for(i = 0; i < node->numaggs; i++)
! 		if (!node->peragg[i].aggcontext_is_shared)
! 			MemoryContextDelete(node->peragg[i].aggcontext);
! 
! 	pfree(node->perfunc);
! 	pfree(node->peragg);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
*************** initialize_peragg(WindowAggState *winsta
*** 1735,1742 ****
--- 2104,2113 ----
  	Oid			aggtranstype;
  	AclResult	aclresult;
  	Oid			transfn_oid,
+ 				invtransfn_oid,
  				finalfn_oid;
  	Expr	   *transfnexpr,
+ 			   *invtransfnexpr,
  			   *finalfnexpr;
  	Datum		textInitVal;
  	int			i;
*************** initialize_peragg(WindowAggState *winsta
*** 1762,1767 ****
--- 2133,2139 ----
  	 */
  
  	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+ 	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
  	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
  	/* Check that aggregate owner has permission to call component fns */
*************** initialize_peragg(WindowAggState *winsta
*** 1783,1788 ****
--- 2155,2171 ----
  			aclcheck_error(aclresult, ACL_KIND_PROC,
  						   get_func_name(transfn_oid));
  		InvokeFunctionExecuteHook(transfn_oid);
+ 
+ 		if (OidIsValid(invtransfn_oid))
+ 		{
+ 			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+ 										 ACL_EXECUTE);
+ 			if (aclresult != ACLCHECK_OK)
+ 				aclcheck_error(aclresult, ACL_KIND_PROC,
+ 				get_func_name(invtransfn_oid));
+ 			InvokeFunctionExecuteHook(invtransfn_oid);
+ 		}
+ 
  		if (OidIsValid(finalfn_oid))
  		{
  			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
*************** initialize_peragg(WindowAggState *winsta
*** 1810,1822 ****
--- 2193,2213 ----
  							wfunc->wintype,
  							wfunc->inputcollid,
  							transfn_oid,
+ 							invtransfn_oid,
  							finalfn_oid,
  							&transfnexpr,
+ 							&invtransfnexpr,
  							&finalfnexpr);
  
  	fmgr_info(transfn_oid, &peraggstate->transfn);
  	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+ 		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+ 	}
+ 
  	if (OidIsValid(finalfn_oid))
  	{
  		fmgr_info(finalfn_oid, &peraggstate->finalfn);
*************** initialize_peragg(WindowAggState *winsta
*** 1860,1867 ****
--- 2251,2324 ----
  							wfunc->winfnoid)));
  	}
  
+ 	/*
+ 	 * Allowing only the forward transition function to be strict would
+ 	 * require handling more special cases in advance_windowaggregate() and
+ 	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 	 * that if the forward transition function is strict that the inverse
+ 	 * transition function is also strict. This should have been checked at
+ 	 * the aggregate function's definition time, but it's better to be safe...
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 				errmsg("stricness of forward and reverse transition functions must match")));
+ 	}
+ 
  	ReleaseSysCache(aggTuple);
  
+ 	/*
+ 	 * We can use the inverse transition function only if the aggregate's
+ 	 * arguments don't contain calls to volatile functions. Otherwise,
+ 	 * the difference between restarting and not restarting the aggregation
+ 	 * would be user-visible. Note that this check also covers the case where
+ 	 * the FILTER's WHERE clause contains a volatile function. If the frame
+ 	 * head cannot move, we won't ever need the inverse transition function,
+ 	 * so we also mark as "don't use" in that case.
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		!contain_volatile_functions((Node *) wfunc) &&
+ 		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+ 	{
+ 		peraggstate->use_invtransfn = true;
+ 	}
+ 	else
+ 	{
+ 		peraggstate->use_invtransfn = false;
+ 	}
+ 
+ 	/*
+ 	 * Invertible aggregates use their own aggcontext.
+ 	 *
+ 	 * This is necessary because they might all restart at different times,
+ 	 * so we might never be able to reset the shared context otherwise. We
+ 	 * can't make it the aggregate's responsibility to clean up after
+ 	 * themselves, because strict aggregates must be restarted whenever we
+ 	 * remove their last non-NULL input, which the aggregate won't be aware
+ 	 * is happening. Also, just pfree()ing the transValue upon restarting
+ 	 * wouldn't help, since we'd miss any indirectly referenced data. We
+ 	 * could, in theory, declare that aggregates with a state type other then
+ 	 * "internal" musn't allocate anything in the aggcontext themselves, that
+ 	 * non-strict aggregates with state type internal must clean up after
+ 	 * themselves when their inverse transfer function returns NULL, and then
+ 	 * only use private aggcontexts for strict aggregates with state type
+ 	 * internal. But that'd be a rather grotty set of requirements.
+ 	 */
+ 	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+ 	if (!peraggstate->aggcontext_is_shared)
+ 	{
+ 		peraggstate->aggcontext =
+ 			AllocSetContextCreate(CurrentMemoryContext,
+ 								  "WindowAgg_AggregatePrivate",
+ 								  ALLOCSET_DEFAULT_MINSIZE,
+ 								  ALLOCSET_DEFAULT_INITSIZE,
+ 								  ALLOCSET_DEFAULT_MAXSIZE);
+ 	}
+ 	else
+ 		peraggstate->aggcontext = winstate->aggcontext_shared;
+ 
  	return peraggstate;
  }
  
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..91bea45 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1203,1210 ****
--- 1203,1212 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr)
  {
  	Param	   *argp;
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1249,1254 ****
--- 1251,1269 ----
  	fexpr->funcvariadic = agg_variadic;
  	*transfnexpr = (Expr *) fexpr;
  
+ 
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+ 												agg_result_type,
+ 												args,
+ 												InvalidOid,
+ 												agg_input_collation,
+ 												COERCE_EXPLICIT_CALL);
+ 	}
+ 	else
+ 		*invtransfnexpr	= NULL;
+ 
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
  	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2ce8e6d..1785a0d 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11512,11517 ****
--- 11512,11518 ----
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
+ 	int			i_agginvtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
  	int			i_hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11520,11525 ****
--- 11521,11527 ----
  	int			i_agginitval;
  	int			i_convertok;
  	const char *aggtransfn;
+ 	const char *agginvtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
  	bool		hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11544,11550 ****
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
--- 11546,11552 ----
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11627,11632 ****
--- 11629,11635 ----
  	res = ExecuteSqlQueryForSingleRow(fout, query->data);
  
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
+ 	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
  	i_hypothetical = PQfnumber(res, "hypothetical");
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11636,11641 ****
--- 11639,11645 ----
  	i_convertok = PQfnumber(res, "convertok");
  
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ 	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11706,11711 ****
--- 11710,11721 ----
  		appendStringLiteralAH(details, agginitval, fout);
  	}
  
+ 	if (strcmp(agginvtransfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    INVFUNC = %s",
+ 			agginvtransfn);
+ 	}
+ 
  	if (strcmp(aggfinalfn, "-") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    FINALFUNC = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index f189998..3412661 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 46,51 ****
--- 46,52 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		agginvtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 69,85 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_agginvtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,277 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 103,279 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	-	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
*************** extern Oid AggregateCreate(const char *a
*** 289,294 ****
--- 291,297 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index aed81cd..c4d4864 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** extern void **find_rendezvous_variable(c
*** 645,652 ****
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly with nodeAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
--- 645,654 ----
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
!  * which is in execute/nodeWindowAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly neither nodeAgg
!  * nor nodeWindowAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..ff558c6 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WindowAggState
*** 1738,1743 ****
--- 1738,1744 ----
  	List	   *funcs;			/* all WindowFunc nodes in targetlist */
  	int			numfuncs;		/* total number of window functions */
  	int			numaggs;		/* number that are plain aggregates */
+ 	int			numaggs_invtrans;	/* number that are invertible aggregates */
  
  	WindowStatePerFunc perfunc; /* per-window-function information */
  	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
*************** typedef struct WindowAggState
*** 1762,1768 ****
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext;	/* context for each aggregate data */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
--- 1763,1769 ----
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext_shared;	/* shared context for agg states */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
*************** typedef struct WindowAggState
*** 1780,1789 ****
--- 1781,1796 ----
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
  
+ 	/* temporary data */
+ 	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+ 
  	/* temporary slots for tuples fetched back from tuplestore */
  	TupleTableSlot *agg_row_slot;
  	TupleTableSlot *temp_slot_1;
  	TupleTableSlot *temp_slot_2;
+ 
+ 	/* Statistics */
+ 	double	aggfwdtrans;	/* number of forward transitions */
  } WindowAggState;
  
  /* ----------------
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void build_aggregate_fnexprs(Oid 
*** 39,46 ****
--- 39,48 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr);
  
  #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..0def229 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select least_agg(variadic array[q1,q2]) 
*** 1580,1582 ****
--- 1580,1606 ----
   -4567890123456789
  (1 row)
  
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict AND NOT pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..d54f390 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** alter aggregate my_rank(VARIADIC "any" O
*** 90,92 ****
--- 90,129 ----
   public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
  (2 rows)
  
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi
+ );
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_n
+ );
+ ERROR:  stricness of forward and reverse transition functions must match
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = intminus
+ );
+ ERROR:  function intminus(double precision, double precision) does not exist
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_int
+ );
+ ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..aa29471 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT nth_value_def(ten) OVER (PARTITIO
*** 1071,1073 ****
--- 1071,1307 ----
               1 |   3 |    3
  (10 rows)
  
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict
+ );
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict
+ );
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+ ----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+  1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+  1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+  1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+  1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+  1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+  2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+  3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+ (9 rows)
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+ ----------+--------------+-------------------+-------------+------------------
+  1,1:NULL | +NULL        | I+NULL            |             | I
+  1,2:-    | +NULL        | I+NULL            |             | I
+  1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+  1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+  1,5:-    |              | I                 |             | I
+  1,6:-    |              | I                 |             | I
+  2,1:-    |              | I                 |             | I
+  2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+  3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+ (9 rows)
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+  logging_agg_strict 
+ --------------------
+  a
+  b
+  c
+ (3 rows)
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invfunc = sum_int_randrestart_invsfunc
+ );
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+  eq1 | eq2 | eq3 
+ -----+-----+-----
+  t   | t   | t
+ (1 row)
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..284ea76 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** drop view aggordview1;
*** 590,592 ****
--- 590,609 ----
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict AND NOT pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..b7fd4a3 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** alter aggregate my_rank(VARIADIC "any" O
*** 101,103 ****
--- 101,144 ----
    rename to test_rank;
  
  \da test_*
+ 
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi
+ );
+ 
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_n
+ );
+ 
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = intminus
+ );
+ 
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_int
+ );
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..676fa45 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** SELECT nth_value_def(n := 2, val := ten)
*** 284,286 ****
--- 284,489 ----
  
  SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
    FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+ 
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ 
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict
+ );
+ 
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ 
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict
+ );
+ 
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ 
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invfunc = sum_int_randrestart_invsfunc
+ );
+ 
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ 
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
invtrans_strictstrict_collecting_be6a58.patchapplication/octet-stream; name=invtrans_strictstrict_collecting_be6a58.patch; x-unix-mode=0644Download
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index c62e3fb..5a3a31d 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** create_singleton_array(FunctionCallInfo 
*** 471,477 ****
  
  
  /*
!  * ARRAY_AGG aggregate function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
--- 471,477 ----
  
  
  /*
!  * ARRAY_AGG aggregate transition function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 508,513 ****
--- 508,537 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * ARRAY_AGG aggregate inverse transition function
+  */
+ Datum
+ array_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since NULLs
+ 	 * need to be removed just like any other value.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "array_agg_invtransfn called with NULL state");
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "array_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	shiftArrayResult(state, 1);
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..2dd7ecb 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** accumArrayResult(ArrayBuildState *astate
*** 4587,4592 ****
--- 4587,4593 ----
  		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
  		astate->mcontext = arr_context;
  		astate->alen = 64;		/* arbitrary starting array size */
+ 		astate->offset = 0;
  		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
  		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
  		astate->nelems = 0;
*************** accumArrayResult(ArrayBuildState *astate
*** 4600,4606 ****
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/* enlarge dvalues[]/dnulls[] if needed */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
--- 4601,4612 ----
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/*
! 		 * If the buffers are filled completely (offset must be zero then),
! 		 * we double their size. If they aren't, but the values extend to the
! 		 * end of the buffers, we reclaim wasted space at the beginning by
! 		 * moving the values to the front of the buffers.
! 		 */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
*************** accumArrayResult(ArrayBuildState *astate
*** 4609,4614 ****
--- 4615,4629 ----
  			astate->dnulls = (bool *)
  				repalloc(astate->dnulls, astate->alen * sizeof(bool));
  		}
+ 		else if (astate->offset + astate->nelems >= astate->alen)
+ 		{
+ 			memmove(astate->dvalues, astate->dvalues + astate->offset,
+ 					astate->alen * sizeof(Datum));
+ 			memmove(astate->dnulls, astate->dnulls + astate->offset,
+ 					astate->alen * sizeof(bool));
+ 			astate->offset = 0;
+ 		}
+ 		Assert(astate->offset + astate->nelems < astate->alen);
  	}
  
  	/*
*************** accumArrayResult(ArrayBuildState *astate
*** 4627,4634 ****
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->nelems] = dvalue;
! 	astate->dnulls[astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
--- 4642,4649 ----
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->offset + astate->nelems] = dvalue;
! 	astate->dnulls[astate->offset + astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
*************** accumArrayResult(ArrayBuildState *astate
*** 4637,4642 ****
--- 4652,4698 ----
  }
  
  /*
+  * shiftArrayResult - shift leading Datums out of an array result
+  *
+  *	astate is working state
+  *	count is the number of leading Datums to shift out
+  *
+  * If count is equal to or larger than the number of relements, the array
+  * result is empty afterwards. If astate is NULL, nothing is done.
+  */
+ void
+ shiftArrayResult(ArrayBuildState *astate, int count)
+ {
+ 	int		i;
+ 
+ 	if (astate == NULL)
+ 		return;
+ 
+ 	/* Limit shift count to number of elements for safety */
+ 	count = Min(count, astate->nelems);
+ 
+ 	/* For pass-by-ref types, free values we shift out */
+ 	if (!astate->typbyval) {
+ 		for(i = astate->offset; i < astate->offset + count; ++i) {
+ 			if (astate->dnulls[i])
+ 				continue;
+ 
+ 			pfree(DatumGetPointer(astate->dvalues[i]));
+ 
+ 			/* For cleanliness' sake */
+ 			astate->dnulls[i] = false;
+ 			astate->dvalues[i] = 0;
+ 		}
+ 
+ 	}
+ 
+ 	/* Update state */
+ 	astate->nelems -= count;
+ 	astate->offset += count;
+ }
+ 
+ 
+ /*
   * makeArrayResult - produce 1-D final result of accumArrayResult
   *
   *	astate is working state (not NULL)
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4679,4686 ****
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues,
! 								astate->dnulls,
  								ndims,
  								dims,
  								lbs,
--- 4735,4742 ----
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues + astate->offset,
! 								astate->dnulls + astate->offset,
  								ndims,
  								dims,
  								lbs,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..768ee49 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** typedef struct
*** 50,55 ****
--- 50,63 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct StringAggState
+ {
+ 	StringInfoData 	string; 	/* Contents */
+ 	int 			offset;		/* Offset into stringinfo's data */
+ 	int 			delimLen;	/* Delim length, -1 initially, -2 if multiple */
+ 	int64			notNullCount;/* Number of non-NULL inputs */
+ } StringAggState;
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
*************** static void appendStringInfoText(StringI
*** 78,84 ****
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
--- 86,95 ----
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo);
! static void prepareAppendStringAggState(StringAggState *state,
! 										int delimLen, int valueLen);
! static bool removeFromStringAggState(StringAggState *state, int valueLen);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
*************** static void text_format_string_conversio
*** 92,97 ****
--- 103,226 ----
  static void text_format_append_string(StringInfo buf, const char *str,
  						  int flags, int width);
  
+ /*****************************************************************************
+  *	 SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA)			 *
+  *****************************************************************************/
+ 
+ /*
+  * subroutine to initialize state
+  */
+ static StringAggState*
+ makeStringAggState(FunctionCallInfo fcinfo)
+ {
+ 	StringAggState*	state;
+ 	MemoryContext aggcontext;
+ 	MemoryContext oldcontext;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 	}
+ 
+ 	/*
+ 	 * Create state in aggregate context.  It'll stay there across subsequent
+ 	 * calls.
+ 	 */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = (StringAggState *) palloc(sizeof(StringAggState));
+ 	initStringInfo(&state->string);
+ 	state->offset = 0;
+ 	state->delimLen = -1;
+ 	state->notNullCount = 0;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * Prepare state for appending a value and a delimiter with specified lengths.
+  * pass -1 for delimLen if no delimiter will be added
+  */
+ void
+ prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen)
+ {
+ 	/*
+ 	 * Reclaim wasted space
+ 	 *
+ 	 * We move the contents to the left if the current contents fit into the
+ 	 * wasted space, i.e. if we waste more than we store. The limit is
+ 	 * somewhat arbitrary, but it's the smallest one that allows
+ 	 * memcpy to be used, because the source and destination don't overlap.
+ 	 * Note that we must check for <, not <=, because we include the trailing
+ 	 * '\0' in the copy.
+ 	 */
+ 	if (state->string.len - state->offset < state->offset)
+ 	{
+ 		state->string.len -= state->offset;
+ 		memcpy(state->string.data, state->string.data + state->offset,
+ 			   state->string.len + 1);
+ 		state->offset = 0;
+ 	}
+ 
+ 	/*
+ 	 * Enlarge StringInfo
+ 	 *
+ 	 * Not strictly necessary, but avoids potentially resizing twice when
+ 	 * the actual append... calls are done by the caller
+ 	 */
+ 	enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen);
+ 
+ 
+ 	/* Track delimiter length */
+ 	if (delimLen == -1)
+ 		{} /* Not specified, don't update */
+ 	else if (state->delimLen == -1)
+ 		state->delimLen = delimLen;
+ 	else if (state->delimLen != delimLen)
+ 		state->delimLen = -2;
+ }
+ 
+ /*
+  * Remove value with given length and the delimiter that follows
+  *
+  * Returns false if removal was impossible because delimiters varied
+  */
+ bool
+ removeFromStringAggState(StringAggState *state, int valueLen)
+ {
+ 	/* Remove the string */
+ 	state->offset += valueLen;
+ 
+ 	/*
+ 	 * Remove delimiter if necessary.
+ 	 *
+ 	 * The delimiter we need to remove isn't the delimiter we were passed, but
+ 	 * rather the delimiter passed when adding the input *after* this one. We
+ 	 * thus need the delimiter length to be all the same to be able to proceed.
+ 	 * If we're removing the last string, there will be no delimiter following
+ 	 * it. In that case, we may reset delimLen to its initial value.
+ 	 */
+ 	if (state->delimLen == -2)
+ 		return false;
+ 	if (state->offset < state->string.len)
+ 	{
+ 		Assert(state->delimLen >= 0);
+ 		state->offset += state->delimLen;
+ 	}
+ 	else
+ 		state->delimLen = -1;
+ 
+ 	/* Don't crash if we're ever asked to remove more than was added */
+ 	if (state->offset > state->string.len)
+ 	{
+ 		state->offset = state->string.len;
+ 		elog(ERROR, "tried to remove more data than was aggregated");
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /*****************************************************************************
   *	 CONVERSION ROUTINES EXPORTED FOR USE BY C CODE							 *
*************** byteasend(PG_FUNCTION_ARGS)
*** 408,435 ****
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		bytea	   *value = PG_GETARG_BYTEA_PP(1);
  
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 		{
! 			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim));
! 		}
  
! 		appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value));
  	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
--- 537,589 ----
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	bytea		   *value,
! 				   *delim;
! 	int				valueLen,
! 					delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
  
! 	value = PG_GETARG_BYTEA_PP(1);
! 	valueLen = VARSIZE_ANY_EXHDR(value);
! 	state->notNullCount++;
  
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
! 	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_BYTEA_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
! 	}
! 	else
! 	{
! 		/* Delimiter is NULL, treat as zero-length string */
! 		delim = NULL;
! 		delimLen = 0;
  	}
  
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, valueLen);
+ 	if (delim)
+ 		appendBinaryStringInfo(&state->string, VARDATA_ANY(delim),
+ 							   VARSIZE_ANY_EXHDR(delim));
+ 	appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen);
+ 
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 438,459 ****
  }
  
  Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
  	{
  		bytea	   *result;
  
! 		result = (bytea *) palloc(state->len + VARHDRSZ);
! 		SET_VARSIZE(result, state->len + VARHDRSZ);
! 		memcpy(VARDATA(result), state->data, state->len);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
--- 592,661 ----
  }
  
  Datum
+ bytea_string_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	int				valueLen;
+ 	StringAggState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since it
+ 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "string_agg_invtransfn called with NULL state");
+ 	else if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (StringAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* We append nothing if the string is NULL, so skip here as well */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_POINTER(state);
+ 
+ 	/* No need to de-toast value, need only the length */
+ 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
+ 	Assert(state->notNullCount >= 1);
+ 	state->notNullCount--;
+ 
+ 	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
+ 	 * The transition type for string_agg() is declared to be "internal",
+ 	 * which is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	if (removeFromStringAggState(state, valueLen))
+ 		PG_RETURN_POINTER(state);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
  	{
+ 		int			resultLen = state->string.len  - state->offset;
  		bytea	   *result;
  
! 		result = (bytea *) palloc(resultLen + VARHDRSZ);
! 		SET_VARSIZE(result, resultLen + VARHDRSZ);
! 		memcpy(VARDATA(result), state->string.data + state->offset, resultLen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3733,3801 ****
   * the associated value.
   */
  
! /* subroutine to initialize state */
! static StringInfo
! makeStringAggState(FunctionCallInfo fcinfo)
  {
! 	StringInfo	state;
! 	MemoryContext aggcontext;
! 	MemoryContext oldcontext;
  
! 	if (!AggCheckCallContext(fcinfo, &aggcontext))
  	{
! 		/* cannot be called directly because of internal-type argument */
! 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
  	}
  
  	/*
! 	 * Create state in aggregate context.  It'll stay there across subsequent
! 	 * calls.
  	 */
! 	oldcontext = MemoryContextSwitchTo(aggcontext);
! 	state = makeStringInfo();
! 	MemoryContextSwitchTo(oldcontext);
! 
! 	return state;
  }
  
  Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 			appendStringInfoText(state, PG_GETARG_TEXT_PP(2));	/* delimiter */
  
! 		appendStringInfoText(state, PG_GETARG_TEXT_PP(1));		/* value */
! 	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len));
  	else
  		PG_RETURN_NULL();
  }
--- 3935,4054 ----
   * the associated value.
   */
  
! Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	text		   *value,
! 				   *delim;
! 	int				delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
! 
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
! 
! 	value = PG_GETARG_TEXT_PP(1);
! 	state->notNullCount++;
! 
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
  	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_TEXT_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
  	}
+ 	else
+ 	{
+ 		/* Delimiter is NULL, treat as zero-length string */
+ 		delim = NULL;
+ 		delimLen = 0;
+ 	}
+ 
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value));
+ 	if (delim)
+ 		appendStringInfoText(&state->string, delim);
+ 	appendStringInfoText(&state->string, value);
  
  	/*
! 	 * The transition type for string_agg() is declared to be "internal",
! 	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
! string_agg_invtransfn(PG_FUNCTION_ARGS)
  {
! 	int				valueLen;
! 	StringAggState *state;
  
! 	/*
! 	 * Shouldn't happen, but we cannot mark this function strict, since it
! 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
! 	 * Must also prevent direct calls because of the "interal" argument
! 	 */
! 	if (PG_ARGISNULL(0))
! 		elog(ERROR, "string_agg_invtransfn called with NULL state");
! 	else if (!AggCheckCallContext(fcinfo, NULL))
! 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
  
! 	state = (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* We append nothing if the string is NULL, so skip here as well */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* No need to de-toast value, need only the length */
! 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
! 	Assert(state->notNullCount >= 1);
! 	state->notNullCount--;
  
  	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	if (removeFromStringAggState(state, valueLen))
! 		PG_RETURN_POINTER(state);
! 	else
! 		PG_RETURN_NULL();
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset,
! 												  state->string.len - state->offset));
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..403bb50 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-	-					0	
*** 250,262 ****
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
--- 250,262 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_invtransfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_invtransfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_invtransfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 11c1e1a..eb3d0b6 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 875,880 ****
--- 875,882 ----
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3461 (  array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2463,2474 ****
--- 2465,2480 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3462 (  string_agg_invtransfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3538 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into a string");
  DATA(insert OID = 3543 (  bytea_string_agg_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3463 (  bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3544 (  bytea_string_agg_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..9a6fc39 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 83,88 ****
--- 83,89 ----
  	Datum	   *dvalues;		/* array of accumulated Datums */
  	bool	   *dnulls;			/* array of is-null flags for Datums */
  	int			alen;			/* allocated length of above arrays */
+ 	int			offset;			/* offset of first element in above arrays */
  	int			nelems;			/* number of valid entries in above arrays */
  	Oid			element_type;	/* data type of the Datums */
  	int16		typlen;			/* needed info about datatype */
*************** extern ArrayBuildState *accumArrayResult
*** 255,260 ****
--- 256,262 ----
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
  				 MemoryContext rcontext);
+ extern void shiftArrayResult(ArrayBuildState *astate, int count);
  extern Datum makeArrayResult(ArrayBuildState *astate,
  				MemoryContext rcontext);
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
*************** extern ArrayType *create_singleton_array
*** 290,295 ****
--- 292,298 ----
  					   int ndims);
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
  
  /*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..7d8e749 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 812,820 ****
--- 812,822 ----
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
  extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS);
  
  extern Datum text_concat(PG_FUNCTION_ARGS);
  extern Datum text_concat_ws(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index aa29471..02f2918 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1305,1307 ****
--- 1305,1351 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
+      row     | str | str_del | str_vardel |      bin       |      bin_del       |      bin_vardel      |       ary        
+ -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------
+  1:1,0100,-  | -   | -       | -          | -              | -                  | -                    | 
+  2:-,0200,2  | 1   | 1       | 1          | \x0100         | \x0100             | \x0100               | {NULL}
+  3:3,----,3  | 1   | 1       | 1          | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2}
+  4:-,0400,4  | 13  | 1,3     | 13         | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2,3}
+  5:5,----,-  | 3   | 3       | 3          | \x02000400     | \x0200000400       | \x0200000400         | {2,3,4}
+  6:6,0600,6  | 35  | 3,5     | 3||5       | \x0400         | \x0400             | \x0400               | {3,4,NULL}
+  7:7,0700,-  | 56  | 5,6     | 56         | \x04000600     | \x0400000600       | \x04000600           | {4,NULL,6}
+  8:8,0800,8  | 567 | 5,6,7   | 56|7       | \x06000700     | \x0600000700       | \x0600000700         | {NULL,6,NULL}
+  9:-,----,-  | 678 | 6,7,8   | 6|7||8     | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8}
+  10:-,----,- | 78  | 7,8     | 7||8       | \x07000800     | \x0700000800       | \x070000000800       | {NULL,8,NULL}
+  11:-,----,- | 8   | 8       | 8          | \x0800         | \x0800             | \x0800               | {8,NULL,NULL}
+  12:-,----,- | -   | -       | -          | -              | -                  | -                    | {NULL,NULL,NULL}
+ (12 rows)
+ 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 676fa45..dd7f02e 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 487,489 ****
--- 487,518 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ 
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
invtrans_strictstrict_docs_0cb944.patchapplication/octet-stream; name=invtrans_strictstrict_docs_0cb944.patch; x-unix-mode=0644Download
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2230c93..44d547f 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 376,381 ****
--- 376,387 ----
        <entry>Transition function</entry>
       </row>
       <row>
+       <entry><structfield>agginvtransfn</structfield></entry>
+       <entry><type>regproc</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>Inverse transition function</entry>
+      </row>
+      <row>
        <entry><structfield>aggfinalfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c0a75de..51c493d 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT xmlagg(x) FROM (SELECT x FROM tes
*** 12885,12890 ****
--- 12885,13012 ----
     <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
     Other frame specifications can be used to obtain other effects.
    </para>
+   
+   <para>
+     Depending on the aggregate function, aggregating over frames starting
+     at a row relative to the current row can be drastically less efficient
+     than aggregating over frames aligned to the start of the partition. The
+     frame starts at a row relative to the current row if <literal>ORDER
+     BY</literal> is used together with any frame start clause other than
+     <literal>UNBOUNDED PRECEDING</literal> (which is the default). Then,
+     aggregates without a suitable <quote>inverse transition function
+     </quote> (see <xref linkend="SQL-CREATEAGGREGATE"> for details) will be
+     computed for each frame from scratch, instead of re-using the previous
+     frame's result, causing <emphasis>quadratic growth</emphasis> of the
+     execution time as the number of rows per partition increases. The table
+     <xref linkend="functions-aggregate-indframe"> list the built-in aggregate
+     functions affected by this. Note that quadratic growth is only a problem
+     if partitions contain many rows - for partitions with only a few rows,
+     even inefficient aggregates are unlikely to cause problems.
+   </para>
+ 
+   <table id="functions-aggregate-indframe">
+    <title>
+      Aggregate Function Behaviour for frames not starting at
+      <literal>UNBOUNDED PRECEDING</literal>.
+    </title>
+ 
+    <tgroup cols="3">
+      
+     <thead>
+      <row>
+       <entry>Aggregate Function</entry>
+       <entry>Input Type</entry>
+       <entry>Computed From Scratch</entry>
+      </row>
+     </thead>
+     
+     <tbody>
+       
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>float4</type>
+        or
+        <type>float8</type>
+       </entry>
+       <entry>always, to avoid error accumulation</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>numeric</type>
+       </entry>
+       <entry>if the maximum number of decimal digits within the inputs changes</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>min</function>
+       </entry>
+       <entry>
+        any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were minimal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>max</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were maximal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>bit_and</function>,<function>bit_or</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>string_agg</function>
+       </entry>
+       <entry>
+        <type>text</type> or
+        <type>bytea</type> or
+       </entry>
+       <entry>if the delimiter lengths vary</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>xmlagg</function>
+       </entry>
+       <entry>
+        <type>xml</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>json_agg</function>
+       </entry>
+       <entry>
+        <type>json</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
  
    <note>
     <para>
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index d15fcba..aaa09a7 100644
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
*************** CREATE AGGREGATE <replaceable class="par
*** 25,30 ****
--- 25,31 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 47,52 ****
--- 48,54 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 84,98 ****
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
!    a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
--- 86,103 ----
    </para>
  
    <para>
!    An aggregate function is made from one, two or three ordinary
     functions:
!    a (forward) state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
+    an optional inverse state transition function
+    <replaceable class="PARAMETER">invfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
+ <replaceable class="PARAMETER">invfunc</replaceable>( internal-state, data-values ) ---> internal-state-without-data-values
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 102,113 ****
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.  After all the rows have been processed,
!    the final function is invoked once to calculate the aggregate's return
!    value.  If there is no final function then the ending state value
!    is returned as-is.
    </para>
  
    <para>
--- 107,134 ----
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the forward state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.
!    If the aggregate is computed over a sliding frame, i.e. if it is used as a
!    <firstterm>window function</firstterm>, the inverse transition function is
!    used to undo the effect of a previous invocation of the forward transition
!    function once argument value(s) fall out of the sliding frame.
!    Conceptually, the forward transition functions thus adds some input
!    value(s) to the state, and the inverse transition functions removes them
!    again. Values are, if they are removed, always removed in the same order
!    they were added, without gaps. Whenever the inverse transition function is
!    invoked, it will thus receive the earliest added but not yet removed
!    argument value(s). If no inverse transition function is supplied, the
!    aggregate can still be used to aggregate over sliding frames, but with
!    reduced efficiency. <productname>PostgreSQL</productname> will then
!    recompute the whole aggregation whenever the start of the frame moves. To
!    calculate the aggregate's return value, the final function is invoked on
!    the ending state value. If there is no final function then ending state
!    value is returned as-is. Either way, the result is assumed to reflect the
!    aggregation of all values added but not yet removed from the state value.
!    Note that if the aggregate is used as a window function, the aggregation
!    may be continued after the final function has been called.
    </para>
  
    <para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 127,135 ****
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values.
!    This is handy for implementing aggregates like <function>max</function>.
!    Note that this behavior is only available when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
--- 148,164 ----
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values. Should inputs later need to be removed again, the
!    inverse transition function (if present) is used as long as some non-null
!    inputs remain part of the state value. In particular, even if the initial
!    state value is null, the inverse transition function might be used to remove
!    the first non-null input, even though that input was never passed to the
!    forward transition function, but instead just replaced the initial state!
!    The last non-null input, however, is not removed by invoking the inverse
!    transition function, but instead the state is simply reset to its initial
!    value. This is handy for implementing aggregates like <function>max</function>.
!    Note that turning the first non-null input into the initial state is only
!    possible when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
*************** CREATE AGGREGATE <replaceable class="PAR
*** 138,147 ****
    </para>
  
    <para>
!    If the state transition function is not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs
!    and null state values for itself.  This allows the aggregate
!    author to have full control over the aggregate's handling of null values.
    </para>
  
    <para>
--- 167,206 ----
    </para>
  
    <para>
!     If the forward transition function is not declared <quote>strict</quote>,
!     but the inverse transition function is, null input values are still not
!     passed to either the forward or the inverse transition function. The
!     system then behaves as it would for a strict forward transition function,
!     except that initial value null isn't handled specially, i.e. isn't
!     replaced by the first argument of the first non-null input. Instead the
!     forward transition function is simply invoked with a null state and
!     non-null argument values. So in effect, in this case the forward
!     transition function is being treated as non-strict in the state argument
!     but strict in all argument values.
!   </para> 
! 
!   <para>
!    If the state transition function is not <quote>strict</quote>, and either
!    no inverse was provided or is also not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs and null
!    state values for itself. The same goes for the inverse transition function.
!    This allows the aggregate author to have full control over the
!    aggregate's handling of null argument values.
!   </para>
!   
!   <para>
!     The inverse transition function can signal by returning null that it is
!     unable to remove a particular input value from a particular state.
!     <productname>PostgreSQL</productname> will then act as if no inverse
!     transition function had been supplied, i.e. it will recompute the whole
!     aggregation, starting with the first argument value that it would not have
!     removed. That allows aggregates like <function>max</function> to still
!     avoid redoing the whole aggregation in <emphasis>some</emphasis> cases,
!     without paying the overhead of tracking enough state to be able to avoid
!     them in <emphasis>all</emphasis> cases. This demands, however, that
!     null isn't used as a valid state value, except as the initial state. If
!     an aggregate provides an inverse transition function, it is therefore an
!     error for the forward transition function to return null.
    </para>
  
    <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 271,277 ****
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
--- 330,336 ----
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the (forward) state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
*************** SELECT col FROM tab ORDER BY col USING s
*** 281,287 ****
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value.
       </para>
  
       <para>
--- 340,348 ----
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value. Note that if an inverse
!       transition function is present, the forward transition function must
!       not return <literal>NULL</>
       </para>
  
       <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 294,299 ****
--- 355,384 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="PARAMETER">invfunc</replaceable></term>
+     <listitem>
+      <para>
+       The name of the inverse state transition function to be called for each
+       input row.  For a normal <replaceable class="PARAMETER">N</>-argument
+       aggregate function, the <replaceable class="PARAMETER">sfunc</>
+       must take <replaceable class="PARAMETER">N</>+1 arguments,
+       the first being of type <replaceable
+       class="PARAMETER">state_data_type</replaceable> and the rest
+       matching the declared input data type(s) of the aggregate.
+       The function must return a value of type <replaceable
+       class="PARAMETER">state_data_type</replaceable>. These are the same
+       demands placed on the forward transition function, meaning that the
+       signatures of the two functions must be identical. Their
+       <quote>strictness</quote> though may differ, but the only allowed
+       combination is a non-strict forward combined with a strict inverse
+       transition function. The inverse transition function may return
+       <literal>NULL</> to force the aggregation to be restarted from
+       scratch.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="PARAMETER">state_data_type</replaceable></term>
      <listitem>
       <para>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index e77ef12..397e398 100644
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 16,22 ****
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
--- 16,22 ----
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a forward state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
***************
*** 24,30 ****
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
--- 24,42 ----
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result. 
!    To enable efficient evaluation of an aggregate used as a window function
!    with a sliding frame (i.e. a frame that starts relative to the current row),
!    an aggregate can optionally provide an inverse state transition function.
!    The inverse transition function takes the the current state and the
!    aggregate's input value(s) for the <emphasis>earliest</emphasis> row passed
!    to the forward transition function, and returns a state equivalent to what
!    the current state had been had the forward transition function never been
!    invoked for that earliest row, only for all rows that followed it. Thus,
!    if an inverse transition function is provided, the rows that were part of
!    the previous row's frame but not of the current row's frame can simply be
!    removed from the state instead of having to redo the whole aggregation
!    over the new frame.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
*************** CREATE AGGREGATE avg (float8)
*** 132,137 ****
--- 144,188 ----
    </note>
  
    <para>
+    When providing an inverse transition function, care should be taken to
+    ensure that it doesn't introduce unexpected user-visible differences
+    between results obtained by reaggregating all inputs vs. using the inverse
+    transition function. An example for an aggregate where adding an inverse
+    transition function seems easy at first, yet were doing so would violate
+    this requirement is <function>sum</> over <type>float</> or
+    <type>double precision</>. A naive declaration of
+    <function>sum(<type>float</>)</function> could be
+    
+    <programlisting>
+    CREATE AGGREGATE unsafe_sum (float8)
+    (
+        stype = float8,
+        sfunc = float8pl,
+        invfunc = float8mi
+    );
+    </programlisting>
+    
+    This aggregate, howevery, can give wildly different results than it would
+    have without the inverse transition function. For example, consider
+    
+    <programlisting>
+    SELECT
+      unsafe_sum(x) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND
+                                                  1 FOLLOWING)
+    FROM (VALUES
+      (1, 1.0e20::float8),
+      (2, 1.0::float8)
+    ) AS v (n,x)
+    </programlisting>
+    
+    which returns 0 as it's second result, yet the expected answer is 1. The
+    reason for this is the limited precision of floating point types - adding
+    1 to 1e20 actually leaves the value unchanged, and so substracting 1e20
+    again yields 0, not 1. Note that this is a limitation of floating point
+    types in general and not a limitation of <productname>PostgreSQL</>.
+   </para>
+   
+   <para>
     Aggregate functions can use polymorphic
     state transition functions or final functions, so that the same functions
     can be used to implement multiple aggregates.
invtrans_strictstrict_minmax_2a0dc2.patchapplication/octet-stream; name=invtrans_strictstrict_minmax_2a0dc2.patch; x-unix-mode=0644Download
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..4a4ca5d 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_larger(PG_FUNCTION_ARGS)
*** 4714,4719 ****
--- 4714,4737 ----
  }
  
  Datum
+ array_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) > 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ 
+ Datum
  array_smaller(PG_FUNCTION_ARGS)
  {
  	ArrayType  *v1,
*************** array_smaller(PG_FUNCTION_ARGS)
*** 4728,4733 ****
--- 4746,4767 ----
  	PG_RETURN_ARRAYTYPE_P(result);
  }
  
+ Datum
+ array_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) < 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
  
  typedef struct generate_subscripts_fctx
  {
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index d419b4a..593460d 100644
*** a/src/backend/utils/adt/bool.c
--- b/src/backend/utils/adt/bool.c
*************** boolge(PG_FUNCTION_ARGS)
*** 285,290 ****
--- 285,292 ----
  
  /* function for standard EVERY aggregate implementation conforming to SQL 2003.
   * must be strict. It is also named bool_and for homogeneity.
+  * Note: this is no longer used for the bool_and() and every() aggregate
+  * functions.
   */
  Datum
  booland_statefunc(PG_FUNCTION_ARGS)
*************** booland_statefunc(PG_FUNCTION_ARGS)
*** 294,302 ****
--- 296,400 ----
  
  /* function for standard ANY/SOME aggregate conforming to SQL 2003.
   * must be strict. The name of the aggregate is bool_or. See the doc.
+  * Note: this is no longer used for the bool_or aggregate function.
   */
  Datum
  boolor_statefunc(PG_FUNCTION_ARGS)
  {
  	PG_RETURN_BOOL(PG_GETARG_BOOL(0) || PG_GETARG_BOOL(1));
  }
+ 
+ typedef struct BoolAggState
+ {
+ 	int64 aggcount; /* number of values aggregated */
+ 	int64 aggtrue; /* number of values aggregated which are true */
+ } BoolAggState;
+ 
+ static BoolAggState *
+ makeBoolAggState(FunctionCallInfo fcinfo)
+ {
+ 	BoolAggState *state;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 	state = (BoolAggState *) palloc(sizeof(BoolAggState));
+ 	state->aggcount = 0;
+ 	state->aggtrue = 0;
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 
+ 	return state;
+ }
+ 
+ Datum
+ bool_accum(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* Create the state data on first call */
+ 	if (state == NULL)
+ 		state = makeBoolAggState(fcinfo);
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount++;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue++;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* bool_accum should have created the state data */
+ 	if (state == NULL)
+ 		elog(ERROR, "bool_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount--;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue--;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_alltrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if all values are true */
+ 	PG_RETURN_BOOL(state->aggcount == state->aggtrue);
+ }
+ 
+ Datum
+ bool_anytrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if any value is true */
+ 	PG_RETURN_BOOL(state->aggtrue > 0);
+ }
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 0158758..cba17a3 100644
*** a/src/backend/utils/adt/cash.c
--- b/src/backend/utils/adt/cash.c
*************** cashlarger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,896 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 > c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cashsmaller()
   * Return smaller of two cash values.
   */
*************** cashsmaller(PG_FUNCTION_ARGS)
*** 892,897 ****
--- 906,925 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 < c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cash_words()
   * This converts a int4 as well but to a representation using words
   * Obviously way North American centric - sorry
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 06cc0cd..0cca0b0 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** date_larger(PG_FUNCTION_ARGS)
*** 396,401 ****
--- 396,415 ----
  }
  
  Datum
+ date_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 > dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  date_smaller(PG_FUNCTION_ARGS)
  {
  	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
*************** date_smaller(PG_FUNCTION_ARGS)
*** 404,409 ****
--- 418,437 ----
  	PG_RETURN_DATEADT((dateVal1 < dateVal2) ? dateVal1 : dateVal2);
  }
  
+ Datum
+ date_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 < dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* Compute difference between two dates in days.
   */
  Datum
*************** time_larger(PG_FUNCTION_ARGS)
*** 1463,1468 ****
--- 1491,1510 ----
  }
  
  Datum
+ time_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 > time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  time_smaller(PG_FUNCTION_ARGS)
  {
  	TimeADT		time1 = PG_GETARG_TIMEADT(0);
*************** time_smaller(PG_FUNCTION_ARGS)
*** 1471,1476 ****
--- 1513,1532 ----
  	PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
  }
  
+ Datum
+ time_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 < time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* overlaps_time() --- implements the SQL OVERLAPS operator.
   *
   * Algorithm is per SQL spec.  This is much harder than you'd think
*************** timetz_larger(PG_FUNCTION_ARGS)
*** 2262,2267 ****
--- 2318,2337 ----
  }
  
  Datum
+ timetz_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) > 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  timetz_smaller(PG_FUNCTION_ARGS)
  {
  	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
*************** timetz_smaller(PG_FUNCTION_ARGS)
*** 2275,2280 ****
--- 2345,2364 ----
  	PG_RETURN_TIMETZADT_P(result);
  }
  
+ Datum
+ timetz_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) < 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* timetz_pl_interval()
   * Add interval to timetz.
   */
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 83c3878..32035e0 100644
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
*************** enum_smaller(PG_FUNCTION_ARGS)
*** 273,278 ****
--- 273,292 ----
  }
  
  Datum
+ enum_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) < 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_larger(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
*************** enum_larger(PG_FUNCTION_ARGS)
*** 282,287 ****
--- 296,315 ----
  }
  
  Datum
+ enum_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) > 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_cmp(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 774267e..04f89b0 100644
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4larger(PG_FUNCTION_ARGS)
*** 637,642 ****
--- 637,658 ----
  }
  
  Datum
+ float4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float4smaller(PG_FUNCTION_ARGS)
  {
  	float4		arg1 = PG_GETARG_FLOAT4(0);
*************** float4smaller(PG_FUNCTION_ARGS)
*** 650,655 ****
--- 666,687 ----
  	PG_RETURN_FLOAT4(result);
  }
  
+ Datum
+ float4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  /*
   *		======================
   *		FLOAT8 BASE OPERATIONS
*************** float8larger(PG_FUNCTION_ARGS)
*** 704,709 ****
--- 736,757 ----
  }
  
  Datum
+ float8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float8smaller(PG_FUNCTION_ARGS)
  {
  	float8		arg1 = PG_GETARG_FLOAT8(0);
*************** float8smaller(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 765,785 ----
  	PG_RETURN_FLOAT8(result);
  }
  
+ Datum
+ float8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *		====================
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 669355e..fe9d736 100644
*** a/src/backend/utils/adt/int.c
--- b/src/backend/utils/adt/int.c
*************** int2larger(PG_FUNCTION_ARGS)
*** 1185,1190 ****
--- 1185,1204 ----
  }
  
  Datum
+ int2larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int2smaller(PG_FUNCTION_ARGS)
  {
  	int16		arg1 = PG_GETARG_INT16(0);
*************** int2smaller(PG_FUNCTION_ARGS)
*** 1194,1199 ****
--- 1208,1227 ----
  }
  
  Datum
+ int2smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4larger(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4larger(PG_FUNCTION_ARGS)
*** 1203,1208 ****
--- 1231,1250 ----
  }
  
  Datum
+ int4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4smaller(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4smaller(PG_FUNCTION_ARGS)
*** 1211,1216 ****
--- 1253,1272 ----
  	PG_RETURN_INT32((arg1 < arg2) ? arg1 : arg2);
  }
  
+ Datum
+ int4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /*
   * Bit-pushing operators
   *
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..820be68 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8larger(PG_FUNCTION_ARGS)
*** 752,757 ****
--- 752,771 ----
  }
  
  Datum
+ int8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int8smaller(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
*************** int8smaller(PG_FUNCTION_ARGS)
*** 764,769 ****
--- 778,797 ----
  }
  
  Datum
+ int8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int84pl(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..4749152 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_smaller(PG_FUNCTION_ARGS)
*** 1834,1839 ****
--- 1834,1854 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) < 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * numeric_larger() -
*************** numeric_larger(PG_FUNCTION_ARGS)
*** 1856,1861 ****
--- 1871,1892 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) > 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /* ----------------------------------------------------------------------
   *
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 8945ef4..745449a 100644
*** a/src/backend/utils/adt/oid.c
--- b/src/backend/utils/adt/oid.c
*************** oidlarger(PG_FUNCTION_ARGS)
*** 397,402 ****
--- 397,416 ----
  }
  
  Datum
+ oidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidsmaller(PG_FUNCTION_ARGS)
  {
  	Oid			arg1 = PG_GETARG_OID(0);
*************** oidsmaller(PG_FUNCTION_ARGS)
*** 406,411 ****
--- 420,439 ----
  }
  
  Datum
+ oidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidvectoreq(PG_FUNCTION_ARGS)
  {
  	int32		cmp = DatumGetInt32(btoidvectorcmp(fcinfo));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 6e2bbdc..57fcf80 100644
*** a/src/backend/utils/adt/tid.c
--- b/src/backend/utils/adt/tid.c
*************** tidlarger(PG_FUNCTION_ARGS)
*** 237,242 ****
--- 237,256 ----
  }
  
  Datum
+ tidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) > 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  tidsmaller(PG_FUNCTION_ARGS)
  {
  	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
*************** tidsmaller(PG_FUNCTION_ARGS)
*** 245,250 ****
--- 259,277 ----
  	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
  }
  
+ Datum
+ tidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) < 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *	Functions to get latest tid of a specified tuple.
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index cf6982b..f13d811 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** timestamp_smaller(PG_FUNCTION_ARGS)
*** 2410,2415 ****
--- 2410,2429 ----
  }
  
  Datum
+ timestamp_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) < 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  timestamp_larger(PG_FUNCTION_ARGS)
  {
  	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
*************** timestamp_larger(PG_FUNCTION_ARGS)
*** 2423,2428 ****
--- 2437,2455 ----
  	PG_RETURN_TIMESTAMP(result);
  }
  
+ Datum
+ timestamp_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) > 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
  
  Datum
  timestamp_mi(PG_FUNCTION_ARGS)
*************** interval_smaller(PG_FUNCTION_ARGS)
*** 2879,2884 ****
--- 2906,2925 ----
  }
  
  Datum
+ interval_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) < 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_larger(PG_FUNCTION_ARGS)
  {
  	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
*************** interval_larger(PG_FUNCTION_ARGS)
*** 2893,2898 ****
--- 2934,2953 ----
  }
  
  Datum
+ interval_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) > 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_pl(PG_FUNCTION_ARGS)
  {
  	Interval   *span1 = PG_GETARG_INTERVAL_P(0);
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 284b5d1..2fc8446 100644
*** a/src/backend/utils/adt/varchar.c
--- b/src/backend/utils/adt/varchar.c
*************** bpchar_larger(PG_FUNCTION_ARGS)
*** 889,894 ****
--- 889,917 ----
  }
  
  Datum
+ bpchar_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp > 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  bpchar_smaller(PG_FUNCTION_ARGS)
  {
  	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
*************** bpchar_smaller(PG_FUNCTION_ARGS)
*** 906,911 ****
--- 929,957 ----
  	PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2);
  }
  
+ Datum
+ bpchar_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp < 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /*
   * bpchar needs a specialized hash function because we want to ignore
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..6b640cb 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** text_larger(PG_FUNCTION_ARGS)
*** 1697,1702 ****
--- 1697,1716 ----
  }
  
  Datum
+ text_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  text_smaller(PG_FUNCTION_ARGS)
  {
  	text	   *arg1 = PG_GETARG_TEXT_PP(0);
*************** text_smaller(PG_FUNCTION_ARGS)
*** 1708,1713 ****
--- 1722,1740 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ Datum
+ text_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * The following operators support character-by-character comparison
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..7d35559 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2113	n 0 interval_pl		-	-	
*** 122,169 ****
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
--- 122,169 ----
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger			int8larger_inv			-		413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger			int4larger_inv			-		521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger			int2larger_inv			-		520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger			oidlarger_inv			-		610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger		float4larger_inv		-		623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger		float8larger_inv		-		674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger			int4larger_inv			-		563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger			date_larger_inv			-		1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger			time_larger_inv			-		1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger		timetz_larger_inv		-		1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger			cashlarger_inv			-		903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	timestamp_larger_inv	-		2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	timestamptz_larger_inv	-		1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger		interval_larger_inv		-		1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger			text_larger_inv			-		666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger		numeric_larger_inv		-		1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger		array_larger_inv		-		1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger		bpchar_larger_inv		-		1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger			tidlarger_inv			-		2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger			enum_larger_inv			-		3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller			int8smaller_inv			-		412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller			int4smaller_inv			-		97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller			int2smaller_inv			-		95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller			oidsmaller_inv			-		609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller		float4smaller_inv		-		622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller		float8smaller_inv		-		672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller			int4smaller_inv			-		562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller		date_smaller_inv		-		1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller		time_smaller_inv		-		1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller		timetz_smaller_inv		-		1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller			cashsmaller_inv			-		902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	timestamp_smaller_inv	-		2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller timestamptz_smaller_inv	-		1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	interval_smaller_inv	-		1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller		text_smaller_inv		-		664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller		numeric_smaller_inv		-		1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller		array_smaller_inv		-		1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller		bpchar_smaller_inv		-		1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller			tidsmaller_inv			-		2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller		enum_smaller_inv		-		3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
*************** DATA(insert ( 2828	n 0 float8_regr_accum
*** 232,240 ****
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
--- 232,240 ----
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
! DATA(insert ( 2518	n 0 bool_accum		bool_accum_inv		bool_anytrue	59	2281		16	_null_ ));
! DATA(insert ( 2519	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 11c1e1a..5111c69 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("larger of two");
*** 367,372 ****
--- 367,377 ----
  DATA(insert OID = 211 (  float4smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4033 (  float4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4034 (  float4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 212 (  int4um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "23" _null_ _null_ _null_ _null_ int4um _null_ _null_ _null_ ));
  DATA(insert OID = 213 (  int2um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "21" _null_ _null_ _null_ _null_ int2um _null_ _null_ _null_ ));
  
*************** DATA(insert OID = 223 (  float8larger	  
*** 386,391 ****
--- 391,400 ----
  DESCR("larger of two");
  DATA(insert OID = 224 (  float8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4035 (  float8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4036 (  float8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 225 (  lseg_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "601" _null_ _null_ _null_ _null_	lseg_center _null_ _null_ _null_ ));
  DATA(insert OID = 226 (  path_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "602" _null_ _null_ _null_ _null_	path_center _null_ _null_ _null_ ));
*************** DATA(insert OID = 458 (  text_larger	   
*** 711,716 ****
--- 720,729 ----
  DESCR("larger of two");
  DATA(insert OID = 459 (  text_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4037 (  text_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4038 (  text_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 460 (  int8in			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "2275" _null_ _null_ _null_ _null_ int8in _null_ _null_ _null_ ));
  DESCR("I/O");
*************** DATA(insert OID = 515 (  array_larger	  
*** 859,864 ****
--- 872,881 ----
  DESCR("larger of two");
  DATA(insert OID = 516 (  array_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4039 (  array_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4040 (  array_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1191 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 23 "2277 23 16" _null_ _null_ _null_ _null_ generate_subscripts _null_ _null_ _null_ ));
  DESCR("array subscripts generator");
  DATA(insert OID = 1192 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 23 "2277 23" _null_ _null_ _null_ _null_ generate_subscripts_nodir _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 913,918 ****
--- 930,945 ----
  DATA(insert OID = 771 (  int2smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4041 (  int4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4042 (  int4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4043 (  int2larger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4044 (  int2smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 774 (  gistgettuple	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_	gistgettuple _null_ _null_ _null_ ));
  DESCR("gist(internal)");
  DATA(insert OID = 638 (  gistgetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	gistgetbitmap _null_ _null_ _null_ ));
*************** DATA(insert OID =  898 (  cashlarger	   
*** 1003,1008 ****
--- 1030,1039 ----
  DESCR("larger of two");
  DATA(insert OID =  899 (  cashsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID =  4045 (  cashlarger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID =  4046 (  cashsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID =  919 (  flt8_mul_cash    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "701 790" _null_ _null_ _null_ _null_	flt8_mul_cash _null_ _null_ _null_ ));
  DATA(insert OID =  935 (  cash_words	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "790" _null_ _null_ _null_ _null_	cash_words _null_ _null_ _null_ ));
  DESCR("output money amount as words");
*************** DATA(insert OID = 1063 (  bpchar_larger 
*** 1157,1162 ****
--- 1188,1197 ----
  DESCR("larger of two");
  DATA(insert OID = 1064 (  bpchar_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4047 (  bpchar_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4048 (  bpchar_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1078 (  bpcharcmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1042 1042" _null_ _null_ _null_ _null_ bpcharcmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1080 (  hashbpchar	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "1042" _null_ _null_ _null_ _null_	hashbpchar _null_ _null_ _null_ ));
*************** DATA(insert OID = 1138 (  date_larger	  
*** 1191,1196 ****
--- 1226,1235 ----
  DESCR("larger of two");
  DATA(insert OID = 1139 (  date_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4049 (  date_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4050 (  date_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1140 (  date_mi		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1082 1082" _null_ _null_ _null_ _null_ date_mi _null_ _null_ _null_ ));
  DATA(insert OID = 1141 (  date_pli		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_pli _null_ _null_ _null_ ));
  DATA(insert OID = 1142 (  date_mii		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_mii _null_ _null_ _null_ ));
*************** DATA(insert OID = 1195 (  timestamptz_sm
*** 1281,1290 ****
--- 1320,1339 ----
  DESCR("smaller of two");
  DATA(insert OID = 1196 (  timestamptz_larger  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4051 (  timestamptz_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4052 (  timestamptz_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1197 (  interval_smaller	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  DATA(insert OID = 1198 (  interval_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4053 (  interval_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4054 (  interval_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1199 (  age				PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_	timestamptz_age _null_ _null_ _null_ ));
  DESCR("date difference preserving months and years");
  
*************** DESCR("larger of two");
*** 1318,1323 ****
--- 1367,1378 ----
  DATA(insert OID = 1237 (  int8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4055 (  int8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4056 (  int8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1238 (  texticregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexeq _null_ _null_ _null_ ));
  DATA(insert OID = 1239 (  texticregexne    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexne _null_ _null_ _null_ ));
  DATA(insert OID = 1240 (  nameicregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "19 25" _null_ _null_ _null_ _null_ nameicregexeq _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1374,1379 ****
--- 1429,1439 ----
  DATA(insert OID = 2796 ( tidsmaller		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4057 ( tidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4058 ( tidsmaller_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1296 (  timedate_pl	   PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1114 "1083 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
  DATA(insert OID = 1297 (  datetimetz_pl    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1082 1266" _null_ _null_ _null_ _null_ datetimetz_timestamptz _null_ _null_ _null_ ));
  DATA(insert OID = 1298 (  timetzdate_pl    PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1184 "1266 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1515,1520 ****
--- 1575,1590 ----
  DATA(insert OID = 1380 (  timetz_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4059 (  time_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4060 (  time_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4061 (  timetz_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4062 (  timetz_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1381 (  char_length	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "25" _null_ _null_ _null_ _null_ textlen _null_ _null_ _null_ ));
  DESCR("character length");
  
*************** DATA(insert OID = 1766 ( numeric_smaller
*** 2277,2282 ****
--- 2347,2357 ----
  DESCR("smaller of two");
  DATA(insert OID = 1767 ( numeric_larger			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4063 ( numeric_smaller_inv		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4064 ( numeric_larger_inv			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1769 ( numeric_cmp			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1700 1700" _null_ _null_ _null_ _null_ numeric_cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1771 ( numeric_uminus			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ numeric_uminus _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 2803,2808 ****
--- 2878,2888 ----
  DATA(insert OID = 1966 (  oidsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4065 (  oidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4066 (  oidsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 timestamp_transform f f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
  DESCR("adjust timestamptz precision");
  DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 time_transform f f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
*************** DATA(insert OID = 2035 (  timestamp_smal
*** 2864,2869 ****
--- 2944,2955 ----
  DESCR("smaller of two");
  DATA(insert OID = 2036 (  timestamp_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4067 (  timestamp_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4068 (  timestamp_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 2037 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 1266 "25 1266" _null_ _null_ _null_ _null_ timetz_zone _null_ _null_ _null_ ));
  DESCR("adjust time with time zone to new zone");
  DATA(insert OID = 2038 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1186 1266" _null_ _null_ _null_ _null_	timetz_izone _null_ _null_ _null_ ));
*************** DATA(insert OID = 939  (  generate_serie
*** 3860,3869 ****
--- 3946,3957 ----
  DESCR("non-persistent series generator");
  
  /* boolean aggregates */
+ /* previous aggregate transition functions, unused now */
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ /* aggregates and new invertible transition functions */
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
*************** DATA(insert OID = 2518 ( bool_or					   
*** 3871,3876 ****
--- 3959,3972 ----
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
+ DATA(insert OID = 4069 ( bool_accum					   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 4070 ( bool_accum_inv				   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4071 ( bool_alltrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_alltrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
+ DATA(insert OID = 4072 ( bool_anytrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_anytrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DATA(insert OID = 3524 (  enum_smaller	P
*** 4252,4257 ****
--- 4348,4358 ----
  DESCR("smaller of two");
  DATA(insert OID = 3525 (  enum_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4073 (  enum_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4074 (  enum_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 3526 (  max			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("maximum value of all enum input values");
  DATA(insert OID = 3527 (  min			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..43e4973 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern Datum array_upper(PG_FUNCTION_ARG
*** 206,212 ****
--- 206,214 ----
  extern Datum array_length(PG_FUNCTION_ARGS);
  extern Datum array_cardinality(PG_FUNCTION_ARGS);
  extern Datum array_larger(PG_FUNCTION_ARGS);
+ extern Datum array_larger_inv(PG_FUNCTION_ARGS);
  extern Datum array_smaller(PG_FUNCTION_ARGS);
+ extern Datum array_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
  extern Datum array_fill(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..c97c910 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum boolle(PG_FUNCTION_ARGS);
*** 121,126 ****
--- 121,130 ----
  extern Datum boolge(PG_FUNCTION_ARGS);
  extern Datum booland_statefunc(PG_FUNCTION_ARGS);
  extern Datum boolor_statefunc(PG_FUNCTION_ARGS);
+ extern Datum bool_accum(PG_FUNCTION_ARGS);
+ extern Datum bool_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum bool_alltrue(PG_FUNCTION_ARGS);
+ extern Datum bool_anytrue(PG_FUNCTION_ARGS);
  extern bool parse_bool(const char *value, bool *result);
  extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
  
*************** extern Datum enum_ge(PG_FUNCTION_ARGS);
*** 167,173 ****
--- 171,179 ----
  extern Datum enum_gt(PG_FUNCTION_ARGS);
  extern Datum enum_cmp(PG_FUNCTION_ARGS);
  extern Datum enum_smaller(PG_FUNCTION_ARGS);
+ extern Datum enum_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum enum_larger(PG_FUNCTION_ARGS);
+ extern Datum enum_larger_inv(PG_FUNCTION_ARGS);
  extern Datum enum_first(PG_FUNCTION_ARGS);
  extern Datum enum_last(PG_FUNCTION_ARGS);
  extern Datum enum_range_bounds(PG_FUNCTION_ARGS);
*************** extern Datum int42div(PG_FUNCTION_ARGS);
*** 241,249 ****
--- 247,259 ----
  extern Datum int4mod(PG_FUNCTION_ARGS);
  extern Datum int2mod(PG_FUNCTION_ARGS);
  extern Datum int2larger(PG_FUNCTION_ARGS);
+ extern Datum int2larger_inv(PG_FUNCTION_ARGS);
  extern Datum int2smaller(PG_FUNCTION_ARGS);
+ extern Datum int2smaller_inv(PG_FUNCTION_ARGS);
  extern Datum int4larger(PG_FUNCTION_ARGS);
+ extern Datum int4larger_inv(PG_FUNCTION_ARGS);
  extern Datum int4smaller(PG_FUNCTION_ARGS);
+ extern Datum int4smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int4and(PG_FUNCTION_ARGS);
  extern Datum int4or(PG_FUNCTION_ARGS);
*************** extern Datum float4abs(PG_FUNCTION_ARGS)
*** 347,358 ****
--- 357,372 ----
  extern Datum float4um(PG_FUNCTION_ARGS);
  extern Datum float4up(PG_FUNCTION_ARGS);
  extern Datum float4larger(PG_FUNCTION_ARGS);
+ extern Datum float4larger_inv(PG_FUNCTION_ARGS);
  extern Datum float4smaller(PG_FUNCTION_ARGS);
+ extern Datum float4smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float8abs(PG_FUNCTION_ARGS);
  extern Datum float8um(PG_FUNCTION_ARGS);
  extern Datum float8up(PG_FUNCTION_ARGS);
  extern Datum float8larger(PG_FUNCTION_ARGS);
+ extern Datum float8larger_inv(PG_FUNCTION_ARGS);
  extern Datum float8smaller(PG_FUNCTION_ARGS);
+ extern Datum float8smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float4pl(PG_FUNCTION_ARGS);
  extern Datum float4mi(PG_FUNCTION_ARGS);
  extern Datum float4mul(PG_FUNCTION_ARGS);
*************** extern Datum oidle(PG_FUNCTION_ARGS);
*** 501,507 ****
--- 515,523 ----
  extern Datum oidge(PG_FUNCTION_ARGS);
  extern Datum oidgt(PG_FUNCTION_ARGS);
  extern Datum oidlarger(PG_FUNCTION_ARGS);
+ extern Datum oidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum oidsmaller(PG_FUNCTION_ARGS);
+ extern Datum oidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum oidvectorin(PG_FUNCTION_ARGS);
  extern Datum oidvectorout(PG_FUNCTION_ARGS);
  extern Datum oidvectorrecv(PG_FUNCTION_ARGS);
*************** extern Datum tidgt(PG_FUNCTION_ARGS);
*** 707,713 ****
--- 723,731 ----
  extern Datum tidge(PG_FUNCTION_ARGS);
  extern Datum bttidcmp(PG_FUNCTION_ARGS);
  extern Datum tidlarger(PG_FUNCTION_ARGS);
+ extern Datum tidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum tidsmaller(PG_FUNCTION_ARGS);
+ extern Datum tidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum currtid_byreloid(PG_FUNCTION_ARGS);
  extern Datum currtid_byrelname(PG_FUNCTION_ARGS);
  
*************** extern Datum bpchargt(PG_FUNCTION_ARGS);
*** 730,736 ****
--- 748,756 ----
  extern Datum bpcharge(PG_FUNCTION_ARGS);
  extern Datum bpcharcmp(PG_FUNCTION_ARGS);
  extern Datum bpchar_larger(PG_FUNCTION_ARGS);
+ extern Datum bpchar_larger_inv(PG_FUNCTION_ARGS);
  extern Datum bpchar_smaller(PG_FUNCTION_ARGS);
+ extern Datum bpchar_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum bpcharlen(PG_FUNCTION_ARGS);
  extern Datum bpcharoctetlen(PG_FUNCTION_ARGS);
  extern Datum hashbpchar(PG_FUNCTION_ARGS);
*************** extern Datum text_le(PG_FUNCTION_ARGS);
*** 770,776 ****
--- 790,798 ----
  extern Datum text_gt(PG_FUNCTION_ARGS);
  extern Datum text_ge(PG_FUNCTION_ARGS);
  extern Datum text_larger(PG_FUNCTION_ARGS);
+ extern Datum text_larger_inv(PG_FUNCTION_ARGS);
  extern Datum text_smaller(PG_FUNCTION_ARGS);
+ extern Datum text_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum text_pattern_lt(PG_FUNCTION_ARGS);
  extern Datum text_pattern_le(PG_FUNCTION_ARGS);
  extern Datum text_pattern_gt(PG_FUNCTION_ARGS);
*************** extern Datum numeric_div_trunc(PG_FUNCTI
*** 980,986 ****
--- 1002,1010 ----
  extern Datum numeric_mod(PG_FUNCTION_ARGS);
  extern Datum numeric_inc(PG_FUNCTION_ARGS);
  extern Datum numeric_smaller(PG_FUNCTION_ARGS);
+ extern Datum numeric_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_larger(PG_FUNCTION_ARGS);
+ extern Datum numeric_larger_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_fac(PG_FUNCTION_ARGS);
  extern Datum numeric_sqrt(PG_FUNCTION_ARGS);
  extern Datum numeric_exp(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/cash.h b/src/include/utils/cash.h
index 3a491f9..4e184cc 100644
*** a/src/include/utils/cash.h
--- b/src/include/utils/cash.h
*************** extern Datum int2_mul_cash(PG_FUNCTION_A
*** 60,66 ****
--- 60,68 ----
  extern Datum cash_div_int2(PG_FUNCTION_ARGS);
  
  extern Datum cashlarger(PG_FUNCTION_ARGS);
+ extern Datum cashlarger_inv(PG_FUNCTION_ARGS);
  extern Datum cashsmaller(PG_FUNCTION_ARGS);
+ extern Datum cashsmaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum cash_words(PG_FUNCTION_ARGS);
  
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 622aa19..9f459ac 100644
*** a/src/include/utils/date.h
--- b/src/include/utils/date.h
*************** extern Datum date_cmp(PG_FUNCTION_ARGS);
*** 108,114 ****
--- 108,116 ----
  extern Datum date_sortsupport(PG_FUNCTION_ARGS);
  extern Datum date_finite(PG_FUNCTION_ARGS);
  extern Datum date_larger(PG_FUNCTION_ARGS);
+ extern Datum date_larger_inv(PG_FUNCTION_ARGS);
  extern Datum date_smaller(PG_FUNCTION_ARGS);
+ extern Datum date_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum date_mi(PG_FUNCTION_ARGS);
  extern Datum date_pli(PG_FUNCTION_ARGS);
  extern Datum date_mii(PG_FUNCTION_ARGS);
*************** extern Datum time_cmp(PG_FUNCTION_ARGS);
*** 168,174 ****
--- 170,178 ----
  extern Datum time_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_time(PG_FUNCTION_ARGS);
  extern Datum time_larger(PG_FUNCTION_ARGS);
+ extern Datum time_larger_inv(PG_FUNCTION_ARGS);
  extern Datum time_smaller(PG_FUNCTION_ARGS);
+ extern Datum time_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum time_mi_time(PG_FUNCTION_ARGS);
  extern Datum timestamp_time(PG_FUNCTION_ARGS);
  extern Datum timestamptz_time(PG_FUNCTION_ARGS);
*************** extern Datum timetz_cmp(PG_FUNCTION_ARGS
*** 195,201 ****
--- 199,207 ----
  extern Datum timetz_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_timetz(PG_FUNCTION_ARGS);
  extern Datum timetz_larger(PG_FUNCTION_ARGS);
+ extern Datum timetz_larger_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_smaller(PG_FUNCTION_ARGS);
+ extern Datum timetz_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_time(PG_FUNCTION_ARGS);
  extern Datum time_timetz(PG_FUNCTION_ARGS);
  extern Datum timestamptz_timetz(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..d102ccb 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8inc(PG_FUNCTION_ARGS);
*** 77,83 ****
--- 77,85 ----
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
+ extern Datum int8larger_inv(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
+ extern Datum int8smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int8and(PG_FUNCTION_ARGS);
  extern Datum int8or(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..85c0283 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum timestamp_cmp(PG_FUNCTION_A
*** 111,117 ****
--- 111,119 ----
  extern Datum timestamp_sortsupport(PG_FUNCTION_ARGS);
  extern Datum timestamp_hash(PG_FUNCTION_ARGS);
  extern Datum timestamp_smaller(PG_FUNCTION_ARGS);
+ extern Datum timestamp_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timestamp_larger(PG_FUNCTION_ARGS);
+ extern Datum timestamp_larger_inv(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS);
  extern Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS);
*************** extern Datum interval_finite(PG_FUNCTION
*** 147,153 ****
--- 149,157 ----
  extern Datum interval_cmp(PG_FUNCTION_ARGS);
  extern Datum interval_hash(PG_FUNCTION_ARGS);
  extern Datum interval_smaller(PG_FUNCTION_ARGS);
+ extern Datum interval_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum interval_larger(PG_FUNCTION_ARGS);
+ extern Datum interval_larger_inv(PG_FUNCTION_ARGS);
  extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
  extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
  extern Datum interval_justify_days(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index aa29471..862da92 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1300,1305 ****
--- 1300,1410 ----
  -- Test the MIN, MAX and boolean inverse transition functions
  --
  --
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  40 |  30
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  20 |  20
+     |  40 |  40
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  30 |  30
+  40 |     |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |  20 |  10
+     |  10 |  10
+  10 |  10 |  10
+ (5 rows)
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |     |  10
+     |     |  10
+  10 |     |  10
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  |  p  | max | min 
+ ----+-----+-----+-----
+  ad | 100 | ae  | ab
+  ab | 100 | ae  | ab
+  ae | 100 | ae  | ae
+  ad | 200 | ad  | aa
+  aa | 200 | aa  | aa
+ (5 rows)
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  b | bool_and | bool_or 
+ ---+----------+---------
+  t | t        | t
+  t | f        | t
+  f | f        | f
+  f | f        | t
+  t | t        | t
+ (5 rows)
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 676fa45..8224b97 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 482,487 ****
--- 482,523 ----
  --
  --
  
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
#145Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#143)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 20 February 2014 01:48, Florian Pflug <fgp@phlo.org> wrote:

On Jan29, 2014, at 13:45 , Florian Pflug <fgp@phlo.org> wrote:

In fact, I'm
currently leaning towards just forbidding non-strict forward transition
function with strict inverses, and adding non-NULL counters to the
aggregates that then require them. It's really only the SUM() aggregates
that are affected by this, I think.

I finally got around to doing that, and the results aren't too bad. The
attached patches required that the strictness settings of the forward and
reverse transition functions agree, and employ exactly the same NULL-skipping
logic we always had.

The only aggregates seriously affected by that change were SUM(int2) and
SUM(int4).

The SUM, AVG and STDDEV aggregates which use NumericAggState where
already mostly prepared for this - all they required were a few adjustments
to correctly handle the last non-NULL, non-NaN input being removed, and a few
additional PG_ARGISNULL calls for the inverse transition functions since they're
now non-strict. I've also modified them to unconditionally allocate the state
at the first call, instead upon seeing the first non-NULL input, but that isn't
strictly required. But without that, the state can have three classes of values -
SQL-NULL, NULL pointer and valid pointer, and that's just confusing...

SUM(int2) and SUM(int4) now simply use the same transition functions as
AVG(int2) and AVG(int4), which use an int8 array to track the sum of the inputs
and the number of inputs, plus a new final function int2int4_sum(). Previously,
they used a single int8 as their state type.

Since I was touching the code anyway, I removed some unnecessary inverse
transition functions - namely, int8_avg_accum_inv and numeric_avg_accum_inv. These
are completely identical to their non-avg cousins - the only difference between
the corresponding forward transition functions is whether they request computation
of sumX2 (i.e. the sum of squares of the inputs) or not.

I haven't yet updated the docs - it'll do that if and when there's consensus
about whether this is the way to go or not.

I haven't looked at this in any detail yet, but that seems much neater
to me. It seems perfectly sensible that the forward and inverse
transition functions should have the same strictness settings, and
enforcing that keeps the logic simple, as well as hopefully making it
easier to document.

It's a shame that more transition functions cannot be made strict,
when they actually ignore null values, but I think trying to solve
that can be regarded as outside the scope of this patch.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#146Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#145)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Feb24, 2014, at 17:50 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 20 February 2014 01:48, Florian Pflug <fgp@phlo.org> wrote:

On Jan29, 2014, at 13:45 , Florian Pflug <fgp@phlo.org> wrote:

In fact, I'm
currently leaning towards just forbidding non-strict forward transition
function with strict inverses, and adding non-NULL counters to the
aggregates that then require them. It's really only the SUM() aggregates
that are affected by this, I think.

I finally got around to doing that, and the results aren't too bad. The
attached patches required that the strictness settings of the forward and
reverse transition functions agree, and employ exactly the same NULL-skipping
logic we always had.

The only aggregates seriously affected by that change were SUM(int2) and
SUM(int4).

I haven't looked at this in any detail yet, but that seems much neater
to me. It seems perfectly sensible that the forward and inverse
transition functions should have the same strictness settings, and
enforcing that keeps the logic simple, as well as hopefully making it
easier to document.

Good to hear that you agree! I'll try to find some time to update the docs.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#147Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#146)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 25 February 2014 12:33, Florian Pflug <fgp@phlo.org> wrote:

On Feb24, 2014, at 17:50 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 20 February 2014 01:48, Florian Pflug <fgp@phlo.org> wrote:

On Jan29, 2014, at 13:45 , Florian Pflug <fgp@phlo.org> wrote:

In fact, I'm
currently leaning towards just forbidding non-strict forward transition
function with strict inverses, and adding non-NULL counters to the
aggregates that then require them. It's really only the SUM() aggregates
that are affected by this, I think.

I finally got around to doing that, and the results aren't too bad. The
attached patches required that the strictness settings of the forward and
reverse transition functions agree, and employ exactly the same NULL-skipping
logic we always had.

The only aggregates seriously affected by that change were SUM(int2) and
SUM(int4).

I haven't looked at this in any detail yet, but that seems much neater
to me. It seems perfectly sensible that the forward and inverse
transition functions should have the same strictness settings, and
enforcing that keeps the logic simple, as well as hopefully making it
easier to document.

Good to hear that you agree! I'll try to find some time to update the docs.

I finally got round to looking at this in more detail. Sorry for the
delay. Here is my more detailed review of the base patch.

Overall, I think that it is in reasonable shape, and as I said I think
the approach of enforcing matching strictness settings on the forward
and inverse transition functions is much simpler and neater. I have a
few comments, some cosmetic, and a couple more substantive:

* In a couple of places:

errmsg("stricness of forward and reverse transition functions must match")

- misspelling: "stricness".
- "reverse" should be "inverse" to match the terminology used elsewhere.

* Grammatical error in the comment for lookup_agg_function() - you
should drop the word "both".

* In show_windowagg_info(), this calculation looks suspicious to me:

double tperrow = winaggstate->aggfwdtrans /
(inst->nloops * inst->ntuples);

If the node is executed multiple times, aggfwdtrans will be reset in
each loop, so the transitions per row figure will be under-estimated.
ISTM that if you want to report on this, you'd need aggfwdtrans to be
reset once per query, but I'm not sure exactly how to do that.

Here's a test case:

explain (verbose, analyse)
select sum(i) over (rows between 4 preceding and current row)
from generate_series(1, 10) i;

which outputs 10 rows with an average of 1 transition per row, but
doing the same window aggregate twice in a nested loop:

explain (verbose, analyse)
select * from (values (10), (10)) v(x),
lateral
(select sum(i) over (rows between 4 preceding and current row)
from generate_series(1, x) i) t;

outputs 20 rows, but only reports 0.5 transitions per row.

Actually, I think it's misleading to only count forward transition
function calls, because a call to the inverse transition function
still represents a state transition, and is likely to be around the
same cost. For a window of size 2, there would not be much advantage
to using inverse transition functions, because it would be around 2
transitions per row either way.

* The function comment for build_aggregate_fnexprs() needs to be
updated to reference the inverse transition function. I'd also be
tempted to have it allow invtransfnexpr be a NULL pointer, if the
inverse transition function expression tree is not required. Then
ExecInitAgg() could simply pass NULL, instead of having the local
variable invtransfnexpr with the slightly cryptic comment "needed but
never used".

* In struct WindowStatePerAggData, I think you should change the field
order to transfn_oid, invtransfn_oid and then finalfn_oid. It's only a
small thing, but that's the order those 3 functions are referred to
everywhere else.

* In struct WindowStatePerAggData, the comment for transValueCount
should read "number of aggregated values".

* If AggCheckCallContext() is called from a window function, and it
asks for an aggcontext, it will fail because calledaggno will be -1.
That can't currently happen for any of our built-in window functions,
and I'm not sure if it's likely to happen in the future, but I think
it would be better to defend against that possibility just in case. So
I think it ought to return the shared context in that case, as the
original code would have done.

* In advance_windowaggregate(), this code

if (peraggstate->transfn.fn_strict) {

is against the project style, which is to have curly braces on new
lines. But also, that test condition is the same as the preceding
block, so the 2 blocks could just be merged.

* I was wondering about the case of a forward transition function
returning NULL in the presence of an inverse transition function. In
this patch there are 3 pieces of code that test for that:

1). advance_windowaggregate() errors out if the forward transition
function returns NULL and there is an inverse transition function.
2). an Assert in advance_windowaggregate() fires if a prior call made
the state NULL and there is an inverse transition function (should be
impossible due to the above error).
3). retreat_windowaggregate() errors out if it sees a NULL state
(which ought to be impossible due to both of the above).

I find the resulting error "transition function with an inverse
returned NULL" surprising. Why shouldn't a transition function return
NULL if it wants to? It can if it's used as a regular aggregate, so it
seems somewhat odd that it can't if it's used in a window context, and
it has an inverse.

Would it not be simpler and more flexible to just allow the forward
transition function to return NULL, and treat a NULL state as
non-invertible, requiring a restart. So delete check (1) and just
allow the forward transition function to return NULL, delete Assert
(2) so that it propagates a NULL state to the end as it would do in
the absence of an inverse transition function, and modify check (3) to
return false forcing a restart if the state is NULL. So then if the
forward transition function did return NULL, the inverse transition
function would not actually be called, and it would compute the answer
the hard way, rather than erroring out.

* In retreat_windowaggregate(), the comment before the check on
transValueCount is missing a "be".

* I think the function comment for eval_windowaggregates() should be
updated to mention that it may also use an inverse transition function
to remove aggregated data from a transition value.

* I found the guts of eval_windowaggregates() a little hard to follow,
although I think the code is correct. It could perhaps use a little
tidying up. Here are a few ideas:

- Maybe numaggs_restart would be better called numtorestart.

- is_first isn't adding much, and it would probably be easier to read
if it were eliminated by inlining it in the one place it is used.

- Similarly I would move the variable "ok" to the block that uses it.

- The variable pos could be eliminated by having the retreat loop
increment winstate->aggregatedbase in each pass, which would better
match its comment which says it updates aggregatedbase, which it
currently doesn't do. For consistency, perhaps this loop should be
written more in the style of the advance loop. The loop should ensure
that, on exit, aggregatedbase is equal to frameheadpos.

- aggregatedupto_nonrestarted is a bit of a mouthful, but I can't
immediately think of a better name. However, there's one comment that
refers to it as aggregatedupto_previous.

- The comment starting "After this, non-restarted aggregated..." is a
bit confusing. What the code following it really seems to be doing is
more along the lines of "If there are aggregates to restart, rewind
aggregatedupto back to frameheadpos so that we can re-aggregate those
values in the aggregates to be restarted...".

* The pg_dump modifications don't look correct to me. I didn't test
it, but I think you need to modify the pre-9.4 queries to return "-"
for agginvtransfn otherwise I think it will be NULL and will seg-fault
if you dump a pre-9.4 database containing custom aggregates.

* The last regression test in aggregates.sql should test for unequal
strictness values, rather than just strict forward transition
functions with non-strict inverses.

I think that's everything for the base patch.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#148Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#147)
5 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mar2, 2014, at 20:39 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

* In a couple of places:

errmsg("stricness of forward and reverse transition functions must match")

- misspelling: "stricness".
- "reverse" should be "inverse" to match the terminology used elsewhere.

Done.

* Grammatical error in the comment for lookup_agg_function() - you
should drop the word "both".

Done.

* In show_windowagg_info(), this calculation looks suspicious to me:

double tperrow = winaggstate->aggfwdtrans /
(inst->nloops * inst->ntuples);

If the node is executed multiple times, aggfwdtrans will be reset in
each loop, so the transitions per row figure will be under-estimated.
ISTM that if you want to report on this, you'd need aggfwdtrans to be
reset once per query, but I'm not sure exactly how to do that.

...

Actually, I think it's misleading to only count forward transition
function calls, because a call to the inverse transition function
still represents a state transition, and is likely to be around the
same cost. For a window of size 2, there would not be much advantage
to using inverse transition functions, because it would be around 2
transitions per row either way.

True. In fact, I pondered whether to avoid using the inverse transition
function for windows of 2 rows. In the end, I didn't because I felt that
it makes custom aggregates harder to test.

On the question of whether to count inverse transition function calls -
the idea of the EXPLAIN VERBOSE ANALYZE output isn't really to show the
number of state transitions, but rather to show whether the aggregation
has O(n) or O(n^2) behaviour. The idea being that a value close to "1"
means "inverse transition function works as expected", and larger values
mean "not working so well".

Regarding multiple evaluations - I think I based the behaviour on how
ntuples works, which also only reports the value of the last evaluation
I think. But maybe I'm confused about this.

* The function comment for build_aggregate_fnexprs() needs to be
updated to reference the inverse transition function. I'd also be
tempted to have it allow invtransfnexpr be a NULL pointer, if the
inverse transition function expression tree is not required. Then
ExecInitAgg() could simply pass NULL, instead of having the local
variable invtransfnexpr with the slightly cryptic comment "needed but
never used".

Hm, I suggested that to David Rowley initially, and he wasn't particularly
convinced - after all, there aren't *that* many callers of that function,
and there isn't much overhead - we just set the value to NULL if an invalid
OID is passed. But now that you brought it up too, it's two yea's agains
one nay, so I changed it.

* In struct WindowStatePerAggData, I think you should change the field
order to transfn_oid, invtransfn_oid and then finalfn_oid. It's only a
small thing, but that's the order those 3 functions are referred to
everywhere else.

Done.

* In struct WindowStatePerAggData, the comment for transValueCount
should read "number of aggregated values".

Done.

* If AggCheckCallContext() is called from a window function, and it
asks for an aggcontext, it will fail because calledaggno will be -1.
That can't currently happen for any of our built-in window functions,
and I'm not sure if it's likely to happen in the future, but I think
it would be better to defend against that possibility just in case. So
I think it ought to return the shared context in that case, as the
original code would have done.

I did it this way on purpose, because it was never actually safe to
use the shared content from window functions - they won't know if and
when that context is reset. I added a note to the function comment
explaining that this function isn't meant to be used by true window
functions

* In advance_windowaggregate(), this code

if (peraggstate->transfn.fn_strict) {

is against the project style, which is to have curly braces on new
lines. But also, that test condition is the same as the preceding
block, so the 2 blocks could just be merged.

Done. The two conditions used to be different, I think, but requiring
the strictness settings to match made them the same.

* I was wondering about the case of a forward transition function
returning NULL in the presence of an inverse transition function. In
this patch there are 3 pieces of code that test for that:

1). advance_windowaggregate() errors out if the forward transition
function returns NULL and there is an inverse transition function.

2). an Assert in advance_windowaggregate() fires if a prior call made
the state NULL and there is an inverse transition function (should be
impossible due to the above error).

Yeah, that assert is just there to document that "yes, this cannot be".

3). retreat_windowaggregate() errors out if it sees a NULL state
(which ought to be impossible due to both of the above).

This too is simply meant to document "No worries, cannot be".

I find the resulting error "transition function with an inverse
returned NULL" surprising. Why shouldn't a transition function return
NULL if it wants to? It can if it's used as a regular aggregate, so it
seems somewhat odd that it can't if it's used in a window context, and
it has an inverse.

Because transition function cannot use NULL as a state value freely if
they have an inverse, since if the inverse returns NULL that means
"sorry, cannot invert, please restart aggregation".

Would it not be simpler and more flexible to just allow the forward
transition function to return NULL, and treat a NULL state as
non-invertible, requiring a restart. So delete check (1) and just
allow the forward transition function to return NULL, delete Assert
(2) so that it propagates a NULL state to the end as it would do in
the absence of an inverse transition function, and modify check (3) to
return false forcing a restart if the state is NULL. So then if the
forward transition function did return NULL, the inverse transition
function would not actually be called, and it would compute the answer
the hard way, rather than erroring out.

Though that would be weird too - if the inverse transition function
is non-strict, we actually can *call* it with a NULL state, it just
cannot really return a NULL state (well it can, but that means
"restart the aggregation, please").

I don't think there's anything to be gained by allowing this, so I'd
prefer if we don't.

* In retreat_windowaggregate(), the comment before the check on
transValueCount is missing a "be".

Done.

* I think the function comment for eval_windowaggregates() should be
updated to mention that it may also use an inverse transition function
to remove aggregated data from a transition value.

Done.

* I found the guts of eval_windowaggregates() a little hard to follow,
although I think the code is correct. It could perhaps use a little
tidying up. Here are a few ideas:

- Maybe numaggs_restart would be better called numtorestart.

Hm, I'm not particularly fond of that - it doesn't really emphasize the
connection to numaggs.

- is_first isn't adding much, and it would probably be easier to read
if it were eliminated by inlining it in the one place it is used.

Done.

- Similarly I would move the variable "ok" to the block that uses it.

Done.

- The variable pos could be eliminated by having the retreat loop
increment winstate->aggregatedbase in each pass, which would better
match its comment which says it updates aggregatedbase, which it
currently doesn't do. For consistency, perhaps this loop should be
written more in the style of the advance loop. The loop should ensure
that, on exit, aggregatedbase is equal to frameheadpos.

Done, mostly. The loops still look a bit different, but that's mostly
because of the tuple-slot optimization in the advance loop (it re-uses
the last row it fetched during the *previous* call to eval_windowaggregates
if possible), and because the retreat loop already knows how far to
retreat, while the advance loop figures that out as it goes.

- aggregatedupto_nonrestarted is a bit of a mouthful, but I can't
immediately think of a better name. However, there's one comment that
refers to it as aggregatedupto_previous.

I don't have a better name either, so I kept the current name.

- The comment starting "After this, non-restarted aggregated..." is a
bit confusing. What the code following it really seems to be doing is
more along the lines of "If there are aggregates to restart, rewind
aggregatedupto back to frameheadpos so that we can re-aggregate those
values in the aggregates to be restarted...".

I re-wrote that comment, and also moved the block that updates the
tuplestore mark up above the restart-block. Thus, the block that updates
aggregatedupto is now immediately before the advance loop, which hopefully
makes the connection more obvious.

* The pg_dump modifications don't look correct to me. I didn't test
it, but I think you need to modify the pre-9.4 queries to return "-"
for agginvtransfn otherwise I think it will be NULL and will seg-fault
if you dump a pre-9.4 database containing custom aggregates.

Hm, I haven't touched this code, it comes from David's original patch.
I agree that it looks wrong - I've updated it per your suggestion, and
checked that it dumps 9.3 and HEAD+patch correctly.

* The last regression test in aggregates.sql should test for unequal
strictness values, rather than just strict forward transition
functions with non-strict inverses.

Done. That was a leftover from when only one case was rejected.

Attached a new versions of all 5 patches. Only the base patch has actually
changed, the others are just included for completeness' sake.

best regards,
Florian Pflug

Attachments:

invtrans_strictstrict_arith_76031b.patchapplication/octet-stream; name=invtrans_strictstrict_arith_76031b.patchDownload
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..e62f2a3 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8inc(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 717,767 ----
  	}
  }
  
+ Datum
+ int8dec(PG_FUNCTION_ARGS)
+ {
+ 	/*
+ 	 * When int8 is pass-by-reference, we provide this special case to avoid
+ 	 * palloc overhead for COUNT(): when called as an inverse transition
+ 	 * aggregate, we know that the argument is modifiable local storage,
+ 	 * so just update it in-place. (If int8 is pass-by-value, then of course
+ 	 * this is useless as well as incorrect, so just ifdef it out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *arg = (int64 *) PG_GETARG_POINTER(0);
+ 		int64		result;
+ 
+ 		result = *arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && *arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		*arg = result;
+ 		PG_RETURN_POINTER(arg);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		/* Not called as an aggregate, so just do it the dumb way */
+ 		int64		arg = PG_GETARG_INT64(0);
+ 		int64		result;
+ 
+ 		result = arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		PG_RETURN_INT64(result);
+ 	}
+ }
+ 
+ 
  /*
   * These functions are exactly like int8inc but are used for aggregates that
   * count only non-null values.	Since the functions are declared strict,
*************** int8inc_any(PG_FUNCTION_ARGS)
*** 733,738 ****
--- 778,789 ----
  }
  
  Datum
+ int8inc_any_inv(PG_FUNCTION_ARGS)
+ {
+ 	return int8dec(fcinfo);
+ }
+ 
+ Datum
  int8inc_float8_float8(PG_FUNCTION_ARGS)
  {
  	return int8inc(fcinfo);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..53e7624 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_float4(PG_FUNCTION_ARGS)
*** 2479,2486 ****
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	bool		isNaN;			/* true if any processed number was NaN */
  	MemoryContext agg_context;	/* context we're calculating in */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
--- 2479,2490 ----
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	int			maxScale;		/* stores the maximum scale seen so far. */
! 	int64		maxScaleCount;  /* tracks the number of values we've
! 								 * seen with the maximum scale */
! 
  	MemoryContext agg_context;	/* context we're calculating in */
+ 	int64		NaNcount;		/* Count of NaN values that are aggregated */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
*************** makeNumericAggState(FunctionCallInfo fci
*** 2505,2510 ****
--- 2509,2518 ----
  	state = (NumericAggState *) palloc0(sizeof(NumericAggState));
  	state->calcSumX2 = calcSumX2;
  	state->agg_context = agg_context;
+ 	state->NaNcount = 0;
+ 
+ 	state->maxScale = 0;
+ 	state->maxScaleCount = 0;
  
  	MemoryContextSwitchTo(old_context);
  
*************** do_numeric_accum(NumericAggState *state,
*** 2522,2536 ****
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (state->isNaN || NUMERIC_IS_NAN(newval))
  	{
! 		state->isNaN = true;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
--- 2530,2558 ----
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (NUMERIC_IS_NAN(newval))
  	{
! 		state->NaNcount++;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
+ 	/*
+ 	 * Track the highest scale that we've seen as if we ever perform an inverse
+ 	 * transition and remove the last numeric with the highest scale that we've
+ 	 * seen then we can no longer perform inverse transitions without risking
+ 	 * having the wrong dscale in the result value.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 		state->maxScaleCount++;
+ 	else if (X.dscale > state->maxScale)
+ 	{
+ 		state->maxScale = X.dscale;
+ 		state->maxScaleCount = 1;
+ 	}
+ 
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2541,2553 ****
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N++ > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
  	}
  	else
  	{
--- 2563,2577 ----
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
+ 
+ 		state->N++;
  	}
  	else
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2556,2567 ****
--- 2580,2692 ----
  
  		if (state->calcSumX2)
  			set_var_from_var(&X2, &(state->sumX2));
+ 
+ 		state->N = 1;
  	}
  
  	MemoryContextSwitchTo(old_context);
  }
  
  /*
+  * do_numeric_discard
+  * Attempts to remove a value from the aggregated state.
+  * If the value cannot be removed then the function will return false, the
+  * possible reasons for failing are described below.
+  *
+  * If we aggregate the values 1.01 and 2 then the result will be 3.01. If we
+  * are then asked to un-aggregate the 1.01 then we must reject this case as we
+  * won't be able to tell what the new aggregated value's dscale should be.
+  * We can't return 2.00 (dscale = 2) as we really should return just 2, but
+  * since we're not tracking any previous highest scales then we must just fail
+  * to perform the inverse transition and just return false.
+  *
+  * Values that are no longer aggregated should not be able to effect the dscale
+  * of the result of the values that *are* still aggregated.
+  *
+  * Note it may be better to track the number of times we've aggregated a
+  * numeric with each scale, then if we ever remove final highest scaled value
+  * then we can step the result's dscale down to the next highest value. This is
+  * perhaps slightly more work than we can afford to do here, but doing it this
+  * way would mean that we could always perform the inverse transition.
+  */
+ static bool
+ do_numeric_discard(NumericAggState *state, Numeric newval)
+ {
+ 	NumericVar	X;
+ 	NumericVar	X2;
+ 	MemoryContext old_context;
+ 
+ 	/* result is NaN if any processed number is NaN */
+ 	if (NUMERIC_IS_NAN(newval))
+ 	{
+ 		state->NaNcount--;
+ 		return true;
+ 	}
+ 
+ 	/* load processed number in short-lived context */
+ 	init_var_from_num(newval, &X);
+ 
+ 	/*
+ 	 * state->sumX's dscale matches the maximum dscale of any of the inputs
+ 	 * Removing the last input with that dscale would require us to recompute
+ 	 * the maximum dscale of the *remaining* inputs, which we cannot do unless
+ 	 * no more non-NaN inputs remain at all. So we report a failure instead,
+ 	 * and force the aggregation to be redone from scratch.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 	{
+ 		if (state->maxScaleCount > 1)
+ 		{
+ 			/* Some remaining inputs have same dscale */
+ 			--state->maxScaleCount;
+ 		}
+ 		else if (state->N == 1)
+ 		{
+ 			/* No remaining non-NaN inputs at all */
+ 			state->maxScale = 0;
+ 			state->maxScaleCount = 0;
+ 		}
+ 		else
+ 		{
+ 			/* No remaining inputs have same dscale */
+ 			return false;
+ 		}
+ 	}
+ 
+ 	/* if we need X^2, calculate that in short-lived context */
+ 	if (state->calcSumX2)
+ 	{
+ 		init_var(&X2);
+ 		mul_var(&X, &X, &X2, X.dscale * 2);
+ 	}
+ 
+ 	/* The rest of this needs to work in the aggregate context */
+ 	old_context = MemoryContextSwitchTo(state->agg_context);
+ 
+ 	if (state->N > 1)
+ 	{
+ 		sub_var(&(state->sumX), &X, &(state->sumX));
+ 		if (state->calcSumX2)
+ 			sub_var(&(state->sumX2), &X2, &(state->sumX2));
+ 		state->N--;
+ 	}
+ 	else if (state->N == 1)
+ 	{
+ 		/* Sums will be reset by next call to do_numeric_accum */
+ 		state->N = 0;
+ 	}
+ 	else
+ 	{
+ 		MemoryContextSwitchTo(old_context);
+ 		elog(ERROR, "cannot discard more values than were accumulated");
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 	return true;
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that require sumX2.
   */
  Datum
*************** numeric_accum(PG_FUNCTION_ARGS)
*** 2571,2588 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
--- 2696,2734 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, true);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * Generic inverse transition function for numeric aggregates
+  */
+ Datum
+ numeric_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL)
+ 		elog(ERROR, "numeric_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		/* Can we perform an inverse transition? if not return NULL. */
+ 		if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 			PG_RETURN_NULL();
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
+ 
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
*************** numeric_avg_accum(PG_FUNCTION_ARGS)
*** 2593,2606 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, false);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
- 	}
  
  	PG_RETURN_POINTER(state);
  }
--- 2739,2750 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, false);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  
  	PG_RETURN_POINTER(state);
  }
*************** int2_accum(PG_FUNCTION_ARGS)
*** 2621,2637 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2765,2780 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int4_accum(PG_FUNCTION_ARGS)
*** 2645,2661 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2788,2803 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2669,2686 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
! 		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
--- 2811,2913 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
+ 		do_numeric_accum(state, newval);
+ 	}
  
! 	PG_RETURN_POINTER(state);
! }
  
! 
! /*
!  * int2_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int2_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int4_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int4_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int8_accum_inv
!  * aggregate inverse transition function.
!  * This function must be declared as strict.
!  */
! Datum
! int8_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
  	}
  
  	PG_RETURN_POINTER(state);
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2688,2693 ****
--- 2915,2921 ----
  
  /*
   * Transition function for int8 input when we don't need sumX2.
+  * For the inverse, we use int8_accum_inv.
   */
  Datum
  int8_avg_accum(PG_FUNCTION_ARGS)
*************** int8_avg_accum(PG_FUNCTION_ARGS)
*** 2696,2719 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, false);
- 
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
- 
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
--- 2924,2945 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, false);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
*************** numeric_avg(PG_FUNCTION_ARGS)
*** 2722,2731 ****
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
--- 2948,2958 ----
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
*************** numeric_sum(PG_FUNCTION_ARGS)
*** 2740,2749 ****
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
--- 2967,2978 ----
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)
! 		/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
*************** numeric_stddev_internal(NumericAggState 
*** 2774,2780 ****
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL)
  	{
  		*is_null = true;
  		return NULL;
--- 3003,3009 ----
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
  	{
  		*is_null = true;
  		return NULL;
*************** numeric_stddev_internal(NumericAggState 
*** 2782,2788 ****
  
  	*is_null = false;
  
! 	if (state->isNaN)
  		return make_result(&const_nan);
  
  	init_var(&vN);
--- 3011,3017 ----
  
  	*is_null = false;
  
! 	if (state->NaNcount > 0)
  		return make_result(&const_nan);
  
  	init_var(&vN);
*************** numeric_stddev_pop(PG_FUNCTION_ARGS)
*** 2913,2932 ****
  }
  
  /*
!  * SUM transition functions for integer datatypes.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   *
!  * Because SQL defines the SUM() of no values to be NULL, not zero,
!  * the initial condition of the transition data value needs to be NULL. This
!  * means we can't rely on ExecAgg to automatically insert the first non-null
!  * data value into the transition data: it doesn't know how to do the type
!  * conversion.	The upshot is that these routines have to be marked non-strict
!  * and handle substitution of the first non-null input themselves.
   */
  
  Datum
--- 3142,3155 ----
  }
  
  /*
!  * Obsolete SUM transition functions for integer datatypes.
   *
!  * These were used to implement SUM aggregates before inverse transition
!  * functions were added. For inverse transitions, we need to know the number
!  * of summands to be able to return NULL whenenver the number of non-NULL
!  * inputs becomes zero. We therefore now use the intX_avg_accum and
!  * intX_avg_accum_inv transition functions, which use int8[] is their
!  * transition type to be able to count the number of inputs.
   */
  
  Datum
*************** int8_sum(PG_FUNCTION_ARGS)
*** 3065,3074 ****
  										NumericGetDatum(oldsum), newval));
  }
  
- 
  /*
!  * Routines for avg(int2) and avg(int4).  The transition datatype
!  * is a two-element int8 array, holding count and sum.
   */
  
  typedef struct Int8TransTypeData
--- 3288,3302 ----
  										NumericGetDatum(oldsum), newval));
  }
  
  /*
!  * Routines for sum(int2), sum(int4), avg(int2) and avg(int4).  The transition
!  * datatype is a two-element int8 array, holding count and sum.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   */
  
  typedef struct Int8TransTypeData
*************** int2_avg_accum(PG_FUNCTION_ARGS)
*** 3106,3111 ****
--- 3334,3368 ----
  }
  
  Datum
+ int2_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int16		newval = PG_GETARG_INT16(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ 
+ Datum
  int4_avg_accum(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray;
*************** int4_avg_accum(PG_FUNCTION_ARGS)
*** 3134,3139 ****
--- 3391,3442 ----
  }
  
  Datum
+ int4_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int32		newval = PG_GETARG_INT32(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ Datum
+ int2int4_sum(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Int8TransTypeData *transdata;
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 
+ 	/* SQL defines SUM of no values to be NULL */
+ 	if (transdata->count == 0)
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_DATUM(Int64GetDatumFast(transdata->sum));
+ }
+ 
+ Datum
  int8_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index cf6982b..eb051ba 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** interval_accum(PG_FUNCTION_ARGS)
*** 3157,3162 ****
--- 3157,3207 ----
  }
  
  Datum
+ interval_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Interval   *newval = PG_GETARG_INTERVAL_P(1);
+ 	Datum	   *transdatums;
+ 	int			ndatums;
+ 	Interval	sumX,
+ 				N;
+ 	Interval   *newsum;
+ 	ArrayType  *result;
+ 
+ 	deconstruct_array(transarray,
+ 					  INTERVALOID, sizeof(Interval), false, 'd',
+ 					  &transdatums, NULL, &ndatums);
+ 	if (ndatums != 2)
+ 		elog(ERROR, "expected 2-element interval array");
+ 
+ 	/*
+ 	 * XXX memcpy, instead of just extracting a pointer, to work around buggy
+ 	 * array code: it won't ensure proper alignment of Interval objects on
+ 	 * machines where double requires 8-byte alignment. That should be fixed,
+ 	 * but in the meantime...
+ 	 *
+ 	 * Note: must use DatumGetPointer here, not DatumGetIntervalP, else some
+ 	 * compilers optimize into double-aligned load/store anyway.
+ 	 */
+ 	memcpy((void *) &sumX, DatumGetPointer(transdatums[0]), sizeof(Interval));
+ 	memcpy((void *) &N, DatumGetPointer(transdatums[1]), sizeof(Interval));
+ 
+ 	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
+ 												   IntervalPGetDatum(&sumX),
+ 												 IntervalPGetDatum(newval)));
+ 	N.time -= 1;
+ 
+ 	transdatums[0] = IntervalPGetDatum(newsum);
+ 	transdatums[1] = IntervalPGetDatum(&N);
+ 
+ 	result = construct_array(transdatums, 2,
+ 							 INTERVALOID, sizeof(Interval), false, 'd');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ 
+ Datum
  interval_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..874aeec 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 103,125 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
--- 103,125 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		int8_accum_inv			numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		int4_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		int2_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_accum_inv		numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum		interval_accum_inv		interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		int8_accum_inv			numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_avg_accum		int4_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2109	n 0 int2_avg_accum		int2_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2110	n 0 float4pl			-						-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl			-						-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl				cash_mi					-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl			interval_mi				-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_accum_inv		numeric_sum		0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
*************** DATA(insert ( 2798	n 0 tidsmaller		-	-		
*** 166,221 ****
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
--- 166,221 ----
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		int8inc_any_inv	-		0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			int8dec			-		0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		int8_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		int4_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		int2_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_accum_inv	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		int8_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		int4_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		int2_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 11c1e1a..d18a6e1 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("truncate interval to specified un
*** 1309,1316 ****
--- 1309,1320 ----
  
  DATA(insert OID = 1219 (  int8inc		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8inc _null_ _null_ _null_ ));
  DESCR("increment");
+ DATA(insert OID = 3210 (  int8dec		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8dec _null_ _null_ _null_ ));
+ DESCR("decrement");
  DATA(insert OID = 2804 (  int8inc_any	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any _null_ _null_ _null_ ));
  DESCR("increment, ignores second argument");
+ DATA(insert OID = 3211 (  int8inc_any_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any_inv _null_ _null_ _null_ ));
+ DESCR("decrement, ignores second argument");
  DATA(insert OID = 1230 (  int8abs		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8abs _null_ _null_ _null_ ));
  
  DATA(insert OID = 1236 (  int8larger	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger _null_ _null_ _null_ ));
*************** DATA(insert OID = 1832 (  float8_stddev_
*** 2396,2401 ****
--- 2400,2407 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3212 (  numeric_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
*************** DATA(insert OID = 1836 (  int8_accum	   
*** 2406,2411 ****
--- 2412,2423 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 2746 (  int8_avg_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3213 (  int2_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3214 (  int4_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3215 (  int8_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
*************** DATA(insert OID = 1842 (  int8_sum		   P
*** 2426,2439 ****
--- 2438,2459 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 1843 (  interval_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3216 (  interval_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1844 (  interval_avg	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1186 "1187" _null_ _null_ _null_ _null_ interval_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1962 (  int2_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1963 (  int4_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3217 (  int2_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 3218 (  int4_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 1964 (  int8_avg		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1016" _null_ _null_ _null_ _null_ int8_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
+ DATA(insert OID = 3219 (  int2int4_sum	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "1016" _null_ _null_ _null_ _null_ int2int4_sum _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  DATA(insert OID = 2805 (  int8inc_float8_float8		PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 20 "20 701 701" _null_ _null_ _null_ _null_ int8inc_float8_float8 _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2806 (  float8_regr_accum			PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 1022 "1022 701 701" _null_ _null_ _null_ _null_ float8_regr_accum _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..9e44693 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum numeric_float8_no_overflow(
*** 999,1008 ****
--- 999,1012 ----
  extern Datum float4_numeric(PG_FUNCTION_ARGS);
  extern Datum numeric_float4(PG_FUNCTION_ARGS);
  extern Datum numeric_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg_accum(PG_FUNCTION_ARGS);
  extern Datum int2_accum(PG_FUNCTION_ARGS);
  extern Datum int4_accum(PG_FUNCTION_ARGS);
  extern Datum int8_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int4_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int8_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg_accum(PG_FUNCTION_ARGS);
  extern Datum numeric_avg(PG_FUNCTION_ARGS);
  extern Datum numeric_sum(PG_FUNCTION_ARGS);
*************** extern Datum int2_sum(PG_FUNCTION_ARGS);
*** 1014,1020 ****
--- 1018,1027 ----
  extern Datum int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_sum(PG_FUNCTION_ARGS);
  extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int4_avg_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int2int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_avg(PG_FUNCTION_ARGS);
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  extern Datum hash_numeric(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..5078e4a 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8div(PG_FUNCTION_ARGS);
*** 74,80 ****
--- 74,82 ----
  extern Datum int8abs(PG_FUNCTION_ARGS);
  extern Datum int8mod(PG_FUNCTION_ARGS);
  extern Datum int8inc(PG_FUNCTION_ARGS);
+ extern Datum int8dec(PG_FUNCTION_ARGS);
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
+ extern Datum int8inc_any_inv(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..e852fbd 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum interval_mul(PG_FUNCTION_AR
*** 179,184 ****
--- 179,185 ----
  extern Datum mul_d_interval(PG_FUNCTION_ARGS);
  extern Datum interval_div(PG_FUNCTION_ARGS);
  extern Datum interval_accum(PG_FUNCTION_ARGS);
+ extern Datum interval_accum_inv(PG_FUNCTION_ARGS);
  extern Datum interval_avg(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_mi(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 8af3d23..55c2b4c 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1295,1300 ****
--- 1295,1783 ----
  -- Test the arithmetic inverse transition functions
  --
  --
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 2.0000000000000000
+  2 | 2.5000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |    avg     
+ ---+------------
+  1 | @ 1.5 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+  i |  sum  
+ ---+-------
+  1 | $3.30
+  2 | $2.20
+  3 |      
+  4 |      
+ (4 rows)
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |   sum    
+ ---+----------
+  1 | @ 3 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 | 3.3
+  2 | 2.2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+  sum  
+ ------
+  6.01
+     5
+     3
+ (3 rows)
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     2
+  2 |     1
+  3 |     0
+  4 |     0
+ (4 rows)
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     4
+  2 |     3
+  3 |     2
+  4 |     1
+ (4 rows)
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   1
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   6
+  3 |   9
+  4 |   7
+ (4 rows)
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+          to_char          
+ --------------------------
+   100000000000000000000
+                       1.0
+ (2 rows)
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+  a |  b  | sum 
+ ---+-----+-----
+  1 |   1 |   1
+  2 |   2 |   3
+  3 | NaN | NaN
+  4 |   3 | NaN
+  5 |   4 |   7
+ (5 rows)
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 117bd6c..29c4226 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 476,481 ****
--- 476,622 ----
  --
  --
  
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
invtrans_strictstrict_base_038070.patchapplication/octet-stream; name=invtrans_strictstrict_base_038070.patchDownload
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..b7fc5f2 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 56,61 ****
--- 56,62 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
*************** AggregateCreate(const char *aggName,
*** 68,78 ****
--- 69,81 ----
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
  	Oid			transfn;
+ 	Oid			invtransfn = InvalidOid; /* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	bool		transIsStrict;
  	Oid			rettype;
  	Oid			finaltype;
  	Oid			fnArgs[FUNC_MAX_ARGS];
*************** AggregateCreate(const char *aggName,
*** 234,241 ****
--- 237,297 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
+ 
+ 	/*
+ 	 * Remember if trans function is strict as we need to validate this
+ 	 * later if when we're dealing with the inverse transition function
+ 	 */
+ 	transIsStrict = proc->proisstrict;
+ 
  	ReleaseSysCache(tup);
  
+ 	/* handle invtransfn, if supplied */
+ 	if (agginvtransfnName)
+ 	{
+ 		/*
+ 		 * This must have the same number of arguments with the same types as
+ 		 * the transition function. We can just borrow the argument details
+ 		 * from the transition function and try to find a function with
+ 		 * the name of the inverse transition function and with a signature
+ 		 * that matches the transition function's.
+ 		 */
+ 		invtransfn = lookup_agg_function(agginvtransfnName,
+ 					nargs_transfn, fnArgs, InvalidOid, &rettype);
+ 
+ 		/*
+ 		 * Ensure the return type of the inverse transition function matches
+ 		 * the expected type.
+ 		 */
+ 		if (rettype != aggTransType)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("return type of inverse transition function %s is not %s",
+ 							NameListToString(agginvtransfnName),
+ 							format_type_be(aggTransType))));
+ 
+ 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+ 		proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 		/*
+ 		 * We force the strictness settings of the forward and inverse
+ 		 * transition functions to agree. This allows places which only need
+ 		 * forward transitions to not look at the inverse transition function
+ 		 * at all.
+ 		 */
+ 		if (transIsStrict != proc->proisstrict)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 						errmsg("strictness of forward and inverse transition functions must match"
+ 						)));
+ 		}
+ 		ReleaseSysCache(tup);
+ 
+ 	}
+ 
  	/* handle finalfn, if supplied */
  	if (aggfinalfnName)
  	{
*************** AggregateCreate(const char *aggName,
*** 391,396 ****
--- 447,453 ----
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+ 	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
*************** AggregateCreate(const char *aggName,
*** 425,430 ****
--- 482,496 ----
  	referenced.objectSubId = 0;
  	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
+ 	/* Depends on inverse transition function, if any */
+ 	if (OidIsValid(invtransfn))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = invtransfn;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
  	{
*************** AggregateCreate(const char *aggName,
*** 447,453 ****
  }
  
  /*
!  * lookup_agg_function -- common code for finding both transfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
--- 513,520 ----
  }
  
  /*
!  * lookup_agg_function
!  * common code for finding transfn, invtransfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..c0ea1f7 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
*************** DefineAggregate(List *name, List *args, 
*** 60,65 ****
--- 60,66 ----
  	AclResult	aclresult;
  	char		aggKind = AGGKIND_NORMAL;
  	List	   *transfuncName = NIL;
+ 	List	   *invtransfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
*************** DefineAggregate(List *name, List *args, 
*** 112,117 ****
--- 113,120 ----
  			transfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
  			transfuncName = defGetQualifiedName(defel);
+ 		else if (pg_strcasecmp(defel->defname, "invsfunc") == 0)
+ 			invtransfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
  			finalfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
*************** DefineAggregate(List *name, List *args, 
*** 283,288 ****
--- 286,292 ----
  						   parameterDefaults,
  						   variadicArgType,
  						   transfuncName,		/* step function name */
+ 						   invtransfuncName,	/* inverse trans function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 08f3167..beee43d 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_group_keys(PlanSta
*** 85,90 ****
--- 85,91 ----
  					 List *ancestors, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
+ static void show_windowagg_info(PlanState *planstate, ExplainState *es);
  static void show_tidbitmap_info(BitmapHeapScanState *planstate,
  								ExplainState *es);
  static void show_instrumentation_count(const char *qlabel, int which,
*************** ExplainNode(PlanState *planstate, List *
*** 1420,1425 ****
--- 1421,1430 ----
  		case T_Hash:
  			show_hash_info((HashState *) planstate, es);
  			break;
+ 		case T_WindowAgg:
+ 			if (es->verbose && planstate->instrument)
+ 				show_windowagg_info(planstate, es);
+ 			break;
  		default:
  			break;
  	}
*************** show_hash_info(HashState *hashstate, Exp
*** 1918,1923 ****
--- 1923,1943 ----
  	}
  }
  
+ static void
+ show_windowagg_info(PlanState *planstate, ExplainState *es)
+ {
+ 	WindowAggState *winaggstate = (WindowAggState *) planstate;
+ 	Instrumentation *inst = planstate->instrument;
+ 
+ 	if (inst->nloops > 0 && inst->ntuples > 0 && winaggstate->numaggs > 0)
+ 	{
+ 		double tperrow = winaggstate->aggfwdtrans /
+ 			(inst->nloops * inst->ntuples);
+ 
+ 		ExplainPropertyFloat("Transitions Per Row", tperrow, 1, es);
+ 	}
+ }
+ 
  /*
   * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
   */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..9a7ed93 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1798,1805 ****
--- 1798,1807 ----
  								aggref->aggtype,
  								aggref->inputcollid,
  								transfn_oid,
+ 								InvalidOid, /* invtrans is not needed here */
  								finalfn_oid,
  								&transfnexpr,
+ 								NULL,
  								&finalfnexpr);
  
  		/* set up infrastructure for calling the transfn and finalfn */
*************** ExecReScanAgg(AggState *node)
*** 2127,2168 ****
  }
  
  /*
-  * AggCheckCallContext - test if a SQL function is being called as an aggregate
-  *
-  * The transition and/or final functions of an aggregate may want to verify
-  * that they are being called as aggregates, rather than as plain SQL
-  * functions.  They should use this function to do so.	The return value
-  * is nonzero if being called as an aggregate, or zero if not.	(Specific
-  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
-  * values could conceivably appear in future.)
-  *
-  * If aggcontext isn't NULL, the function also stores at *aggcontext the
-  * identity of the memory context that aggregate transition values are
-  * being stored in.
-  */
- int
- AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
- {
- 	if (fcinfo->context && IsA(fcinfo->context, AggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_AGGREGATE;
- 	}
- 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_WINDOW;
- 	}
- 
- 	/* this is just to prevent "uninitialized variable" warnings */
- 	if (aggcontext)
- 		*aggcontext = NULL;
- 	return 0;
- }
- 
- /*
   * AggGetAggref - allow an aggregate support function to get its Aggref
   *
   * If the function is being called as an aggregate support function,
--- 2129,2134 ----
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..2b64175 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** typedef struct WindowStatePerFuncData
*** 102,119 ****
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transfer functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transfer functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/*
  	 * initial value from pg_aggregate entry
  	 */
--- 102,125 ----
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transition functions */
  	Oid			transfn_oid;
+ 	Oid			invtransfn_oid; /* may be InvalidOid */
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transition functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
+ 	FmgrInfo	invtransfn;
  	FmgrInfo	finalfn;
  
+ 	/* Aggregate properties */
+ 	bool		use_invtransfn;			/* whether to use the invtransfn */
+ 	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+ 
  	/*
  	 * initial value from pg_aggregate entry
  	 */
*************** typedef struct WindowStatePerAggData
*** 140,149 ****
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	Datum		transValue;		/* current transition value */
! 	bool		transValueIsNull;
  
! 	bool		noTransValue;	/* true if transValue not set yet */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
--- 146,158 ----
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	MemoryContext	aggcontext;			/* context for transValue */
! 	int64			transValueCount;	/* Number of aggregated values*/
! 	Datum			transValue;			/* current transition value */
! 	bool			transValueIsNull;
  
! 	/* Data local to eval_windowaggregates() */
! 	bool			restart;			/* tmp marker that agg needs restart */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
*************** static void initialize_windowaggregate(W
*** 152,157 ****
--- 161,169 ----
  static void advance_windowaggregate(WindowAggState *winstate,
  						WindowStatePerFunc perfuncstate,
  						WindowStatePerAgg peraggstate);
+ static bool retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate);
  static void finalize_windowaggregate(WindowAggState *winstate,
  						 WindowStatePerFunc perfuncstate,
  						 WindowStatePerAgg peraggstate,
*************** static bool are_peers(WindowAggState *wi
*** 181,186 ****
--- 193,245 ----
  static bool window_gettupleslot(WindowObject winobj, int64 pos,
  					TupleTableSlot *slot);
  
+ /*
+  * AggCheckCallContext - test if a SQL function is being called as an aggregate
+  *
+  * The transition and/or final functions of an aggregate may want to verify
+  * that they are being called as aggregates, rather than as plain SQL
+  * functions.  They should use this function to do so.	The return value
+  * is nonzero if being called as an aggregate, or zero if not.	(Specific
+  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+  * values could conceivably appear in future.)
+  *
+  * If aggcontext isn't NULL, the function also stores at *aggcontext the
+  * identity of the memory context that aggregate transition values are
+  * being stored in.
+  *
+  * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+  * is private.
+  *
+  * Note that this function is only meant to be used by aggregate support
+  * functions, NOT by true window functions.
+  */
+ int
+ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggState))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ 		return AGG_CONTEXT_AGGREGATE;
+ 	}
+ 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ 	{
+ 		if (aggcontext)
+ 		{
+ 			/* Must lookup per-aggregate context */
+ 			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+ 			int				aggno = winstate->calledaggno;
+ 			Assert(0 <= aggno && aggno < winstate->numaggs);
+ 			*aggcontext = winstate->peragg[aggno].aggcontext;
+ 		}
+ 		return AGG_CONTEXT_WINDOW;
+ 	}
+ 
+ 	/* this is just to prevent "uninitialized variable" warnings */
+ 	if (aggcontext)
+ 		*aggcontext = NULL;
+ 	return 0;
+ }
  
  /*
   * initialize_windowaggregate
*************** initialize_windowaggregate(WindowAggStat
*** 193,210 ****
  {
  	MemoryContext oldContext;
  
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = peraggstate->initValue;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->noTransValue = peraggstate->initValueIsNull;
  	peraggstate->resultValueIsNull = true;
  }
  
--- 252,277 ----
  {
  	MemoryContext oldContext;
  
+ 	/* If we're using a private aggcontext, we may reset it here. But if the
+ 	 * context is shared, we don't know which other aggregates may still need
+ 	 * it, so we must leave it to the caller to reset at an appropriate time
+ 	 */
+ 	if (!peraggstate->aggcontext_is_shared)
+ 		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+ 
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = (Datum) 0;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
+ 	peraggstate->transValueCount = 0;
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->resultValue = (Datum) 0;
  	peraggstate->resultValueIsNull = true;
  }
  
*************** advance_windowaggregate(WindowAggState *
*** 256,265 ****
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/*
! 		 * For a strict transfn, nothing happens when there's a NULL input; we
! 		 * just keep the prior transValue.
! 		 */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
--- 323,329 ----
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/* Skip NULL inputs for aggregates which desire that. */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
*************** advance_windowaggregate(WindowAggState *
*** 268,308 ****
  				return;
  			}
  		}
! 		if (peraggstate->noTransValue)
  		{
! 			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
! 			 * already checked that the agg's input type is binary-compatible
! 			 * with its transtype, so straight copy here is OK.)
! 			 *
! 			 * We must copy the datum into aggcontext if it is pass-by-ref. We
! 			 * do not need to pfree the old transValue, since it's NULL.
! 			 */
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->noTransValue = false;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  		if (peraggstate->transValueIsNull)
  		{
- 			/*
- 			 * Don't call a strict function with NULL inputs.  Note it is
- 			 * possible to get here despite the above tests, if the transfn is
- 			 * strict *and* returned a NULL on a prior cycle. If that happens
- 			 * we will propagate the NULL all the way to the end.
- 			 */
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  	}
  
  	/*
  	 * OK to call the transition function
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
--- 332,388 ----
  				return;
  			}
  		}
! 
! 		/*
! 		 * For strict transfer functions with initial value NULL we use the
! 		 * first non-NULL input as the initial state. (We already checked that
! 		 * the agg's input type is binary-compatible with its transtype, so
! 		 * straight copy here is OK.)
! 		 *
! 		 * We must copy the datum into aggcontext if it is pass-by-ref. We do
! 		 * not need to pfree the old transValue, since it's NULL.
! 		 */
! 		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->transValueCount = 1;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
+ 
+ 		/*
+ 		 * Don't call a strict function with NULL inputs.  Note it is possible
+ 		 * to get here despite the above tests, if the transfn is strict *and*
+ 		 * returned a NULL on a prior cycle. If that happens we will propagate
+ 		 * the NULL all the way to the end. That can only happen if there's no
+ 		 * inverse transition function, though, since we disallow transitions
+ 		 * back to NULL if there is one below.
+ 		 */
  		if (peraggstate->transValueIsNull)
  		{
  			MemoryContextSwitchTo(oldContext);
+ 			Assert(peraggstate->invtransfn_oid == InvalidOid);
  			return;
  		}
  	}
  
  	/*
+ 	 * We must track the number of inputs that we add to transValue, since
+ 	 * to remove the last input, retreat_windowaggregate() musn't call the
+ 	 * inverse transition function, but simply reset transValue back to its
+ 	 * initial value.
+ 	 */
+ 	Assert(peraggstate->transValueCount >= 0);
+ 	peraggstate->transValueCount++;
+ 
+ 	/*
  	 * OK to call the transition function
+ 	 * Transfer functions with an inverse MUST not return NULL, see
+ 	 * retreat_windowaggregate()
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
*************** advance_windowaggregate(WindowAggState *
*** 310,316 ****
--- 390,404 ----
  							 (void *) winstate, NULL);
  	fcinfo->arg[0] = peraggstate->transValue;
  	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
  	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("transition function with an inverse returned NULL")));
+ 	}
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
*************** advance_windowaggregate(WindowAggState *
*** 322,328 ****
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
--- 410,416 ----
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
*************** advance_windowaggregate(WindowAggState *
*** 337,342 ****
--- 425,571 ----
  }
  
  /*
+  * retreat_windowaggregate
+  * removes tuples from aggregation.
+  * The calling function must ensure that each aggregate has
+  * a valid inverse transition function.
+  */
+ static bool
+ retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate)
+ {
+ 	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
+ 	int			numArguments = perfuncstate->numArguments;
+ 	FunctionCallInfoData fcinfodata;
+ 	FunctionCallInfo fcinfo = &fcinfodata;
+ 	Datum		newVal;
+ 	ListCell   *arg;
+ 	int			i;
+ 	MemoryContext oldContext;
+ 	ExprContext *econtext = winstate->tmpcontext;
+ 	ExprState  *filter = wfuncstate->aggfilter;
+ 
+ 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ 
+ 	/* Skip anything FILTERed out */
+ 	if (filter)
+ 	{
+ 		bool		isnull;
+ 		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ 
+ 		if (isnull || !DatumGetBool(res))
+ 		{
+ 			MemoryContextSwitchTo(oldContext);
+ 			return true;
+ 		}
+ 	}
+ 
+ 	/* We start from 1, since the 0th arg will be the transition value */
+ 	i = 1;
+ 	foreach(arg, wfuncstate->args)
+ 	{
+ 		ExprState  *argstate = (ExprState *) lfirst(arg);
+ 
+ 		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
+ 									  &fcinfo->argnull[i], NULL);
+ 		i++;
+ 	}
+ 
+ 	/* Skip inputs containing NULLS for aggregates that require this */
+ 	if (peraggstate->invtransfn.fn_strict)
+ 	{
+ 		for (i = 1; i <= numArguments; i++)
+ 		{
+ 			if (fcinfo->argnull[i])
+ 			{
+ 				MemoryContextSwitchTo(oldContext);
+ 				return true;
+ 			}
+ 		}
+ 	}
+ 
+ 	/* There should still be an added but not yet removed value */
+ 	Assert(peraggstate->transValueCount >= 1);
+ 
+ 	/*
+ 	 * We mustn't use the inverse transition function to remove the last
+ 	 * input. Doing so would yield a non-NULL state, whereas we should be
+ 	 * in the initial state afterwards which may very well be NULL. So
+ 	 * instead, we simply re-initialize the aggregation in this case.
+ 	 */
+ 	if (peraggstate->transValueCount == 1)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		initialize_windowaggregate(winstate,
+ 								&winstate->perfunc[peraggstate->wfuncno],
+ 								peraggstate);
+ 		return true;
+ 	}
+ 
+ 	/*
+ 	 * Perform the inverse transition.
+ 	 *
+ 	 * For pairs of forward and inverse transition functions, the state may
+ 	 * never be NULL, except in the ignore_nulls case, and then only until
+ 	 * until we see the first non-NULL input during which time should never
+ 	 * attempt to invoke the inverse transition function. Excluding NULL
+ 	 * as a possible state value allows us to make it mean "sorry, can't
+ 	 * do an inverse transition in this case" when returned by the inverse
+ 	 * transition function. In that case, we report the failure to the
+ 	 * caller.
+ 	 */
+ 	if (peraggstate->transValueIsNull)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		elog(ERROR, "transition value is NULL during inverse transition");
+ 	}
+ 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
+ 							 numArguments + 1,
+ 							 perfuncstate->winCollation,
+ 							 (void *) winstate, NULL);
+ 	fcinfo->arg[0] = peraggstate->transValue;
+ 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
+ 	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (fcinfo->isnull)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		return false;
+ 	}
+ 
+ 	/* Update number of added but not yet removed values */
+ 	peraggstate->transValueCount--;
+ 
+ 	/*
+ 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
+ 	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
+ 	 * first input, we don't need to do anything.
+ 	 */
+ 	if (!peraggstate->transtypeByVal &&
+ 		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
+ 	{
+ 		if (!fcinfo->isnull)
+ 		{
+ 			MemoryContextSwitchTo(peraggstate->aggcontext);
+ 			newVal = datumCopy(newVal,
+ 							   peraggstate->transtypeByVal,
+ 							   peraggstate->transtypeLen);
+ 		}
+ 		if (!peraggstate->transValueIsNull)
+ 			pfree(DatumGetPointer(peraggstate->transValue));
+ 	}
+ 
+ 	MemoryContextSwitchTo(oldContext);
+ 	peraggstate->transValue = newVal;
+ 	peraggstate->transValueIsNull = fcinfo->isnull;
+ 
+ 	return true;
+ }
+ 
+ 
+ /*
   * finalize_windowaggregate
   * parallel to finalize_aggregate in nodeAgg.c
   */
*************** finalize_windowaggregate(WindowAggState 
*** 370,376 ****
--- 599,607 ----
  		}
  		else
  		{
+ 			winstate->calledaggno = perfuncstate->aggno;
  			*result = FunctionCallInvoke(&fcinfo);
+ 			winstate->calledaggno = -1;
  			*isnull = fcinfo.isnull;
  		}
  	}
*************** finalize_windowaggregate(WindowAggState 
*** 392,402 ****
  	MemoryContextSwitchTo(oldContext);
  }
  
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * Much of this is duplicated from nodeAgg.c.  But NOTE that we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
--- 623,636 ----
  	MemoryContextSwitchTo(oldContext);
  }
  
+ 
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * This differes from nodeAgg.c in two ways. First, if the window's frame
!  * start position moves, we use the inverse transfer function (if it exists)
!  * to remove values from the transition value. And second, we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
*************** eval_windowaggregates(WindowAggState *wi
*** 406,417 ****
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs;
! 	int			i;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
--- 640,654 ----
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs,
! 				numaggs_restart = 0,
! 				i;
! 	int64		aggregatedupto_nonrestarted;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
! 	TupleTableSlot *temp_slot = winstate->temp_slot_1;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
*************** eval_windowaggregates(WindowAggState *wi
*** 420,426 ****
  	/* final output execution is in ps_ExprContext */
  	econtext = winstate->ss.ps.ps_ExprContext;
  	agg_winobj = winstate->agg_winobj;
- 	agg_row_slot = winstate->agg_row_slot;
  
  	/*
  	 * Currently, we support only a subset of the SQL-standard window framing
--- 657,662 ----
*************** eval_windowaggregates(WindowAggState *wi
*** 438,446 ****
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * For other frame start rules, we discard the aggregate state and re-run
! 	 * the aggregates whenever the frame head row moves.  We can still
! 	 * optimize as above whenever successive rows share the same frame head.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
--- 674,693 ----
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * We can still optimize as above whenever successive rows share the same
! 	 * frame head, but if the frame head moves beyond the aggregated base point
! 	 * we use the aggregate function's inverse transition function. This
! 	 * removes the tuple from aggregation and restores the aggregate's current
! 	 * state to what it would be if the removed row had never been aggregated
! 	 * in the first place. Inverse transition functions may optionally return
! 	 * NULL, this indicates that the function was unable to remove the tuple
! 	 * from aggregation, when this happens we must perform the aggregation all
! 	 * over again for all tuples in the new frame boundary.
! 	 *
! 	 * If the aggregate function does not have a inverse transition function
! 	 * and the frame head moves beyond the aggregated position then we must
! 	 * discard the aggregated state and re-aggregate similar to how we would
! 	 * have to if the inverse transition function had returned NULL.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
*************** eval_windowaggregates(WindowAggState *wi
*** 452,526 ****
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
- 	 *
- 	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
- 	 * some aggregates like SUM and COUNT we could avoid that by implementing
- 	 * a "negative transition function" that would be called for each row as
- 	 * it exits the frame.	We'd have to think about avoiding recalculation of
- 	 * volatile arguments of aggregate functions, too.
  	 */
  
  	/*
  	 * First, update the frame head position.
  	 */
! 	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
  
  	/*
! 	 * Initialize aggregates on first call for partition, or if the frame head
! 	 * position moved since last time.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos != winstate->aggregatedbase)
  	{
- 		/*
- 		 * Discard transient aggregate values
- 		 */
- 		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
- 
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  
  		/*
! 		 * If we created a mark pointer for aggregates, keep it pushed up to
! 		 * frame head, so that tuplestore can discard unnecessary rows.
  		 */
! 		if (agg_winobj->markptr >= 0)
! 			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
  
  		/*
! 		 * Initialize for loop below
  		 */
! 		ExecClearTuple(agg_row_slot);
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 		winstate->aggregatedupto = winstate->frameheadpos;
  	}
  
  	/*
! 	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
! 	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
! 	 * have to recalculate when the frame head moves or currentpos has
! 	 * advanced past the place we'd aggregated up to.  Check for these cases
! 	 * and if so, reuse the saved result values.
  	 */
! 	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
! 		for (i = 0; i < numaggs; i++)
  		{
- 			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
! 		return;
  	}
  
  	/*
--- 699,885 ----
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
  	 */
  
  	/*
  	 * First, update the frame head position.
+ 	 *
+ 	 * The frame head should never move backwards, and the code below wouldn't
+ 	 * cope if it did, so for safety we complain if it does.
  	 */
! 	update_frameheadpos(agg_winobj, temp_slot);
! 	if (winstate->frameheadpos < winstate->aggregatedbase)
! 		elog(ERROR, "frame moved backwards unexpectedly");
  
  	/*
! 	 * If the frame didn't change compared to the previous row, we can re-use
! 	 * the cached result. Since we don't know the current frame's end yet, we
! 	 * cannot check that the obvious way. But we can exploit that if the frame
! 	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
! 	 * row lies within the previous row's frame, the two frame's ends must
! 	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
! 	 * so we don't need to check for that explicitly here.
  	 */
! 	if (winstate->aggregatedbase == winstate->frameheadpos &&
! 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
+ 		return;
+ 	}
  
+ 	/* Initialize restart flags.
+ 	 *
+ 	 * We restart the aggregation
+ 	 *   - if we're processing the first row in the partition, or
+ 	 *   - if we the frame's head moved and we cannot use an inverse
+ 	 *     transition function, or
+ 	 *   - if the new frame doesn't overlap the old one
+ 	 *
+ 	 * Note that we don't strictly need to restart in the last case, but
+ 	 * if we're going to remove *all* rows from the aggregation anyway, a
+ 	 * restart surely is faster.
+ 	 */
+ 	for (i = 0; i < numaggs; i++)
+ 	{
+ 		peraggstate = &winstate->peragg[i];
+ 		if (winstate->currentpos == 0 ||
+ 			(winstate->aggregatedbase < winstate->frameheadpos &&
+ 			!peraggstate->use_invtransfn) ||
+ 			winstate->aggregatedupto <= winstate->frameheadpos)
+ 		{
+ 			peraggstate->restart = true;
+ 			numaggs_restart++;
+ 		}
+ 		else
+ 			peraggstate->restart = false;
+ 	}
+ 
+ 	/*
+ 	 * Attempt to update aggregatedbase to match the frame's head by
+ 	 * removing those inputs from the aggregations which fell off the top
+ 	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+ 	 * return false, in which case we restart that aggregate below.
+ 	 *
+ 	 * Aftwards, aggregatedbase equals frameheadpos.
+ 	 */
+ 	while(winstate->aggregatedbase < winstate->frameheadpos)
+ 	{
  		/*
! 		 * Fetch the tuple where the current aggregation started from.
! 		 * This should never fail as we should have been here before.
  		 */
! 		if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
! 								 temp_slot))
! 			elog(ERROR, "Unable to find tuple in tuplestore");
! 
! 		/* Set tuple context for evaluation of aggregate arguments */
! 		winstate->tmpcontext->ecxt_outertuple = temp_slot;
  
  		/*
! 		 * Perform the inverse transition for each aggregate function in
! 		 * the window, unless it has already been marked as needing a
! 		 * restart.
  		 */
! 		for (i = 0; i < numaggs; i++)
! 		{
! 			bool	ok;
! 
! 			peraggstate = &winstate->peragg[i];
! 			if (peraggstate->restart)
! 				continue;
! 
! 			wfuncno = peraggstate->wfuncno;
! 			ok = retreat_windowaggregate(winstate,
! 										 &winstate->perfunc[wfuncno],
! 										 peraggstate);
! 			if (!ok)
! 			{
! 				/* Inverse transition function has failed, must restart */
! 				peraggstate->restart = true;
! 				numaggs_restart++;
! 			}
! 		}
! 
! 		/* Reset per-input-tuple context after each tuple */
! 		ResetExprContext(winstate->tmpcontext);
! 
! 		/* And advance the aggregated-row state */
! 		winstate->aggregatedbase++;
! 		ExecClearTuple(temp_slot);
! 
! 		/* If no more retreatable aggregates are left, we stop early */
! 		if (numaggs_restart == numaggs)
! 		{
! 			winstate->aggregatedbase = winstate->frameheadpos;
! 			break;
! 		}
  	}
  
  	/*
! 	 * If we created a mark pointer for aggregates, keep it pushed up to
! 	 * frame head, so that tuplestore can discard unnecessary rows.
  	 */
! 	if (agg_winobj->markptr >= 0)
! 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
! 
! 	/*
! 	 * Then restart the aggregates which require it.
! 	 *
! 	 * We assume that aggregates using the shared context always restart
! 	 * if *any* aggregate restarts, and we may thus cleanup the shared
! 	 * aggcontext if that is the case. The private aggcontexts are reset
! 	 * by initialize_windowaggregate() if their owning aggregate restarts,
! 	 * otherwise we just pfree() the cached result.
! 	 */
! 	if (numaggs_restart > 0)
! 		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < numaggs; i++)
  	{
! 		peraggstate = &winstate->peragg[i];
! 		/* Aggregates using the shared ctx must restart if *any* agg does */
! 		Assert(!peraggstate->aggcontext_is_shared ||
! 			   !numaggs_restart || peraggstate->restart);
! 		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
! 			!peraggstate->resulttypeByVal)
! 		{
! 			pfree(DatumGetPointer(peraggstate->resultValue));
! 			peraggstate->resultValue = (Datum) 0;
! 			peraggstate->resultValueIsNull = true;
! 		}
! 		else if (peraggstate->restart)
  		{
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
! 	}
! 
! 	/*
! 	 * Non-restarted aggregates now contain the rows between aggregatedbase
! 	 * (i.e. frameheadpos) and aggregatedupto, and restarted aggregates
! 	 * contain no rows. If there are any restarted aggregates, we must thus
! 	 * begin aggregating anew at frameheadpos, otherwise we may simply
! 	 * continue at aggregatedupto. Since we possibly reset aggregatedupto, we
! 	 * must remember the old value to know how long to skip non-restarted
! 	 * aggregates. If we modify aggregatedupto, we must also clear
! 	 * agg_row_slot, per the loop invariant below.
! 	 */
! 	aggregatedupto_nonrestarted = winstate->aggregatedupto;
! 	if (numaggs_restart > 0 &&
! 		winstate->aggregatedupto != winstate->frameheadpos)
! 	{
! 		winstate->aggregatedupto = winstate->frameheadpos;
! 		ExecClearTuple(agg_row_slot);
  	}
  
  	/*
*************** eval_windowaggregates(WindowAggState *wi
*** 551,556 ****
--- 910,920 ----
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
+ 			/* Non-restarted aggs skip until aggregatedupto_nonrestarted */
+ 			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+ 				!peraggstate->restart)
+ 				continue;
+ 
  			wfuncno = peraggstate->wfuncno;
  			advance_windowaggregate(winstate,
  									&winstate->perfunc[wfuncno],
*************** eval_windowaggregates(WindowAggState *wi
*** 565,570 ****
--- 929,945 ----
  		ExecClearTuple(agg_row_slot);
  	}
  
+ 	/* The frame's end is not supposed to move backwards, ever */
+ 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+ 
+ 	/* Update statistics */
+ 	if (numaggs_restart > 0)
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - winstate->frameheadpos);
+ 	else
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - aggregatedupto_nonrestarted);
+ 
  	/*
  	 * finalize aggregates and fill result/isnull fields.
  	 */
*************** eval_windowaggregates(WindowAggState *wi
*** 589,616 ****
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal)
  		{
! 			/*
! 			 * clear old resultValue in order not to leak memory.  (Note: the
! 			 * new result can't possibly be the same datum as old resultValue,
! 			 * because we never passed it to the trans function.)
! 			 */
! 			if (!peraggstate->resultValueIsNull)
! 				pfree(DatumGetPointer(peraggstate->resultValue));
! 
! 			/*
! 			 * If pass-by-ref, copy it into our aggregate context.
! 			 */
! 			if (!*isnull)
! 			{
! 				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
! 				peraggstate->resultValue =
! 					datumCopy(*result,
! 							  peraggstate->resulttypeByVal,
! 							  peraggstate->resulttypeLen);
! 				MemoryContextSwitchTo(oldContext);
! 			}
  		}
  		else
  		{
--- 964,977 ----
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal && !*isnull)
  		{
! 			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
! 			peraggstate->resultValue =
! 				datumCopy(*result,
! 						  peraggstate->resulttypeByVal,
! 						  peraggstate->resulttypeLen);
! 			MemoryContextSwitchTo(oldContext);
  		}
  		else
  		{
*************** eval_windowfunction(WindowAggState *wins
*** 651,656 ****
--- 1012,1018 ----
  	/* Just in case, make all the regular argument slots be null */
  	memset(fcinfo.argnull, true, perfuncstate->numArguments);
  
+ 	winstate->calledaggno = -1;
  	*result = FunctionCallInvoke(&fcinfo);
  	*isnull = fcinfo.isnull;
  
*************** spool_tuples(WindowAggState *winstate, i
*** 794,800 ****
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kluge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
--- 1156,1162 ----
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kludge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
*************** release_partition(WindowAggState *winsta
*** 869,875 ****
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
--- 1231,1240 ----
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < winstate->numaggs; ++i)
! 		if (!winstate->peragg[i].aggcontext_is_shared)
! 			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1419,1425 ****
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno;
  	ListCell   *l;
  
  	/* check for unsupported flags */
--- 1784,1791 ----
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno,
! 				numaggs_invtrans;
  	ListCell   *l;
  
  	/* check for unsupported flags */
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1450,1457 ****
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived context for aggregate trans values etc */
! 	winstate->aggcontext =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
--- 1816,1825 ----
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived contexts for aggregate trans values etc
! 	 * Note that invertible aggregates use their own private context
! 	 */
! 	winstate->aggcontext_shared =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1535,1540 ****
--- 1903,1909 ----
  
  	wfuncno = -1;
  	aggno = -1;
+ 	numaggs_invtrans = 0;
  	foreach(l, winstate->funcs)
  	{
  		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1603,1608 ****
--- 1972,1979 ----
  			peraggstate = &winstate->peragg[aggno];
  			initialize_peragg(winstate, wfunc, peraggstate);
  			peraggstate->wfuncno = wfuncno;
+ 			if (peraggstate->use_invtransfn)
+ 				numaggs_invtrans++;
  		}
  		else
  		{
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1618,1623 ****
--- 1989,1995 ----
  	/* Update numfuncs, numaggs to match number of unique functions found */
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
+ 	winstate->numaggs_invtrans = numaggs_invtrans;
  
  	/* Set up WindowObject for aggregates, if needed */
  	if (winstate->numaggs > 0)
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1646,1651 ****
--- 2018,2029 ----
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	/* initialize temporary data */
+ 	winstate->calledaggno = -1;
+ 
+ 	/* initialize statistics */
+ 	winstate->aggfwdtrans = 0;
+ 
  	return winstate;
  }
  
*************** void
*** 1657,1668 ****
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
  
  	release_partition(node);
  
- 	pfree(node->perfunc);
- 	pfree(node->peragg);
- 
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
--- 2035,2044 ----
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
+ 	int			i;
  
  	release_partition(node);
  
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
*************** ExecEndWindowAgg(WindowAggState *node)
*** 1677,1683 ****
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
--- 2053,2065 ----
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext_shared);
! 	for(i = 0; i < node->numaggs; i++)
! 		if (!node->peragg[i].aggcontext_is_shared)
! 			MemoryContextDelete(node->peragg[i].aggcontext);
! 
! 	pfree(node->perfunc);
! 	pfree(node->peragg);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
*************** initialize_peragg(WindowAggState *winsta
*** 1735,1742 ****
--- 2117,2126 ----
  	Oid			aggtranstype;
  	AclResult	aclresult;
  	Oid			transfn_oid,
+ 				invtransfn_oid,
  				finalfn_oid;
  	Expr	   *transfnexpr,
+ 			   *invtransfnexpr,
  			   *finalfnexpr;
  	Datum		textInitVal;
  	int			i;
*************** initialize_peragg(WindowAggState *winsta
*** 1762,1767 ****
--- 2146,2152 ----
  	 */
  
  	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+ 	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
  	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
  	/* Check that aggregate owner has permission to call component fns */
*************** initialize_peragg(WindowAggState *winsta
*** 1783,1788 ****
--- 2168,2184 ----
  			aclcheck_error(aclresult, ACL_KIND_PROC,
  						   get_func_name(transfn_oid));
  		InvokeFunctionExecuteHook(transfn_oid);
+ 
+ 		if (OidIsValid(invtransfn_oid))
+ 		{
+ 			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+ 										 ACL_EXECUTE);
+ 			if (aclresult != ACLCHECK_OK)
+ 				aclcheck_error(aclresult, ACL_KIND_PROC,
+ 				get_func_name(invtransfn_oid));
+ 			InvokeFunctionExecuteHook(invtransfn_oid);
+ 		}
+ 
  		if (OidIsValid(finalfn_oid))
  		{
  			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
*************** initialize_peragg(WindowAggState *winsta
*** 1810,1822 ****
--- 2206,2226 ----
  							wfunc->wintype,
  							wfunc->inputcollid,
  							transfn_oid,
+ 							invtransfn_oid,
  							finalfn_oid,
  							&transfnexpr,
+ 							&invtransfnexpr,
  							&finalfnexpr);
  
  	fmgr_info(transfn_oid, &peraggstate->transfn);
  	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+ 		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+ 	}
+ 
  	if (OidIsValid(finalfn_oid))
  	{
  		fmgr_info(finalfn_oid, &peraggstate->finalfn);
*************** initialize_peragg(WindowAggState *winsta
*** 1860,1867 ****
--- 2264,2337 ----
  							wfunc->winfnoid)));
  	}
  
+ 	/*
+ 	 * Allowing only the forward transition function to be strict would
+ 	 * require handling more special cases in advance_windowaggregate() and
+ 	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 	 * that if the forward transition function is strict that the inverse
+ 	 * transition function is also strict. This should have been checked at
+ 	 * the aggregate function's definition time, but it's better to be safe...
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 				errmsg("strictness of forward and inverse transition functions must match")));
+ 	}
+ 
  	ReleaseSysCache(aggTuple);
  
+ 	/*
+ 	 * We can use the inverse transition function only if the aggregate's
+ 	 * arguments don't contain calls to volatile functions. Otherwise,
+ 	 * the difference between restarting and not restarting the aggregation
+ 	 * would be user-visible. Note that this check also covers the case where
+ 	 * the FILTER's WHERE clause contains a volatile function. If the frame
+ 	 * head cannot move, we won't ever need the inverse transition function,
+ 	 * so we also mark as "don't use" in that case.
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		!contain_volatile_functions((Node *) wfunc) &&
+ 		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+ 	{
+ 		peraggstate->use_invtransfn = true;
+ 	}
+ 	else
+ 	{
+ 		peraggstate->use_invtransfn = false;
+ 	}
+ 
+ 	/*
+ 	 * Invertible aggregates use their own aggcontext.
+ 	 *
+ 	 * This is necessary because they might all restart at different times,
+ 	 * so we might never be able to reset the shared context otherwise. We
+ 	 * can't make it the aggregate's responsibility to clean up after
+ 	 * themselves, because strict aggregates must be restarted whenever we
+ 	 * remove their last non-NULL input, which the aggregate won't be aware
+ 	 * is happening. Also, just pfree()ing the transValue upon restarting
+ 	 * wouldn't help, since we'd miss any indirectly referenced data. We
+ 	 * could, in theory, declare that aggregates with a state type other then
+ 	 * "internal" musn't allocate anything in the aggcontext themselves, that
+ 	 * non-strict aggregates with state type internal must clean up after
+ 	 * themselves when their inverse transfer function returns NULL, and then
+ 	 * only use private aggcontexts for strict aggregates with state type
+ 	 * internal. But that'd be a rather grotty set of requirements.
+ 	 */
+ 	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+ 	if (!peraggstate->aggcontext_is_shared)
+ 	{
+ 		peraggstate->aggcontext =
+ 			AllocSetContextCreate(CurrentMemoryContext,
+ 								  "WindowAgg_AggregatePrivate",
+ 								  ALLOCSET_DEFAULT_MINSIZE,
+ 								  ALLOCSET_DEFAULT_INITSIZE,
+ 								  ALLOCSET_DEFAULT_MAXSIZE);
+ 	}
+ 	else
+ 		peraggstate->aggcontext = winstate->aggcontext_shared;
+ 
  	return peraggstate;
  }
  
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..ef84e4c 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** resolve_aggregate_transtype(Oid aggfunci
*** 1187,1197 ****
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid and finalfn_oid identify the funcs to be called; the latter
!  * may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr and
!  * *finalfnexpr.  The latter is set to NULL if there's no finalfn.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
--- 1187,1199 ----
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid, invtransfn_oid and finalfn_oid identify the funcs to be
!  * called; the latter two may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr,
!  * *invtransfnexpr and *finalfnexpr. If there is invtransfn or finalfn, the
!  * respective pointers are set to NULL. Since use of the invtransfn is
!  * optional, NULL may be passed for invtransfnexpr.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1203,1210 ****
--- 1205,1214 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr)
  {
  	Param	   *argp;
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1249,1254 ****
--- 1253,1270 ----
  	fexpr->funcvariadic = agg_variadic;
  	*transfnexpr = (Expr *) fexpr;
  
+ 	if (OidIsValid(invtransfn_oid) && invtransfnexpr != NULL)
+ 	{
+ 		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+ 												agg_result_type,
+ 												args,
+ 												InvalidOid,
+ 												agg_input_collation,
+ 												COERCE_EXPLICIT_CALL);
+ 	}
+ 	else if (invtransfnexpr != NULL)
+ 		*invtransfnexpr	= NULL;
+ 
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
  	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2ce8e6d..21a7347 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11512,11517 ****
--- 11512,11518 ----
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
+ 	int			i_agginvtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
  	int			i_hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11520,11525 ****
--- 11521,11527 ----
  	int			i_agginitval;
  	int			i_convertok;
  	const char *aggtransfn;
+ 	const char *agginvtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
  	bool		hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11544,11550 ****
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
--- 11546,11552 ----
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11560,11565 ****
--- 11562,11568 ----
  	else if (fout->remoteVersion >= 80400)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11575,11580 ****
--- 11578,11584 ----
  	else if (fout->remoteVersion >= 80100)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11588,11593 ****
--- 11592,11598 ----
  	else if (fout->remoteVersion >= 70300)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11600,11606 ****
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
--- 11605,11613 ----
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
! 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
! 						  "aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11613,11618 ****
--- 11620,11626 ----
  	else
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn1 AS aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, "
  						  "(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
  						  "0 AS aggsortop, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11627,11632 ****
--- 11635,11641 ----
  	res = ExecuteSqlQueryForSingleRow(fout, query->data);
  
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
+ 	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
  	i_hypothetical = PQfnumber(res, "hypothetical");
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11636,11641 ****
--- 11645,11651 ----
  	i_convertok = PQfnumber(res, "convertok");
  
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ 	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11694,11699 ****
--- 11704,11715 ----
  						  fmtId(aggtranstype));
  	}
  
+ 	if (strcmp(agginvtransfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    INVSFUNC = %s",
+ 						  agginvtransfn);
+ 	}
+ 
  	if (strcmp(aggtransspace, "0") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    SSPACE = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index f189998..3412661 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 46,51 ****
--- 46,52 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		agginvtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 69,85 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_agginvtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,277 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 103,279 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	-	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
*************** extern Oid AggregateCreate(const char *a
*** 289,294 ****
--- 291,297 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index aed81cd..c4d4864 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** extern void **find_rendezvous_variable(c
*** 645,652 ****
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly with nodeAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
--- 645,654 ----
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
!  * which is in execute/nodeWindowAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly neither nodeAgg
!  * nor nodeWindowAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..ff558c6 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WindowAggState
*** 1738,1743 ****
--- 1738,1744 ----
  	List	   *funcs;			/* all WindowFunc nodes in targetlist */
  	int			numfuncs;		/* total number of window functions */
  	int			numaggs;		/* number that are plain aggregates */
+ 	int			numaggs_invtrans;	/* number that are invertible aggregates */
  
  	WindowStatePerFunc perfunc; /* per-window-function information */
  	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
*************** typedef struct WindowAggState
*** 1762,1768 ****
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext;	/* context for each aggregate data */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
--- 1763,1769 ----
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext_shared;	/* shared context for agg states */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
*************** typedef struct WindowAggState
*** 1780,1789 ****
--- 1781,1796 ----
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
  
+ 	/* temporary data */
+ 	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+ 
  	/* temporary slots for tuples fetched back from tuplestore */
  	TupleTableSlot *agg_row_slot;
  	TupleTableSlot *temp_slot_1;
  	TupleTableSlot *temp_slot_2;
+ 
+ 	/* Statistics */
+ 	double	aggfwdtrans;	/* number of forward transitions */
  } WindowAggState;
  
  /* ----------------
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void build_aggregate_fnexprs(Oid 
*** 39,46 ****
--- 39,48 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr);
  
  #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..080cbd2 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select least_agg(variadic array[q1,q2]) 
*** 1580,1582 ****
--- 1580,1606 ----
   -4567890123456789
  (1 row)
  
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..1a52423 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** alter aggregate my_rank(VARIADIC "any" O
*** 90,92 ****
--- 90,129 ----
   public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
  (2 rows)
  
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ ERROR:  strictness of forward and inverse transition functions must match
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ ERROR:  function intminus(double precision, double precision) does not exist
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
+ ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..8af3d23 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT nth_value_def(ten) OVER (PARTITIO
*** 1071,1073 ****
--- 1071,1307 ----
               1 |   3 |    3
  (10 rows)
  
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+ ----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+  1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+  1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+  1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+  1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+  1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+  2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+  3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+ (9 rows)
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+ ----------+--------------+-------------------+-------------+------------------
+  1,1:NULL | +NULL        | I+NULL            |             | I
+  1,2:-    | +NULL        | I+NULL            |             | I
+  1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+  1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+  1,5:-    |              | I                 |             | I
+  1,6:-    |              | I                 |             | I
+  2,1:-    |              | I                 |             | I
+  2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+  3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+ (9 rows)
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+  logging_agg_strict 
+ --------------------
+  a
+  b
+  c
+ (3 rows)
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+  eq1 | eq2 | eq3 
+ -----+-----+-----
+  t   | t   | t
+ (1 row)
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..34f7004 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** drop view aggordview1;
*** 590,592 ****
--- 590,609 ----
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..130489c 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** alter aggregate my_rank(VARIADIC "any" O
*** 101,103 ****
--- 101,144 ----
    rename to test_rank;
  
  \da test_*
+ 
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ 
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ 
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ 
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..117bd6c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** SELECT nth_value_def(n := 2, val := ten)
*** 284,286 ****
--- 284,489 ----
  
  SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
    FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+ 
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ 
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ 
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ 
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ 
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ 
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ 
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ 
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
invtrans_strictstrict_collecting_1a1ca1d.patchapplication/octet-stream; name=invtrans_strictstrict_collecting_1a1ca1d.patchDownload
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index c62e3fb..5a3a31d 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** create_singleton_array(FunctionCallInfo 
*** 471,477 ****
  
  
  /*
!  * ARRAY_AGG aggregate function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
--- 471,477 ----
  
  
  /*
!  * ARRAY_AGG aggregate transition function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 508,513 ****
--- 508,537 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * ARRAY_AGG aggregate inverse transition function
+  */
+ Datum
+ array_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since NULLs
+ 	 * need to be removed just like any other value.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "array_agg_invtransfn called with NULL state");
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "array_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	shiftArrayResult(state, 1);
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..2dd7ecb 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** accumArrayResult(ArrayBuildState *astate
*** 4587,4592 ****
--- 4587,4593 ----
  		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
  		astate->mcontext = arr_context;
  		astate->alen = 64;		/* arbitrary starting array size */
+ 		astate->offset = 0;
  		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
  		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
  		astate->nelems = 0;
*************** accumArrayResult(ArrayBuildState *astate
*** 4600,4606 ****
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/* enlarge dvalues[]/dnulls[] if needed */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
--- 4601,4612 ----
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/*
! 		 * If the buffers are filled completely (offset must be zero then),
! 		 * we double their size. If they aren't, but the values extend to the
! 		 * end of the buffers, we reclaim wasted space at the beginning by
! 		 * moving the values to the front of the buffers.
! 		 */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
*************** accumArrayResult(ArrayBuildState *astate
*** 4609,4614 ****
--- 4615,4629 ----
  			astate->dnulls = (bool *)
  				repalloc(astate->dnulls, astate->alen * sizeof(bool));
  		}
+ 		else if (astate->offset + astate->nelems >= astate->alen)
+ 		{
+ 			memmove(astate->dvalues, astate->dvalues + astate->offset,
+ 					astate->alen * sizeof(Datum));
+ 			memmove(astate->dnulls, astate->dnulls + astate->offset,
+ 					astate->alen * sizeof(bool));
+ 			astate->offset = 0;
+ 		}
+ 		Assert(astate->offset + astate->nelems < astate->alen);
  	}
  
  	/*
*************** accumArrayResult(ArrayBuildState *astate
*** 4627,4634 ****
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->nelems] = dvalue;
! 	astate->dnulls[astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
--- 4642,4649 ----
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->offset + astate->nelems] = dvalue;
! 	astate->dnulls[astate->offset + astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
*************** accumArrayResult(ArrayBuildState *astate
*** 4637,4642 ****
--- 4652,4698 ----
  }
  
  /*
+  * shiftArrayResult - shift leading Datums out of an array result
+  *
+  *	astate is working state
+  *	count is the number of leading Datums to shift out
+  *
+  * If count is equal to or larger than the number of relements, the array
+  * result is empty afterwards. If astate is NULL, nothing is done.
+  */
+ void
+ shiftArrayResult(ArrayBuildState *astate, int count)
+ {
+ 	int		i;
+ 
+ 	if (astate == NULL)
+ 		return;
+ 
+ 	/* Limit shift count to number of elements for safety */
+ 	count = Min(count, astate->nelems);
+ 
+ 	/* For pass-by-ref types, free values we shift out */
+ 	if (!astate->typbyval) {
+ 		for(i = astate->offset; i < astate->offset + count; ++i) {
+ 			if (astate->dnulls[i])
+ 				continue;
+ 
+ 			pfree(DatumGetPointer(astate->dvalues[i]));
+ 
+ 			/* For cleanliness' sake */
+ 			astate->dnulls[i] = false;
+ 			astate->dvalues[i] = 0;
+ 		}
+ 
+ 	}
+ 
+ 	/* Update state */
+ 	astate->nelems -= count;
+ 	astate->offset += count;
+ }
+ 
+ 
+ /*
   * makeArrayResult - produce 1-D final result of accumArrayResult
   *
   *	astate is working state (not NULL)
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4679,4686 ****
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues,
! 								astate->dnulls,
  								ndims,
  								dims,
  								lbs,
--- 4735,4742 ----
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues + astate->offset,
! 								astate->dnulls + astate->offset,
  								ndims,
  								dims,
  								lbs,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..768ee49 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** typedef struct
*** 50,55 ****
--- 50,63 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct StringAggState
+ {
+ 	StringInfoData 	string; 	/* Contents */
+ 	int 			offset;		/* Offset into stringinfo's data */
+ 	int 			delimLen;	/* Delim length, -1 initially, -2 if multiple */
+ 	int64			notNullCount;/* Number of non-NULL inputs */
+ } StringAggState;
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
*************** static void appendStringInfoText(StringI
*** 78,84 ****
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
--- 86,95 ----
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo);
! static void prepareAppendStringAggState(StringAggState *state,
! 										int delimLen, int valueLen);
! static bool removeFromStringAggState(StringAggState *state, int valueLen);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
*************** static void text_format_string_conversio
*** 92,97 ****
--- 103,226 ----
  static void text_format_append_string(StringInfo buf, const char *str,
  						  int flags, int width);
  
+ /*****************************************************************************
+  *	 SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA)			 *
+  *****************************************************************************/
+ 
+ /*
+  * subroutine to initialize state
+  */
+ static StringAggState*
+ makeStringAggState(FunctionCallInfo fcinfo)
+ {
+ 	StringAggState*	state;
+ 	MemoryContext aggcontext;
+ 	MemoryContext oldcontext;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 	}
+ 
+ 	/*
+ 	 * Create state in aggregate context.  It'll stay there across subsequent
+ 	 * calls.
+ 	 */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = (StringAggState *) palloc(sizeof(StringAggState));
+ 	initStringInfo(&state->string);
+ 	state->offset = 0;
+ 	state->delimLen = -1;
+ 	state->notNullCount = 0;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * Prepare state for appending a value and a delimiter with specified lengths.
+  * pass -1 for delimLen if no delimiter will be added
+  */
+ void
+ prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen)
+ {
+ 	/*
+ 	 * Reclaim wasted space
+ 	 *
+ 	 * We move the contents to the left if the current contents fit into the
+ 	 * wasted space, i.e. if we waste more than we store. The limit is
+ 	 * somewhat arbitrary, but it's the smallest one that allows
+ 	 * memcpy to be used, because the source and destination don't overlap.
+ 	 * Note that we must check for <, not <=, because we include the trailing
+ 	 * '\0' in the copy.
+ 	 */
+ 	if (state->string.len - state->offset < state->offset)
+ 	{
+ 		state->string.len -= state->offset;
+ 		memcpy(state->string.data, state->string.data + state->offset,
+ 			   state->string.len + 1);
+ 		state->offset = 0;
+ 	}
+ 
+ 	/*
+ 	 * Enlarge StringInfo
+ 	 *
+ 	 * Not strictly necessary, but avoids potentially resizing twice when
+ 	 * the actual append... calls are done by the caller
+ 	 */
+ 	enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen);
+ 
+ 
+ 	/* Track delimiter length */
+ 	if (delimLen == -1)
+ 		{} /* Not specified, don't update */
+ 	else if (state->delimLen == -1)
+ 		state->delimLen = delimLen;
+ 	else if (state->delimLen != delimLen)
+ 		state->delimLen = -2;
+ }
+ 
+ /*
+  * Remove value with given length and the delimiter that follows
+  *
+  * Returns false if removal was impossible because delimiters varied
+  */
+ bool
+ removeFromStringAggState(StringAggState *state, int valueLen)
+ {
+ 	/* Remove the string */
+ 	state->offset += valueLen;
+ 
+ 	/*
+ 	 * Remove delimiter if necessary.
+ 	 *
+ 	 * The delimiter we need to remove isn't the delimiter we were passed, but
+ 	 * rather the delimiter passed when adding the input *after* this one. We
+ 	 * thus need the delimiter length to be all the same to be able to proceed.
+ 	 * If we're removing the last string, there will be no delimiter following
+ 	 * it. In that case, we may reset delimLen to its initial value.
+ 	 */
+ 	if (state->delimLen == -2)
+ 		return false;
+ 	if (state->offset < state->string.len)
+ 	{
+ 		Assert(state->delimLen >= 0);
+ 		state->offset += state->delimLen;
+ 	}
+ 	else
+ 		state->delimLen = -1;
+ 
+ 	/* Don't crash if we're ever asked to remove more than was added */
+ 	if (state->offset > state->string.len)
+ 	{
+ 		state->offset = state->string.len;
+ 		elog(ERROR, "tried to remove more data than was aggregated");
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /*****************************************************************************
   *	 CONVERSION ROUTINES EXPORTED FOR USE BY C CODE							 *
*************** byteasend(PG_FUNCTION_ARGS)
*** 408,435 ****
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		bytea	   *value = PG_GETARG_BYTEA_PP(1);
  
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 		{
! 			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim));
! 		}
  
! 		appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value));
  	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
--- 537,589 ----
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	bytea		   *value,
! 				   *delim;
! 	int				valueLen,
! 					delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
  
! 	value = PG_GETARG_BYTEA_PP(1);
! 	valueLen = VARSIZE_ANY_EXHDR(value);
! 	state->notNullCount++;
  
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
! 	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_BYTEA_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
! 	}
! 	else
! 	{
! 		/* Delimiter is NULL, treat as zero-length string */
! 		delim = NULL;
! 		delimLen = 0;
  	}
  
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, valueLen);
+ 	if (delim)
+ 		appendBinaryStringInfo(&state->string, VARDATA_ANY(delim),
+ 							   VARSIZE_ANY_EXHDR(delim));
+ 	appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen);
+ 
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 438,459 ****
  }
  
  Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
  	{
  		bytea	   *result;
  
! 		result = (bytea *) palloc(state->len + VARHDRSZ);
! 		SET_VARSIZE(result, state->len + VARHDRSZ);
! 		memcpy(VARDATA(result), state->data, state->len);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
--- 592,661 ----
  }
  
  Datum
+ bytea_string_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	int				valueLen;
+ 	StringAggState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since it
+ 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "string_agg_invtransfn called with NULL state");
+ 	else if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (StringAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* We append nothing if the string is NULL, so skip here as well */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_POINTER(state);
+ 
+ 	/* No need to de-toast value, need only the length */
+ 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
+ 	Assert(state->notNullCount >= 1);
+ 	state->notNullCount--;
+ 
+ 	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
+ 	 * The transition type for string_agg() is declared to be "internal",
+ 	 * which is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	if (removeFromStringAggState(state, valueLen))
+ 		PG_RETURN_POINTER(state);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
  	{
+ 		int			resultLen = state->string.len  - state->offset;
  		bytea	   *result;
  
! 		result = (bytea *) palloc(resultLen + VARHDRSZ);
! 		SET_VARSIZE(result, resultLen + VARHDRSZ);
! 		memcpy(VARDATA(result), state->string.data + state->offset, resultLen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3733,3801 ****
   * the associated value.
   */
  
! /* subroutine to initialize state */
! static StringInfo
! makeStringAggState(FunctionCallInfo fcinfo)
  {
! 	StringInfo	state;
! 	MemoryContext aggcontext;
! 	MemoryContext oldcontext;
  
! 	if (!AggCheckCallContext(fcinfo, &aggcontext))
  	{
! 		/* cannot be called directly because of internal-type argument */
! 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
  	}
  
  	/*
! 	 * Create state in aggregate context.  It'll stay there across subsequent
! 	 * calls.
  	 */
! 	oldcontext = MemoryContextSwitchTo(aggcontext);
! 	state = makeStringInfo();
! 	MemoryContextSwitchTo(oldcontext);
! 
! 	return state;
  }
  
  Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 			appendStringInfoText(state, PG_GETARG_TEXT_PP(2));	/* delimiter */
  
! 		appendStringInfoText(state, PG_GETARG_TEXT_PP(1));		/* value */
! 	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len));
  	else
  		PG_RETURN_NULL();
  }
--- 3935,4054 ----
   * the associated value.
   */
  
! Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	text		   *value,
! 				   *delim;
! 	int				delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
! 
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
! 
! 	value = PG_GETARG_TEXT_PP(1);
! 	state->notNullCount++;
! 
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
  	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_TEXT_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
  	}
+ 	else
+ 	{
+ 		/* Delimiter is NULL, treat as zero-length string */
+ 		delim = NULL;
+ 		delimLen = 0;
+ 	}
+ 
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value));
+ 	if (delim)
+ 		appendStringInfoText(&state->string, delim);
+ 	appendStringInfoText(&state->string, value);
  
  	/*
! 	 * The transition type for string_agg() is declared to be "internal",
! 	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
! string_agg_invtransfn(PG_FUNCTION_ARGS)
  {
! 	int				valueLen;
! 	StringAggState *state;
  
! 	/*
! 	 * Shouldn't happen, but we cannot mark this function strict, since it
! 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
! 	 * Must also prevent direct calls because of the "interal" argument
! 	 */
! 	if (PG_ARGISNULL(0))
! 		elog(ERROR, "string_agg_invtransfn called with NULL state");
! 	else if (!AggCheckCallContext(fcinfo, NULL))
! 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
  
! 	state = (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* We append nothing if the string is NULL, so skip here as well */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* No need to de-toast value, need only the length */
! 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
! 	Assert(state->notNullCount >= 1);
! 	state->notNullCount--;
  
  	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	if (removeFromStringAggState(state, valueLen))
! 		PG_RETURN_POINTER(state);
! 	else
! 		PG_RETURN_NULL();
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset,
! 												  state->string.len - state->offset));
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..403bb50 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-	-					0	
*** 250,262 ****
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
--- 250,262 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_invtransfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_invtransfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_invtransfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 11c1e1a..eb3d0b6 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 875,880 ****
--- 875,882 ----
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3461 (  array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2463,2474 ****
--- 2465,2480 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3462 (  string_agg_invtransfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3538 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into a string");
  DATA(insert OID = 3543 (  bytea_string_agg_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3463 (  bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3544 (  bytea_string_agg_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..9a6fc39 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 83,88 ****
--- 83,89 ----
  	Datum	   *dvalues;		/* array of accumulated Datums */
  	bool	   *dnulls;			/* array of is-null flags for Datums */
  	int			alen;			/* allocated length of above arrays */
+ 	int			offset;			/* offset of first element in above arrays */
  	int			nelems;			/* number of valid entries in above arrays */
  	Oid			element_type;	/* data type of the Datums */
  	int16		typlen;			/* needed info about datatype */
*************** extern ArrayBuildState *accumArrayResult
*** 255,260 ****
--- 256,262 ----
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
  				 MemoryContext rcontext);
+ extern void shiftArrayResult(ArrayBuildState *astate, int count);
  extern Datum makeArrayResult(ArrayBuildState *astate,
  				MemoryContext rcontext);
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
*************** extern ArrayType *create_singleton_array
*** 290,295 ****
--- 292,298 ----
  					   int ndims);
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
  
  /*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..7d8e749 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 812,820 ****
--- 812,822 ----
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
  extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS);
  
  extern Datum text_concat(PG_FUNCTION_ARGS);
  extern Datum text_concat_ws(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 8af3d23..b33f241 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1305,1307 ****
--- 1305,1351 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
+      row     | str | str_del | str_vardel |      bin       |      bin_del       |      bin_vardel      |       ary        
+ -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------
+  1:1,0100,-  | -   | -       | -          | -              | -                  | -                    | 
+  2:-,0200,2  | 1   | 1       | 1          | \x0100         | \x0100             | \x0100               | {NULL}
+  3:3,----,3  | 1   | 1       | 1          | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2}
+  4:-,0400,4  | 13  | 1,3     | 13         | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2,3}
+  5:5,----,-  | 3   | 3       | 3          | \x02000400     | \x0200000400       | \x0200000400         | {2,3,4}
+  6:6,0600,6  | 35  | 3,5     | 3||5       | \x0400         | \x0400             | \x0400               | {3,4,NULL}
+  7:7,0700,-  | 56  | 5,6     | 56         | \x04000600     | \x0400000600       | \x04000600           | {4,NULL,6}
+  8:8,0800,8  | 567 | 5,6,7   | 56|7       | \x06000700     | \x0600000700       | \x0600000700         | {NULL,6,NULL}
+  9:-,----,-  | 678 | 6,7,8   | 6|7||8     | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8}
+  10:-,----,- | 78  | 7,8     | 7||8       | \x07000800     | \x0700000800       | \x070000000800       | {NULL,8,NULL}
+  11:-,----,- | 8   | 8       | 8          | \x0800         | \x0800             | \x0800               | {8,NULL,NULL}
+  12:-,----,- | -   | -       | -          | -              | -                  | -                    | {NULL,NULL,NULL}
+ (12 rows)
+ 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 117bd6c..9bb911c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 487,489 ****
--- 487,518 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ 
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
invtrans_strictstrict_docs_0cb944.patchapplication/octet-stream; name=invtrans_strictstrict_docs_0cb944.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2230c93..44d547f 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 376,381 ****
--- 376,387 ----
        <entry>Transition function</entry>
       </row>
       <row>
+       <entry><structfield>agginvtransfn</structfield></entry>
+       <entry><type>regproc</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>Inverse transition function</entry>
+      </row>
+      <row>
        <entry><structfield>aggfinalfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c0a75de..51c493d 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT xmlagg(x) FROM (SELECT x FROM tes
*** 12885,12890 ****
--- 12885,13012 ----
     <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
     Other frame specifications can be used to obtain other effects.
    </para>
+   
+   <para>
+     Depending on the aggregate function, aggregating over frames starting
+     at a row relative to the current row can be drastically less efficient
+     than aggregating over frames aligned to the start of the partition. The
+     frame starts at a row relative to the current row if <literal>ORDER
+     BY</literal> is used together with any frame start clause other than
+     <literal>UNBOUNDED PRECEDING</literal> (which is the default). Then,
+     aggregates without a suitable <quote>inverse transition function
+     </quote> (see <xref linkend="SQL-CREATEAGGREGATE"> for details) will be
+     computed for each frame from scratch, instead of re-using the previous
+     frame's result, causing <emphasis>quadratic growth</emphasis> of the
+     execution time as the number of rows per partition increases. The table
+     <xref linkend="functions-aggregate-indframe"> list the built-in aggregate
+     functions affected by this. Note that quadratic growth is only a problem
+     if partitions contain many rows - for partitions with only a few rows,
+     even inefficient aggregates are unlikely to cause problems.
+   </para>
+ 
+   <table id="functions-aggregate-indframe">
+    <title>
+      Aggregate Function Behaviour for frames not starting at
+      <literal>UNBOUNDED PRECEDING</literal>.
+    </title>
+ 
+    <tgroup cols="3">
+      
+     <thead>
+      <row>
+       <entry>Aggregate Function</entry>
+       <entry>Input Type</entry>
+       <entry>Computed From Scratch</entry>
+      </row>
+     </thead>
+     
+     <tbody>
+       
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>float4</type>
+        or
+        <type>float8</type>
+       </entry>
+       <entry>always, to avoid error accumulation</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>numeric</type>
+       </entry>
+       <entry>if the maximum number of decimal digits within the inputs changes</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>min</function>
+       </entry>
+       <entry>
+        any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were minimal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>max</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were maximal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>bit_and</function>,<function>bit_or</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>string_agg</function>
+       </entry>
+       <entry>
+        <type>text</type> or
+        <type>bytea</type> or
+       </entry>
+       <entry>if the delimiter lengths vary</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>xmlagg</function>
+       </entry>
+       <entry>
+        <type>xml</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>json_agg</function>
+       </entry>
+       <entry>
+        <type>json</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
  
    <note>
     <para>
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index d15fcba..aaa09a7 100644
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
*************** CREATE AGGREGATE <replaceable class="par
*** 25,30 ****
--- 25,31 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 47,52 ****
--- 48,54 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 84,98 ****
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
!    a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
--- 86,103 ----
    </para>
  
    <para>
!    An aggregate function is made from one, two or three ordinary
     functions:
!    a (forward) state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
+    an optional inverse state transition function
+    <replaceable class="PARAMETER">invfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
+ <replaceable class="PARAMETER">invfunc</replaceable>( internal-state, data-values ) ---> internal-state-without-data-values
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 102,113 ****
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.  After all the rows have been processed,
!    the final function is invoked once to calculate the aggregate's return
!    value.  If there is no final function then the ending state value
!    is returned as-is.
    </para>
  
    <para>
--- 107,134 ----
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the forward state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.
!    If the aggregate is computed over a sliding frame, i.e. if it is used as a
!    <firstterm>window function</firstterm>, the inverse transition function is
!    used to undo the effect of a previous invocation of the forward transition
!    function once argument value(s) fall out of the sliding frame.
!    Conceptually, the forward transition functions thus adds some input
!    value(s) to the state, and the inverse transition functions removes them
!    again. Values are, if they are removed, always removed in the same order
!    they were added, without gaps. Whenever the inverse transition function is
!    invoked, it will thus receive the earliest added but not yet removed
!    argument value(s). If no inverse transition function is supplied, the
!    aggregate can still be used to aggregate over sliding frames, but with
!    reduced efficiency. <productname>PostgreSQL</productname> will then
!    recompute the whole aggregation whenever the start of the frame moves. To
!    calculate the aggregate's return value, the final function is invoked on
!    the ending state value. If there is no final function then ending state
!    value is returned as-is. Either way, the result is assumed to reflect the
!    aggregation of all values added but not yet removed from the state value.
!    Note that if the aggregate is used as a window function, the aggregation
!    may be continued after the final function has been called.
    </para>
  
    <para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 127,135 ****
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values.
!    This is handy for implementing aggregates like <function>max</function>.
!    Note that this behavior is only available when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
--- 148,164 ----
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values. Should inputs later need to be removed again, the
!    inverse transition function (if present) is used as long as some non-null
!    inputs remain part of the state value. In particular, even if the initial
!    state value is null, the inverse transition function might be used to remove
!    the first non-null input, even though that input was never passed to the
!    forward transition function, but instead just replaced the initial state!
!    The last non-null input, however, is not removed by invoking the inverse
!    transition function, but instead the state is simply reset to its initial
!    value. This is handy for implementing aggregates like <function>max</function>.
!    Note that turning the first non-null input into the initial state is only
!    possible when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
*************** CREATE AGGREGATE <replaceable class="PAR
*** 138,147 ****
    </para>
  
    <para>
!    If the state transition function is not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs
!    and null state values for itself.  This allows the aggregate
!    author to have full control over the aggregate's handling of null values.
    </para>
  
    <para>
--- 167,206 ----
    </para>
  
    <para>
!     If the forward transition function is not declared <quote>strict</quote>,
!     but the inverse transition function is, null input values are still not
!     passed to either the forward or the inverse transition function. The
!     system then behaves as it would for a strict forward transition function,
!     except that initial value null isn't handled specially, i.e. isn't
!     replaced by the first argument of the first non-null input. Instead the
!     forward transition function is simply invoked with a null state and
!     non-null argument values. So in effect, in this case the forward
!     transition function is being treated as non-strict in the state argument
!     but strict in all argument values.
!   </para> 
! 
!   <para>
!    If the state transition function is not <quote>strict</quote>, and either
!    no inverse was provided or is also not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs and null
!    state values for itself. The same goes for the inverse transition function.
!    This allows the aggregate author to have full control over the
!    aggregate's handling of null argument values.
!   </para>
!   
!   <para>
!     The inverse transition function can signal by returning null that it is
!     unable to remove a particular input value from a particular state.
!     <productname>PostgreSQL</productname> will then act as if no inverse
!     transition function had been supplied, i.e. it will recompute the whole
!     aggregation, starting with the first argument value that it would not have
!     removed. That allows aggregates like <function>max</function> to still
!     avoid redoing the whole aggregation in <emphasis>some</emphasis> cases,
!     without paying the overhead of tracking enough state to be able to avoid
!     them in <emphasis>all</emphasis> cases. This demands, however, that
!     null isn't used as a valid state value, except as the initial state. If
!     an aggregate provides an inverse transition function, it is therefore an
!     error for the forward transition function to return null.
    </para>
  
    <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 271,277 ****
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
--- 330,336 ----
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the (forward) state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
*************** SELECT col FROM tab ORDER BY col USING s
*** 281,287 ****
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value.
       </para>
  
       <para>
--- 340,348 ----
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value. Note that if an inverse
!       transition function is present, the forward transition function must
!       not return <literal>NULL</>
       </para>
  
       <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 294,299 ****
--- 355,384 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="PARAMETER">invfunc</replaceable></term>
+     <listitem>
+      <para>
+       The name of the inverse state transition function to be called for each
+       input row.  For a normal <replaceable class="PARAMETER">N</>-argument
+       aggregate function, the <replaceable class="PARAMETER">sfunc</>
+       must take <replaceable class="PARAMETER">N</>+1 arguments,
+       the first being of type <replaceable
+       class="PARAMETER">state_data_type</replaceable> and the rest
+       matching the declared input data type(s) of the aggregate.
+       The function must return a value of type <replaceable
+       class="PARAMETER">state_data_type</replaceable>. These are the same
+       demands placed on the forward transition function, meaning that the
+       signatures of the two functions must be identical. Their
+       <quote>strictness</quote> though may differ, but the only allowed
+       combination is a non-strict forward combined with a strict inverse
+       transition function. The inverse transition function may return
+       <literal>NULL</> to force the aggregation to be restarted from
+       scratch.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="PARAMETER">state_data_type</replaceable></term>
      <listitem>
       <para>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index e77ef12..397e398 100644
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 16,22 ****
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
--- 16,22 ----
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a forward state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
***************
*** 24,30 ****
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
--- 24,42 ----
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result. 
!    To enable efficient evaluation of an aggregate used as a window function
!    with a sliding frame (i.e. a frame that starts relative to the current row),
!    an aggregate can optionally provide an inverse state transition function.
!    The inverse transition function takes the the current state and the
!    aggregate's input value(s) for the <emphasis>earliest</emphasis> row passed
!    to the forward transition function, and returns a state equivalent to what
!    the current state had been had the forward transition function never been
!    invoked for that earliest row, only for all rows that followed it. Thus,
!    if an inverse transition function is provided, the rows that were part of
!    the previous row's frame but not of the current row's frame can simply be
!    removed from the state instead of having to redo the whole aggregation
!    over the new frame.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
*************** CREATE AGGREGATE avg (float8)
*** 132,137 ****
--- 144,188 ----
    </note>
  
    <para>
+    When providing an inverse transition function, care should be taken to
+    ensure that it doesn't introduce unexpected user-visible differences
+    between results obtained by reaggregating all inputs vs. using the inverse
+    transition function. An example for an aggregate where adding an inverse
+    transition function seems easy at first, yet were doing so would violate
+    this requirement is <function>sum</> over <type>float</> or
+    <type>double precision</>. A naive declaration of
+    <function>sum(<type>float</>)</function> could be
+    
+    <programlisting>
+    CREATE AGGREGATE unsafe_sum (float8)
+    (
+        stype = float8,
+        sfunc = float8pl,
+        invfunc = float8mi
+    );
+    </programlisting>
+    
+    This aggregate, howevery, can give wildly different results than it would
+    have without the inverse transition function. For example, consider
+    
+    <programlisting>
+    SELECT
+      unsafe_sum(x) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND
+                                                  1 FOLLOWING)
+    FROM (VALUES
+      (1, 1.0e20::float8),
+      (2, 1.0::float8)
+    ) AS v (n,x)
+    </programlisting>
+    
+    which returns 0 as it's second result, yet the expected answer is 1. The
+    reason for this is the limited precision of floating point types - adding
+    1 to 1e20 actually leaves the value unchanged, and so substracting 1e20
+    again yields 0, not 1. Note that this is a limitation of floating point
+    types in general and not a limitation of <productname>PostgreSQL</>.
+   </para>
+   
+   <para>
     Aggregate functions can use polymorphic
     state transition functions or final functions, so that the same functions
     can be used to implement multiple aggregates.
invtrans_strictstrict_minmax_237683.patchapplication/octet-stream; name=invtrans_strictstrict_minmax_237683.patchDownload
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..4a4ca5d 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_larger(PG_FUNCTION_ARGS)
*** 4714,4719 ****
--- 4714,4737 ----
  }
  
  Datum
+ array_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) > 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ 
+ Datum
  array_smaller(PG_FUNCTION_ARGS)
  {
  	ArrayType  *v1,
*************** array_smaller(PG_FUNCTION_ARGS)
*** 4728,4733 ****
--- 4746,4767 ----
  	PG_RETURN_ARRAYTYPE_P(result);
  }
  
+ Datum
+ array_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) < 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
  
  typedef struct generate_subscripts_fctx
  {
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index d419b4a..593460d 100644
*** a/src/backend/utils/adt/bool.c
--- b/src/backend/utils/adt/bool.c
*************** boolge(PG_FUNCTION_ARGS)
*** 285,290 ****
--- 285,292 ----
  
  /* function for standard EVERY aggregate implementation conforming to SQL 2003.
   * must be strict. It is also named bool_and for homogeneity.
+  * Note: this is no longer used for the bool_and() and every() aggregate
+  * functions.
   */
  Datum
  booland_statefunc(PG_FUNCTION_ARGS)
*************** booland_statefunc(PG_FUNCTION_ARGS)
*** 294,302 ****
--- 296,400 ----
  
  /* function for standard ANY/SOME aggregate conforming to SQL 2003.
   * must be strict. The name of the aggregate is bool_or. See the doc.
+  * Note: this is no longer used for the bool_or aggregate function.
   */
  Datum
  boolor_statefunc(PG_FUNCTION_ARGS)
  {
  	PG_RETURN_BOOL(PG_GETARG_BOOL(0) || PG_GETARG_BOOL(1));
  }
+ 
+ typedef struct BoolAggState
+ {
+ 	int64 aggcount; /* number of values aggregated */
+ 	int64 aggtrue; /* number of values aggregated which are true */
+ } BoolAggState;
+ 
+ static BoolAggState *
+ makeBoolAggState(FunctionCallInfo fcinfo)
+ {
+ 	BoolAggState *state;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 	state = (BoolAggState *) palloc(sizeof(BoolAggState));
+ 	state->aggcount = 0;
+ 	state->aggtrue = 0;
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 
+ 	return state;
+ }
+ 
+ Datum
+ bool_accum(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* Create the state data on first call */
+ 	if (state == NULL)
+ 		state = makeBoolAggState(fcinfo);
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount++;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue++;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* bool_accum should have created the state data */
+ 	if (state == NULL)
+ 		elog(ERROR, "bool_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount--;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue--;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_alltrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if all values are true */
+ 	PG_RETURN_BOOL(state->aggcount == state->aggtrue);
+ }
+ 
+ Datum
+ bool_anytrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if any value is true */
+ 	PG_RETURN_BOOL(state->aggtrue > 0);
+ }
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 0158758..cba17a3 100644
*** a/src/backend/utils/adt/cash.c
--- b/src/backend/utils/adt/cash.c
*************** cashlarger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,896 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 > c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cashsmaller()
   * Return smaller of two cash values.
   */
*************** cashsmaller(PG_FUNCTION_ARGS)
*** 892,897 ****
--- 906,925 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 < c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cash_words()
   * This converts a int4 as well but to a representation using words
   * Obviously way North American centric - sorry
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 06cc0cd..0cca0b0 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** date_larger(PG_FUNCTION_ARGS)
*** 396,401 ****
--- 396,415 ----
  }
  
  Datum
+ date_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 > dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  date_smaller(PG_FUNCTION_ARGS)
  {
  	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
*************** date_smaller(PG_FUNCTION_ARGS)
*** 404,409 ****
--- 418,437 ----
  	PG_RETURN_DATEADT((dateVal1 < dateVal2) ? dateVal1 : dateVal2);
  }
  
+ Datum
+ date_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 < dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* Compute difference between two dates in days.
   */
  Datum
*************** time_larger(PG_FUNCTION_ARGS)
*** 1463,1468 ****
--- 1491,1510 ----
  }
  
  Datum
+ time_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 > time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  time_smaller(PG_FUNCTION_ARGS)
  {
  	TimeADT		time1 = PG_GETARG_TIMEADT(0);
*************** time_smaller(PG_FUNCTION_ARGS)
*** 1471,1476 ****
--- 1513,1532 ----
  	PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
  }
  
+ Datum
+ time_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 < time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* overlaps_time() --- implements the SQL OVERLAPS operator.
   *
   * Algorithm is per SQL spec.  This is much harder than you'd think
*************** timetz_larger(PG_FUNCTION_ARGS)
*** 2262,2267 ****
--- 2318,2337 ----
  }
  
  Datum
+ timetz_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) > 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  timetz_smaller(PG_FUNCTION_ARGS)
  {
  	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
*************** timetz_smaller(PG_FUNCTION_ARGS)
*** 2275,2280 ****
--- 2345,2364 ----
  	PG_RETURN_TIMETZADT_P(result);
  }
  
+ Datum
+ timetz_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) < 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* timetz_pl_interval()
   * Add interval to timetz.
   */
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 83c3878..32035e0 100644
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
*************** enum_smaller(PG_FUNCTION_ARGS)
*** 273,278 ****
--- 273,292 ----
  }
  
  Datum
+ enum_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) < 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_larger(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
*************** enum_larger(PG_FUNCTION_ARGS)
*** 282,287 ****
--- 296,315 ----
  }
  
  Datum
+ enum_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) > 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_cmp(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 774267e..04f89b0 100644
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4larger(PG_FUNCTION_ARGS)
*** 637,642 ****
--- 637,658 ----
  }
  
  Datum
+ float4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float4smaller(PG_FUNCTION_ARGS)
  {
  	float4		arg1 = PG_GETARG_FLOAT4(0);
*************** float4smaller(PG_FUNCTION_ARGS)
*** 650,655 ****
--- 666,687 ----
  	PG_RETURN_FLOAT4(result);
  }
  
+ Datum
+ float4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  /*
   *		======================
   *		FLOAT8 BASE OPERATIONS
*************** float8larger(PG_FUNCTION_ARGS)
*** 704,709 ****
--- 736,757 ----
  }
  
  Datum
+ float8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float8smaller(PG_FUNCTION_ARGS)
  {
  	float8		arg1 = PG_GETARG_FLOAT8(0);
*************** float8smaller(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 765,785 ----
  	PG_RETURN_FLOAT8(result);
  }
  
+ Datum
+ float8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *		====================
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 669355e..fe9d736 100644
*** a/src/backend/utils/adt/int.c
--- b/src/backend/utils/adt/int.c
*************** int2larger(PG_FUNCTION_ARGS)
*** 1185,1190 ****
--- 1185,1204 ----
  }
  
  Datum
+ int2larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int2smaller(PG_FUNCTION_ARGS)
  {
  	int16		arg1 = PG_GETARG_INT16(0);
*************** int2smaller(PG_FUNCTION_ARGS)
*** 1194,1199 ****
--- 1208,1227 ----
  }
  
  Datum
+ int2smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4larger(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4larger(PG_FUNCTION_ARGS)
*** 1203,1208 ****
--- 1231,1250 ----
  }
  
  Datum
+ int4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4smaller(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4smaller(PG_FUNCTION_ARGS)
*** 1211,1216 ****
--- 1253,1272 ----
  	PG_RETURN_INT32((arg1 < arg2) ? arg1 : arg2);
  }
  
+ Datum
+ int4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /*
   * Bit-pushing operators
   *
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..820be68 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8larger(PG_FUNCTION_ARGS)
*** 752,757 ****
--- 752,771 ----
  }
  
  Datum
+ int8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int8smaller(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
*************** int8smaller(PG_FUNCTION_ARGS)
*** 764,769 ****
--- 778,797 ----
  }
  
  Datum
+ int8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int84pl(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..4749152 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_smaller(PG_FUNCTION_ARGS)
*** 1834,1839 ****
--- 1834,1854 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) < 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * numeric_larger() -
*************** numeric_larger(PG_FUNCTION_ARGS)
*** 1856,1861 ****
--- 1871,1892 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) > 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /* ----------------------------------------------------------------------
   *
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 8945ef4..745449a 100644
*** a/src/backend/utils/adt/oid.c
--- b/src/backend/utils/adt/oid.c
*************** oidlarger(PG_FUNCTION_ARGS)
*** 397,402 ****
--- 397,416 ----
  }
  
  Datum
+ oidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidsmaller(PG_FUNCTION_ARGS)
  {
  	Oid			arg1 = PG_GETARG_OID(0);
*************** oidsmaller(PG_FUNCTION_ARGS)
*** 406,411 ****
--- 420,439 ----
  }
  
  Datum
+ oidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidvectoreq(PG_FUNCTION_ARGS)
  {
  	int32		cmp = DatumGetInt32(btoidvectorcmp(fcinfo));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 6e2bbdc..57fcf80 100644
*** a/src/backend/utils/adt/tid.c
--- b/src/backend/utils/adt/tid.c
*************** tidlarger(PG_FUNCTION_ARGS)
*** 237,242 ****
--- 237,256 ----
  }
  
  Datum
+ tidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) > 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  tidsmaller(PG_FUNCTION_ARGS)
  {
  	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
*************** tidsmaller(PG_FUNCTION_ARGS)
*** 245,250 ****
--- 259,277 ----
  	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
  }
  
+ Datum
+ tidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) < 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *	Functions to get latest tid of a specified tuple.
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index cf6982b..f13d811 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** timestamp_smaller(PG_FUNCTION_ARGS)
*** 2410,2415 ****
--- 2410,2429 ----
  }
  
  Datum
+ timestamp_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) < 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  timestamp_larger(PG_FUNCTION_ARGS)
  {
  	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
*************** timestamp_larger(PG_FUNCTION_ARGS)
*** 2423,2428 ****
--- 2437,2455 ----
  	PG_RETURN_TIMESTAMP(result);
  }
  
+ Datum
+ timestamp_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) > 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
  
  Datum
  timestamp_mi(PG_FUNCTION_ARGS)
*************** interval_smaller(PG_FUNCTION_ARGS)
*** 2879,2884 ****
--- 2906,2925 ----
  }
  
  Datum
+ interval_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) < 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_larger(PG_FUNCTION_ARGS)
  {
  	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
*************** interval_larger(PG_FUNCTION_ARGS)
*** 2893,2898 ****
--- 2934,2953 ----
  }
  
  Datum
+ interval_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) > 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_pl(PG_FUNCTION_ARGS)
  {
  	Interval   *span1 = PG_GETARG_INTERVAL_P(0);
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 284b5d1..2fc8446 100644
*** a/src/backend/utils/adt/varchar.c
--- b/src/backend/utils/adt/varchar.c
*************** bpchar_larger(PG_FUNCTION_ARGS)
*** 889,894 ****
--- 889,917 ----
  }
  
  Datum
+ bpchar_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp > 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  bpchar_smaller(PG_FUNCTION_ARGS)
  {
  	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
*************** bpchar_smaller(PG_FUNCTION_ARGS)
*** 906,911 ****
--- 929,957 ----
  	PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2);
  }
  
+ Datum
+ bpchar_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp < 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /*
   * bpchar needs a specialized hash function because we want to ignore
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..6b640cb 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** text_larger(PG_FUNCTION_ARGS)
*** 1697,1702 ****
--- 1697,1716 ----
  }
  
  Datum
+ text_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  text_smaller(PG_FUNCTION_ARGS)
  {
  	text	   *arg1 = PG_GETARG_TEXT_PP(0);
*************** text_smaller(PG_FUNCTION_ARGS)
*** 1708,1713 ****
--- 1722,1740 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ Datum
+ text_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * The following operators support character-by-character comparison
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..7d35559 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2113	n 0 interval_pl		-	-	
*** 122,169 ****
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
--- 122,169 ----
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger			int8larger_inv			-		413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger			int4larger_inv			-		521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger			int2larger_inv			-		520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger			oidlarger_inv			-		610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger		float4larger_inv		-		623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger		float8larger_inv		-		674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger			int4larger_inv			-		563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger			date_larger_inv			-		1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger			time_larger_inv			-		1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger		timetz_larger_inv		-		1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger			cashlarger_inv			-		903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	timestamp_larger_inv	-		2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	timestamptz_larger_inv	-		1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger		interval_larger_inv		-		1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger			text_larger_inv			-		666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger		numeric_larger_inv		-		1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger		array_larger_inv		-		1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger		bpchar_larger_inv		-		1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger			tidlarger_inv			-		2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger			enum_larger_inv			-		3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller			int8smaller_inv			-		412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller			int4smaller_inv			-		97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller			int2smaller_inv			-		95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller			oidsmaller_inv			-		609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller		float4smaller_inv		-		622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller		float8smaller_inv		-		672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller			int4smaller_inv			-		562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller		date_smaller_inv		-		1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller		time_smaller_inv		-		1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller		timetz_smaller_inv		-		1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller			cashsmaller_inv			-		902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	timestamp_smaller_inv	-		2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller timestamptz_smaller_inv	-		1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	interval_smaller_inv	-		1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller		text_smaller_inv		-		664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller		numeric_smaller_inv		-		1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller		array_smaller_inv		-		1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller		bpchar_smaller_inv		-		1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller			tidsmaller_inv			-		2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller		enum_smaller_inv		-		3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
*************** DATA(insert ( 2828	n 0 float8_regr_accum
*** 232,240 ****
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
--- 232,240 ----
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
! DATA(insert ( 2518	n 0 bool_accum		bool_accum_inv		bool_anytrue	59	2281		16	_null_ ));
! DATA(insert ( 2519	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 11c1e1a..5111c69 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("larger of two");
*** 367,372 ****
--- 367,377 ----
  DATA(insert OID = 211 (  float4smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4033 (  float4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4034 (  float4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 212 (  int4um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "23" _null_ _null_ _null_ _null_ int4um _null_ _null_ _null_ ));
  DATA(insert OID = 213 (  int2um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "21" _null_ _null_ _null_ _null_ int2um _null_ _null_ _null_ ));
  
*************** DATA(insert OID = 223 (  float8larger	  
*** 386,391 ****
--- 391,400 ----
  DESCR("larger of two");
  DATA(insert OID = 224 (  float8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4035 (  float8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4036 (  float8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 225 (  lseg_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "601" _null_ _null_ _null_ _null_	lseg_center _null_ _null_ _null_ ));
  DATA(insert OID = 226 (  path_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "602" _null_ _null_ _null_ _null_	path_center _null_ _null_ _null_ ));
*************** DATA(insert OID = 458 (  text_larger	   
*** 711,716 ****
--- 720,729 ----
  DESCR("larger of two");
  DATA(insert OID = 459 (  text_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4037 (  text_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4038 (  text_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 460 (  int8in			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "2275" _null_ _null_ _null_ _null_ int8in _null_ _null_ _null_ ));
  DESCR("I/O");
*************** DATA(insert OID = 515 (  array_larger	  
*** 859,864 ****
--- 872,881 ----
  DESCR("larger of two");
  DATA(insert OID = 516 (  array_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4039 (  array_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4040 (  array_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1191 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 23 "2277 23 16" _null_ _null_ _null_ _null_ generate_subscripts _null_ _null_ _null_ ));
  DESCR("array subscripts generator");
  DATA(insert OID = 1192 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 23 "2277 23" _null_ _null_ _null_ _null_ generate_subscripts_nodir _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 913,918 ****
--- 930,945 ----
  DATA(insert OID = 771 (  int2smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4041 (  int4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4042 (  int4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4043 (  int2larger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4044 (  int2smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 774 (  gistgettuple	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_	gistgettuple _null_ _null_ _null_ ));
  DESCR("gist(internal)");
  DATA(insert OID = 638 (  gistgetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	gistgetbitmap _null_ _null_ _null_ ));
*************** DATA(insert OID =  898 (  cashlarger	   
*** 1003,1008 ****
--- 1030,1039 ----
  DESCR("larger of two");
  DATA(insert OID =  899 (  cashsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID =  4045 (  cashlarger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID =  4046 (  cashsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID =  919 (  flt8_mul_cash    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "701 790" _null_ _null_ _null_ _null_	flt8_mul_cash _null_ _null_ _null_ ));
  DATA(insert OID =  935 (  cash_words	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "790" _null_ _null_ _null_ _null_	cash_words _null_ _null_ _null_ ));
  DESCR("output money amount as words");
*************** DATA(insert OID = 1063 (  bpchar_larger 
*** 1157,1162 ****
--- 1188,1197 ----
  DESCR("larger of two");
  DATA(insert OID = 1064 (  bpchar_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4047 (  bpchar_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4048 (  bpchar_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1078 (  bpcharcmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1042 1042" _null_ _null_ _null_ _null_ bpcharcmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1080 (  hashbpchar	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "1042" _null_ _null_ _null_ _null_	hashbpchar _null_ _null_ _null_ ));
*************** DATA(insert OID = 1138 (  date_larger	  
*** 1191,1196 ****
--- 1226,1235 ----
  DESCR("larger of two");
  DATA(insert OID = 1139 (  date_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 4049 (  date_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4050 (  date_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1140 (  date_mi		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1082 1082" _null_ _null_ _null_ _null_ date_mi _null_ _null_ _null_ ));
  DATA(insert OID = 1141 (  date_pli		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_pli _null_ _null_ _null_ ));
  DATA(insert OID = 1142 (  date_mii		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_mii _null_ _null_ _null_ ));
*************** DATA(insert OID = 1195 (  timestamptz_sm
*** 1281,1290 ****
--- 1320,1339 ----
  DESCR("smaller of two");
  DATA(insert OID = 1196 (  timestamptz_larger  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4051 (  timestamptz_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4052 (  timestamptz_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1197 (  interval_smaller	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  DATA(insert OID = 1198 (  interval_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4053 (  interval_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4054 (  interval_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1199 (  age				PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_	timestamptz_age _null_ _null_ _null_ ));
  DESCR("date difference preserving months and years");
  
*************** DESCR("larger of two");
*** 1318,1323 ****
--- 1367,1378 ----
  DATA(insert OID = 1237 (  int8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4055 (  int8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4056 (  int8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1238 (  texticregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexeq _null_ _null_ _null_ ));
  DATA(insert OID = 1239 (  texticregexne    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexne _null_ _null_ _null_ ));
  DATA(insert OID = 1240 (  nameicregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "19 25" _null_ _null_ _null_ _null_ nameicregexeq _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1374,1379 ****
--- 1429,1439 ----
  DATA(insert OID = 2796 ( tidsmaller		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4057 ( tidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4058 ( tidsmaller_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1296 (  timedate_pl	   PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1114 "1083 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
  DATA(insert OID = 1297 (  datetimetz_pl    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1082 1266" _null_ _null_ _null_ _null_ datetimetz_timestamptz _null_ _null_ _null_ ));
  DATA(insert OID = 1298 (  timetzdate_pl    PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1184 "1266 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1515,1520 ****
--- 1575,1590 ----
  DATA(insert OID = 1380 (  timetz_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4059 (  time_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4060 (  time_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4061 (  timetz_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4062 (  timetz_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1381 (  char_length	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "25" _null_ _null_ _null_ _null_ textlen _null_ _null_ _null_ ));
  DESCR("character length");
  
*************** DATA(insert OID = 1766 ( numeric_smaller
*** 2277,2282 ****
--- 2347,2357 ----
  DESCR("smaller of two");
  DATA(insert OID = 1767 ( numeric_larger			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4063 ( numeric_smaller_inv		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4064 ( numeric_larger_inv			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1769 ( numeric_cmp			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1700 1700" _null_ _null_ _null_ _null_ numeric_cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1771 ( numeric_uminus			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ numeric_uminus _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 2803,2808 ****
--- 2878,2888 ----
  DATA(insert OID = 1966 (  oidsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4065 (  oidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4066 (  oidsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 timestamp_transform f f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
  DESCR("adjust timestamptz precision");
  DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 time_transform f f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
*************** DATA(insert OID = 2035 (  timestamp_smal
*** 2864,2869 ****
--- 2944,2955 ----
  DESCR("smaller of two");
  DATA(insert OID = 2036 (  timestamp_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4067 (  timestamp_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4068 (  timestamp_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 2037 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 1266 "25 1266" _null_ _null_ _null_ _null_ timetz_zone _null_ _null_ _null_ ));
  DESCR("adjust time with time zone to new zone");
  DATA(insert OID = 2038 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1186 1266" _null_ _null_ _null_ _null_	timetz_izone _null_ _null_ _null_ ));
*************** DATA(insert OID = 939  (  generate_serie
*** 3860,3869 ****
--- 3946,3957 ----
  DESCR("non-persistent series generator");
  
  /* boolean aggregates */
+ /* previous aggregate transition functions, unused now */
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ /* aggregates and new invertible transition functions */
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
*************** DATA(insert OID = 2518 ( bool_or					   
*** 3871,3876 ****
--- 3959,3972 ----
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
+ DATA(insert OID = 4069 ( bool_accum					   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 4070 ( bool_accum_inv				   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4071 ( bool_alltrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_alltrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
+ DATA(insert OID = 4072 ( bool_anytrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_anytrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DATA(insert OID = 3524 (  enum_smaller	P
*** 4252,4257 ****
--- 4348,4358 ----
  DESCR("smaller of two");
  DATA(insert OID = 3525 (  enum_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4073 (  enum_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4074 (  enum_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 3526 (  max			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("maximum value of all enum input values");
  DATA(insert OID = 3527 (  min			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..43e4973 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern Datum array_upper(PG_FUNCTION_ARG
*** 206,212 ****
--- 206,214 ----
  extern Datum array_length(PG_FUNCTION_ARGS);
  extern Datum array_cardinality(PG_FUNCTION_ARGS);
  extern Datum array_larger(PG_FUNCTION_ARGS);
+ extern Datum array_larger_inv(PG_FUNCTION_ARGS);
  extern Datum array_smaller(PG_FUNCTION_ARGS);
+ extern Datum array_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
  extern Datum array_fill(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..c97c910 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum boolle(PG_FUNCTION_ARGS);
*** 121,126 ****
--- 121,130 ----
  extern Datum boolge(PG_FUNCTION_ARGS);
  extern Datum booland_statefunc(PG_FUNCTION_ARGS);
  extern Datum boolor_statefunc(PG_FUNCTION_ARGS);
+ extern Datum bool_accum(PG_FUNCTION_ARGS);
+ extern Datum bool_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum bool_alltrue(PG_FUNCTION_ARGS);
+ extern Datum bool_anytrue(PG_FUNCTION_ARGS);
  extern bool parse_bool(const char *value, bool *result);
  extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
  
*************** extern Datum enum_ge(PG_FUNCTION_ARGS);
*** 167,173 ****
--- 171,179 ----
  extern Datum enum_gt(PG_FUNCTION_ARGS);
  extern Datum enum_cmp(PG_FUNCTION_ARGS);
  extern Datum enum_smaller(PG_FUNCTION_ARGS);
+ extern Datum enum_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum enum_larger(PG_FUNCTION_ARGS);
+ extern Datum enum_larger_inv(PG_FUNCTION_ARGS);
  extern Datum enum_first(PG_FUNCTION_ARGS);
  extern Datum enum_last(PG_FUNCTION_ARGS);
  extern Datum enum_range_bounds(PG_FUNCTION_ARGS);
*************** extern Datum int42div(PG_FUNCTION_ARGS);
*** 241,249 ****
--- 247,259 ----
  extern Datum int4mod(PG_FUNCTION_ARGS);
  extern Datum int2mod(PG_FUNCTION_ARGS);
  extern Datum int2larger(PG_FUNCTION_ARGS);
+ extern Datum int2larger_inv(PG_FUNCTION_ARGS);
  extern Datum int2smaller(PG_FUNCTION_ARGS);
+ extern Datum int2smaller_inv(PG_FUNCTION_ARGS);
  extern Datum int4larger(PG_FUNCTION_ARGS);
+ extern Datum int4larger_inv(PG_FUNCTION_ARGS);
  extern Datum int4smaller(PG_FUNCTION_ARGS);
+ extern Datum int4smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int4and(PG_FUNCTION_ARGS);
  extern Datum int4or(PG_FUNCTION_ARGS);
*************** extern Datum float4abs(PG_FUNCTION_ARGS)
*** 347,358 ****
--- 357,372 ----
  extern Datum float4um(PG_FUNCTION_ARGS);
  extern Datum float4up(PG_FUNCTION_ARGS);
  extern Datum float4larger(PG_FUNCTION_ARGS);
+ extern Datum float4larger_inv(PG_FUNCTION_ARGS);
  extern Datum float4smaller(PG_FUNCTION_ARGS);
+ extern Datum float4smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float8abs(PG_FUNCTION_ARGS);
  extern Datum float8um(PG_FUNCTION_ARGS);
  extern Datum float8up(PG_FUNCTION_ARGS);
  extern Datum float8larger(PG_FUNCTION_ARGS);
+ extern Datum float8larger_inv(PG_FUNCTION_ARGS);
  extern Datum float8smaller(PG_FUNCTION_ARGS);
+ extern Datum float8smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float4pl(PG_FUNCTION_ARGS);
  extern Datum float4mi(PG_FUNCTION_ARGS);
  extern Datum float4mul(PG_FUNCTION_ARGS);
*************** extern Datum oidle(PG_FUNCTION_ARGS);
*** 501,507 ****
--- 515,523 ----
  extern Datum oidge(PG_FUNCTION_ARGS);
  extern Datum oidgt(PG_FUNCTION_ARGS);
  extern Datum oidlarger(PG_FUNCTION_ARGS);
+ extern Datum oidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum oidsmaller(PG_FUNCTION_ARGS);
+ extern Datum oidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum oidvectorin(PG_FUNCTION_ARGS);
  extern Datum oidvectorout(PG_FUNCTION_ARGS);
  extern Datum oidvectorrecv(PG_FUNCTION_ARGS);
*************** extern Datum tidgt(PG_FUNCTION_ARGS);
*** 707,713 ****
--- 723,731 ----
  extern Datum tidge(PG_FUNCTION_ARGS);
  extern Datum bttidcmp(PG_FUNCTION_ARGS);
  extern Datum tidlarger(PG_FUNCTION_ARGS);
+ extern Datum tidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum tidsmaller(PG_FUNCTION_ARGS);
+ extern Datum tidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum currtid_byreloid(PG_FUNCTION_ARGS);
  extern Datum currtid_byrelname(PG_FUNCTION_ARGS);
  
*************** extern Datum bpchargt(PG_FUNCTION_ARGS);
*** 730,736 ****
--- 748,756 ----
  extern Datum bpcharge(PG_FUNCTION_ARGS);
  extern Datum bpcharcmp(PG_FUNCTION_ARGS);
  extern Datum bpchar_larger(PG_FUNCTION_ARGS);
+ extern Datum bpchar_larger_inv(PG_FUNCTION_ARGS);
  extern Datum bpchar_smaller(PG_FUNCTION_ARGS);
+ extern Datum bpchar_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum bpcharlen(PG_FUNCTION_ARGS);
  extern Datum bpcharoctetlen(PG_FUNCTION_ARGS);
  extern Datum hashbpchar(PG_FUNCTION_ARGS);
*************** extern Datum text_le(PG_FUNCTION_ARGS);
*** 770,776 ****
--- 790,798 ----
  extern Datum text_gt(PG_FUNCTION_ARGS);
  extern Datum text_ge(PG_FUNCTION_ARGS);
  extern Datum text_larger(PG_FUNCTION_ARGS);
+ extern Datum text_larger_inv(PG_FUNCTION_ARGS);
  extern Datum text_smaller(PG_FUNCTION_ARGS);
+ extern Datum text_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum text_pattern_lt(PG_FUNCTION_ARGS);
  extern Datum text_pattern_le(PG_FUNCTION_ARGS);
  extern Datum text_pattern_gt(PG_FUNCTION_ARGS);
*************** extern Datum numeric_div_trunc(PG_FUNCTI
*** 980,986 ****
--- 1002,1010 ----
  extern Datum numeric_mod(PG_FUNCTION_ARGS);
  extern Datum numeric_inc(PG_FUNCTION_ARGS);
  extern Datum numeric_smaller(PG_FUNCTION_ARGS);
+ extern Datum numeric_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_larger(PG_FUNCTION_ARGS);
+ extern Datum numeric_larger_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_fac(PG_FUNCTION_ARGS);
  extern Datum numeric_sqrt(PG_FUNCTION_ARGS);
  extern Datum numeric_exp(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/cash.h b/src/include/utils/cash.h
index 3a491f9..4e184cc 100644
*** a/src/include/utils/cash.h
--- b/src/include/utils/cash.h
*************** extern Datum int2_mul_cash(PG_FUNCTION_A
*** 60,66 ****
--- 60,68 ----
  extern Datum cash_div_int2(PG_FUNCTION_ARGS);
  
  extern Datum cashlarger(PG_FUNCTION_ARGS);
+ extern Datum cashlarger_inv(PG_FUNCTION_ARGS);
  extern Datum cashsmaller(PG_FUNCTION_ARGS);
+ extern Datum cashsmaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum cash_words(PG_FUNCTION_ARGS);
  
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 622aa19..9f459ac 100644
*** a/src/include/utils/date.h
--- b/src/include/utils/date.h
*************** extern Datum date_cmp(PG_FUNCTION_ARGS);
*** 108,114 ****
--- 108,116 ----
  extern Datum date_sortsupport(PG_FUNCTION_ARGS);
  extern Datum date_finite(PG_FUNCTION_ARGS);
  extern Datum date_larger(PG_FUNCTION_ARGS);
+ extern Datum date_larger_inv(PG_FUNCTION_ARGS);
  extern Datum date_smaller(PG_FUNCTION_ARGS);
+ extern Datum date_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum date_mi(PG_FUNCTION_ARGS);
  extern Datum date_pli(PG_FUNCTION_ARGS);
  extern Datum date_mii(PG_FUNCTION_ARGS);
*************** extern Datum time_cmp(PG_FUNCTION_ARGS);
*** 168,174 ****
--- 170,178 ----
  extern Datum time_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_time(PG_FUNCTION_ARGS);
  extern Datum time_larger(PG_FUNCTION_ARGS);
+ extern Datum time_larger_inv(PG_FUNCTION_ARGS);
  extern Datum time_smaller(PG_FUNCTION_ARGS);
+ extern Datum time_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum time_mi_time(PG_FUNCTION_ARGS);
  extern Datum timestamp_time(PG_FUNCTION_ARGS);
  extern Datum timestamptz_time(PG_FUNCTION_ARGS);
*************** extern Datum timetz_cmp(PG_FUNCTION_ARGS
*** 195,201 ****
--- 199,207 ----
  extern Datum timetz_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_timetz(PG_FUNCTION_ARGS);
  extern Datum timetz_larger(PG_FUNCTION_ARGS);
+ extern Datum timetz_larger_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_smaller(PG_FUNCTION_ARGS);
+ extern Datum timetz_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_time(PG_FUNCTION_ARGS);
  extern Datum time_timetz(PG_FUNCTION_ARGS);
  extern Datum timestamptz_timetz(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..d102ccb 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8inc(PG_FUNCTION_ARGS);
*** 77,83 ****
--- 77,85 ----
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
+ extern Datum int8larger_inv(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
+ extern Datum int8smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int8and(PG_FUNCTION_ARGS);
  extern Datum int8or(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..85c0283 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum timestamp_cmp(PG_FUNCTION_A
*** 111,117 ****
--- 111,119 ----
  extern Datum timestamp_sortsupport(PG_FUNCTION_ARGS);
  extern Datum timestamp_hash(PG_FUNCTION_ARGS);
  extern Datum timestamp_smaller(PG_FUNCTION_ARGS);
+ extern Datum timestamp_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timestamp_larger(PG_FUNCTION_ARGS);
+ extern Datum timestamp_larger_inv(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS);
  extern Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS);
*************** extern Datum interval_finite(PG_FUNCTION
*** 147,153 ****
--- 149,157 ----
  extern Datum interval_cmp(PG_FUNCTION_ARGS);
  extern Datum interval_hash(PG_FUNCTION_ARGS);
  extern Datum interval_smaller(PG_FUNCTION_ARGS);
+ extern Datum interval_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum interval_larger(PG_FUNCTION_ARGS);
+ extern Datum interval_larger_inv(PG_FUNCTION_ARGS);
  extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
  extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
  extern Datum interval_justify_days(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 8af3d23..4399f40 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1300,1305 ****
--- 1300,1410 ----
  -- Test the MIN, MAX and boolean inverse transition functions
  --
  --
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  40 |  30
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  20 |  20
+     |  40 |  40
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  30 |  30
+  40 |     |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |  20 |  10
+     |  10 |  10
+  10 |  10 |  10
+ (5 rows)
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |     |  10
+     |     |  10
+  10 |     |  10
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  |  p  | max | min 
+ ----+-----+-----+-----
+  ad | 100 | ae  | ab
+  ab | 100 | ae  | ab
+  ae | 100 | ae  | ae
+  ad | 200 | ad  | aa
+  aa | 200 | aa  | aa
+ (5 rows)
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  b | bool_and | bool_or 
+ ---+----------+---------
+  t | t        | t
+  t | f        | t
+  f | f        | f
+  f | f        | t
+  t | t        | t
+ (5 rows)
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 117bd6c..41065c9 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 482,487 ****
--- 482,523 ----
  --
  --
  
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
#149Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#148)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 3 March 2014 23:00, Florian Pflug <fgp@phlo.org> wrote:

* In show_windowagg_info(), this calculation looks suspicious to me:

double tperrow = winaggstate->aggfwdtrans /
(inst->nloops * inst->ntuples);

If the node is executed multiple times, aggfwdtrans will be reset in
each loop, so the transitions per row figure will be under-estimated.
ISTM that if you want to report on this, you'd need aggfwdtrans to be
reset once per query, but I'm not sure exactly how to do that.

...

Actually, I think it's misleading to only count forward transition
function calls, because a call to the inverse transition function
still represents a state transition, and is likely to be around the
same cost. For a window of size 2, there would not be much advantage
to using inverse transition functions, because it would be around 2
transitions per row either way.

True. In fact, I pondered whether to avoid using the inverse transition
function for windows of 2 rows. In the end, I didn't because I felt that
it makes custom aggregates harder to test.

On the question of whether to count inverse transition function calls -
the idea of the EXPLAIN VERBOSE ANALYZE output isn't really to show the
number of state transitions, but rather to show whether the aggregation
has O(n) or O(n^2) behaviour. The idea being that a value close to "1"
means "inverse transition function works as expected", and larger values
mean "not working so well".

Regarding multiple evaluations - I think I based the behaviour on how
ntuples works, which also only reports the value of the last evaluation
I think. But maybe I'm confused about this.

No, it doesn't look like that's correct for multiple loops. Consider
this example:

explain (verbose, analyse)
select * from (values (10), (20), (30), (40)) v(x),
lateral
(select sum(i) over (rows between 4 preceding and current row)
from generate_series(1, x) i) t;

QUERY
PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..170.06 rows=4000 width=12) (actual
time=0.027..0.414 rows=100 loops=1)
Output: "*VALUES*".column1, (sum(i.i) OVER (?))
-> Values Scan on "*VALUES*" (cost=0.00..0.05 rows=4 width=4)
(actual time=0.002..0.006 rows=4 loops=1)
Output: "*VALUES*".column1
-> WindowAgg (cost=0.00..22.50 rows=1000 width=4) (actual
time=0.019..0.094 rows=25 loops=4)
Output: sum(i.i) OVER (?)
Transitions Per Row: 0.2
-> Function Scan on pg_catalog.generate_series i
(cost=0.00..10.00 rows=1000 width=4) (actual time=0.010..0.015 rows=25
loops=4)
Output: i.i
Function Call: generate_series(1, "*VALUES*".column1)

It turns out that show_windowagg_info() is only called once at the
end, with ntuples=100, nloops=4 and aggfwdtrans=100, so it's computing
tperrow=100/(4*100)=0.25, which then gets truncated to 0.2. So to get
1, you'd have to use this formula:

double tperrow = winaggstate->aggfwdtrans / inst->ntuples;

I'm still not convinced that's the most useful thing to report though.
Personally, I'd prefer to just see the separate counts, e.g.:

-> WindowAgg (cost=0.00..22.50 rows=1000 width=4) (actual
time=0.019..0.094 rows=25 loops=4)
Output: sum(i.i) OVER (?)
Forward transitions: 25 Inverse transitions: 25

IMO that gives a clearer picture of what's going on.

Thoughts?

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#150Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#149)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mar4, 2014, at 21:09 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 3 March 2014 23:00, Florian Pflug <fgp@phlo.org> wrote:

* In show_windowagg_info(), this calculation looks suspicious to me:

double tperrow = winaggstate->aggfwdtrans /
(inst->nloops * inst->ntuples);

If the node is executed multiple times, aggfwdtrans will be reset in
each loop, so the transitions per row figure will be under-estimated.
ISTM that if you want to report on this, you'd need aggfwdtrans to be
reset once per query, but I'm not sure exactly how to do that.

...

Actually, I think it's misleading to only count forward transition
function calls, because a call to the inverse transition function
still represents a state transition, and is likely to be around the
same cost. For a window of size 2, there would not be much advantage
to using inverse transition functions, because it would be around 2
transitions per row either way.

True. In fact, I pondered whether to avoid using the inverse transition
function for windows of 2 rows. In the end, I didn't because I felt that
it makes custom aggregates harder to test.

On the question of whether to count inverse transition function calls -
the idea of the EXPLAIN VERBOSE ANALYZE output isn't really to show the
number of state transitions, but rather to show whether the aggregation
has O(n) or O(n^2) behaviour. The idea being that a value close to "1"
means "inverse transition function works as expected", and larger values
mean "not working so well".

Regarding multiple evaluations - I think I based the behaviour on how
ntuples works, which also only reports the value of the last evaluation
I think. But maybe I'm confused about this.

No, it doesn't look like that's correct for multiple loops. Consider
this example:

...

It turns out that show_windowagg_info() is only called once at the
end, with ntuples=100, nloops=4 and aggfwdtrans=100, so it's computing
tperrow=100/(4*100)=0.25, which then gets truncated to 0.2. So to get
1, you'd have to use this formula:

double tperrow = winaggstate->aggfwdtrans / inst->ntuples;

Hm, so I *was* confused - seems I mixed up ntuples (which counts the
total number of tuples over all loops) with what we report as "rows"
(i.e. the average number of rows per loop). Thanks for clearing that up!

I'm still not convinced that's the most useful thing to report though.
Personally, I'd prefer to just see the separate counts, e.g.:

-> WindowAgg (cost=0.00..22.50 rows=1000 width=4) (actual
time=0.019..0.094 rows=25 loops=4)
Output: sum(i.i) OVER (?)
Forward transitions: 25 Inverse transitions: 25

IMO that gives a clearer picture of what's going on.

My problem with this is that if there are multiple aggregates, the
numbers don't really count the number of forward and reverse transfer
function invocations. What nodeWindowAgg.c really counts is the number
of *rows* it has to fetch from the tuple store to perform forward
aggregations - the relevant code snippet is

if (numaggs_restart > 0)
winstate->aggfwdtrans += (winstate->aggregatedupto
- winstate->frameheadpos);
else
winstate->aggfwdtrans += (winstate->aggregatedupto
- aggregatedupto_nonrestarted);

Now we could of course report these figures per aggregate instead of
only once per aggregation node. But as I said earlier, my guess is that
people usually won't be interested in the precise counts - most likely,
what you want to know is "how much do the restarts cost me"

When I added the EXPLAIN stuff, I initially simply reported the number
of times nodeWindowAgg has to restart the aggregation. The problem with
that approach is that not all restarts carry the same cost. If the frames
either don't overlap at all or are identical, restarts don't cause any
additional work. And for fixed-length frames (BETWEEN n PRECEEDING AND
m FOLLOWING), the performance effects of restarts depends on m-n.

Which is why I made it count the number of aggregated input rows instead.

Having said that, I' not really 100% happy with the name
"Transitions Per Row" for this - it was simply the best I could come up with
that was reasonably short. And I'm certainly open to reporting the absolute
count instead of a factor relative to ntuples.

If we made it an absolute count, would calling this "Aggregated Rows" work
for you?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#151Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#150)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 5 March 2014 14:35, Florian Pflug <fgp@phlo.org> wrote:

On Mar4, 2014, at 21:09 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 3 March 2014 23:00, Florian Pflug <fgp@phlo.org> wrote:

* In show_windowagg_info(), this calculation looks suspicious to me:

double tperrow = winaggstate->aggfwdtrans /
(inst->nloops * inst->ntuples);

If the node is executed multiple times, aggfwdtrans will be reset in
each loop, so the transitions per row figure will be under-estimated.
ISTM that if you want to report on this, you'd need aggfwdtrans to be
reset once per query, but I'm not sure exactly how to do that.

...

Actually, I think it's misleading to only count forward transition
function calls, because a call to the inverse transition function
still represents a state transition, and is likely to be around the
same cost. For a window of size 2, there would not be much advantage
to using inverse transition functions, because it would be around 2
transitions per row either way.

True. In fact, I pondered whether to avoid using the inverse transition
function for windows of 2 rows. In the end, I didn't because I felt that
it makes custom aggregates harder to test.

On the question of whether to count inverse transition function calls -
the idea of the EXPLAIN VERBOSE ANALYZE output isn't really to show the
number of state transitions, but rather to show whether the aggregation
has O(n) or O(n^2) behaviour. The idea being that a value close to "1"
means "inverse transition function works as expected", and larger values
mean "not working so well".

Regarding multiple evaluations - I think I based the behaviour on how
ntuples works, which also only reports the value of the last evaluation
I think. But maybe I'm confused about this.

No, it doesn't look like that's correct for multiple loops. Consider
this example:

...

It turns out that show_windowagg_info() is only called once at the
end, with ntuples=100, nloops=4 and aggfwdtrans=100, so it's computing
tperrow=100/(4*100)=0.25, which then gets truncated to 0.2. So to get
1, you'd have to use this formula:

double tperrow = winaggstate->aggfwdtrans / inst->ntuples;

Hm, so I *was* confused - seems I mixed up ntuples (which counts the
total number of tuples over all loops) with what we report as "rows"
(i.e. the average number of rows per loop). Thanks for clearing that up!

I'm still not convinced that's the most useful thing to report though.
Personally, I'd prefer to just see the separate counts, e.g.:

-> WindowAgg (cost=0.00..22.50 rows=1000 width=4) (actual
time=0.019..0.094 rows=25 loops=4)
Output: sum(i.i) OVER (?)
Forward transitions: 25 Inverse transitions: 25

IMO that gives a clearer picture of what's going on.

My problem with this is that if there are multiple aggregates, the
numbers don't really count the number of forward and reverse transfer
function invocations. What nodeWindowAgg.c really counts is the number
of *rows* it has to fetch from the tuple store to perform forward
aggregations - the relevant code snippet is

if (numaggs_restart > 0)
winstate->aggfwdtrans += (winstate->aggregatedupto
- winstate->frameheadpos);
else
winstate->aggfwdtrans += (winstate->aggregatedupto
- aggregatedupto_nonrestarted);

Now we could of course report these figures per aggregate instead of
only once per aggregation node. But as I said earlier, my guess is that
people usually won't be interested in the precise counts - most likely,
what you want to know is "how much do the restarts cost me"

The problem I have with the single "Transitions Per Row" figure, and
the idea that a value close to 1.0 is supposed to be good, is that
it's not really true. For example, with a window size of 2 and a
"perfect" invertible aggregate, you'd get a value of 1.0, but with a
non-invertible aggregate you'd get a value of 2.0, when actually it
wouldn't do any better if it had an inverse. I think trying to
represent this as a single number is too simplistic.

When I added the EXPLAIN stuff, I initially simply reported the number
of times nodeWindowAgg has to restart the aggregation. The problem with
that approach is that not all restarts carry the same cost. If the frames
either don't overlap at all or are identical, restarts don't cause any
additional work. And for fixed-length frames (BETWEEN n PRECEEDING AND
m FOLLOWING), the performance effects of restarts depends on m-n.

Which is why I made it count the number of aggregated input rows instead.

Having said that, I' not really 100% happy with the name
"Transitions Per Row" for this - it was simply the best I could come up with
that was reasonably short. And I'm certainly open to reporting the absolute
count instead of a factor relative to ntuples.

If we made it an absolute count, would calling this "Aggregated Rows" work
for you?

I'm not sure about naming, but I think my preference would be to
output the correct absolute counts for both the forward and inverse
transitions (i.e. multiply by the right combinations of numaggs and
numaggs_restart). The EXPLAIN output already tells you how many
aggregates there are, and how many rows there are, so you'd be able to
work out from that how much extra work it's doing.

I think we really need a larger consensus on this though, so I'd be
interested to hear what others think.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#152Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#151)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

I think we really need a larger consensus on this though, so I'd be
interested to hear what others think.

My advice is to lose the EXPLAIN output entirely. If the authors of
the patch can't agree on what it means, what hope have everyday users
got of making sense of it?

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

#153Vladimir Sitnikov
sitnikov.vladimir@gmail.com
In reply to: Tom Lane (#152)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Tom,

I did not follow the thread very close, so I need to look what the
ambiguity is there, however I would love to see window rescans in explain
analyze.

I have great experience in tuning Oracle queries.
There are features in PostgreSQL's explain analyze that I miss badly in
Oracle: 'rows removed by filter' is my favourite one. Improving explain
analyze is great for performance analysis.

I would say target audience for 'explain analyze' is not all the users, but
someone closer to 'performance engineers'. Those beings are used to
triple-check results and build/validate hypothesis since all the counters
tend to lie, so 'a bit misleading counter' is not a showstopper.

I did not think of
'non-being-able-to-use-negative-transition-since-floats-do-not-commute'
before.
Thanks to this discussion I see what kind of dragons live here.

I would vote (if I had any vote at all) for the inclusion of performance
statistics to explain analyze (e.g. number of rescans and number of rows
fed to aggregate) provided performance impact is tolerable in both regular
and explain analyze mode.

I wonder how Oracle handles negative transition (does it?), however that is
a different discussion.

Regards,
Vladimir Sitnikov

#154Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#152)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mar5, 2014, at 18:37 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

I think we really need a larger consensus on this though, so I'd be
interested to hear what others think.

My advice is to lose the EXPLAIN output entirely. If the authors of
the patch can't agree on what it means, what hope have everyday users
got of making sense of it?

The question isn't what the current output means, but whether it's a
good metric to report or not.

If we don't report anything, then how would a user check whether a query
is slow because of O(n^2) behaviour of a windowed aggregate, or because
of some other reasons? If inevitability where a purely static property,
then maybe we could get away with that, and say "check whether your
aggregates are invertible or not". But since we have partially invertible
aggregates, the performance characteristics depends on the input data,
so we IMHO need some way for users to check what's actually happening.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#155Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#154)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

On Mar5, 2014, at 18:37 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

My advice is to lose the EXPLAIN output entirely. If the authors of
the patch can't agree on what it means, what hope have everyday users
got of making sense of it?

The question isn't what the current output means, but whether it's a
good metric to report or not.

If you can't agree, then it isn't.

If we don't report anything, then how would a user check whether a query
is slow because of O(n^2) behaviour of a windowed aggregate, or because
of some other reasons?

[ shrug... ] They can see whether the Window plan node is where the time
is going. It's not apparent to me that the extra numbers you propose to
report will edify anybody.

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

#156Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#155)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mar5, 2014, at 23:49 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

On Mar5, 2014, at 18:37 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

My advice is to lose the EXPLAIN output entirely. If the authors of
the patch can't agree on what it means, what hope have everyday users
got of making sense of it?

The question isn't what the current output means, but whether it's a
good metric to report or not.

If you can't agree, then it isn't.

Probably, yes, so let's find something that *is* a good metric.

(BTW, it's not the authors who disagree here. It was me who put the EXPLAIN
feature in, and Dean reviewed it and found it confusing. The original
author David seems to run out of time to work on this, and AFAIK hasn't
weighted in on that particular feature at all)

If we don't report anything, then how would a user check whether a query
is slow because of O(n^2) behaviour of a windowed aggregate, or because
of some other reasons?

[ shrug... ] They can see whether the Window plan node is where the time
is going. It's not apparent to me that the extra numbers you propose to
report will edify anybody.

By the same line of reasoning, every metric except execution time is
superfluous. Comparing execution times really is a horrible way to measure
this - not only does it include all kinds of thing that have nothing to do
with the restart behaviour of aggregates, you'd also have to construct a
base-line query first which is guaranteed to not restart.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#157Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#151)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mar5, 2014, at 18:27 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 5 March 2014 14:35, Florian Pflug <fgp@phlo.org> wrote:

When I added the EXPLAIN stuff, I initially simply reported the number
of times nodeWindowAgg has to restart the aggregation. The problem with
that approach is that not all restarts carry the same cost. If the frames
either don't overlap at all or are identical, restarts don't cause any
additional work. And for fixed-length frames (BETWEEN n PRECEEDING AND
m FOLLOWING), the performance effects of restarts depends on m-n.

Which is why I made it count the number of aggregated input rows instead.

Having said that, I' not really 100% happy with the name
"Transitions Per Row" for this - it was simply the best I could come up with
that was reasonably short. And I'm certainly open to reporting the absolute
count instead of a factor relative to ntuples.

If we made it an absolute count, would calling this "Aggregated Rows" work
for you?

I'm not sure about naming, but I think my preference would be to
output the correct absolute counts for both the forward and inverse
transitions (i.e. multiply by the right combinations of numaggs and
numaggs_restart). The EXPLAIN output already tells you how many
aggregates there are, and how many rows there are, so you'd be able to
work out from that how much extra work it's doing.

Hm, if we do that we might as well go all the way and simply report these
numbers per aggregate, instead of once per window aggregation node. That'd
provide the maximum amount of information, and be quite unambiguous.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#158Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#155)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mar5, 2014, at 23:49 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

On Mar5, 2014, at 18:37 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

My advice is to lose the EXPLAIN output entirely. If the authors of
the patch can't agree on what it means, what hope have everyday users
got of making sense of it?

The question isn't what the current output means, but whether it's a
good metric to report or not.

If you can't agree, then it isn't.

Probably, yes, so let's find something that *is* a good metric.

(BTW, it's not the authors who disagree here. It was me who put the EXPLAIN
feature in, and Dean reviewed it and found it confusing. The original
author David seems to run out of time to work on this, and AFAIK hasn't
weighted in on that particular feature at all)

If we don't report anything, then how would a user check whether a query
is slow because of O(n^2) behaviour of a windowed aggregate, or because
of some other reasons?

[ shrug... ] They can see whether the Window plan node is where the time
is going. It's not apparent to me that the extra numbers you propose to
report will edify anybody.

By the same line of reasoning, every metric except execution time is
superfluous. Comparing execution times really is a horrible way to measure
this - not only does it include all kinds of thing that have nothing to do
with the restart behaviour of aggregates, you'd also have to construct a
base-line query first which is guaranteed to not restart.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#159Greg Stark
stark@mit.edu
In reply to: Tom Lane (#155)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Wed, Mar 5, 2014 at 10:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

[ shrug... ] They can see whether the Window plan node is where the time
is going. It's not apparent to me that the extra numbers you propose to
report will edify anybody.

Perhaps just saying "Incremental Window Function" versus "Iterated
Window Function" or something like that be sufficient? At least that
way query tuning quidelines have a keyword they can say to watch out
for. And someone trying to figure out *why* the time is being spent in
this node has something they might notice a correlation with.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#160David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#152)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, Mar 6, 2014 at 6:37 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

I think we really need a larger consensus on this though, so I'd be
interested to hear what others think.

My advice is to lose the EXPLAIN output entirely. If the authors of
the patch can't agree on what it means, what hope have everyday users
got of making sense of it?

Hi All,

I'd just like to thank Florian and Dean for all the hard work that has gone
on while I've been away.
I've been reading the thread but I've not had much time to respond to
messages and I've lost touch a bit with the progress of the code.

It looks like Florian has addressed everything that Dean pointed out in his
detailed review, apart from the explain output.
I've thought about this and I think it would be a shame if the patch got
delayed for something so trivial. I thought the stats were quite useful as
for things like restarts on SUM(numeric) it could be quite difficult to
tell how many restarts there were otherwise. Since I have no extra ideas
about what might be more useful then I've just pulled it out of the patch
and I propose that we continue without it until someone comes up with an
idea that everyone can agree on.

I've attached an updated invtrans_strictstrict_base patch which has the
feature removed.

Regards

David Rowley

Show quoted text

regards, tom lane

Attachments:

invtrans_strictstrict_base.patchapplication/octet-stream; name=invtrans_strictstrict_base.patchDownload
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..b7fc5f2 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -56,6 +56,7 @@ AggregateCreate(const char *aggName,
 				List *parameterDefaults,
 				Oid variadicArgType,
 				List *aggtransfnName,
+				List *agginvtransfnName,
 				List *aggfinalfnName,
 				List *aggsortopName,
 				Oid aggTransType,
@@ -68,11 +69,13 @@ AggregateCreate(const char *aggName,
 	Datum		values[Natts_pg_aggregate];
 	Form_pg_proc proc;
 	Oid			transfn;
+	Oid			invtransfn = InvalidOid; /* can be omitted */
 	Oid			finalfn = InvalidOid;	/* can be omitted */
 	Oid			sortop = InvalidOid;	/* can be omitted */
 	Oid		   *aggArgTypes = parameterTypes->values;
 	bool		hasPolyArg;
 	bool		hasInternalArg;
+	bool		transIsStrict;
 	Oid			rettype;
 	Oid			finaltype;
 	Oid			fnArgs[FUNC_MAX_ARGS];
@@ -234,8 +237,61 @@ AggregateCreate(const char *aggName,
 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
 	}
+
+	/*
+	 * Remember if trans function is strict as we need to validate this
+	 * later if when we're dealing with the inverse transition function
+	 */
+	transIsStrict = proc->proisstrict;
+
 	ReleaseSysCache(tup);
 
+	/* handle invtransfn, if supplied */
+	if (agginvtransfnName)
+	{
+		/*
+		 * This must have the same number of arguments with the same types as
+		 * the transition function. We can just borrow the argument details
+		 * from the transition function and try to find a function with
+		 * the name of the inverse transition function and with a signature
+		 * that matches the transition function's.
+		 */
+		invtransfn = lookup_agg_function(agginvtransfnName,
+					nargs_transfn, fnArgs, InvalidOid, &rettype);
+
+		/*
+		 * Ensure the return type of the inverse transition function matches
+		 * the expected type.
+		 */
+		if (rettype != aggTransType)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+						errmsg("return type of inverse transition function %s is not %s",
+							NameListToString(agginvtransfnName),
+							format_type_be(aggTransType))));
+
+		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+		if (!HeapTupleIsValid(tup))
+			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+		proc = (Form_pg_proc) GETSTRUCT(tup);
+
+		/*
+		 * We force the strictness settings of the forward and inverse
+		 * transition functions to agree. This allows places which only need
+		 * forward transitions to not look at the inverse transition function
+		 * at all.
+		 */
+		if (transIsStrict != proc->proisstrict)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+						errmsg("strictness of forward and inverse transition functions must match"
+						)));
+		}
+		ReleaseSysCache(tup);
+
+	}
+
 	/* handle finalfn, if supplied */
 	if (aggfinalfnName)
 	{
@@ -391,6 +447,7 @@ AggregateCreate(const char *aggName,
 	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
 	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
 	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
 	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
 	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
 	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
@@ -425,6 +482,15 @@ AggregateCreate(const char *aggName,
 	referenced.objectSubId = 0;
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
+	/* Depends on inverse transition function, if any */
+	if (OidIsValid(invtransfn))
+	{
+		referenced.classId = ProcedureRelationId;
+		referenced.objectId = invtransfn;
+		referenced.objectSubId = 0;
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
 	{
@@ -447,7 +513,8 @@ AggregateCreate(const char *aggName,
 }
 
 /*
- * lookup_agg_function -- common code for finding both transfn and finalfn
+ * lookup_agg_function
+ * common code for finding transfn, invtransfn and finalfn
  */
 static Oid
 lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..c0ea1f7 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -60,6 +60,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 	AclResult	aclresult;
 	char		aggKind = AGGKIND_NORMAL;
 	List	   *transfuncName = NIL;
+	List	   *invtransfuncName = NIL;
 	List	   *finalfuncName = NIL;
 	List	   *sortoperatorName = NIL;
 	TypeName   *baseType = NULL;
@@ -112,6 +113,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 			transfuncName = defGetQualifiedName(defel);
 		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
 			transfuncName = defGetQualifiedName(defel);
+		else if (pg_strcasecmp(defel->defname, "invsfunc") == 0)
+			invtransfuncName = defGetQualifiedName(defel);
 		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
 			finalfuncName = defGetQualifiedName(defel);
 		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
@@ -283,6 +286,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 						   parameterDefaults,
 						   variadicArgType,
 						   transfuncName,		/* step function name */
+						   invtransfuncName,	/* inverse trans function name */
 						   finalfuncName,		/* final function name */
 						   sortoperatorName,	/* sort operator name */
 						   transTypeId, /* transition data type */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..9a7ed93 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1798,8 +1798,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 								aggref->aggtype,
 								aggref->inputcollid,
 								transfn_oid,
+								InvalidOid, /* invtrans is not needed here */
 								finalfn_oid,
 								&transfnexpr,
+								NULL,
 								&finalfnexpr);
 
 		/* set up infrastructure for calling the transfn and finalfn */
@@ -2127,42 +2129,6 @@ ExecReScanAgg(AggState *node)
 }
 
 /*
- * AggCheckCallContext - test if a SQL function is being called as an aggregate
- *
- * The transition and/or final functions of an aggregate may want to verify
- * that they are being called as aggregates, rather than as plain SQL
- * functions.  They should use this function to do so.	The return value
- * is nonzero if being called as an aggregate, or zero if not.	(Specific
- * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
- * values could conceivably appear in future.)
- *
- * If aggcontext isn't NULL, the function also stores at *aggcontext the
- * identity of the memory context that aggregate transition values are
- * being stored in.
- */
-int
-AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
-{
-	if (fcinfo->context && IsA(fcinfo->context, AggState))
-	{
-		if (aggcontext)
-			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
-		return AGG_CONTEXT_AGGREGATE;
-	}
-	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
-	{
-		if (aggcontext)
-			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
-		return AGG_CONTEXT_WINDOW;
-	}
-
-	/* this is just to prevent "uninitialized variable" warnings */
-	if (aggcontext)
-		*aggcontext = NULL;
-	return 0;
-}
-
-/*
  * AggGetAggref - allow an aggregate support function to get its Aggref
  *
  * If the function is being called as an aggregate support function,
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..c3350ac 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -102,18 +102,24 @@ typedef struct WindowStatePerFuncData
  */
 typedef struct WindowStatePerAggData
 {
-	/* Oids of transfer functions */
+	/* Oids of transition functions */
 	Oid			transfn_oid;
+	Oid			invtransfn_oid; /* may be InvalidOid */
 	Oid			finalfn_oid;	/* may be InvalidOid */
 
 	/*
-	 * fmgr lookup data for transfer functions --- only valid when
+	 * fmgr lookup data for transition functions --- only valid when
 	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
 	 * flags are kept here.
 	 */
 	FmgrInfo	transfn;
+	FmgrInfo	invtransfn;
 	FmgrInfo	finalfn;
 
+	/* Aggregate properties */
+	bool		use_invtransfn;			/* whether to use the invtransfn */
+	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+
 	/*
 	 * initial value from pg_aggregate entry
 	 */
@@ -140,10 +146,13 @@ typedef struct WindowStatePerAggData
 	int			wfuncno;		/* index of associated PerFuncData */
 
 	/* Current transition value */
-	Datum		transValue;		/* current transition value */
-	bool		transValueIsNull;
+	MemoryContext	aggcontext;			/* context for transValue */
+	int64			transValueCount;	/* Number of aggregated values*/
+	Datum			transValue;			/* current transition value */
+	bool			transValueIsNull;
 
-	bool		noTransValue;	/* true if transValue not set yet */
+	/* Data local to eval_windowaggregates() */
+	bool			restart;			/* tmp marker that agg needs restart */
 } WindowStatePerAggData;
 
 static void initialize_windowaggregate(WindowAggState *winstate,
@@ -152,6 +161,9 @@ static void initialize_windowaggregate(WindowAggState *winstate,
 static void advance_windowaggregate(WindowAggState *winstate,
 						WindowStatePerFunc perfuncstate,
 						WindowStatePerAgg peraggstate);
+static bool retreat_windowaggregate(WindowAggState *winstate,
+						WindowStatePerFunc perfuncstate,
+						WindowStatePerAgg peraggstate);
 static void finalize_windowaggregate(WindowAggState *winstate,
 						 WindowStatePerFunc perfuncstate,
 						 WindowStatePerAgg peraggstate,
@@ -181,6 +193,53 @@ static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 					TupleTableSlot *slot);
 
+/*
+ * AggCheckCallContext - test if a SQL function is being called as an aggregate
+ *
+ * The transition and/or final functions of an aggregate may want to verify
+ * that they are being called as aggregates, rather than as plain SQL
+ * functions.  They should use this function to do so.	The return value
+ * is nonzero if being called as an aggregate, or zero if not.	(Specific
+ * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+ * values could conceivably appear in future.)
+ *
+ * If aggcontext isn't NULL, the function also stores at *aggcontext the
+ * identity of the memory context that aggregate transition values are
+ * being stored in.
+ *
+ * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+ * is private.
+ *
+ * Note that this function is only meant to be used by aggregate support
+ * functions, NOT by true window functions.
+ */
+int
+AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+{
+	if (fcinfo->context && IsA(fcinfo->context, AggState))
+	{
+		if (aggcontext)
+			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+		return AGG_CONTEXT_AGGREGATE;
+	}
+	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+	{
+		if (aggcontext)
+		{
+			/* Must lookup per-aggregate context */
+			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+			int				aggno = winstate->calledaggno;
+			Assert(0 <= aggno && aggno < winstate->numaggs);
+			*aggcontext = winstate->peragg[aggno].aggcontext;
+		}
+		return AGG_CONTEXT_WINDOW;
+	}
+
+	/* this is just to prevent "uninitialized variable" warnings */
+	if (aggcontext)
+		*aggcontext = NULL;
+	return 0;
+}
 
 /*
  * initialize_windowaggregate
@@ -193,18 +252,26 @@ initialize_windowaggregate(WindowAggState *winstate,
 {
 	MemoryContext oldContext;
 
+	/* If we're using a private aggcontext, we may reset it here. But if the
+	 * context is shared, we don't know which other aggregates may still need
+	 * it, so we must leave it to the caller to reset at an appropriate time
+	 */
+	if (!peraggstate->aggcontext_is_shared)
+		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+
 	if (peraggstate->initValueIsNull)
-		peraggstate->transValue = peraggstate->initValue;
+		peraggstate->transValue = (Datum) 0;
 	else
 	{
-		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
+		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
 		peraggstate->transValue = datumCopy(peraggstate->initValue,
 											peraggstate->transtypeByVal,
 											peraggstate->transtypeLen);
 		MemoryContextSwitchTo(oldContext);
 	}
+	peraggstate->transValueCount = 0;
 	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
-	peraggstate->noTransValue = peraggstate->initValueIsNull;
+	peraggstate->resultValue = (Datum) 0;
 	peraggstate->resultValueIsNull = true;
 }
 
@@ -256,10 +323,7 @@ advance_windowaggregate(WindowAggState *winstate,
 
 	if (peraggstate->transfn.fn_strict)
 	{
-		/*
-		 * For a strict transfn, nothing happens when there's a NULL input; we
-		 * just keep the prior transValue.
-		 */
+		/* Skip NULL inputs for aggregates which desire that. */
 		for (i = 1; i <= numArguments; i++)
 		{
 			if (fcinfo->argnull[i])
@@ -268,41 +332,57 @@ advance_windowaggregate(WindowAggState *winstate,
 				return;
 			}
 		}
-		if (peraggstate->noTransValue)
+
+		/*
+		 * For strict transfer functions with initial value NULL we use the
+		 * first non-NULL input as the initial state. (We already checked that
+		 * the agg's input type is binary-compatible with its transtype, so
+		 * straight copy here is OK.)
+		 *
+		 * We must copy the datum into aggcontext if it is pass-by-ref. We do
+		 * not need to pfree the old transValue, since it's NULL.
+		 */
+		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
 		{
-			/*
-			 * transValue has not been initialized. This is the first non-NULL
-			 * input value. We use it as the initial value for transValue. (We
-			 * already checked that the agg's input type is binary-compatible
-			 * with its transtype, so straight copy here is OK.)
-			 *
-			 * We must copy the datum into aggcontext if it is pass-by-ref. We
-			 * do not need to pfree the old transValue, since it's NULL.
-			 */
-			MemoryContextSwitchTo(winstate->aggcontext);
+			MemoryContextSwitchTo(peraggstate->aggcontext);
 			peraggstate->transValue = datumCopy(fcinfo->arg[1],
 												peraggstate->transtypeByVal,
 												peraggstate->transtypeLen);
 			peraggstate->transValueIsNull = false;
-			peraggstate->noTransValue = false;
+			peraggstate->transValueCount = 1;
 			MemoryContextSwitchTo(oldContext);
 			return;
 		}
+
+		/*
+		 * Don't call a strict function with NULL inputs.  Note it is possible
+		 * to get here despite the above tests, if the transfn is strict *and*
+		 * returned a NULL on a prior cycle. If that happens we will propagate
+		 * the NULL all the way to the end. That can only happen if there's no
+		 * inverse transition function, though, since we disallow transitions
+		 * back to NULL if there is one below.
+		 */
 		if (peraggstate->transValueIsNull)
 		{
-			/*
-			 * Don't call a strict function with NULL inputs.  Note it is
-			 * possible to get here despite the above tests, if the transfn is
-			 * strict *and* returned a NULL on a prior cycle. If that happens
-			 * we will propagate the NULL all the way to the end.
-			 */
 			MemoryContextSwitchTo(oldContext);
+			Assert(peraggstate->invtransfn_oid == InvalidOid);
 			return;
 		}
 	}
 
 	/*
+	 * We must track the number of inputs that we add to transValue, since
+	 * to remove the last input, retreat_windowaggregate() musn't call the
+	 * inverse transition function, but simply reset transValue back to its
+	 * initial value.
+	 */
+	Assert(peraggstate->transValueCount >= 0);
+	peraggstate->transValueCount++;
+
+	/*
 	 * OK to call the transition function
+	 * Transfer functions with an inverse MUST not return NULL, see
+	 * retreat_windowaggregate()
 	 */
 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
 							 numArguments + 1,
@@ -310,7 +390,15 @@ advance_windowaggregate(WindowAggState *winstate,
 							 (void *) winstate, NULL);
 	fcinfo->arg[0] = peraggstate->transValue;
 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+	winstate->calledaggno = perfuncstate->aggno;
 	newVal = FunctionCallInvoke(fcinfo);
+	winstate->calledaggno = -1;
+	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("transition function with an inverse returned NULL")));
+	}
 
 	/*
 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
@@ -322,7 +410,145 @@ advance_windowaggregate(WindowAggState *winstate,
 	{
 		if (!fcinfo->isnull)
 		{
-			MemoryContextSwitchTo(winstate->aggcontext);
+			MemoryContextSwitchTo(peraggstate->aggcontext);
+			newVal = datumCopy(newVal,
+							   peraggstate->transtypeByVal,
+							   peraggstate->transtypeLen);
+		}
+		if (!peraggstate->transValueIsNull)
+			pfree(DatumGetPointer(peraggstate->transValue));
+	}
+
+	MemoryContextSwitchTo(oldContext);
+	peraggstate->transValue = newVal;
+	peraggstate->transValueIsNull = fcinfo->isnull;
+}
+
+/*
+ * retreat_windowaggregate
+ * removes tuples from aggregation.
+ * The calling function must ensure that each aggregate has
+ * a valid inverse transition function.
+ */
+static bool
+retreat_windowaggregate(WindowAggState *winstate,
+						WindowStatePerFunc perfuncstate,
+						WindowStatePerAgg peraggstate)
+{
+	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
+	int			numArguments = perfuncstate->numArguments;
+	FunctionCallInfoData fcinfodata;
+	FunctionCallInfo fcinfo = &fcinfodata;
+	Datum		newVal;
+	ListCell   *arg;
+	int			i;
+	MemoryContext oldContext;
+	ExprContext *econtext = winstate->tmpcontext;
+	ExprState  *filter = wfuncstate->aggfilter;
+
+	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	/* Skip anything FILTERed out */
+	if (filter)
+	{
+		bool		isnull;
+		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+
+		if (isnull || !DatumGetBool(res))
+		{
+			MemoryContextSwitchTo(oldContext);
+			return true;
+		}
+	}
+
+	/* We start from 1, since the 0th arg will be the transition value */
+	i = 1;
+	foreach(arg, wfuncstate->args)
+	{
+		ExprState  *argstate = (ExprState *) lfirst(arg);
+
+		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
+									  &fcinfo->argnull[i], NULL);
+		i++;
+	}
+
+	/* Skip inputs containing NULLS for aggregates that require this */
+	if (peraggstate->invtransfn.fn_strict)
+	{
+		for (i = 1; i <= numArguments; i++)
+		{
+			if (fcinfo->argnull[i])
+			{
+				MemoryContextSwitchTo(oldContext);
+				return true;
+			}
+		}
+	}
+
+	/* There should still be an added but not yet removed value */
+	Assert(peraggstate->transValueCount >= 1);
+
+	/*
+	 * We mustn't use the inverse transition function to remove the last
+	 * input. Doing so would yield a non-NULL state, whereas we should be
+	 * in the initial state afterwards which may very well be NULL. So
+	 * instead, we simply re-initialize the aggregation in this case.
+	 */
+	if (peraggstate->transValueCount == 1)
+	{
+		MemoryContextSwitchTo(oldContext);
+		initialize_windowaggregate(winstate,
+								&winstate->perfunc[peraggstate->wfuncno],
+								peraggstate);
+		return true;
+	}
+
+	/*
+	 * Perform the inverse transition.
+	 *
+	 * For pairs of forward and inverse transition functions, the state may
+	 * never be NULL, except in the ignore_nulls case, and then only until
+	 * until we see the first non-NULL input during which time should never
+	 * attempt to invoke the inverse transition function. Excluding NULL
+	 * as a possible state value allows us to make it mean "sorry, can't
+	 * do an inverse transition in this case" when returned by the inverse
+	 * transition function. In that case, we report the failure to the
+	 * caller.
+	 */
+	if (peraggstate->transValueIsNull)
+	{
+		MemoryContextSwitchTo(oldContext);
+		elog(ERROR, "transition value is NULL during inverse transition");
+	}
+	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
+							 numArguments + 1,
+							 perfuncstate->winCollation,
+							 (void *) winstate, NULL);
+	fcinfo->arg[0] = peraggstate->transValue;
+	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+	winstate->calledaggno = perfuncstate->aggno;
+	newVal = FunctionCallInvoke(fcinfo);
+	winstate->calledaggno = -1;
+	if (fcinfo->isnull)
+	{
+		MemoryContextSwitchTo(oldContext);
+		return false;
+	}
+
+	/* Update number of added but not yet removed values */
+	peraggstate->transValueCount--;
+
+	/*
+	 * If pass-by-ref datatype, must copy the new value into aggcontext and
+	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
+	 * first input, we don't need to do anything.
+	 */
+	if (!peraggstate->transtypeByVal &&
+		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
+	{
+		if (!fcinfo->isnull)
+		{
+			MemoryContextSwitchTo(peraggstate->aggcontext);
 			newVal = datumCopy(newVal,
 							   peraggstate->transtypeByVal,
 							   peraggstate->transtypeLen);
@@ -334,8 +560,11 @@ advance_windowaggregate(WindowAggState *winstate,
 	MemoryContextSwitchTo(oldContext);
 	peraggstate->transValue = newVal;
 	peraggstate->transValueIsNull = fcinfo->isnull;
+
+	return true;
 }
 
+
 /*
  * finalize_windowaggregate
  * parallel to finalize_aggregate in nodeAgg.c
@@ -370,7 +599,9 @@ finalize_windowaggregate(WindowAggState *winstate,
 		}
 		else
 		{
+			winstate->calledaggno = perfuncstate->aggno;
 			*result = FunctionCallInvoke(&fcinfo);
+			winstate->calledaggno = -1;
 			*isnull = fcinfo.isnull;
 		}
 	}
@@ -392,11 +623,14 @@ finalize_windowaggregate(WindowAggState *winstate,
 	MemoryContextSwitchTo(oldContext);
 }
 
+
 /*
  * eval_windowaggregates
  * evaluate plain aggregates being used as window functions
  *
- * Much of this is duplicated from nodeAgg.c.  But NOTE that we expect to be
+ * This differes from nodeAgg.c in two ways. First, if the window's frame
+ * start position moves, we use the inverse transfer function (if it exists)
+ * to remove values from the transition value. And second, we expect to be
  * able to call aggregate final functions repeatedly after aggregating more
  * data onto the same transition value.  This is not a behavior required by
  * nodeAgg.c.
@@ -406,12 +640,15 @@ eval_windowaggregates(WindowAggState *winstate)
 {
 	WindowStatePerAgg peraggstate;
 	int			wfuncno,
-				numaggs;
-	int			i;
+				numaggs,
+				numaggs_restart = 0,
+				i;
+	int64		aggregatedupto_nonrestarted;
 	MemoryContext oldContext;
 	ExprContext *econtext;
 	WindowObject agg_winobj;
-	TupleTableSlot *agg_row_slot;
+	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
+	TupleTableSlot *temp_slot = winstate->temp_slot_1;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -420,7 +657,6 @@ eval_windowaggregates(WindowAggState *winstate)
 	/* final output execution is in ps_ExprContext */
 	econtext = winstate->ss.ps.ps_ExprContext;
 	agg_winobj = winstate->agg_winobj;
-	agg_row_slot = winstate->agg_row_slot;
 
 	/*
 	 * Currently, we support only a subset of the SQL-standard window framing
@@ -438,9 +674,20 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * damage the running transition value, but we have the same assumption in
 	 * nodeAgg.c too (when it rescans an existing hash table).
 	 *
-	 * For other frame start rules, we discard the aggregate state and re-run
-	 * the aggregates whenever the frame head row moves.  We can still
-	 * optimize as above whenever successive rows share the same frame head.
+	 * We can still optimize as above whenever successive rows share the same
+	 * frame head, but if the frame head moves beyond the aggregated base point
+	 * we use the aggregate function's inverse transition function. This
+	 * removes the tuple from aggregation and restores the aggregate's current
+	 * state to what it would be if the removed row had never been aggregated
+	 * in the first place. Inverse transition functions may optionally return
+	 * NULL, this indicates that the function was unable to remove the tuple
+	 * from aggregation, when this happens we must perform the aggregation all
+	 * over again for all tuples in the new frame boundary.
+	 *
+	 * If the aggregate function does not have a inverse transition function
+	 * and the frame head moves beyond the aggregated position then we must
+	 * discard the aggregated state and re-aggregate similar to how we would
+	 * have to if the inverse transition function had returned NULL.
 	 *
 	 * In many common cases, multiple rows share the same frame and hence the
 	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
@@ -452,75 +699,187 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * 'aggregatedupto' keeps track of the first row that has not yet been
 	 * accumulated into the aggregate transition values.  Whenever we start a
 	 * new peer group, we accumulate forward to the end of the peer group.
-	 *
-	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
-	 * some aggregates like SUM and COUNT we could avoid that by implementing
-	 * a "negative transition function" that would be called for each row as
-	 * it exits the frame.	We'd have to think about avoiding recalculation of
-	 * volatile arguments of aggregate functions, too.
 	 */
 
 	/*
 	 * First, update the frame head position.
+	 *
+	 * The frame head should never move backwards, and the code below wouldn't
+	 * cope if it did, so for safety we complain if it does.
 	 */
-	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
+	update_frameheadpos(agg_winobj, temp_slot);
+	if (winstate->frameheadpos < winstate->aggregatedbase)
+		elog(ERROR, "frame moved backwards unexpectedly");
 
 	/*
-	 * Initialize aggregates on first call for partition, or if the frame head
-	 * position moved since last time.
+	 * If the frame didn't change compared to the previous row, we can re-use
+	 * the cached result. Since we don't know the current frame's end yet, we
+	 * cannot check that the obvious way. But we can exploit that if the frame
+	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
+	 * row lies within the previous row's frame, the two frame's ends must
+	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
+	 * so we don't need to check for that explicitly here.
 	 */
-	if (winstate->currentpos == 0 ||
-		winstate->frameheadpos != winstate->aggregatedbase)
+	if (winstate->aggregatedbase == winstate->frameheadpos &&
+		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
+								   FRAMEOPTION_END_CURRENT_ROW)) &&
+		winstate->aggregatedbase <= winstate->currentpos &&
+		winstate->aggregatedupto > winstate->currentpos)
 	{
-		/*
-		 * Discard transient aggregate values
-		 */
-		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
-
 		for (i = 0; i < numaggs; i++)
 		{
 			peraggstate = &winstate->peragg[i];
 			wfuncno = peraggstate->wfuncno;
-			initialize_windowaggregate(winstate,
-									   &winstate->perfunc[wfuncno],
-									   peraggstate);
+			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
+			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
 		}
+		return;
+	}
+
+	/* Initialize restart flags.
+	 *
+	 * We restart the aggregation
+	 *   - if we're processing the first row in the partition, or
+	 *   - if we the frame's head moved and we cannot use an inverse
+	 *     transition function, or
+	 *   - if the new frame doesn't overlap the old one
+	 *
+	 * Note that we don't strictly need to restart in the last case, but
+	 * if we're going to remove *all* rows from the aggregation anyway, a
+	 * restart surely is faster.
+	 */
+	for (i = 0; i < numaggs; i++)
+	{
+		peraggstate = &winstate->peragg[i];
+		if (winstate->currentpos == 0 ||
+			(winstate->aggregatedbase < winstate->frameheadpos &&
+			!peraggstate->use_invtransfn) ||
+			winstate->aggregatedupto <= winstate->frameheadpos)
+		{
+			peraggstate->restart = true;
+			numaggs_restart++;
+		}
+		else
+			peraggstate->restart = false;
+	}
 
+	/*
+	 * Attempt to update aggregatedbase to match the frame's head by
+	 * removing those inputs from the aggregations which fell off the top
+	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+	 * return false, in which case we restart that aggregate below.
+	 *
+	 * Aftwards, aggregatedbase equals frameheadpos.
+	 */
+	while(winstate->aggregatedbase < winstate->frameheadpos)
+	{
 		/*
-		 * If we created a mark pointer for aggregates, keep it pushed up to
-		 * frame head, so that tuplestore can discard unnecessary rows.
+		 * Fetch the tuple where the current aggregation started from.
+		 * This should never fail as we should have been here before.
 		 */
-		if (agg_winobj->markptr >= 0)
-			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+		if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
+								 temp_slot))
+			elog(ERROR, "Unable to find tuple in tuplestore");
+
+		/* Set tuple context for evaluation of aggregate arguments */
+		winstate->tmpcontext->ecxt_outertuple = temp_slot;
 
 		/*
-		 * Initialize for loop below
+		 * Perform the inverse transition for each aggregate function in
+		 * the window, unless it has already been marked as needing a
+		 * restart.
 		 */
-		ExecClearTuple(agg_row_slot);
-		winstate->aggregatedbase = winstate->frameheadpos;
-		winstate->aggregatedupto = winstate->frameheadpos;
+		for (i = 0; i < numaggs; i++)
+		{
+			bool	ok;
+
+			peraggstate = &winstate->peragg[i];
+			if (peraggstate->restart)
+				continue;
+
+			wfuncno = peraggstate->wfuncno;
+			ok = retreat_windowaggregate(winstate,
+										 &winstate->perfunc[wfuncno],
+										 peraggstate);
+			if (!ok)
+			{
+				/* Inverse transition function has failed, must restart */
+				peraggstate->restart = true;
+				numaggs_restart++;
+			}
+		}
+
+		/* Reset per-input-tuple context after each tuple */
+		ResetExprContext(winstate->tmpcontext);
+
+		/* And advance the aggregated-row state */
+		winstate->aggregatedbase++;
+		ExecClearTuple(temp_slot);
+
+		/* If no more retreatable aggregates are left, we stop early */
+		if (numaggs_restart == numaggs)
+		{
+			winstate->aggregatedbase = winstate->frameheadpos;
+			break;
+		}
 	}
 
 	/*
-	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
-	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
-	 * have to recalculate when the frame head moves or currentpos has
-	 * advanced past the place we'd aggregated up to.  Check for these cases
-	 * and if so, reuse the saved result values.
+	 * If we created a mark pointer for aggregates, keep it pushed up to
+	 * frame head, so that tuplestore can discard unnecessary rows.
 	 */
-	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
-								   FRAMEOPTION_END_CURRENT_ROW)) &&
-		winstate->aggregatedbase <= winstate->currentpos &&
-		winstate->aggregatedupto > winstate->currentpos)
+	if (agg_winobj->markptr >= 0)
+		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+
+	/*
+	 * Then restart the aggregates which require it.
+	 *
+	 * We assume that aggregates using the shared context always restart
+	 * if *any* aggregate restarts, and we may thus cleanup the shared
+	 * aggcontext if that is the case. The private aggcontexts are reset
+	 * by initialize_windowaggregate() if their owning aggregate restarts,
+	 * otherwise we just pfree() the cached result.
+	 */
+	if (numaggs_restart > 0)
+		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
+	for (i = 0; i < numaggs; i++)
 	{
-		for (i = 0; i < numaggs; i++)
+		peraggstate = &winstate->peragg[i];
+		/* Aggregates using the shared ctx must restart if *any* agg does */
+		Assert(!peraggstate->aggcontext_is_shared ||
+			   !numaggs_restart || peraggstate->restart);
+		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
+			!peraggstate->resulttypeByVal)
+		{
+			pfree(DatumGetPointer(peraggstate->resultValue));
+			peraggstate->resultValue = (Datum) 0;
+			peraggstate->resultValueIsNull = true;
+		}
+		else if (peraggstate->restart)
 		{
-			peraggstate = &winstate->peragg[i];
 			wfuncno = peraggstate->wfuncno;
-			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
-			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
+			initialize_windowaggregate(winstate,
+									   &winstate->perfunc[wfuncno],
+									   peraggstate);
 		}
-		return;
+	}
+
+	/*
+	 * Non-restarted aggregates now contain the rows between aggregatedbase
+	 * (i.e. frameheadpos) and aggregatedupto, and restarted aggregates
+	 * contain no rows. If there are any restarted aggregates, we must thus
+	 * begin aggregating anew at frameheadpos, otherwise we may simply
+	 * continue at aggregatedupto. Since we possibly reset aggregatedupto, we
+	 * must remember the old value to know how long to skip non-restarted
+	 * aggregates. If we modify aggregatedupto, we must also clear
+	 * agg_row_slot, per the loop invariant below.
+	 */
+	aggregatedupto_nonrestarted = winstate->aggregatedupto;
+	if (numaggs_restart > 0 &&
+		winstate->aggregatedupto != winstate->frameheadpos)
+	{
+		winstate->aggregatedupto = winstate->frameheadpos;
+		ExecClearTuple(agg_row_slot);
 	}
 
 	/*
@@ -551,6 +910,11 @@ eval_windowaggregates(WindowAggState *winstate)
 		for (i = 0; i < numaggs; i++)
 		{
 			peraggstate = &winstate->peragg[i];
+			/* Non-restarted aggs skip until aggregatedupto_nonrestarted */
+			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+				!peraggstate->restart)
+				continue;
+
 			wfuncno = peraggstate->wfuncno;
 			advance_windowaggregate(winstate,
 									&winstate->perfunc[wfuncno],
@@ -565,6 +929,9 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	/* The frame's end is not supposed to move backwards, ever */
+	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+
 	/*
 	 * finalize aggregates and fill result/isnull fields.
 	 */
@@ -589,28 +956,14 @@ eval_windowaggregates(WindowAggState *winstate)
 		 * advance that the next row can't possibly share the same frame. Is
 		 * it worth detecting that and skipping this code?
 		 */
-		if (!peraggstate->resulttypeByVal)
+		if (!peraggstate->resulttypeByVal && !*isnull)
 		{
-			/*
-			 * clear old resultValue in order not to leak memory.  (Note: the
-			 * new result can't possibly be the same datum as old resultValue,
-			 * because we never passed it to the trans function.)
-			 */
-			if (!peraggstate->resultValueIsNull)
-				pfree(DatumGetPointer(peraggstate->resultValue));
-
-			/*
-			 * If pass-by-ref, copy it into our aggregate context.
-			 */
-			if (!*isnull)
-			{
-				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
-				peraggstate->resultValue =
-					datumCopy(*result,
-							  peraggstate->resulttypeByVal,
-							  peraggstate->resulttypeLen);
-				MemoryContextSwitchTo(oldContext);
-			}
+			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
+			peraggstate->resultValue =
+				datumCopy(*result,
+						  peraggstate->resulttypeByVal,
+						  peraggstate->resulttypeLen);
+			MemoryContextSwitchTo(oldContext);
 		}
 		else
 		{
@@ -651,6 +1004,7 @@ eval_windowfunction(WindowAggState *winstate, WindowStatePerFunc perfuncstate,
 	/* Just in case, make all the regular argument slots be null */
 	memset(fcinfo.argnull, true, perfuncstate->numArguments);
 
+	winstate->calledaggno = -1;
 	*result = FunctionCallInvoke(&fcinfo);
 	*isnull = fcinfo.isnull;
 
@@ -794,7 +1148,7 @@ spool_tuples(WindowAggState *winstate, int64 pos)
 	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
 	 * to force the entire partition to get spooled in one go.
 	 *
-	 * XXX this is a horrid kluge --- it'd be better to fix the performance
+	 * XXX this is a horrid kludge --- it'd be better to fix the performance
 	 * problem inside tuplestore.  FIXME
 	 */
 	if (!tuplestore_in_memory(winstate->buffer))
@@ -869,7 +1223,10 @@ release_partition(WindowAggState *winstate)
 	 * aggregates might have allocated data we don't have direct pointers to.
 	 */
 	MemoryContextResetAndDeleteChildren(winstate->partcontext);
-	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
+	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
+	for (i = 0; i < winstate->numaggs; ++i)
+		if (!winstate->peragg[i].aggcontext_is_shared)
+			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
 
 	if (winstate->buffer)
 		tuplestore_end(winstate->buffer);
@@ -1419,7 +1776,8 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	int			numfuncs,
 				wfuncno,
 				numaggs,
-				aggno;
+				aggno,
+				numaggs_invtrans;
 	ListCell   *l;
 
 	/* check for unsupported flags */
@@ -1450,8 +1808,10 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 							  ALLOCSET_DEFAULT_INITSIZE,
 							  ALLOCSET_DEFAULT_MAXSIZE);
 
-	/* Create mid-lived context for aggregate trans values etc */
-	winstate->aggcontext =
+	/* Create mid-lived contexts for aggregate trans values etc
+	 * Note that invertible aggregates use their own private context
+	 */
+	winstate->aggcontext_shared =
 		AllocSetContextCreate(CurrentMemoryContext,
 							  "WindowAgg_Aggregates",
 							  ALLOCSET_DEFAULT_MINSIZE,
@@ -1535,6 +1895,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 
 	wfuncno = -1;
 	aggno = -1;
+	numaggs_invtrans = 0;
 	foreach(l, winstate->funcs)
 	{
 		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
@@ -1603,6 +1964,8 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 			peraggstate = &winstate->peragg[aggno];
 			initialize_peragg(winstate, wfunc, peraggstate);
 			peraggstate->wfuncno = wfuncno;
+			if (peraggstate->use_invtransfn)
+				numaggs_invtrans++;
 		}
 		else
 		{
@@ -1618,6 +1981,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	/* Update numfuncs, numaggs to match number of unique functions found */
 	winstate->numfuncs = wfuncno + 1;
 	winstate->numaggs = aggno + 1;
+	winstate->numaggs_invtrans = numaggs_invtrans;
 
 	/* Set up WindowObject for aggregates, if needed */
 	if (winstate->numaggs > 0)
@@ -1646,6 +2010,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
 
+	/* initialize temporary data */
+	winstate->calledaggno = -1;
+
 	return winstate;
 }
 
@@ -1657,12 +2024,10 @@ void
 ExecEndWindowAgg(WindowAggState *node)
 {
 	PlanState  *outerPlan;
+	int			i;
 
 	release_partition(node);
 
-	pfree(node->perfunc);
-	pfree(node->peragg);
-
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
 	ExecClearTuple(node->first_part_slot);
 	ExecClearTuple(node->agg_row_slot);
@@ -1677,7 +2042,13 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecFreeExprContext(&node->ss.ps);
 
 	MemoryContextDelete(node->partcontext);
-	MemoryContextDelete(node->aggcontext);
+	MemoryContextDelete(node->aggcontext_shared);
+	for(i = 0; i < node->numaggs; i++)
+		if (!node->peragg[i].aggcontext_is_shared)
+			MemoryContextDelete(node->peragg[i].aggcontext);
+
+	pfree(node->perfunc);
+	pfree(node->peragg);
 
 	outerPlan = outerPlanState(node);
 	ExecEndNode(outerPlan);
@@ -1735,8 +2106,10 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 	Oid			aggtranstype;
 	AclResult	aclresult;
 	Oid			transfn_oid,
+				invtransfn_oid,
 				finalfn_oid;
 	Expr	   *transfnexpr,
+			   *invtransfnexpr,
 			   *finalfnexpr;
 	Datum		textInitVal;
 	int			i;
@@ -1762,6 +2135,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 	 */
 
 	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
 	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
 
 	/* Check that aggregate owner has permission to call component fns */
@@ -1783,6 +2157,17 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 			aclcheck_error(aclresult, ACL_KIND_PROC,
 						   get_func_name(transfn_oid));
 		InvokeFunctionExecuteHook(transfn_oid);
+
+		if (OidIsValid(invtransfn_oid))
+		{
+			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+										 ACL_EXECUTE);
+			if (aclresult != ACLCHECK_OK)
+				aclcheck_error(aclresult, ACL_KIND_PROC,
+				get_func_name(invtransfn_oid));
+			InvokeFunctionExecuteHook(invtransfn_oid);
+		}
+
 		if (OidIsValid(finalfn_oid))
 		{
 			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
@@ -1810,13 +2195,21 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 							wfunc->wintype,
 							wfunc->inputcollid,
 							transfn_oid,
+							invtransfn_oid,
 							finalfn_oid,
 							&transfnexpr,
+							&invtransfnexpr,
 							&finalfnexpr);
 
 	fmgr_info(transfn_oid, &peraggstate->transfn);
 	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
 
+	if (OidIsValid(invtransfn_oid))
+	{
+		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+	}
+
 	if (OidIsValid(finalfn_oid))
 	{
 		fmgr_info(finalfn_oid, &peraggstate->finalfn);
@@ -1860,8 +2253,74 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 							wfunc->winfnoid)));
 	}
 
+	/*
+	 * Allowing only the forward transition function to be strict would
+	 * require handling more special cases in advance_windowaggregate() and
+	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+	 * that if the forward transition function is strict that the inverse
+	 * transition function is also strict. This should have been checked at
+	 * the aggregate function's definition time, but it's better to be safe...
+	 */
+	if (OidIsValid(invtransfn_oid) &&
+		peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+				errmsg("strictness of forward and inverse transition functions must match")));
+	}
+
 	ReleaseSysCache(aggTuple);
 
+	/*
+	 * We can use the inverse transition function only if the aggregate's
+	 * arguments don't contain calls to volatile functions. Otherwise,
+	 * the difference between restarting and not restarting the aggregation
+	 * would be user-visible. Note that this check also covers the case where
+	 * the FILTER's WHERE clause contains a volatile function. If the frame
+	 * head cannot move, we won't ever need the inverse transition function,
+	 * so we also mark as "don't use" in that case.
+	 */
+	if (OidIsValid(invtransfn_oid) &&
+		!contain_volatile_functions((Node *) wfunc) &&
+		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+	{
+		peraggstate->use_invtransfn = true;
+	}
+	else
+	{
+		peraggstate->use_invtransfn = false;
+	}
+
+	/*
+	 * Invertible aggregates use their own aggcontext.
+	 *
+	 * This is necessary because they might all restart at different times,
+	 * so we might never be able to reset the shared context otherwise. We
+	 * can't make it the aggregate's responsibility to clean up after
+	 * themselves, because strict aggregates must be restarted whenever we
+	 * remove their last non-NULL input, which the aggregate won't be aware
+	 * is happening. Also, just pfree()ing the transValue upon restarting
+	 * wouldn't help, since we'd miss any indirectly referenced data. We
+	 * could, in theory, declare that aggregates with a state type other then
+	 * "internal" musn't allocate anything in the aggcontext themselves, that
+	 * non-strict aggregates with state type internal must clean up after
+	 * themselves when their inverse transfer function returns NULL, and then
+	 * only use private aggcontexts for strict aggregates with state type
+	 * internal. But that'd be a rather grotty set of requirements.
+	 */
+	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+	if (!peraggstate->aggcontext_is_shared)
+	{
+		peraggstate->aggcontext =
+			AllocSetContextCreate(CurrentMemoryContext,
+								  "WindowAgg_AggregatePrivate",
+								  ALLOCSET_DEFAULT_MINSIZE,
+								  ALLOCSET_DEFAULT_INITSIZE,
+								  ALLOCSET_DEFAULT_MAXSIZE);
+	}
+	else
+		peraggstate->aggcontext = winstate->aggcontext_shared;
+
 	return peraggstate;
 }
 
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..ef84e4c 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -1187,11 +1187,13 @@ resolve_aggregate_transtype(Oid aggfuncid,
  * For an ordered-set aggregate, remember that agg_input_types describes
  * the direct arguments followed by the aggregated arguments.
  *
- * transfn_oid and finalfn_oid identify the funcs to be called; the latter
- * may be InvalidOid.
+ * transfn_oid, invtransfn_oid and finalfn_oid identify the funcs to be
+ * called; the latter two may be InvalidOid.
  *
- * Pointers to the constructed trees are returned into *transfnexpr and
- * *finalfnexpr.  The latter is set to NULL if there's no finalfn.
+ * Pointers to the constructed trees are returned into *transfnexpr,
+ * *invtransfnexpr and *finalfnexpr. If there is invtransfn or finalfn, the
+ * respective pointers are set to NULL. Since use of the invtransfn is
+ * optional, NULL may be passed for invtransfnexpr.
  */
 void
 build_aggregate_fnexprs(Oid *agg_input_types,
@@ -1203,8 +1205,10 @@ build_aggregate_fnexprs(Oid *agg_input_types,
 						Oid agg_result_type,
 						Oid agg_input_collation,
 						Oid transfn_oid,
+						Oid invtransfn_oid,
 						Oid finalfn_oid,
 						Expr **transfnexpr,
+						Expr **invtransfnexpr,
 						Expr **finalfnexpr)
 {
 	Param	   *argp;
@@ -1249,6 +1253,18 @@ build_aggregate_fnexprs(Oid *agg_input_types,
 	fexpr->funcvariadic = agg_variadic;
 	*transfnexpr = (Expr *) fexpr;
 
+	if (OidIsValid(invtransfn_oid) && invtransfnexpr != NULL)
+	{
+		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+												agg_result_type,
+												args,
+												InvalidOid,
+												agg_input_collation,
+												COERCE_EXPLICIT_CALL);
+	}
+	else if (invtransfnexpr != NULL)
+		*invtransfnexpr	= NULL;
+
 	/* see if we have a final function */
 	if (!OidIsValid(finalfn_oid))
 	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2ce8e6d..21a7347 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11512,6 +11512,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	char	   *aggsig_tag;
 	PGresult   *res;
 	int			i_aggtransfn;
+	int			i_agginvtransfn;
 	int			i_aggfinalfn;
 	int			i_aggsortop;
 	int			i_hypothetical;
@@ -11520,6 +11521,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	int			i_agginitval;
 	int			i_convertok;
 	const char *aggtransfn;
+	const char *agginvtransfn;
 	const char *aggfinalfn;
 	const char *aggsortop;
 	bool		hypothetical;
@@ -11544,7 +11546,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	/* Get aggregate-specific details */
 	if (fout->remoteVersion >= 90400)
 	{
-		appendPQExpBuffer(query, "SELECT aggtransfn, "
+		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
 						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
 						  "aggsortop::pg_catalog.regoperator, "
 						  "(aggkind = 'h') as hypothetical, "
@@ -11560,6 +11562,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBuffer(query, "SELECT aggtransfn, "
+						  "'-'::pg_catalog.regproc AS agginvtransfn, "
 						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
 						  "aggsortop::pg_catalog.regoperator, "
 						  "false as hypothetical, "
@@ -11575,6 +11578,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	else if (fout->remoteVersion >= 80100)
 	{
 		appendPQExpBuffer(query, "SELECT aggtransfn, "
+						  "'-'::pg_catalog.regproc AS agginvtransfn, "
 						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
 						  "aggsortop::pg_catalog.regoperator, "
 						  "false as hypothetical, "
@@ -11588,6 +11592,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	else if (fout->remoteVersion >= 70300)
 	{
 		appendPQExpBuffer(query, "SELECT aggtransfn, "
+						  "'-'::pg_catalog.regproc AS agginvtransfn, "
 						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
 						  "0 AS aggsortop, "
 						  "'f'::boolean as hypothetical, "
@@ -11600,7 +11605,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	}
 	else if (fout->remoteVersion >= 70100)
 	{
-		appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
+		appendPQExpBuffer(query, "SELECT aggtransfn, "
+						  "'-'::pg_catalog.regproc AS agginvtransfn, "
+						  "aggfinalfn, "
 						  "format_type(aggtranstype, NULL) AS aggtranstype, "
 						  "0 AS aggsortop, "
 						  "'f'::boolean as hypothetical, "
@@ -11613,6 +11620,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	else
 	{
 		appendPQExpBuffer(query, "SELECT aggtransfn1 AS aggtransfn, "
+						  "'-'::pg_catalog.regproc AS agginvtransfn, "
 						  "aggfinalfn, "
 						  "(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
 						  "0 AS aggsortop, "
@@ -11627,6 +11635,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	res = ExecuteSqlQueryForSingleRow(fout, query->data);
 
 	i_aggtransfn = PQfnumber(res, "aggtransfn");
+	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
 	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
 	i_aggsortop = PQfnumber(res, "aggsortop");
 	i_hypothetical = PQfnumber(res, "hypothetical");
@@ -11636,6 +11645,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 	i_convertok = PQfnumber(res, "convertok");
 
 	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
 	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
 	aggsortop = PQgetvalue(res, 0, i_aggsortop);
 	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
@@ -11694,6 +11704,12 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 						  fmtId(aggtranstype));
 	}
 
+	if (strcmp(agginvtransfn, "-") != 0)
+	{
+		appendPQExpBuffer(details, ",\n    INVSFUNC = %s",
+						  agginvtransfn);
+	}
+
 	if (strcmp(aggtransspace, "0") != 0)
 	{
 		appendPQExpBuffer(details, ",\n    SSPACE = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index f189998..3412661 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -46,6 +46,7 @@ CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
 	char		aggkind;
 	int16		aggnumdirectargs;
 	regproc		aggtransfn;
+	regproc		agginvtransfn;
 	regproc		aggfinalfn;
 	Oid			aggsortop;
 	Oid			aggtranstype;
@@ -68,16 +69,17 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
  * ----------------
  */
 
-#define Natts_pg_aggregate					9
+#define Natts_pg_aggregate					10
 #define Anum_pg_aggregate_aggfnoid			1
 #define Anum_pg_aggregate_aggkind			2
 #define Anum_pg_aggregate_aggnumdirectargs	3
 #define Anum_pg_aggregate_aggtransfn		4
-#define Anum_pg_aggregate_aggfinalfn		5
-#define Anum_pg_aggregate_aggsortop			6
-#define Anum_pg_aggregate_aggtranstype		7
-#define Anum_pg_aggregate_aggtransspace		8
-#define Anum_pg_aggregate_agginitval		9
+#define Anum_pg_aggregate_agginvtransfn		5
+#define Anum_pg_aggregate_aggfinalfn		6
+#define Anum_pg_aggregate_aggsortop			7
+#define Anum_pg_aggregate_aggtranstype		8
+#define Anum_pg_aggregate_aggtransspace		9
+#define Anum_pg_aggregate_agginitval		10
 
 /*
  * Symbolic values for aggkind column.	We distinguish normal aggregates
@@ -101,177 +103,177 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
  */
 
 /* avg */
-DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
-DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
-DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
-DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
-DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
+DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
+DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
+DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
+DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
+DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
 
 /* sum */
-DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
-DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
-DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
-DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
-DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
-DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
-DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
-DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
+DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
+DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
+DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
+DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
+DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
+DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
+DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
+DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
 
 /* max */
-DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
-DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
-DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
-DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
-DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
-DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
-DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
-DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
-DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
-DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
-DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
-DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
-DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
-DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
-DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
-DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
-DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
-DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
-DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
-DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
+DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
+DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
+DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
+DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
+DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
+DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
+DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
+DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
+DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
+DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
+DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
+DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
+DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
+DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
+DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
+DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
+DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
+DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
+DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
+DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
 
 /* min */
-DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
-DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
-DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
-DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
-DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
-DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
-DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
-DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
-DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
-DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
-DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
-DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
-DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
-DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
-DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
-DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
-DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
-DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
-DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
-DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
+DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
+DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
+DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
+DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
+DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
+DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
+DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
+DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
+DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
+DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
+DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
+DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
+DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
+DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
+DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
+DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
+DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
+DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
+DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
+DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
 
 /* count */
-DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
-DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
+DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
+DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
 
 /* var_pop */
-DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
-DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
-DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
-DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
+DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
+DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
+DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
+DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
 
 /* var_samp */
-DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
-DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
-DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
-DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
+DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
+DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
+DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
+DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
 
 /* variance: historical Postgres syntax for var_samp */
-DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
-DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
-DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
-DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
+DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
+DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
+DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
+DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
 
 /* stddev_pop */
-DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
-DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
-DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
-DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
+DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
+DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
+DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
+DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
 
 /* stddev_samp */
-DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
-DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
-DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
-DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
+DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
+DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
+DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
+DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
 
 /* stddev: historical Postgres syntax for stddev_samp */
-DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
-DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
-DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
-DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
-DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
+DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
+DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
+DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
+DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
+DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
 
 /* SQL2003 binary regression aggregates */
-DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
-DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
-DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
+DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
+DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
 
 /* boolean-and and boolean-or */
-DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
-DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
-DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
+DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
+DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
+DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
 
 /* bitwise integer */
-DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
-DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
-DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
-DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
-DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
-DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
-DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
-DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
+DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
+DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
+DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
+DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
+DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
+DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
+DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
+DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
 
 /* xml */
-DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
+DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
 
 /* array */
-DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
+DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
 
 /* text */
-DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
+DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
 
 /* bytea */
-DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
+DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
 
 /* json */
-DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
-DATA(insert ( 3197	n 0 json_object_agg_transfn	json_object_agg_finalfn	0	2281	0	_null_ ));
+DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
+DATA(insert ( 3197	n 0 json_object_agg_transfn	-	json_object_agg_finalfn	0	2281	0	_null_ ));
 
 /* ordered-set and hypothetical-set aggregates */
-DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
-DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
-DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
-DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
-DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
-DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
-DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
-DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
-DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
-DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
-DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
+DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
+DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
+DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
+DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
+DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
+DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
+DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
+DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
+DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
+DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
+DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
 
 
 /*
@@ -289,6 +291,7 @@ extern Oid AggregateCreate(const char *aggName,
 				List *parameterDefaults,
 				Oid variadicArgType,
 				List *aggtransfnName,
+				List *agginvtransfnName,
 				List *aggfinalfnName,
 				List *aggsortopName,
 				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index aed81cd..c4d4864 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -645,8 +645,10 @@ extern void **find_rendezvous_variable(const char *varName);
 /*
  * Support for aggregate functions
  *
- * These are actually in executor/nodeAgg.c, but we declare them here since
- * the whole point is for callers to not be overly friendly with nodeAgg.
+ * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
+ * which is in execute/nodeWindowAgg.c, but we declare them here since
+ * the whole point is for callers to not be overly friendly neither nodeAgg
+ * nor nodeWindowAgg.
  */
 
 /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..dbce296 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1738,6 +1738,7 @@ typedef struct WindowAggState
 	List	   *funcs;			/* all WindowFunc nodes in targetlist */
 	int			numfuncs;		/* total number of window functions */
 	int			numaggs;		/* number that are plain aggregates */
+	int			numaggs_invtrans;	/* number that are invertible aggregates */
 
 	WindowStatePerFunc perfunc; /* per-window-function information */
 	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
@@ -1762,7 +1763,7 @@ typedef struct WindowAggState
 	Datum		endOffsetValue; /* result of endOffset evaluation */
 
 	MemoryContext partcontext;	/* context for partition-lifespan data */
-	MemoryContext aggcontext;	/* context for each aggregate data */
+	MemoryContext aggcontext_shared;	/* shared context for agg states */
 	ExprContext *tmpcontext;	/* short-term evaluation context */
 
 	bool		all_first;		/* true if the scan is starting */
@@ -1780,6 +1781,9 @@ typedef struct WindowAggState
 	TupleTableSlot *first_part_slot;	/* first tuple of current or next
 										 * partition */
 
+	/* temporary data */
+	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+
 	/* temporary slots for tuples fetched back from tuplestore */
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -39,8 +39,10 @@ extern void build_aggregate_fnexprs(Oid *agg_input_types,
 						Oid agg_result_type,
 						Oid agg_input_collation,
 						Oid transfn_oid,
+						Oid invtransfn_oid,
 						Oid finalfn_oid,
 						Expr **transfnexpr,
+						Expr **invtransfnexpr,
 						Expr **finalfnexpr);
 
 #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..080cbd2 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1580,3 +1580,27 @@ select least_agg(variadic array[q1,q2]) from int8_tbl;
  -4567890123456789
 (1 row)
 
+-- built in aggregate function transition function args must match the inverse
+-- transition function arguments.
+SELECT ptrns.proname, pinvtrns.proname
+  FROM pg_aggregate agg
+  INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+  INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+  WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+    AND agg.aggfnoid <= 9999;
+ proname | proname 
+---------+---------
+(0 rows)
+
+-- built in aggregate function transition function may only be strict if
+-- the inverse is also strict.
+SELECT ptrns.proname, pinvtrns.proname
+  FROM pg_aggregate agg
+  INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+  INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+  WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+    AND agg.aggfnoid <= 9999;
+ proname | proname 
+---------+---------
+(0 rows)
+
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..1a52423 100644
--- a/src/test/regress/expected/create_aggregate.out
+++ b/src/test/regress/expected/create_aggregate.out
@@ -90,3 +90,40 @@ alter aggregate my_rank(VARIADIC "any" ORDER BY VARIADIC "any")
  public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
 (2 rows)
 
+-- inverse transition functions
+CREATE AGGREGATE sumdouble (float8)
+(
+    stype = float8,
+    sfunc = float8pl,
+    invsfunc = float8mi
+);
+CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+$$ SELECT $1 - $2; $$
+LANGUAGE SQL;
+CREATE AGGREGATE invalidsumdouble (float8)
+(
+    stype = float8,
+    sfunc = float8pl,
+    invsfunc = float8mi_n
+);
+ERROR:  strictness of forward and inverse transition functions must match
+CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+$$ SELECT CAST($1 - $2 AS float8); $$
+LANGUAGE SQL;
+CREATE AGGREGATE wrongtype (float8)
+(
+    stype = float8,
+    sfunc = float8pl,
+    invsfunc = intminus
+);
+ERROR:  function intminus(double precision, double precision) does not exist
+CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+$$ SELECT CAST($1 - $2 AS INT); $$
+LANGUAGE SQL;
+CREATE AGGREGATE wrongreturntype (float8)
+(
+    stype = float8,
+    sfunc = float8pl,
+    invsfunc = float8mi_int
+);
+ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..8af3d23 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1071,3 +1071,237 @@ SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
              1 |   3 |    3
 (10 rows)
 
+--
+--
+-- Test the basic inverse transition function machinery
+--
+--
+-- create aggregates which log their calls
+-- invsfunc is *not* a real inverse here!
+CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+$$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+LANGUAGE SQL IMMUTABLE;
+CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+$$SELECT $1 || '-' || quote_nullable($2)$$
+LANGUAGE SQL IMMUTABLE;
+CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+(
+	stype = text,
+	sfunc = logging_sfunc_nonstrict,
+	invsfunc = logging_invsfunc_nonstrict
+);
+CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+(
+	stype = text,
+	sfunc = logging_sfunc_nonstrict,
+	invsfunc = logging_invsfunc_nonstrict,
+	initcond = 'I'
+);
+CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+$$SELECT $1 || '+' || quote_nullable($2)$$
+LANGUAGE SQL STRICT IMMUTABLE;
+CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+$$SELECT $1 || '-' || quote_nullable($2)$$
+LANGUAGE SQL STRICT IMMUTABLE;
+CREATE AGGREGATE logging_agg_strict (text)
+(
+	stype = text,
+	sfunc = logging_sfunc_strict,
+	invsfunc = logging_invsfunc_strict
+);
+CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+(
+	stype = text,
+	sfunc = logging_sfunc_strict,
+	invsfunc = logging_invsfunc_strict,
+	initcond = 'I'
+);
+-- test strict and non-strict cases
+SELECT
+	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+	logging_agg_nonstrict(v) over wnd as nstrict,
+	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+	logging_agg_strict(v::text) over wnd as strict,
+	logging_agg_strict_initcond(v) over wnd as strict_init
+FROM (VALUES
+	(1, 1, NULL),
+	(1, 2, 'a'),
+	(1, 3, 'b'),
+	(1, 4, NULL),
+	(1, 5, NULL),
+	(1, 6, 'c'),
+	(2, 1, NULL),
+	(2, 2, 'x'),
+	(3, 1, 'z')
+) AS t(p, i, v)
+WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ORDER BY p, i;
+   row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+ 1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+ 1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+ 1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+ 1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+ 1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+ 1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+ 2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+ 2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+ 3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+(9 rows)
+
+-- and again, but with filter
+SELECT
+	p::text || ',' || i::text || ':' ||
+		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+FROM (VALUES
+	(1, 1, true,  NULL),
+	(1, 2, false, 'a'),
+	(1, 3, true,  'b'),
+	(1, 4, false, NULL),
+	(1, 5, false, NULL),
+	(1, 6, false, 'c'),
+	(2, 1, false, NULL),
+	(2, 2, true,  'x'),
+	(3, 1, true,  'z')
+) AS t(p, i, f, v)
+WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ORDER BY p, i;
+   row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+----------+--------------+-------------------+-------------+------------------
+ 1,1:NULL | +NULL        | I+NULL            |             | I
+ 1,2:-    | +NULL        | I+NULL            |             | I
+ 1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+ 1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+ 1,5:-    |              | I                 |             | I
+ 1,6:-    |              | I                 |             | I
+ 2,1:-    |              | I                 |             | I
+ 2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+ 3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+(9 rows)
+
+-- tests that volatile arguments disable inverse transition functions
+SELECT
+	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+	logging_agg_strict(v::text)
+		over wnd as inverse,
+	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+		over wnd as noinverse
+FROM (VALUES
+	(1, 'a'),
+	(2, 'b'),
+	(3, 'c')
+) AS t(i, v)
+WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ORDER BY i;
+ row |    inverse    | noinverse 
+-----+---------------+-----------
+ 1:a | a             | a
+ 2:b | a+'b'         | a+'b'
+ 3:c | a+'b'-'a'+'c' | b+'c'
+(3 rows)
+
+-- tests that volatile filters disable inverse transition functions
+SELECT
+	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+	logging_agg_strict(v::text) filter(where true)
+		over wnd as inverse,
+	logging_agg_strict(v::text) filter(where random() >= 0)
+		over wnd as noinverse
+FROM (VALUES
+	(1, 'a'),
+	(2, 'b'),
+	(3, 'c')
+) AS t(i, v)
+WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ORDER BY i;
+ row |    inverse    | noinverse 
+-----+---------------+-----------
+ 1:a | a             | a
+ 2:b | a+'b'         | a+'b'
+ 3:c | a+'b'-'a'+'c' | b+'c'
+(3 rows)
+
+-- test that non-overlapping windows don't use inverse transitions
+SELECT
+	logging_agg_strict(v::text) OVER wnd
+FROM (VALUES
+	(1, 'a'),
+	(2, 'b'),
+	(3, 'c')
+) AS t(i, v)
+WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ORDER BY i;
+ logging_agg_strict 
+--------------------
+ a
+ b
+ c
+(3 rows)
+
+-- test that returning NULL from the inverse transition functions
+-- restarts the aggregation from scratch. The second aggregate is supposed
+-- to test cases where only some aggregates restart, the third one checks
+-- the one aggregate restarting doesn't cause others to restart
+CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+$$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+LANGUAGE SQL STRICT IMMUTABLE;
+CREATE AGGREGATE sum_int_randomrestart (int4)
+(
+	stype = int4,
+	sfunc = int4pl,
+	invsfunc = sum_int_randrestart_invsfunc
+);
+WITH
+vs AS (
+	SELECT i, (random() * 100)::int4 AS v
+	FROM generate_series(1, 100) AS i
+),
+sum_following AS (
+	SELECT i, SUM(v) OVER
+		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+	FROM vs
+)
+SELECT DISTINCT
+	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+FROM vs
+JOIN sum_following ON sum_following.i = vs.i
+WINDOW fwd AS (
+	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+);
+ eq1 | eq2 | eq3 
+-----+-----+-----
+ t   | t   | t
+(1 row)
+
+-- cleanup aggregates
+DROP AGGREGATE sum_int_randomrestart (int4);
+DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+DROP AGGREGATE logging_agg_strict(text);
+DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+DROP FUNCTION logging_sfunc_strict(text, anyelement);
+DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+DROP AGGREGATE logging_agg_nonstrict(anyelement);
+DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+--
+--
+-- Test the arithmetic inverse transition functions
+--
+--
+--
+--
+-- Test the MIN, MAX and boolean inverse transition functions
+--
+--
+--
+--
+-- Tests of the collecting inverse transition functions
+--
+--
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..34f7004 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -590,3 +590,20 @@ drop view aggordview1;
 -- variadic aggregates
 select least_agg(q1,q2) from int8_tbl;
 select least_agg(variadic array[q1,q2]) from int8_tbl;
+-- built in aggregate function transition function args must match the inverse
+-- transition function arguments.
+SELECT ptrns.proname, pinvtrns.proname
+  FROM pg_aggregate agg
+  INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+  INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+  WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+    AND agg.aggfnoid <= 9999;
+
+-- built in aggregate function transition function may only be strict if
+-- the inverse is also strict.
+SELECT ptrns.proname, pinvtrns.proname
+  FROM pg_aggregate agg
+  INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+  INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+  WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+    AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..130489c 100644
--- a/src/test/regress/sql/create_aggregate.sql
+++ b/src/test/regress/sql/create_aggregate.sql
@@ -101,3 +101,44 @@ alter aggregate my_rank(VARIADIC "any" ORDER BY VARIADIC "any")
   rename to test_rank;
 
 \da test_*
+
+-- inverse transition functions
+CREATE AGGREGATE sumdouble (float8)
+(
+    stype = float8,
+    sfunc = float8pl,
+    invsfunc = float8mi
+);
+
+CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+$$ SELECT $1 - $2; $$
+LANGUAGE SQL;
+
+CREATE AGGREGATE invalidsumdouble (float8)
+(
+    stype = float8,
+    sfunc = float8pl,
+    invsfunc = float8mi_n
+);
+
+CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+$$ SELECT CAST($1 - $2 AS float8); $$
+LANGUAGE SQL;
+
+CREATE AGGREGATE wrongtype (float8)
+(
+    stype = float8,
+    sfunc = float8pl,
+    invsfunc = intminus
+);
+
+CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+$$ SELECT CAST($1 - $2 AS INT); $$
+LANGUAGE SQL;
+
+CREATE AGGREGATE wrongreturntype (float8)
+(
+    stype = float8,
+    sfunc = float8pl,
+    invsfunc = float8mi_int
+);
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..117bd6c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -284,3 +284,206 @@ SELECT nth_value_def(n := 2, val := ten) OVER (PARTITION BY four), ten, four
 
 SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
   FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+
+--
+--
+-- Test the basic inverse transition function machinery
+--
+--
+
+-- create aggregates which log their calls
+-- invsfunc is *not* a real inverse here!
+CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+$$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+LANGUAGE SQL IMMUTABLE;
+
+CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+$$SELECT $1 || '-' || quote_nullable($2)$$
+LANGUAGE SQL IMMUTABLE;
+
+CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+(
+	stype = text,
+	sfunc = logging_sfunc_nonstrict,
+	invsfunc = logging_invsfunc_nonstrict
+);
+
+CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+(
+	stype = text,
+	sfunc = logging_sfunc_nonstrict,
+	invsfunc = logging_invsfunc_nonstrict,
+	initcond = 'I'
+);
+
+CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+$$SELECT $1 || '+' || quote_nullable($2)$$
+LANGUAGE SQL STRICT IMMUTABLE;
+
+CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+$$SELECT $1 || '-' || quote_nullable($2)$$
+LANGUAGE SQL STRICT IMMUTABLE;
+
+CREATE AGGREGATE logging_agg_strict (text)
+(
+	stype = text,
+	sfunc = logging_sfunc_strict,
+	invsfunc = logging_invsfunc_strict
+);
+
+CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+(
+	stype = text,
+	sfunc = logging_sfunc_strict,
+	invsfunc = logging_invsfunc_strict,
+	initcond = 'I'
+);
+
+-- test strict and non-strict cases
+SELECT
+	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+	logging_agg_nonstrict(v) over wnd as nstrict,
+	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+	logging_agg_strict(v::text) over wnd as strict,
+	logging_agg_strict_initcond(v) over wnd as strict_init
+FROM (VALUES
+	(1, 1, NULL),
+	(1, 2, 'a'),
+	(1, 3, 'b'),
+	(1, 4, NULL),
+	(1, 5, NULL),
+	(1, 6, 'c'),
+	(2, 1, NULL),
+	(2, 2, 'x'),
+	(3, 1, 'z')
+) AS t(p, i, v)
+WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ORDER BY p, i;
+
+-- and again, but with filter
+SELECT
+	p::text || ',' || i::text || ':' ||
+		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+FROM (VALUES
+	(1, 1, true,  NULL),
+	(1, 2, false, 'a'),
+	(1, 3, true,  'b'),
+	(1, 4, false, NULL),
+	(1, 5, false, NULL),
+	(1, 6, false, 'c'),
+	(2, 1, false, NULL),
+	(2, 2, true,  'x'),
+	(3, 1, true,  'z')
+) AS t(p, i, f, v)
+WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ORDER BY p, i;
+
+-- tests that volatile arguments disable inverse transition functions
+SELECT
+	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+	logging_agg_strict(v::text)
+		over wnd as inverse,
+	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+		over wnd as noinverse
+FROM (VALUES
+	(1, 'a'),
+	(2, 'b'),
+	(3, 'c')
+) AS t(i, v)
+WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ORDER BY i;
+
+-- tests that volatile filters disable inverse transition functions
+SELECT
+	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+	logging_agg_strict(v::text) filter(where true)
+		over wnd as inverse,
+	logging_agg_strict(v::text) filter(where random() >= 0)
+		over wnd as noinverse
+FROM (VALUES
+	(1, 'a'),
+	(2, 'b'),
+	(3, 'c')
+) AS t(i, v)
+WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ORDER BY i;
+
+-- test that non-overlapping windows don't use inverse transitions
+SELECT
+	logging_agg_strict(v::text) OVER wnd
+FROM (VALUES
+	(1, 'a'),
+	(2, 'b'),
+	(3, 'c')
+) AS t(i, v)
+WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ORDER BY i;
+
+-- test that returning NULL from the inverse transition functions
+-- restarts the aggregation from scratch. The second aggregate is supposed
+-- to test cases where only some aggregates restart, the third one checks
+-- the one aggregate restarting doesn't cause others to restart
+CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+$$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+LANGUAGE SQL STRICT IMMUTABLE;
+
+CREATE AGGREGATE sum_int_randomrestart (int4)
+(
+	stype = int4,
+	sfunc = int4pl,
+	invsfunc = sum_int_randrestart_invsfunc
+);
+
+WITH
+vs AS (
+	SELECT i, (random() * 100)::int4 AS v
+	FROM generate_series(1, 100) AS i
+),
+sum_following AS (
+	SELECT i, SUM(v) OVER
+		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+	FROM vs
+)
+SELECT DISTINCT
+	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+FROM vs
+JOIN sum_following ON sum_following.i = vs.i
+WINDOW fwd AS (
+	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+);
+
+-- cleanup aggregates
+DROP AGGREGATE sum_int_randomrestart (int4);
+DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+DROP AGGREGATE logging_agg_strict(text);
+DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+DROP FUNCTION logging_sfunc_strict(text, anyelement);
+DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+DROP AGGREGATE logging_agg_nonstrict(anyelement);
+DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+
+--
+--
+-- Test the arithmetic inverse transition functions
+--
+--
+
+--
+--
+-- Test the MIN, MAX and boolean inverse transition functions
+--
+--
+
+--
+--
+-- Tests of the collecting inverse transition functions
+--
+--
#161Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#160)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

David Rowley <dgrowleyml@gmail.com> writes:

I've attached an updated invtrans_strictstrict_base patch which has the
feature removed.

What is the state of play on this patch? Is the latest version what's in
/messages/by-id/64F96FD9-64D1-40B9-8861-E6182029220B@phlo.org
plus this sub-patch? Is everybody reasonably happy with it? I don't
see it marked "ready for committer" in the CF app, but time is running
out.

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

#162David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#161)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, Mar 27, 2014 at 7:33 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I've attached an updated invtrans_strictstrict_base patch which has the
feature removed.

What is the state of play on this patch? Is the latest version what's in

/messages/by-id/64F96FD9-64D1-40B9-8861-E6182029220B@phlo.org
plus this sub-patch? Is everybody reasonably happy with it? I don't
see it marked "ready for committer" in the CF app, but time is running
out.

As far as I know the only concern left was around the extra stats in the
explain output, which I removed in the patch I attached in the previous
email.

The invtrans_strictstrict_base.patch in my previous email replaces the
invtrans_strictstrict_base_038070.patch in that Florian sent here
/messages/by-id/64F96FD9-64D1-40B9-8861-E6182029220B@phlo.orgall
of the other patches are unchanged so it's save to use Florian's
latest
ones

Perhaps Dean can confirm that there's nothing else outstanding?

Show quoted text

regards, tom lane

#163Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: David Rowley (#162)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 26 March 2014 19:43, David Rowley <dgrowleyml@gmail.com> wrote:

On Thu, Mar 27, 2014 at 7:33 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I've attached an updated invtrans_strictstrict_base patch which has the
feature removed.

What is the state of play on this patch? Is the latest version what's in

/messages/by-id/64F96FD9-64D1-40B9-8861-E6182029220B@phlo.org
plus this sub-patch? Is everybody reasonably happy with it? I don't
see it marked "ready for committer" in the CF app, but time is running
out.

As far as I know the only concern left was around the extra stats in the
explain output, which I removed in the patch I attached in the previous
email.

Agreed. That was my last concern regarding the base patch, and I agree
that removing the new explain output is probably the best course of
action, given that we haven't reached consensus as to what the most
useful output would be.

The invtrans_strictstrict_base.patch in my previous email replaces the
invtrans_strictstrict_base_038070.patch in that Florian sent here
/messages/by-id/64F96FD9-64D1-40B9-8861-E6182029220B@phlo.org
all of the other patches are unchanged so it's save to use Florian's latest
ones

Perhaps Dean can confirm that there's nothing else outstanding?

Florian mentioned upthread that the docs hadn't been updated to
reflect the latest changes, so I think they need a little attention.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#164Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#163)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

First, sorry guys for letting this slide - I was overwhelmed by other works,
and this kind of slipped my mind :-(

On Mar27, 2014, at 09:04 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 26 March 2014 19:43, David Rowley <dgrowleyml@gmail.com> wrote:

On Thu, Mar 27, 2014 at 7:33 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I've attached an updated invtrans_strictstrict_base patch which has the
feature removed.

What is the state of play on this patch? Is the latest version what's in

/messages/by-id/64F96FD9-64D1-40B9-8861-E6182029220B@phlo.org
plus this sub-patch? Is everybody reasonably happy with it? I don't
see it marked "ready for committer" in the CF app, but time is running
out.

As far as I know the only concern left was around the extra stats in the
explain output, which I removed in the patch I attached in the previous
email.

Agreed. That was my last concern regarding the base patch, and I agree
that removing the new explain output is probably the best course of
action, given that we haven't reached consensus as to what the most
useful output would be.

After re-reading the thread, I'd prefer to go with Dean's suggestion, i.e.
simply reporting the total number of invocations of the forward transition
functions, and the total number of invocations of the reverse transition
function, over reporting nothing. The labels of the two counts would simply
be "Forward Transitions" and "Reverse Transitions".

But I don't want this issue to prevent us from getting this patch into 9.4,
so if there are objections to this, I'll rip out the EXPLAIN stuff all
together.

The invtrans_strictstrict_base.patch in my previous email replaces the
invtrans_strictstrict_base_038070.patch in that Florian sent here
/messages/by-id/64F96FD9-64D1-40B9-8861-E6182029220B@phlo.org
all of the other patches are unchanged so it's save to use Florian's latest
ones

Perhaps Dean can confirm that there's nothing else outstanding?

Florian mentioned upthread that the docs hadn't been updated to
reflect the latest changes, so I think they need a little attention.

I'll see to updating the docs, and will post a final patch within the next
few days.

Dean, have you by chance looked at the other patches yet?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#165Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#164)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 27 March 2014 21:01, Florian Pflug <fgp@phlo.org> wrote:

First, sorry guys for letting this slide - I was overwhelmed by other works,
and this kind of slipped my mind :-(

On Mar27, 2014, at 09:04 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 26 March 2014 19:43, David Rowley <dgrowleyml@gmail.com> wrote:

On Thu, Mar 27, 2014 at 7:33 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I've attached an updated invtrans_strictstrict_base patch which has the
feature removed.

What is the state of play on this patch? Is the latest version what's in

/messages/by-id/64F96FD9-64D1-40B9-8861-E6182029220B@phlo.org
plus this sub-patch? Is everybody reasonably happy with it? I don't
see it marked "ready for committer" in the CF app, but time is running
out.

As far as I know the only concern left was around the extra stats in the
explain output, which I removed in the patch I attached in the previous
email.

Agreed. That was my last concern regarding the base patch, and I agree
that removing the new explain output is probably the best course of
action, given that we haven't reached consensus as to what the most
useful output would be.

After re-reading the thread, I'd prefer to go with Dean's suggestion, i.e.
simply reporting the total number of invocations of the forward transition
functions, and the total number of invocations of the reverse transition
function, over reporting nothing. The labels of the two counts would simply
be "Forward Transitions" and "Reverse Transitions".

That should be "Inverse" not "Reverse" according to the terminology
agreed upthread.

Personally, I'm not a big fan of that terminology because "forward"
and "inverse" aren't natural antonyms. But actually I think that it's
"forward" that is the wrong word to use, because they actually both
move (different ends of) the frame forwards. The only alternatives I
can think of are "direct" and "inverse", which are natural antonyms,
but I don't want to hold up this patch bikeshedding over this. OTOH
this is not the first time on this thread that someone has slipped
into calling them "forward" and "reverse" transitions.

But I don't want this issue to prevent us from getting this patch into 9.4,
so if there are objections to this, I'll rip out the EXPLAIN stuff all
together.

The invtrans_strictstrict_base.patch in my previous email replaces the
invtrans_strictstrict_base_038070.patch in that Florian sent here
/messages/by-id/64F96FD9-64D1-40B9-8861-E6182029220B@phlo.org
all of the other patches are unchanged so it's save to use Florian's latest
ones

Perhaps Dean can confirm that there's nothing else outstanding?

Florian mentioned upthread that the docs hadn't been updated to
reflect the latest changes, so I think they need a little attention.

I'll see to updating the docs, and will post a final patch within the next
few days.

Dean, have you by chance looked at the other patches yet?

No, sorry. I too have been swamped by other work. I will try to look
at them over the next few days. I don't anticipate that they will be
as complex as the base patch, so I hope that this can be finished in
time for 9.4.

If there are any other reviewers with spare cycles, feel free to jump in too.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#166Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#165)
5 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Mar28, 2014, at 08:58 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 27 March 2014 21:01, Florian Pflug <fgp@phlo.org> wrote:

After re-reading the thread, I'd prefer to go with Dean's suggestion, i.e.
simply reporting the total number of invocations of the forward transition
functions, and the total number of invocations of the reverse transition
function, over reporting nothing. The labels of the two counts would simply
be "Forward Transitions" and "Reverse Transitions".

That should be "Inverse" not "Reverse" according to the terminology
agreed upthread.

Ups, yeah.

Personally, I'm not a big fan of that terminology because "forward"
and "inverse" aren't natural antonyms. But actually I think that it's
"forward" that is the wrong word to use, because they actually both
move (different ends of) the frame forwards. The only alternatives I
can think of are "direct" and "inverse", which are natural antonyms,
but I don't want to hold up this patch bikeshedding over this. OTOH
this is not the first time on this thread that someone has slipped
into calling them "forward" and "reverse" transitions.

Head and tail come to mind. But having said that, I'd strongly prefer
not to start bikeschedding over this either. It's not user-visible
enough to fuss about it...

Also, for order-independent transfer functions, it really *is* an
inverse functions, in the sense that g(f(x,a),a) = x.

Florian mentioned upthread that the docs hadn't been updated to
reflect the latest changes, so I think they need a little attention.

I'll see to updating the docs, and will post a final patch within the next
few days.

Attached are updated patches that include the EXPLAIN changes mentioned
above and updated docs.

best regards,
Florian Pflug

Attachments:

invtrans_arith_3283b8.patchapplication/octet-stream; name=invtrans_arith_3283b8.patch; x-unix-mode=0644Download
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..e62f2a3 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8inc(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 717,767 ----
  	}
  }
  
+ Datum
+ int8dec(PG_FUNCTION_ARGS)
+ {
+ 	/*
+ 	 * When int8 is pass-by-reference, we provide this special case to avoid
+ 	 * palloc overhead for COUNT(): when called as an inverse transition
+ 	 * aggregate, we know that the argument is modifiable local storage,
+ 	 * so just update it in-place. (If int8 is pass-by-value, then of course
+ 	 * this is useless as well as incorrect, so just ifdef it out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *arg = (int64 *) PG_GETARG_POINTER(0);
+ 		int64		result;
+ 
+ 		result = *arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && *arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		*arg = result;
+ 		PG_RETURN_POINTER(arg);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		/* Not called as an aggregate, so just do it the dumb way */
+ 		int64		arg = PG_GETARG_INT64(0);
+ 		int64		result;
+ 
+ 		result = arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		PG_RETURN_INT64(result);
+ 	}
+ }
+ 
+ 
  /*
   * These functions are exactly like int8inc but are used for aggregates that
   * count only non-null values.	Since the functions are declared strict,
*************** int8inc_any(PG_FUNCTION_ARGS)
*** 733,738 ****
--- 778,789 ----
  }
  
  Datum
+ int8inc_any_inv(PG_FUNCTION_ARGS)
+ {
+ 	return int8dec(fcinfo);
+ }
+ 
+ Datum
  int8inc_float8_float8(PG_FUNCTION_ARGS)
  {
  	return int8inc(fcinfo);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..345ae47 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_float4(PG_FUNCTION_ARGS)
*** 2479,2486 ****
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	bool		isNaN;			/* true if any processed number was NaN */
  	MemoryContext agg_context;	/* context we're calculating in */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
--- 2479,2490 ----
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	int			maxScale;		/* stores the maximum scale seen so far. */
! 	int64		maxScaleCount;  /* tracks the number of values we've
! 								 * seen with the maximum scale */
! 
  	MemoryContext agg_context;	/* context we're calculating in */
+ 	int64		NaNcount;		/* Count of NaN values that are aggregated */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
*************** makeNumericAggState(FunctionCallInfo fci
*** 2505,2510 ****
--- 2509,2518 ----
  	state = (NumericAggState *) palloc0(sizeof(NumericAggState));
  	state->calcSumX2 = calcSumX2;
  	state->agg_context = agg_context;
+ 	state->NaNcount = 0;
+ 
+ 	state->maxScale = 0;
+ 	state->maxScaleCount = 0;
  
  	MemoryContextSwitchTo(old_context);
  
*************** do_numeric_accum(NumericAggState *state,
*** 2522,2536 ****
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (state->isNaN || NUMERIC_IS_NAN(newval))
  	{
! 		state->isNaN = true;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
--- 2530,2558 ----
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (NUMERIC_IS_NAN(newval))
  	{
! 		state->NaNcount++;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
+ 	/*
+ 	 * Track the highest scale that we've seen as if we ever perform an inverse
+ 	 * transition and remove the last numeric with the highest scale that we've
+ 	 * seen then we can no longer perform inverse transitions without risking
+ 	 * having the wrong dscale in the result value.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 		state->maxScaleCount++;
+ 	else if (X.dscale > state->maxScale)
+ 	{
+ 		state->maxScale = X.dscale;
+ 		state->maxScaleCount = 1;
+ 	}
+ 
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2562,2567 ****
--- 2584,2676 ----
  }
  
  /*
+  * do_numeric_discard
+  * Attempts to remove a value from the aggregated state.
+  * If the value cannot be removed then the function will return false, the
+  * possible reasons for failing are described below.
+  *
+  * If we aggregate the values 1.01 and 2 then the result will be 3.01. If we
+  * are then asked to un-aggregate the 1.01 then we must reject this case as we
+  * won't be able to tell what the new aggregated value's dscale should be.
+  * We can't return 2.00 (dscale = 2) as we really should return just 2, but
+  * since we're not tracking any previous highest scales then we must just fail
+  * to perform the inverse transition and just return false.
+  *
+  * Values that are no longer aggregated should not be able to effect the dscale
+  * of the result of the values that *are* still aggregated.
+  *
+  * Note it may be better to track the number of times we've aggregated a
+  * numeric with each scale, then if we ever remove final highest scaled value
+  * then we can step the result's dscale down to the next highest value. This is
+  * perhaps slightly more work than we can afford to do here, but doing it this
+  * way would mean that we could always perform the inverse transition.
+  */
+ static bool
+ do_numeric_discard(NumericAggState *state, Numeric newval)
+ {
+ 	NumericVar	X;
+ 	NumericVar	X2;
+ 	MemoryContext old_context;
+ 
+ 	/* result is NaN if any processed number is NaN */
+ 	if (NUMERIC_IS_NAN(newval))
+ 	{
+ 		state->NaNcount--;
+ 		return true;
+ 	}
+ 
+ 	/* load processed number in short-lived context */
+ 	init_var_from_num(newval, &X);
+ 
+ 	/*
+ 	 * If we're asked to remove a numeric value that has a dscale the same as
+ 	 * the highest dscale that we've encountered so far, then we need to
+ 	 * update the count of the number of times that we've seen that dscale.
+ 	 * Once we get to the last value that has this maximum dscale we must
+ 	 * report that we've failed to perform the inverse transition.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 	{
+ 		/*
+ 		 * are we on the last value with maxScale?
+ 		 * if so we can't do any more inverse transitions.
+ 		 */
+ 		if (state->maxScaleCount == 1)
+ 			return false;
+ 
+ 		state->maxScaleCount--;
+ 	}
+ 
+ 	/* if we need X^2, calculate that in short-lived context */
+ 	if (state->calcSumX2)
+ 	{
+ 		init_var(&X2);
+ 		mul_var(&X, &X, &X2, X.dscale * 2);
+ 	}
+ 
+ 	/* The rest of this needs to work in the aggregate context */
+ 	old_context = MemoryContextSwitchTo(state->agg_context);
+ 
+ 	if (state->N-- > 0)
+ 	{
+ 		/* Subtract X from state */
+ 		sub_var(&(state->sumX), &X, &(state->sumX));
+ 
+ 		if (state->calcSumX2)
+ 			sub_var(&(state->sumX2), &X2, &(state->sumX2));
+ 	}
+ 	else
+ 	{
+ 		MemoryContextSwitchTo(old_context);
+ 		elog(ERROR, "cannot discard more values than were accumulated");
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 	return true;
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that require sumX2.
   */
  Datum
*************** numeric_accum(PG_FUNCTION_ARGS)
*** 2584,2589 ****
--- 2693,2716 ----
  }
  
  /*
+  * numeric_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ numeric_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* can we perform an inverse transition? if not return NULL. */
+ 	if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
  Datum
*************** numeric_avg_accum(PG_FUNCTION_ARGS)
*** 2606,2611 ****
--- 2733,2755 ----
  }
  
  /*
+  * numeric_avg_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict
+  */
+ Datum
+ numeric_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* can we perform an inverse transition? if not return NULL. */
+ 	if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
   * Integer data types all use Numeric accumulators to share code and
   * avoid risk of overflow.	For int2 and int4 inputs, Numeric accumulation
   * is overkill for the N and sum(X) values, but definitely not overkill
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2686,2691 ****
--- 2830,2908 ----
  	PG_RETURN_POINTER(state);
  }
  
+ 
+ /*
+  * int2_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int2_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 	Numeric		newval;
+ 
+ 	newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
+ 							 PG_GETARG_DATUM(1)));
+ 
+ 	/*
+ 	 * do_numeric_discard should never fail with numerics converted
+ 	 * from int types as the dscale should always be 0.
+ 	 */
+ 	if (!do_numeric_discard(state, newval))
+ 		elog(ERROR, "Unable to perform inverse transition on int type");
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * int4_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int4_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 	Numeric		newval;
+ 
+ 	newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
+ 							 PG_GETARG_DATUM(1)));
+ 
+ 	/*
+ 	 * do_numeric_discard should never fail with numerics converted
+ 	 * from int types as the dscale should always be 0.
+ 	 */
+ 	if (!do_numeric_discard(state, newval))
+ 		elog(ERROR, "Unable to perform inverse transition on int type");
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * int8_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int8_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 	Numeric		newval;
+ 
+ 	newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+ 							 PG_GETARG_DATUM(1)));
+ 
+ 	/*
+ 	 * do_numeric_discard should never fail with numerics converted
+ 	 * from int types as the dscale should always be 0.
+ 	 */
+ 	if (!do_numeric_discard(state, newval))
+ 		elog(ERROR, "Unable to perform inverse transition on int type");
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  /*
   * Transition function for int8 input when we don't need sumX2.
   */
*************** int8_avg_accum(PG_FUNCTION_ARGS)
*** 2713,2718 ****
--- 2930,2958 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * int8_avg_accum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int8_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state = (NumericAggState *) PG_GETARG_POINTER(0);
+ 	Numeric		newval;
+ 
+ 	newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+ 		PG_GETARG_DATUM(1)));
+ 
+ 	/*
+ 	 * do_numeric_discard should never fail with numerics converted
+ 	 * from int types as the dscale should always be 0.
+ 	 */
+ 	if (!do_numeric_discard(state, newval))
+ 		elog(ERROR, "Unable to perform inverse transition on int type");
+ 
+ 	PG_RETURN_POINTER(state);
+ }
  
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
*************** numeric_avg(PG_FUNCTION_ARGS)
*** 2725,2731 ****
  	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
--- 2965,2971 ----
  	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
*************** numeric_sum(PG_FUNCTION_ARGS)
*** 2743,2749 ****
  	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
--- 2983,2989 ----
  	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
*************** numeric_stddev_internal(NumericAggState 
*** 2782,2788 ****
  
  	*is_null = false;
  
! 	if (state->isNaN)
  		return make_result(&const_nan);
  
  	init_var(&vN);
--- 3022,3028 ----
  
  	*is_null = false;
  
! 	if (state->NaNcount > 0)
  		return make_result(&const_nan);
  
  	init_var(&vN);
*************** int2_sum(PG_FUNCTION_ARGS)
*** 2978,2983 ****
--- 3218,3261 ----
  	}
  }
  
+ /*
+  * int2_sum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int2_sum_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		newval;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to avoid palloc overhead. If not, we need to return
+ 	 * the new value of the transition variable. (If int8 is pass-by-value,
+ 	 * then of course this is useless as well as incorrect, so just ifdef it
+ 	 * out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *oldsum = (int64 *) PG_GETARG_POINTER(0);
+ 
+ 		*oldsum = *oldsum - (int64) PG_GETARG_INT16(1);
+ 
+ 		PG_RETURN_POINTER(oldsum);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		int64		oldsum = PG_GETARG_INT64(0);
+ 
+ 		/* OK to do the subtraction. */
+ 		newval = oldsum - (int64) PG_GETARG_INT16(1);
+ 
+ 		PG_RETURN_INT64(newval);
+ 	}
+ }
+ 
  Datum
  int4_sum(PG_FUNCTION_ARGS)
  {
*************** int4_sum(PG_FUNCTION_ARGS)
*** 3028,3033 ****
--- 3306,3350 ----
  }
  
  /*
+  * int4_sum_inv
+  * aggregate inverse transition function.
+  * This function must be declared as strict.
+  */
+ Datum
+ int4_sum_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		newval;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to avoid palloc overhead. If not, we need to return
+ 	 * the new value of the transition variable. (If int8 is pass-by-value,
+ 	 * then of course this is useless as well as incorrect, so just ifdef it
+ 	 * out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *oldsum = (int64 *) PG_GETARG_POINTER(0);
+ 
+ 		*oldsum = *oldsum - (int64) PG_GETARG_INT32(1);
+ 
+ 		PG_RETURN_POINTER(oldsum);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		int64		oldsum = PG_GETARG_INT64(0);
+ 
+ 		/* OK to do the subtraction. */
+ 		newval = oldsum - (int64) PG_GETARG_INT32(1);
+ 
+ 		PG_RETURN_INT64(newval);
+ 	}
+ }
+ 
+ 
+ /*
   * Note: this function is obsolete, it's no longer used for SUM(int8).
   */
  Datum
*************** int2_avg_accum(PG_FUNCTION_ARGS)
*** 3106,3111 ****
--- 3423,3457 ----
  }
  
  Datum
+ int2_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int16		newval = PG_GETARG_INT16(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ 
+ Datum
  int4_avg_accum(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray;
*************** int4_avg_accum(PG_FUNCTION_ARGS)
*** 3134,3139 ****
--- 3480,3513 ----
  }
  
  Datum
+ int4_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int32		newval = PG_GETARG_INT32(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ Datum
  int8_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 4581862..fd6af59 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** interval_accum(PG_FUNCTION_ARGS)
*** 3073,3078 ****
--- 3073,3123 ----
  }
  
  Datum
+ interval_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Interval   *newval = PG_GETARG_INTERVAL_P(1);
+ 	Datum	   *transdatums;
+ 	int			ndatums;
+ 	Interval	sumX,
+ 				N;
+ 	Interval   *newsum;
+ 	ArrayType  *result;
+ 
+ 	deconstruct_array(transarray,
+ 					  INTERVALOID, sizeof(Interval), false, 'd',
+ 					  &transdatums, NULL, &ndatums);
+ 	if (ndatums != 2)
+ 		elog(ERROR, "expected 2-element interval array");
+ 
+ 	/*
+ 	 * XXX memcpy, instead of just extracting a pointer, to work around buggy
+ 	 * array code: it won't ensure proper alignment of Interval objects on
+ 	 * machines where double requires 8-byte alignment. That should be fixed,
+ 	 * but in the meantime...
+ 	 *
+ 	 * Note: must use DatumGetPointer here, not DatumGetIntervalP, else some
+ 	 * compilers optimize into double-aligned load/store anyway.
+ 	 */
+ 	memcpy((void *) &sumX, DatumGetPointer(transdatums[0]), sizeof(Interval));
+ 	memcpy((void *) &N, DatumGetPointer(transdatums[1]), sizeof(Interval));
+ 
+ 	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
+ 												   IntervalPGetDatum(&sumX),
+ 												 IntervalPGetDatum(newval)));
+ 	N.time -= 1;
+ 
+ 	transdatums[0] = IntervalPGetDatum(newsum);
+ 	transdatums[1] = IntervalPGetDatum(&N);
+ 
+ 	result = construct_array(transdatums, 2,
+ 							 INTERVALOID, sizeof(Interval), false, 'd');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ 
+ Datum
  interval_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 2fb3871..0e6bf89 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 103,125 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
--- 103,125 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		int8_avg_accum_inv		numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		int4_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		int2_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg_accum_inv	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum		interval_accum_inv		interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		int8_avg_accum_inv		numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum			int4_sum_inv			-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum			int2_sum_inv			-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl			-						-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl			-						-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl				cash_mi					-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl			interval_mi				-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_avg_accum_inv	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
*************** DATA(insert ( 2798	n 0 tidsmaller		-	-		
*** 166,221 ****
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
--- 166,221 ----
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		int8inc_any_inv	-		0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			int8dec			-		0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		int8_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		int4_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		int2_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_accum_inv	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		int8_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		int4_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		int2_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ad9774c..6ff5baa 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("truncate interval to specified un
*** 1309,1316 ****
--- 1309,1320 ----
  
  DATA(insert OID = 1219 (  int8inc		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8inc _null_ _null_ _null_ ));
  DESCR("increment");
+ DATA(insert OID = 3475 (  int8dec		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8dec _null_ _null_ _null_ ));
+ DESCR("decrement");
  DATA(insert OID = 2804 (  int8inc_any	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any _null_ _null_ _null_ ));
  DESCR("increment, ignores second argument");
+ DATA(insert OID = 3222 (  int8inc_any_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any_inv _null_ _null_ _null_ ));
+ DESCR("decrement, ignores second argument");
  DATA(insert OID = 1230 (  int8abs		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8abs _null_ _null_ _null_ ));
  
  DATA(insert OID = 1236 (  int8larger	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger _null_ _null_ _null_ ));
*************** DATA(insert OID = 1832 (  float8_stddev_
*** 2396,2411 ****
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1835 (  int4_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1836 (  int8_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
! DATA(insert OID = 2746 (  int8_avg_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
--- 2400,2427 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3233 (  numeric_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3234 (  numeric_avg_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1835 (  int4_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1836 (  int8_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
! DATA(insert OID = 2746 (  int8_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3235 (  int2_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3236 (  int4_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3237 (  int8_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3238 (  int8_avg_accum_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
*************** DATA(insert OID = 2596 (  numeric_stddev
*** 2418,2423 ****
--- 2434,2443 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1839 (  numeric_stddev_samp	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_stddev_samp _null_ _null_ _null_ ));
  DESCR("aggregate final function");
+ DATA(insert OID = 3239 (  int4_sum_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 23" _null_ _null_ _null_ _null_ int4_sum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3240 (  int2_sum_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 21" _null_ _null_ _null_ _null_ int2_sum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1840 (  int2_sum		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 20 "20 21" _null_ _null_ _null_ _null_ int2_sum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1841 (  int4_sum		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 20 "20 23" _null_ _null_ _null_ _null_ int4_sum _null_ _null_ _null_ ));
*************** DATA(insert OID = 1842 (  int8_sum		   P
*** 2426,2437 ****
--- 2446,2463 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 1843 (  interval_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3241 (  interval_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1844 (  interval_avg	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1186 "1187" _null_ _null_ _null_ _null_ interval_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1962 (  int2_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1963 (  int4_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3242 (  int2_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 3243 (  int4_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 1964 (  int8_avg		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1016" _null_ _null_ _null_ _null_ int8_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2805 (  int8inc_float8_float8		PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 20 "20 701 701" _null_ _null_ _null_ _null_ int8inc_float8_float8 _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..e2b1879 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum numeric_float8_no_overflow(
*** 999,1009 ****
--- 999,1015 ----
  extern Datum float4_numeric(PG_FUNCTION_ARGS);
  extern Datum numeric_float4(PG_FUNCTION_ARGS);
  extern Datum numeric_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int2_accum(PG_FUNCTION_ARGS);
  extern Datum int4_accum(PG_FUNCTION_ARGS);
  extern Datum int8_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int4_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int8_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int8_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg(PG_FUNCTION_ARGS);
  extern Datum numeric_sum(PG_FUNCTION_ARGS);
  extern Datum numeric_var_pop(PG_FUNCTION_ARGS);
*************** extern Datum numeric_var_samp(PG_FUNCTIO
*** 1011,1020 ****
--- 1017,1030 ----
  extern Datum numeric_stddev_pop(PG_FUNCTION_ARGS);
  extern Datum numeric_stddev_samp(PG_FUNCTION_ARGS);
  extern Datum int2_sum(PG_FUNCTION_ARGS);
+ extern Datum int2_sum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_sum(PG_FUNCTION_ARGS);
+ extern Datum int4_sum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_sum(PG_FUNCTION_ARGS);
  extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int4_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg(PG_FUNCTION_ARGS);
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  extern Datum hash_numeric(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..5078e4a 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8div(PG_FUNCTION_ARGS);
*** 74,80 ****
--- 74,82 ----
  extern Datum int8abs(PG_FUNCTION_ARGS);
  extern Datum int8mod(PG_FUNCTION_ARGS);
  extern Datum int8inc(PG_FUNCTION_ARGS);
+ extern Datum int8dec(PG_FUNCTION_ARGS);
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
+ extern Datum int8inc_any_inv(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..e852fbd 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum interval_mul(PG_FUNCTION_AR
*** 179,184 ****
--- 179,185 ----
  extern Datum mul_d_interval(PG_FUNCTION_ARGS);
  extern Datum interval_div(PG_FUNCTION_ARGS);
  extern Datum interval_accum(PG_FUNCTION_ARGS);
+ extern Datum interval_accum_inv(PG_FUNCTION_ARGS);
  extern Datum interval_avg(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_mi(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index df676ad..78e304d 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1278,1283 ****
--- 1278,1766 ----
  -- Test the arithmetic inverse transition functions
  --
  --
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 2.0000000000000000
+  2 | 2.5000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |    avg     
+ ---+------------
+  1 | @ 1.5 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+  i |  sum  
+ ---+-------
+  1 | $3.30
+  2 | $2.20
+  3 |      
+  4 |      
+ (4 rows)
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |   sum    
+ ---+----------
+  1 | @ 3 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 | 3.3
+  2 | 2.2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+  sum  
+ ------
+  6.01
+     5
+     3
+ (3 rows)
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     2
+  2 |     1
+  3 |     0
+  4 |     0
+ (4 rows)
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     4
+  2 |     3
+  3 |     2
+  4 |     1
+ (4 rows)
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   1
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   6
+  3 |   9
+  4 |   7
+ (4 rows)
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+          to_char          
+ --------------------------
+   100000000000000000000
+                       1.0
+ (2 rows)
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+  a |  b  | sum 
+ ---+-----+-----
+  1 |   1 |   1
+  2 |   2 |   3
+  3 | NaN | NaN
+  4 |   3 | NaN
+  5 |   4 |   7
+ (5 rows)
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 65c1005..21a7e0c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 465,470 ****
--- 465,611 ----
  --
  --
  
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
invtrans_base_548630.patchapplication/octet-stream; name=invtrans_base_548630.patch; x-unix-mode=0644Download
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..178bf88 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 56,61 ****
--- 56,62 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
*************** AggregateCreate(const char *aggName,
*** 68,78 ****
--- 69,81 ----
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
  	Oid			transfn;
+ 	Oid			invtransfn = InvalidOid; /* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	bool		transIsStrict;
  	Oid			rettype;
  	Oid			finaltype;
  	Oid			fnArgs[FUNC_MAX_ARGS];
*************** AggregateCreate(const char *aggName,
*** 234,241 ****
--- 237,298 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
+ 
+ 	/*
+ 	 * Remember if trans function is strict as we need to validate this
+ 	 * later if when we're dealing with the inverse transition function
+ 	 */
+ 	transIsStrict = proc->proisstrict;
+ 
  	ReleaseSysCache(tup);
  
+ 	/* handle invtransfn, if supplied */
+ 	if (agginvtransfnName)
+ 	{
+ 		/*
+ 		 * This must have the same number of arguments with the same types as
+ 		 * the transition function. We can just borrow the argument details
+ 		 * from the transition function and try to find a function with
+ 		 * the name of the inverse transition function and with a signature
+ 		 * that matches the transition function's.
+ 		 */
+ 		invtransfn = lookup_agg_function(agginvtransfnName,
+ 					nargs_transfn, fnArgs, InvalidOid, &rettype);
+ 
+ 		/*
+ 		 * Ensure the return type of the inverse transition function matches
+ 		 * the expected type.
+ 		 */
+ 		if (rettype != aggTransType)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("return type of inverse transition function %s is not %s",
+ 							NameListToString(agginvtransfnName),
+ 							format_type_be(aggTransType))));
+ 
+ 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+ 		proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 		/*
+ 		 * Allowing only the forward transition function to be strict would
+ 		 * require handling more special cases in advance_windowaggregate() and
+ 		 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 		 * that if the forward transition function is strict that the inverse
+ 		 * transition function is also strict.
+ 		 */
+ 		if (transIsStrict && !proc->proisstrict)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 						errmsg("aggregate functions inverse transition function must strict if the forward transition function is"
+ 						)));
+ 		}
+ 		ReleaseSysCache(tup);
+ 
+ 	}
+ 
  	/* handle finalfn, if supplied */
  	if (aggfinalfnName)
  	{
*************** AggregateCreate(const char *aggName,
*** 391,396 ****
--- 448,454 ----
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+ 	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
*************** AggregateCreate(const char *aggName,
*** 425,430 ****
--- 483,497 ----
  	referenced.objectSubId = 0;
  	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
+ 	/* Depends on inverse transition function, if any */
+ 	if (OidIsValid(invtransfn))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = invtransfn;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
  	{
*************** AggregateCreate(const char *aggName,
*** 447,453 ****
  }
  
  /*
!  * lookup_agg_function -- common code for finding both transfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
--- 514,521 ----
  }
  
  /*
!  * lookup_agg_function
!  * common code for finding both transfn, invtransfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..0cfaa75 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
*************** DefineAggregate(List *name, List *args, 
*** 60,65 ****
--- 60,66 ----
  	AclResult	aclresult;
  	char		aggKind = AGGKIND_NORMAL;
  	List	   *transfuncName = NIL;
+ 	List	   *invtransfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
*************** DefineAggregate(List *name, List *args, 
*** 112,117 ****
--- 113,120 ----
  			transfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
  			transfuncName = defGetQualifiedName(defel);
+ 		else if (pg_strcasecmp(defel->defname, "invfunc") == 0)
+ 			invtransfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
  			finalfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
*************** DefineAggregate(List *name, List *args, 
*** 283,288 ****
--- 286,292 ----
  						   parameterDefaults,
  						   variadicArgType,
  						   transfuncName,		/* step function name */
+ 						   invtransfuncName,	/* inverse trans function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0dba928..e86caaf 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_group_keys(PlanSta
*** 85,90 ****
--- 85,91 ----
  					 List *ancestors, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
+ static void show_windowagg_info(PlanState *planstate, ExplainState *es);
  static void show_tidbitmap_info(BitmapHeapScanState *planstate,
  								ExplainState *es);
  static void show_instrumentation_count(const char *qlabel, int which,
*************** ExplainNode(PlanState *planstate, List *
*** 1402,1407 ****
--- 1403,1412 ----
  		case T_Hash:
  			show_hash_info((HashState *) planstate, es);
  			break;
+ 		case T_WindowAgg:
+ 			if (es->verbose && planstate->instrument)
+ 				show_windowagg_info(planstate, es);
+ 			break;
  		default:
  			break;
  	}
*************** show_hash_info(HashState *hashstate, Exp
*** 1900,1905 ****
--- 1905,1925 ----
  	}
  }
  
+ static void
+ show_windowagg_info(PlanState *planstate, ExplainState *es)
+ {
+ 	WindowAggState *winaggstate = (WindowAggState *) planstate;
+ 	Instrumentation *inst = planstate->instrument;
+ 
+ 	if (inst->nloops > 0 && inst->ntuples > 0 && winaggstate->numaggs > 0)
+ 	{
+ 		double tperrow = winaggstate->aggfwdtrans /
+ 			(inst->nloops * inst->ntuples);
+ 
+ 		ExplainPropertyFloat("Transitions Per Row", tperrow, 1, es);
+ 	}
+ }
+ 
  /*
   * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
   */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..04f2ebe 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1679,1684 ****
--- 1679,1685 ----
  		Oid			transfn_oid,
  					finalfn_oid;
  		Expr	   *transfnexpr,
+ 				   *invtransfnexpr, /* needed but never used */
  				   *finalfnexpr;
  		Datum		textInitVal;
  		int			i;
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1798,1805 ****
--- 1799,1808 ----
  								aggref->aggtype,
  								aggref->inputcollid,
  								transfn_oid,
+ 								InvalidOid, /* invtrans is not needed here */
  								finalfn_oid,
  								&transfnexpr,
+ 								&invtransfnexpr,
  								&finalfnexpr);
  
  		/* set up infrastructure for calling the transfn and finalfn */
*************** ExecReScanAgg(AggState *node)
*** 2127,2168 ****
  }
  
  /*
-  * AggCheckCallContext - test if a SQL function is being called as an aggregate
-  *
-  * The transition and/or final functions of an aggregate may want to verify
-  * that they are being called as aggregates, rather than as plain SQL
-  * functions.  They should use this function to do so.	The return value
-  * is nonzero if being called as an aggregate, or zero if not.	(Specific
-  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
-  * values could conceivably appear in future.)
-  *
-  * If aggcontext isn't NULL, the function also stores at *aggcontext the
-  * identity of the memory context that aggregate transition values are
-  * being stored in.
-  */
- int
- AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
- {
- 	if (fcinfo->context && IsA(fcinfo->context, AggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_AGGREGATE;
- 	}
- 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_WINDOW;
- 	}
- 
- 	/* this is just to prevent "uninitialized variable" warnings */
- 	if (aggcontext)
- 		*aggcontext = NULL;
- 	return 0;
- }
- 
- /*
   * AggGetAggref - allow an aggregate support function to get its Aggref
   *
   * If the function is being called as an aggregate support function,
--- 2130,2135 ----
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..c88c718 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** typedef struct WindowStatePerFuncData
*** 102,119 ****
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transfer functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transfer functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/*
  	 * initial value from pg_aggregate entry
  	 */
--- 102,126 ----
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transition functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
+ 	Oid			invtransfn_oid; /* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transition functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
+ 	FmgrInfo	invtransfn;
  	FmgrInfo	finalfn;
  
+ 	/* Aggregate properties */
+ 	bool		use_invtransfn;			/* whether to use the invtransfn */
+ 	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+ 	bool		ignore_nulls;			/* whether we skip NULL inputs */
+ 
  	/*
  	 * initial value from pg_aggregate entry
  	 */
*************** typedef struct WindowStatePerAggData
*** 140,149 ****
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	Datum		transValue;		/* current transition value */
! 	bool		transValueIsNull;
  
! 	bool		noTransValue;	/* true if transValue not set yet */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
--- 147,159 ----
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	MemoryContext	aggcontext;			/* context for transValue */
! 	int64			transValueCount;	/* Number of aggregates values*/
! 	Datum			transValue;			/* current transition value */
! 	bool			transValueIsNull;
  
! 	/* Data local to eval_windowaggregates() */
! 	bool			restart;			/* tmp marker that agg needs restart */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
*************** static void initialize_windowaggregate(W
*** 152,157 ****
--- 162,170 ----
  static void advance_windowaggregate(WindowAggState *winstate,
  						WindowStatePerFunc perfuncstate,
  						WindowStatePerAgg peraggstate);
+ static bool retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate);
  static void finalize_windowaggregate(WindowAggState *winstate,
  						 WindowStatePerFunc perfuncstate,
  						 WindowStatePerAgg peraggstate,
*************** static bool are_peers(WindowAggState *wi
*** 181,186 ****
--- 194,243 ----
  static bool window_gettupleslot(WindowObject winobj, int64 pos,
  					TupleTableSlot *slot);
  
+ /*
+  * AggCheckCallContext - test if a SQL function is being called as an aggregate
+  *
+  * The transition and/or final functions of an aggregate may want to verify
+  * that they are being called as aggregates, rather than as plain SQL
+  * functions.  They should use this function to do so.	The return value
+  * is nonzero if being called as an aggregate, or zero if not.	(Specific
+  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+  * values could conceivably appear in future.)
+  *
+  * If aggcontext isn't NULL, the function also stores at *aggcontext the
+  * identity of the memory context that aggregate transition values are
+  * being stored in.
+  *
+  * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+  * is private.
+  */
+ int
+ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggState))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ 		return AGG_CONTEXT_AGGREGATE;
+ 	}
+ 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ 	{
+ 		if (aggcontext)
+ 		{
+ 			/* Must lookup per-aggregate context */
+ 			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+ 			int				aggno = winstate->calledaggno;
+ 			Assert(0 <= aggno && aggno < winstate->numaggs);
+ 			*aggcontext = winstate->peragg[aggno].aggcontext;
+ 		}
+ 		return AGG_CONTEXT_WINDOW;
+ 	}
+ 
+ 	/* this is just to prevent "uninitialized variable" warnings */
+ 	if (aggcontext)
+ 		*aggcontext = NULL;
+ 	return 0;
+ }
  
  /*
   * initialize_windowaggregate
*************** initialize_windowaggregate(WindowAggStat
*** 193,210 ****
  {
  	MemoryContext oldContext;
  
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = peraggstate->initValue;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->noTransValue = peraggstate->initValueIsNull;
  	peraggstate->resultValueIsNull = true;
  }
  
--- 250,275 ----
  {
  	MemoryContext oldContext;
  
+ 	/* If we're using a private aggcontext, we may reset it here. But if the
+ 	 * context is shared, we don't know which other aggregates may still need
+ 	 * it, so we must leave it to the caller to reset at an appropriate time
+ 	 */
+ 	if (!peraggstate->aggcontext_is_shared)
+ 		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+ 
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = (Datum) 0;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
+ 	peraggstate->transValueCount = 0;
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->resultValue = (Datum) 0;
  	peraggstate->resultValueIsNull = true;
  }
  
*************** advance_windowaggregate(WindowAggState *
*** 254,265 ****
  		i++;
  	}
  
! 	if (peraggstate->transfn.fn_strict)
  	{
- 		/*
- 		 * For a strict transfn, nothing happens when there's a NULL input; we
- 		 * just keep the prior transValue.
- 		 */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
--- 319,327 ----
  		i++;
  	}
  
! 	/* Skip NULL inputs for aggregates which desire that. */
! 	if (peraggstate->ignore_nulls)
  	{
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
*************** advance_windowaggregate(WindowAggState *
*** 268,290 ****
  				return;
  			}
  		}
! 		if (peraggstate->noTransValue)
  		{
  			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
  			 * already checked that the agg's input type is binary-compatible
  			 * with its transtype, so straight copy here is OK.)
  			 *
  			 * We must copy the datum into aggcontext if it is pass-by-ref. We
  			 * do not need to pfree the old transValue, since it's NULL.
  			 */
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->noTransValue = false;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
--- 330,357 ----
  				return;
  			}
  		}
! 	}
! 	else
! 		Assert(!peraggstate->transfn.fn_strict);
! 
! 	if (peraggstate->transfn.fn_strict) {
! 		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
  		{
  			/*
! 			 * For strict transfer functions with initial value NULL we use
! 			 * the first non-NULL input as the initial state. (We
  			 * already checked that the agg's input type is binary-compatible
  			 * with its transtype, so straight copy here is OK.)
  			 *
  			 * We must copy the datum into aggcontext if it is pass-by-ref. We
  			 * do not need to pfree the old transValue, since it's NULL.
  			 */
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->transValueCount = 1;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
*************** advance_windowaggregate(WindowAggState *
*** 294,308 ****
  			 * Don't call a strict function with NULL inputs.  Note it is
  			 * possible to get here despite the above tests, if the transfn is
  			 * strict *and* returned a NULL on a prior cycle. If that happens
! 			 * we will propagate the NULL all the way to the end.
  			 */
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  	}
  
  	/*
  	 * OK to call the transition function
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
--- 361,389 ----
  			 * Don't call a strict function with NULL inputs.  Note it is
  			 * possible to get here despite the above tests, if the transfn is
  			 * strict *and* returned a NULL on a prior cycle. If that happens
! 			 * we will propagate the NULL all the way to the end. That can only
! 			 * happen if there's no inverse transition function, though, since
! 			 * we disallow transitions back to NULL if there is one below.
  			 */
  			MemoryContextSwitchTo(oldContext);
+ 			Assert(peraggstate->invtransfn_oid == InvalidOid);
  			return;
  		}
  	}
  
  	/*
+ 	 * We must track the number of inputs that we add to transValue, since
+ 	 * to remove the last input, retreat_windowaggregate() musn't call the
+ 	 * inverse transition function, but simply reset transValue back to its
+ 	 * initial value.
+ 	 */
+ 	Assert(peraggstate->transValueCount >= 0);
+ 	peraggstate->transValueCount++;
+ 
+ 	/*
  	 * OK to call the transition function
+ 	 * Transfer functions with an inverse MUST not return NULL, see
+ 	 * retreat_windowaggregate()
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
*************** advance_windowaggregate(WindowAggState *
*** 310,316 ****
--- 391,405 ----
  							 (void *) winstate, NULL);
  	fcinfo->arg[0] = peraggstate->transValue;
  	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
  	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("transition function with an inverse returned NULL")));
+ 	}
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
*************** advance_windowaggregate(WindowAggState *
*** 322,328 ****
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
--- 411,417 ----
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
*************** advance_windowaggregate(WindowAggState *
*** 337,342 ****
--- 426,574 ----
  }
  
  /*
+  * retreat_windowaggregate
+  * removes tuples from aggregation.
+  * The calling function must ensure that each aggregate has
+  * a valid inverse transition function.
+  */
+ static bool
+ retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate)
+ {
+ 	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
+ 	int			numArguments = perfuncstate->numArguments;
+ 	FunctionCallInfoData fcinfodata;
+ 	FunctionCallInfo fcinfo = &fcinfodata;
+ 	Datum		newVal;
+ 	ListCell   *arg;
+ 	int			i;
+ 	MemoryContext oldContext;
+ 	ExprContext *econtext = winstate->tmpcontext;
+ 	ExprState  *filter = wfuncstate->aggfilter;
+ 
+ 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ 
+ 	/* Skip anything FILTERed out */
+ 	if (filter)
+ 	{
+ 		bool		isnull;
+ 		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ 
+ 		if (isnull || !DatumGetBool(res))
+ 		{
+ 			MemoryContextSwitchTo(oldContext);
+ 			return true;
+ 		}
+ 	}
+ 
+ 	/* We start from 1, since the 0th arg will be the transition value */
+ 	i = 1;
+ 	foreach(arg, wfuncstate->args)
+ 	{
+ 		ExprState  *argstate = (ExprState *) lfirst(arg);
+ 
+ 		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
+ 									  &fcinfo->argnull[i], NULL);
+ 		i++;
+ 	}
+ 
+ 	/* Skip inputs containing NULLS for aggregates that require this */
+ 	if (peraggstate->ignore_nulls)
+ 	{
+ 		for (i = 1; i <= numArguments; i++)
+ 		{
+ 			if (fcinfo->argnull[i])
+ 			{
+ 				MemoryContextSwitchTo(oldContext);
+ 				return true;
+ 			}
+ 		}
+ 	}
+ 	else
+ 		Assert(!peraggstate->invtransfn.fn_strict);
+ 
+ 	/* There should still an added but not yet removed value */
+ 	Assert(peraggstate->transValueCount >= 1);
+ 
+ 	/*
+ 	 * We mustn't use the inverse transition function to remove the last
+ 	 * input. Doing so would yield a non-NULL state, whereas we should be
+ 	 * in the initial state afterwards which may very well be NULL. So
+ 	 * instead, we simply re-initialize the aggregation in this case.
+ 	 */
+ 	if (peraggstate->transValueCount == 1)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		initialize_windowaggregate(winstate,
+ 								&winstate->perfunc[peraggstate->wfuncno],
+ 								peraggstate);
+ 		return true;
+ 	}
+ 
+ 	/*
+ 	 * Perform the inverse transition.
+ 	 *
+ 	 * For pairs of forward and inverse transition functions, the state may
+ 	 * never be NULL, except in the ignore_nulls case, and then only until
+ 	 * until we see the first non-NULL input during which time should never
+ 	 * attempt to invoke the inverse transition function. Excluding NULL
+ 	 * as a possible state value allows us to make it mean "sorry, can't
+ 	 * do an inverse transition in this case" when returned by the inverse
+ 	 * transition function. In that case, we report the failure to the
+ 	 * caller.
+ 	 */
+ 	if (peraggstate->transValueIsNull)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		elog(ERROR, "transition value is NULL during inverse transition");
+ 	}
+ 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
+ 							 numArguments + 1,
+ 							 perfuncstate->winCollation,
+ 							 (void *) winstate, NULL);
+ 	fcinfo->arg[0] = peraggstate->transValue;
+ 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
+ 	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (fcinfo->isnull)
+ 	{
+ 		MemoryContextSwitchTo(oldContext);
+ 		return false;
+ 	}
+ 
+ 	/* Update number of added but not yet removed values */
+ 	peraggstate->transValueCount--;
+ 
+ 	/*
+ 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
+ 	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
+ 	 * first input, we don't need to do anything.
+ 	 */
+ 	if (!peraggstate->transtypeByVal &&
+ 		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
+ 	{
+ 		if (!fcinfo->isnull)
+ 		{
+ 			MemoryContextSwitchTo(peraggstate->aggcontext);
+ 			newVal = datumCopy(newVal,
+ 							   peraggstate->transtypeByVal,
+ 							   peraggstate->transtypeLen);
+ 		}
+ 		if (!peraggstate->transValueIsNull)
+ 			pfree(DatumGetPointer(peraggstate->transValue));
+ 	}
+ 
+ 	MemoryContextSwitchTo(oldContext);
+ 	peraggstate->transValue = newVal;
+ 	peraggstate->transValueIsNull = fcinfo->isnull;
+ 
+ 	return true;
+ }
+ 
+ 
+ /*
   * finalize_windowaggregate
   * parallel to finalize_aggregate in nodeAgg.c
   */
*************** finalize_windowaggregate(WindowAggState 
*** 370,376 ****
--- 602,610 ----
  		}
  		else
  		{
+ 			winstate->calledaggno = perfuncstate->aggno;
  			*result = FunctionCallInvoke(&fcinfo);
+ 			winstate->calledaggno = -1;
  			*isnull = fcinfo.isnull;
  		}
  	}
*************** finalize_windowaggregate(WindowAggState 
*** 392,397 ****
--- 626,632 ----
  	MemoryContextSwitchTo(oldContext);
  }
  
+ 
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
*************** eval_windowaggregates(WindowAggState *wi
*** 406,417 ****
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs;
! 	int			i;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
--- 641,658 ----
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs,
! 				numaggs_restart = 0,
! 				i;
! 	int64		pos,
! 				aggregatedupto_nonrestarted;
! 	bool		is_first = (winstate->currentpos == 0),
! 				ok;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
! 	TupleTableSlot *temp_slot = winstate->temp_slot_1;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
*************** eval_windowaggregates(WindowAggState *wi
*** 420,426 ****
  	/* final output execution is in ps_ExprContext */
  	econtext = winstate->ss.ps.ps_ExprContext;
  	agg_winobj = winstate->agg_winobj;
- 	agg_row_slot = winstate->agg_row_slot;
  
  	/*
  	 * Currently, we support only a subset of the SQL-standard window framing
--- 661,666 ----
*************** eval_windowaggregates(WindowAggState *wi
*** 438,446 ****
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * For other frame start rules, we discard the aggregate state and re-run
! 	 * the aggregates whenever the frame head row moves.  We can still
! 	 * optimize as above whenever successive rows share the same frame head.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
--- 678,697 ----
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * We can still optimize as above whenever successive rows share the same
! 	 * frame head, but if the frame head moves beyond the aggregated base point
! 	 * we use the aggregate function's inverse transition function. This
! 	 * removes the tuple from aggregation and restores the aggregate's current
! 	 * state to what it would be if the removed row had never been aggregated
! 	 * in the first place. Inverse transition functions may optionally return
! 	 * NULL, this indicates that the function was unable to remove the tuple
! 	 * from aggregation, when this happens we must perform the aggregation all
! 	 * over again for all tuples in the new frame boundary.
! 	 *
! 	 * If the aggregate function does not have a inverse transition function
! 	 * and the frame head moves beyond the aggregated position then we must
! 	 * discard the aggregated state and re-aggregate similar to how we would
! 	 * have to if the inverse transition function had returned NULL.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
*************** eval_windowaggregates(WindowAggState *wi
*** 452,529 ****
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
- 	 *
- 	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
- 	 * some aggregates like SUM and COUNT we could avoid that by implementing
- 	 * a "negative transition function" that would be called for each row as
- 	 * it exits the frame.	We'd have to think about avoiding recalculation of
- 	 * volatile arguments of aggregate functions, too.
  	 */
  
  	/*
  	 * First, update the frame head position.
  	 */
! 	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
  
  	/*
! 	 * Initialize aggregates on first call for partition, or if the frame head
! 	 * position moved since last time.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos != winstate->aggregatedbase)
  	{
- 		/*
- 		 * Discard transient aggregate values
- 		 */
- 		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
- 
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  
  		/*
! 		 * If we created a mark pointer for aggregates, keep it pushed up to
! 		 * frame head, so that tuplestore can discard unnecessary rows.
  		 */
! 		if (agg_winobj->markptr >= 0)
! 			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
  
  		/*
! 		 * Initialize for loop below
  		 */
! 		ExecClearTuple(agg_row_slot);
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 		winstate->aggregatedupto = winstate->frameheadpos;
  	}
  
  	/*
! 	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
! 	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
! 	 * have to recalculate when the frame head moves or currentpos has
! 	 * advanced past the place we'd aggregated up to.  Check for these cases
! 	 * and if so, reuse the saved result values.
  	 */
! 	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
! 		for (i = 0; i < numaggs; i++)
  		{
- 			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
- 		return;
  	}
  
  	/*
  	 * Advance until we reach a row not in frame (or end of partition).
  	 *
  	 * Note the loop invariant: agg_row_slot is either empty or holds the row
--- 703,880 ----
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
  	 */
  
  	/*
  	 * First, update the frame head position.
+ 	 *
+ 	 * The frame head should never move backwards, and the code below wouldn't
+ 	 * cope if it did, so for safety we complain if it does.
  	 */
! 	update_frameheadpos(agg_winobj, temp_slot);
! 	if (winstate->frameheadpos < winstate->aggregatedbase)
! 		elog(ERROR, "frame moved backwards unexpectedly");
  
  	/*
! 	 * If the frame didn't change compared to the previous row, we can re-use
! 	 * the cached result. Since we don't know the current frame's end yet, we
! 	 * cannot check that the obvious way. But we can exploit that if the frame
! 	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
! 	 * row lies within the previous row's frame, the two frame's ends must
! 	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
! 	 * so we don't need to check for that explicitly here.
  	 */
! 	if (winstate->aggregatedbase == winstate->frameheadpos &&
! 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
! 		}
! 		return;
! 	}
! 
! 	/* Initialize restart flags.
! 	 *
! 	 * We restart the aggregation
! 	 *   - if we're processing the first row in the partition, or
! 	 *   - if we the frame's head moved and we cannot use an inverse
! 	 *     transition function, or
! 	 *   - if the new frame doesn't overlap the old one
! 	 *
! 	 * Note that we don't strictly need to restart in the last case, but
! 	 * if we're going to remove *all* rows from the aggregation anyway, a
! 	 * restart surely is faster.
! 	 */
! 	for (i = 0; i < numaggs; i++)
! 	{
! 		peraggstate = &winstate->peragg[i];
! 		if (is_first ||
! 			(winstate->aggregatedbase < winstate->frameheadpos &&
! 			!peraggstate->use_invtransfn) ||
! 			(winstate->aggregatedupto <= winstate->frameheadpos))
! 		{
! 			peraggstate->restart = true;
! 			numaggs_restart++;
  		}
+ 		else
+ 			peraggstate->restart = false;
+ 	}
  
+ 	/*
+ 	 * Attempt to update aggregatedbase to match the frame's head by
+ 	 * removing those inputs from the aggregations which fell off the top
+ 	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+ 	 * return false, in which case we restart that aggregate below.
+ 	 *
+ 	 * We track the number of aggregates which require a restart, and
+ 	 * exit as soon as there are no more retreatable aggregates left.
+ 	 */
+ 	for(pos = winstate->aggregatedbase;
+ 		pos < winstate->frameheadpos && numaggs_restart < numaggs;
+ 		++pos)
+ 	{
  		/*
! 		 * Fetch the tuple where the current aggregation started from.
! 		 * This should never fail as we should have been here before.
  		 */
! 		if (!window_gettupleslot(agg_winobj, pos, temp_slot))
! 			elog(ERROR, "Unable to find tuple in tuplestore");
! 
! 		/* Set tuple context for evaluation of aggregate arguments */
! 		winstate->tmpcontext->ecxt_outertuple = temp_slot;
  
  		/*
! 		 * Perform the inverse transition for each aggregate function in
! 		 * the window, unless it has already been marked as needing a
! 		 * restart.
  		 */
! 		for (i = 0; i < numaggs; i++)
! 		{
! 			peraggstate = &winstate->peragg[i];
! 			if (peraggstate->restart)
! 				continue;
! 
! 			wfuncno = peraggstate->wfuncno;
! 			ok = retreat_windowaggregate(winstate,
! 										 &winstate->perfunc[wfuncno],
! 										 peraggstate);
! 			if (!ok)
! 			{
! 				/* Inverse transition function has failed, must restart */
! 				peraggstate->restart = true;
! 				numaggs_restart++;
! 			}
! 		}
! 
! 		/* Reset per-input-tuple context and tuple after each tuple */
! 		ResetExprContext(winstate->tmpcontext);
! 		ExecClearTuple(temp_slot);
  	}
  
  	/*
! 	 * Then restart the aggregates which require it.
! 	 *
! 	 * We assume that aggregates using the shared context always restart
! 	 * if *any* aggregate restarts, and we may thus cleanup the shared
! 	 * aggcontext if that is the case. The private aggcontexts are reset
! 	 * by initialize_windowaggregate() if their owning aggregate restarts,
! 	 * otherwise we just pfree() the cached result.
  	 */
! 	if (numaggs_restart > 0)
! 		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < numaggs; i++)
  	{
! 		peraggstate = &winstate->peragg[i];
! 		/* Aggregates using the shared ctx must restart if *any* agg does */
! 		Assert(!peraggstate->aggcontext_is_shared ||
! 			   !numaggs_restart || peraggstate->restart);
! 		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
! 			!peraggstate->resulttypeByVal)
! 		{
! 			pfree(DatumGetPointer(peraggstate->resultValue));
! 			peraggstate->resultValue = (Datum) 0;
! 			peraggstate->resultValueIsNull = true;
! 		}
! 		else if (peraggstate->restart)
  		{
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  	}
  
  	/*
+ 	 * After this, non-restarted aggregated contain the rows between
+ 	 * aggregatedbase and aggregatedupto_nonrestarted, and restarted aggregates
+ 	 * the rows between aggregatedbase and aggregatedupto, which is to say none,
+ 	 * and aggregatedbase matches frameheadpos. If we modify we must clear
+ 	 * agg_row_slot, see the loop invariant below.
+ 	 */
+ 	winstate->aggregatedbase = winstate->frameheadpos;
+ 	aggregatedupto_nonrestarted = winstate->aggregatedupto;
+ 	if (numaggs_restart > 0 &&
+ 		winstate->aggregatedupto != winstate->frameheadpos)
+ 	{
+ 		winstate->aggregatedupto = winstate->frameheadpos;
+ 		ExecClearTuple(agg_row_slot);
+ 	}
+ 
+ 	/*
+ 	 * If we created a mark pointer for aggregates, keep it pushed up to
+ 	 * frame head, so that tuplestore can discard unnecessary rows.
+ 	 */
+ 	if (agg_winobj->markptr >= 0)
+ 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+ 
+ 	/*
  	 * Advance until we reach a row not in frame (or end of partition).
  	 *
  	 * Note the loop invariant: agg_row_slot is either empty or holds the row
*************** eval_windowaggregates(WindowAggState *wi
*** 551,556 ****
--- 902,912 ----
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
+ 			/* Non-restarted aggregates skip until aggregatedupto_previous*/
+ 			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+ 				!peraggstate->restart)
+ 				continue;
+ 
  			wfuncno = peraggstate->wfuncno;
  			advance_windowaggregate(winstate,
  									&winstate->perfunc[wfuncno],
*************** eval_windowaggregates(WindowAggState *wi
*** 565,570 ****
--- 921,937 ----
  		ExecClearTuple(agg_row_slot);
  	}
  
+ 	/* The frame's end is not supposed to move backwards, ever */
+ 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+ 
+ 	/* Update statistics */
+ 	if (numaggs_restart > 0)
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - winstate->frameheadpos);
+ 	else
+ 		winstate->aggfwdtrans += (winstate->aggregatedupto
+ 								  - aggregatedupto_nonrestarted);
+ 
  	/*
  	 * finalize aggregates and fill result/isnull fields.
  	 */
*************** eval_windowaggregates(WindowAggState *wi
*** 589,616 ****
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal)
  		{
! 			/*
! 			 * clear old resultValue in order not to leak memory.  (Note: the
! 			 * new result can't possibly be the same datum as old resultValue,
! 			 * because we never passed it to the trans function.)
! 			 */
! 			if (!peraggstate->resultValueIsNull)
! 				pfree(DatumGetPointer(peraggstate->resultValue));
! 
! 			/*
! 			 * If pass-by-ref, copy it into our aggregate context.
! 			 */
! 			if (!*isnull)
! 			{
! 				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
! 				peraggstate->resultValue =
! 					datumCopy(*result,
! 							  peraggstate->resulttypeByVal,
! 							  peraggstate->resulttypeLen);
! 				MemoryContextSwitchTo(oldContext);
! 			}
  		}
  		else
  		{
--- 956,969 ----
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal && !*isnull)
  		{
! 			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
! 			peraggstate->resultValue =
! 				datumCopy(*result,
! 						  peraggstate->resulttypeByVal,
! 						  peraggstate->resulttypeLen);
! 			MemoryContextSwitchTo(oldContext);
  		}
  		else
  		{
*************** eval_windowfunction(WindowAggState *wins
*** 651,656 ****
--- 1004,1010 ----
  	/* Just in case, make all the regular argument slots be null */
  	memset(fcinfo.argnull, true, perfuncstate->numArguments);
  
+ 	winstate->calledaggno = -1;
  	*result = FunctionCallInvoke(&fcinfo);
  	*isnull = fcinfo.isnull;
  
*************** spool_tuples(WindowAggState *winstate, i
*** 794,800 ****
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kluge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
--- 1148,1154 ----
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kludge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
*************** release_partition(WindowAggState *winsta
*** 869,875 ****
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
--- 1223,1232 ----
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < winstate->numaggs; ++i)
! 		if (!winstate->peragg[i].aggcontext_is_shared)
! 			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1419,1425 ****
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno;
  	ListCell   *l;
  
  	/* check for unsupported flags */
--- 1776,1783 ----
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno,
! 				numaggs_invtrans;
  	ListCell   *l;
  
  	/* check for unsupported flags */
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1450,1457 ****
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived context for aggregate trans values etc */
! 	winstate->aggcontext =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
--- 1808,1817 ----
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived contexts for aggregate trans values etc
! 	 * Note that invertible aggregates use their own private context
! 	 */
! 	winstate->aggcontext_shared =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1535,1540 ****
--- 1895,1901 ----
  
  	wfuncno = -1;
  	aggno = -1;
+ 	numaggs_invtrans = 0;
  	foreach(l, winstate->funcs)
  	{
  		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1603,1608 ****
--- 1964,1971 ----
  			peraggstate = &winstate->peragg[aggno];
  			initialize_peragg(winstate, wfunc, peraggstate);
  			peraggstate->wfuncno = wfuncno;
+ 			if (peraggstate->use_invtransfn)
+ 				numaggs_invtrans++;
  		}
  		else
  		{
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1618,1623 ****
--- 1981,1987 ----
  	/* Update numfuncs, numaggs to match number of unique functions found */
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
+ 	winstate->numaggs_invtrans = numaggs_invtrans;
  
  	/* Set up WindowObject for aggregates, if needed */
  	if (winstate->numaggs > 0)
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1646,1651 ****
--- 2010,2021 ----
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	/* initialize temporary data */
+ 	winstate->calledaggno = -1;
+ 
+ 	/* initialize statistics */
+ 	winstate->aggfwdtrans = 0;
+ 
  	return winstate;
  }
  
*************** void
*** 1657,1668 ****
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
  
  	release_partition(node);
  
- 	pfree(node->perfunc);
- 	pfree(node->peragg);
- 
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
--- 2027,2036 ----
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
+ 	int			i;
  
  	release_partition(node);
  
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
*************** ExecEndWindowAgg(WindowAggState *node)
*** 1677,1683 ****
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
--- 2045,2057 ----
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext_shared);
! 	for(i = 0; i < node->numaggs; i++)
! 		if (!node->peragg[i].aggcontext_is_shared)
! 			MemoryContextDelete(node->peragg[i].aggcontext);
! 
! 	pfree(node->perfunc);
! 	pfree(node->peragg);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
*************** initialize_peragg(WindowAggState *winsta
*** 1735,1742 ****
--- 2109,2118 ----
  	Oid			aggtranstype;
  	AclResult	aclresult;
  	Oid			transfn_oid,
+ 				invtransfn_oid,
  				finalfn_oid;
  	Expr	   *transfnexpr,
+ 			   *invtransfnexpr,
  			   *finalfnexpr;
  	Datum		textInitVal;
  	int			i;
*************** initialize_peragg(WindowAggState *winsta
*** 1762,1767 ****
--- 2138,2144 ----
  	 */
  
  	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+ 	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
  	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
  	/* Check that aggregate owner has permission to call component fns */
*************** initialize_peragg(WindowAggState *winsta
*** 1783,1788 ****
--- 2160,2176 ----
  			aclcheck_error(aclresult, ACL_KIND_PROC,
  						   get_func_name(transfn_oid));
  		InvokeFunctionExecuteHook(transfn_oid);
+ 
+ 		if (OidIsValid(invtransfn_oid))
+ 		{
+ 			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+ 										 ACL_EXECUTE);
+ 			if (aclresult != ACLCHECK_OK)
+ 				aclcheck_error(aclresult, ACL_KIND_PROC,
+ 				get_func_name(invtransfn_oid));
+ 			InvokeFunctionExecuteHook(invtransfn_oid);
+ 		}
+ 
  		if (OidIsValid(finalfn_oid))
  		{
  			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
*************** initialize_peragg(WindowAggState *winsta
*** 1810,1822 ****
--- 2198,2218 ----
  							wfunc->wintype,
  							wfunc->inputcollid,
  							transfn_oid,
+ 							invtransfn_oid,
  							finalfn_oid,
  							&transfnexpr,
+ 							&invtransfnexpr,
  							&finalfnexpr);
  
  	fmgr_info(transfn_oid, &peraggstate->transfn);
  	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+ 		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+ 	}
+ 
  	if (OidIsValid(finalfn_oid))
  	{
  		fmgr_info(finalfn_oid, &peraggstate->finalfn);
*************** initialize_peragg(WindowAggState *winsta
*** 1860,1867 ****
--- 2256,2341 ----
  							wfunc->winfnoid)));
  	}
  
+ 	/*
+ 	 * Allowing only the forward transition function to be strict would
+ 	 * require handling more special cases in advance_windowaggregate() and
+ 	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 	 * that if the forward transition function is strict that the inverse
+ 	 * transition function is also strict. This should have been checked at
+ 	 * the aggregate function's definition time, but it's better to be safe...
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		peraggstate->transfn.fn_strict && !peraggstate->invtransfn.fn_strict)
+ 	{
+ 		ereport(ERROR,
+ 			(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 			errmsg("aggregate functions inverse transition function must strict if the forward transition function is")));
+ 	}
+ 
  	ReleaseSysCache(aggTuple);
  
+ 	/*
+ 	 * We ignore NULLs if either of the transition function is strict, i.e
+ 	 * if in that case neither of the functions will ever see NULL inputs.
+ 	 * If the forward transition function is non-strict, and the initial value
+ 	 * is NULL, it will receive a NULL state upon the first non-NULL input
+ 	 * though.
+ 	 */
+ 	peraggstate->ignore_nulls = ((OidIsValid(transfn_oid) &&
+ 								  peraggstate->transfn.fn_strict) ||
+ 								 (OidIsValid(invtransfn_oid) &&
+ 								  peraggstate->invtransfn.fn_strict));
+ 
+ 	/*
+ 	 * We can use the inverse transition function only if the aggregate's
+ 	 * arguments don't contain calls to volatile functions. Otherwise,
+ 	 * the difference between restarting and not restarting the aggregation
+ 	 * would be user-visible. Note that this check also covers the case where
+ 	 * the FILTER's WHERE clause contains a volatile function. If the frame
+ 	 * head cannot move, we won't ever need the inverse transition function,
+ 	 * so we also mark as "don't use" in that case.
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		!contain_volatile_functions((Node *) wfunc) &&
+ 		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+ 	{
+ 		peraggstate->use_invtransfn = true;
+ 	}
+ 	else
+ 	{
+ 		peraggstate->use_invtransfn = false;
+ 	}
+ 
+ 	/*
+ 	 * Invertible aggregates use their own aggcontext.
+ 	 *
+ 	 * This is necessary because they might all restart at different times,
+ 	 * so we might never be able to reset the shared context otherwise. We
+ 	 * can't make it the aggregate's responsibility to clean up after
+ 	 * themselves, because strict aggregates must be restarted whenever we
+ 	 * remove their last non-NULL input, which the aggregate won't be aware
+ 	 * is happening. Also, just pfree()ing the transValue upon restarting
+ 	 * wouldn't help, since we'd miss any indirectly referenced data. We
+ 	 * could, in theory, declare that aggregates with a state type other then
+ 	 * "internal" musn't allocate anything in the aggcontext themselves, that
+ 	 * non-strict aggregates with state type internal must clean up after
+ 	 * themselves when their inverse transfer function returns NULL, and then
+ 	 * only use private aggcontexts for strict aggregates with state type
+ 	 * internal. But that'd be a rather grotty set of requirements.
+ 	 */
+ 	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+ 	if (!peraggstate->aggcontext_is_shared)
+ 	{
+ 		peraggstate->aggcontext =
+ 			AllocSetContextCreate(CurrentMemoryContext,
+ 								  "WindowAgg_AggregatePrivate",
+ 								  ALLOCSET_DEFAULT_MINSIZE,
+ 								  ALLOCSET_DEFAULT_INITSIZE,
+ 								  ALLOCSET_DEFAULT_MAXSIZE);
+ 	}
+ 	else
+ 		peraggstate->aggcontext = winstate->aggcontext_shared;
+ 
  	return peraggstate;
  }
  
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..91bea45 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1203,1210 ****
--- 1203,1212 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr)
  {
  	Param	   *argp;
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1249,1254 ****
--- 1251,1269 ----
  	fexpr->funcvariadic = agg_variadic;
  	*transfnexpr = (Expr *) fexpr;
  
+ 
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+ 												agg_result_type,
+ 												args,
+ 												InvalidOid,
+ 												agg_input_collation,
+ 												COERCE_EXPLICIT_CALL);
+ 	}
+ 	else
+ 		*invtransfnexpr	= NULL;
+ 
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
  	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3862f05..6b28572 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11510,11515 ****
--- 11510,11516 ----
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
+ 	int			i_agginvtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
  	int			i_hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11518,11523 ****
--- 11519,11525 ----
  	int			i_agginitval;
  	int			i_convertok;
  	const char *aggtransfn;
+ 	const char *agginvtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
  	bool		hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11542,11548 ****
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
--- 11544,11550 ----
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11625,11630 ****
--- 11627,11633 ----
  	res = ExecuteSqlQueryForSingleRow(fout, query->data);
  
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
+ 	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
  	i_hypothetical = PQfnumber(res, "hypothetical");
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11634,11639 ****
--- 11637,11643 ----
  	i_convertok = PQfnumber(res, "convertok");
  
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ 	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11704,11709 ****
--- 11708,11719 ----
  		appendStringLiteralAH(details, agginitval, fout);
  	}
  
+ 	if (strcmp(agginvtransfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    INVFUNC = %s",
+ 			agginvtransfn);
+ 	}
+ 
  	if (strcmp(aggfinalfn, "-") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    FINALFUNC = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 96f08d3..2fb3871 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 46,51 ****
--- 46,52 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		agginvtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 69,85 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_agginvtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,276 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 103,278 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	-	json_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
*************** extern Oid AggregateCreate(const char *a
*** 288,293 ****
--- 290,296 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 455c089..c2bde24 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** extern void **find_rendezvous_variable(c
*** 644,651 ****
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly with nodeAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
--- 644,653 ----
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
!  * which is in execute/nodeWindowAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly neither nodeAgg
!  * nor nodeWindowAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..ff558c6 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WindowAggState
*** 1738,1743 ****
--- 1738,1744 ----
  	List	   *funcs;			/* all WindowFunc nodes in targetlist */
  	int			numfuncs;		/* total number of window functions */
  	int			numaggs;		/* number that are plain aggregates */
+ 	int			numaggs_invtrans;	/* number that are invertible aggregates */
  
  	WindowStatePerFunc perfunc; /* per-window-function information */
  	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
*************** typedef struct WindowAggState
*** 1762,1768 ****
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext;	/* context for each aggregate data */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
--- 1763,1769 ----
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext_shared;	/* shared context for agg states */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
*************** typedef struct WindowAggState
*** 1780,1789 ****
--- 1781,1796 ----
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
  
+ 	/* temporary data */
+ 	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+ 
  	/* temporary slots for tuples fetched back from tuplestore */
  	TupleTableSlot *agg_row_slot;
  	TupleTableSlot *temp_slot_1;
  	TupleTableSlot *temp_slot_2;
+ 
+ 	/* Statistics */
+ 	double	aggfwdtrans;	/* number of forward transitions */
  } WindowAggState;
  
  /* ----------------
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void build_aggregate_fnexprs(Oid 
*** 39,46 ****
--- 39,48 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr);
  
  #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..0def229 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select least_agg(variadic array[q1,q2]) 
*** 1580,1582 ****
--- 1580,1606 ----
   -4567890123456789
  (1 row)
  
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict AND NOT pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..6e2723f 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** alter aggregate my_rank(VARIADIC "any" O
*** 90,92 ****
--- 90,129 ----
   public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
  (2 rows)
  
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi
+ );
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_n
+ );
+ ERROR:  aggregate functions inverse transition function must strict if the forward transition function is
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = intminus
+ );
+ ERROR:  function intminus(double precision, double precision) does not exist
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_int
+ );
+ ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..df676ad 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT nth_value_def(ten) OVER (PARTITIO
*** 1071,1073 ****
--- 1071,1290 ----
               1 |   3 |    3
  (10 rows)
  
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict
+ );
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict
+ );
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+ ----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+  1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+  1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+  1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+  1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+  1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+  2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+  3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+ (9 rows)
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+ ----------+--------------+-------------------+-------------+------------------
+  1,1:NULL | +NULL        | I+NULL            |             | I
+  1,2:-    | +NULL        | I+NULL            |             | I
+  1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+  1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+  1,5:-    |              | I                 |             | I
+  1,6:-    |              | I                 |             | I
+  2,1:-    |              | I                 |             | I
+  2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+  3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+ (9 rows)
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invfunc = sum_int_randrestart_invsfunc
+ );
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+  eq1 | eq2 | eq3 
+ -----+-----+-----
+  t   | t   | t
+ (1 row)
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..284ea76 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** drop view aggordview1;
*** 590,592 ****
--- 590,609 ----
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict AND NOT pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..b7fd4a3 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** alter aggregate my_rank(VARIADIC "any" O
*** 101,103 ****
--- 101,144 ----
    rename to test_rank;
  
  \da test_*
+ 
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi
+ );
+ 
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_n
+ );
+ 
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = intminus
+ );
+ 
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invfunc = float8mi_int
+ );
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..65c1005 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** SELECT nth_value_def(n := 2, val := ten)
*** 284,286 ****
--- 284,478 ----
  
  SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
    FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+ 
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ 
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict
+ );
+ 
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ 
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict
+ );
+ 
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ 
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invfunc = sum_int_randrestart_invsfunc
+ );
+ 
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ 
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
invtrans_collecting_0e7079.patchapplication/octet-stream; name=invtrans_collecting_0e7079.patch; x-unix-mode=0644Download
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index c62e3fb..5a3a31d 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** create_singleton_array(FunctionCallInfo 
*** 471,477 ****
  
  
  /*
!  * ARRAY_AGG aggregate function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
--- 471,477 ----
  
  
  /*
!  * ARRAY_AGG aggregate transition function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 508,513 ****
--- 508,537 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * ARRAY_AGG aggregate inverse transition function
+  */
+ Datum
+ array_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since NULLs
+ 	 * need to be removed just like any other value.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "array_agg_invtransfn called with NULL state");
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "array_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	shiftArrayResult(state, 1);
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 311d0c2..828e72d 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** accumArrayResult(ArrayBuildState *astate
*** 4587,4592 ****
--- 4587,4593 ----
  		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
  		astate->mcontext = arr_context;
  		astate->alen = 64;		/* arbitrary starting array size */
+ 		astate->offset = 0;
  		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
  		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
  		astate->nelems = 0;
*************** accumArrayResult(ArrayBuildState *astate
*** 4600,4606 ****
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/* enlarge dvalues[]/dnulls[] if needed */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
--- 4601,4612 ----
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/*
! 		 * If the buffers are filled completely (offset must be zero then),
! 		 * we double their size. If they aren't, but the values extend to the
! 		 * end of the buffers, we reclaim wasted space at the beginning by
! 		 * moving the values to the front of the buffers.
! 		 */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
*************** accumArrayResult(ArrayBuildState *astate
*** 4609,4614 ****
--- 4615,4629 ----
  			astate->dnulls = (bool *)
  				repalloc(astate->dnulls, astate->alen * sizeof(bool));
  		}
+ 		else if (astate->offset + astate->nelems >= astate->alen)
+ 		{
+ 			memmove(astate->dvalues, astate->dvalues + astate->offset,
+ 					astate->alen * sizeof(Datum));
+ 			memmove(astate->dnulls, astate->dnulls + astate->offset,
+ 					astate->alen * sizeof(bool));
+ 			astate->offset = 0;
+ 		}
+ 		Assert(astate->offset + astate->nelems < astate->alen);
  	}
  
  	/*
*************** accumArrayResult(ArrayBuildState *astate
*** 4627,4634 ****
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->nelems] = dvalue;
! 	astate->dnulls[astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
--- 4642,4649 ----
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->offset + astate->nelems] = dvalue;
! 	astate->dnulls[astate->offset + astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
*************** accumArrayResult(ArrayBuildState *astate
*** 4637,4642 ****
--- 4652,4698 ----
  }
  
  /*
+  * shiftArrayResult - shift leading Datums out of an array result
+  *
+  *	astate is working state
+  *	count is the number of leading Datums to shift out
+  *
+  * If count is equal to or larger than the number of relements, the array
+  * result is empty afterwards. If astate is NULL, nothing is done.
+  */
+ void
+ shiftArrayResult(ArrayBuildState *astate, int count)
+ {
+ 	int		i;
+ 
+ 	if (astate == NULL)
+ 		return;
+ 
+ 	/* Limit shift count to number of elements for safety */
+ 	count = Min(count, astate->nelems);
+ 
+ 	/* For pass-by-ref types, free values we shift out */
+ 	if (!astate->typbyval) {
+ 		for(i = astate->offset; i < astate->offset + count; ++i) {
+ 			if (astate->dnulls[i])
+ 				continue;
+ 
+ 			pfree(DatumGetPointer(astate->dvalues[i]));
+ 
+ 			/* For cleanliness' sake */
+ 			astate->dnulls[i] = false;
+ 			astate->dvalues[i] = 0;
+ 		}
+ 
+ 	}
+ 
+ 	/* Update state */
+ 	astate->nelems -= count;
+ 	astate->offset += count;
+ }
+ 
+ 
+ /*
   * makeArrayResult - produce 1-D final result of accumArrayResult
   *
   *	astate is working state (not NULL)
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4679,4686 ****
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues,
! 								astate->dnulls,
  								ndims,
  								dims,
  								lbs,
--- 4735,4742 ----
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues + astate->offset,
! 								astate->dnulls + astate->offset,
  								ndims,
  								dims,
  								lbs,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..768ee49 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** typedef struct
*** 50,55 ****
--- 50,63 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct StringAggState
+ {
+ 	StringInfoData 	string; 	/* Contents */
+ 	int 			offset;		/* Offset into stringinfo's data */
+ 	int 			delimLen;	/* Delim length, -1 initially, -2 if multiple */
+ 	int64			notNullCount;/* Number of non-NULL inputs */
+ } StringAggState;
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
*************** static void appendStringInfoText(StringI
*** 78,84 ****
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
--- 86,95 ----
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo);
! static void prepareAppendStringAggState(StringAggState *state,
! 										int delimLen, int valueLen);
! static bool removeFromStringAggState(StringAggState *state, int valueLen);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
*************** static void text_format_string_conversio
*** 92,97 ****
--- 103,226 ----
  static void text_format_append_string(StringInfo buf, const char *str,
  						  int flags, int width);
  
+ /*****************************************************************************
+  *	 SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA)			 *
+  *****************************************************************************/
+ 
+ /*
+  * subroutine to initialize state
+  */
+ static StringAggState*
+ makeStringAggState(FunctionCallInfo fcinfo)
+ {
+ 	StringAggState*	state;
+ 	MemoryContext aggcontext;
+ 	MemoryContext oldcontext;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 	}
+ 
+ 	/*
+ 	 * Create state in aggregate context.  It'll stay there across subsequent
+ 	 * calls.
+ 	 */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = (StringAggState *) palloc(sizeof(StringAggState));
+ 	initStringInfo(&state->string);
+ 	state->offset = 0;
+ 	state->delimLen = -1;
+ 	state->notNullCount = 0;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * Prepare state for appending a value and a delimiter with specified lengths.
+  * pass -1 for delimLen if no delimiter will be added
+  */
+ void
+ prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen)
+ {
+ 	/*
+ 	 * Reclaim wasted space
+ 	 *
+ 	 * We move the contents to the left if the current contents fit into the
+ 	 * wasted space, i.e. if we waste more than we store. The limit is
+ 	 * somewhat arbitrary, but it's the smallest one that allows
+ 	 * memcpy to be used, because the source and destination don't overlap.
+ 	 * Note that we must check for <, not <=, because we include the trailing
+ 	 * '\0' in the copy.
+ 	 */
+ 	if (state->string.len - state->offset < state->offset)
+ 	{
+ 		state->string.len -= state->offset;
+ 		memcpy(state->string.data, state->string.data + state->offset,
+ 			   state->string.len + 1);
+ 		state->offset = 0;
+ 	}
+ 
+ 	/*
+ 	 * Enlarge StringInfo
+ 	 *
+ 	 * Not strictly necessary, but avoids potentially resizing twice when
+ 	 * the actual append... calls are done by the caller
+ 	 */
+ 	enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen);
+ 
+ 
+ 	/* Track delimiter length */
+ 	if (delimLen == -1)
+ 		{} /* Not specified, don't update */
+ 	else if (state->delimLen == -1)
+ 		state->delimLen = delimLen;
+ 	else if (state->delimLen != delimLen)
+ 		state->delimLen = -2;
+ }
+ 
+ /*
+  * Remove value with given length and the delimiter that follows
+  *
+  * Returns false if removal was impossible because delimiters varied
+  */
+ bool
+ removeFromStringAggState(StringAggState *state, int valueLen)
+ {
+ 	/* Remove the string */
+ 	state->offset += valueLen;
+ 
+ 	/*
+ 	 * Remove delimiter if necessary.
+ 	 *
+ 	 * The delimiter we need to remove isn't the delimiter we were passed, but
+ 	 * rather the delimiter passed when adding the input *after* this one. We
+ 	 * thus need the delimiter length to be all the same to be able to proceed.
+ 	 * If we're removing the last string, there will be no delimiter following
+ 	 * it. In that case, we may reset delimLen to its initial value.
+ 	 */
+ 	if (state->delimLen == -2)
+ 		return false;
+ 	if (state->offset < state->string.len)
+ 	{
+ 		Assert(state->delimLen >= 0);
+ 		state->offset += state->delimLen;
+ 	}
+ 	else
+ 		state->delimLen = -1;
+ 
+ 	/* Don't crash if we're ever asked to remove more than was added */
+ 	if (state->offset > state->string.len)
+ 	{
+ 		state->offset = state->string.len;
+ 		elog(ERROR, "tried to remove more data than was aggregated");
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /*****************************************************************************
   *	 CONVERSION ROUTINES EXPORTED FOR USE BY C CODE							 *
*************** byteasend(PG_FUNCTION_ARGS)
*** 408,435 ****
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		bytea	   *value = PG_GETARG_BYTEA_PP(1);
  
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 		{
! 			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim));
! 		}
  
! 		appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value));
  	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
--- 537,589 ----
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	bytea		   *value,
! 				   *delim;
! 	int				valueLen,
! 					delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
  
! 	value = PG_GETARG_BYTEA_PP(1);
! 	valueLen = VARSIZE_ANY_EXHDR(value);
! 	state->notNullCount++;
  
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
! 	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_BYTEA_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
! 	}
! 	else
! 	{
! 		/* Delimiter is NULL, treat as zero-length string */
! 		delim = NULL;
! 		delimLen = 0;
  	}
  
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, valueLen);
+ 	if (delim)
+ 		appendBinaryStringInfo(&state->string, VARDATA_ANY(delim),
+ 							   VARSIZE_ANY_EXHDR(delim));
+ 	appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen);
+ 
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 438,459 ****
  }
  
  Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
  	{
  		bytea	   *result;
  
! 		result = (bytea *) palloc(state->len + VARHDRSZ);
! 		SET_VARSIZE(result, state->len + VARHDRSZ);
! 		memcpy(VARDATA(result), state->data, state->len);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
--- 592,661 ----
  }
  
  Datum
+ bytea_string_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	int				valueLen;
+ 	StringAggState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since it
+ 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "string_agg_invtransfn called with NULL state");
+ 	else if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (StringAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* We append nothing if the string is NULL, so skip here as well */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_POINTER(state);
+ 
+ 	/* No need to de-toast value, need only the length */
+ 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
+ 	Assert(state->notNullCount >= 1);
+ 	state->notNullCount--;
+ 
+ 	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
+ 	 * The transition type for string_agg() is declared to be "internal",
+ 	 * which is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	if (removeFromStringAggState(state, valueLen))
+ 		PG_RETURN_POINTER(state);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
  	{
+ 		int			resultLen = state->string.len  - state->offset;
  		bytea	   *result;
  
! 		result = (bytea *) palloc(resultLen + VARHDRSZ);
! 		SET_VARSIZE(result, resultLen + VARHDRSZ);
! 		memcpy(VARDATA(result), state->string.data + state->offset, resultLen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3733,3801 ****
   * the associated value.
   */
  
! /* subroutine to initialize state */
! static StringInfo
! makeStringAggState(FunctionCallInfo fcinfo)
  {
! 	StringInfo	state;
! 	MemoryContext aggcontext;
! 	MemoryContext oldcontext;
  
! 	if (!AggCheckCallContext(fcinfo, &aggcontext))
  	{
! 		/* cannot be called directly because of internal-type argument */
! 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
  	}
  
  	/*
! 	 * Create state in aggregate context.  It'll stay there across subsequent
! 	 * calls.
  	 */
! 	oldcontext = MemoryContextSwitchTo(aggcontext);
! 	state = makeStringInfo();
! 	MemoryContextSwitchTo(oldcontext);
! 
! 	return state;
  }
  
  Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 			appendStringInfoText(state, PG_GETARG_TEXT_PP(2));	/* delimiter */
  
! 		appendStringInfoText(state, PG_GETARG_TEXT_PP(1));		/* value */
! 	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len));
  	else
  		PG_RETURN_NULL();
  }
--- 3935,4054 ----
   * the associated value.
   */
  
! Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	text		   *value,
! 				   *delim;
! 	int				delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
! 
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
! 
! 	value = PG_GETARG_TEXT_PP(1);
! 	state->notNullCount++;
! 
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
  	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_TEXT_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
  	}
+ 	else
+ 	{
+ 		/* Delimiter is NULL, treat as zero-length string */
+ 		delim = NULL;
+ 		delimLen = 0;
+ 	}
+ 
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value));
+ 	if (delim)
+ 		appendStringInfoText(&state->string, delim);
+ 	appendStringInfoText(&state->string, value);
  
  	/*
! 	 * The transition type for string_agg() is declared to be "internal",
! 	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
! string_agg_invtransfn(PG_FUNCTION_ARGS)
  {
! 	int				valueLen;
! 	StringAggState *state;
  
! 	/*
! 	 * Shouldn't happen, but we cannot mark this function strict, since it
! 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
! 	 * Must also prevent direct calls because of the "interal" argument
! 	 */
! 	if (PG_ARGISNULL(0))
! 		elog(ERROR, "string_agg_invtransfn called with NULL state");
! 	else if (!AggCheckCallContext(fcinfo, NULL))
! 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
  
! 	state = (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* We append nothing if the string is NULL, so skip here as well */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* No need to de-toast value, need only the length */
! 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
! 	Assert(state->notNullCount >= 1);
! 	state->notNullCount--;
  
  	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	if (removeFromStringAggState(state, valueLen))
! 		PG_RETURN_POINTER(state);
! 	else
! 		PG_RETURN_NULL();
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset,
! 												  state->string.len - state->offset));
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 2fb3871..5c9488f 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-	-					0	
*** 250,262 ****
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn	-	json_agg_finalfn	0	2281	0	_null_ ));
--- 250,262 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_invtransfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_invtransfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_invtransfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn	-	json_agg_finalfn	0	2281	0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ad9774c..1c2d8bb 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 875,880 ****
--- 875,882 ----
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3180 (  array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2463,2474 ****
--- 2465,2480 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3546 (  string_agg_invtransfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3538 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into a string");
  DATA(insert OID = 3543 (  bytea_string_agg_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3547 (  bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3544 (  bytea_string_agg_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..9a6fc39 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 83,88 ****
--- 83,89 ----
  	Datum	   *dvalues;		/* array of accumulated Datums */
  	bool	   *dnulls;			/* array of is-null flags for Datums */
  	int			alen;			/* allocated length of above arrays */
+ 	int			offset;			/* offset of first element in above arrays */
  	int			nelems;			/* number of valid entries in above arrays */
  	Oid			element_type;	/* data type of the Datums */
  	int16		typlen;			/* needed info about datatype */
*************** extern ArrayBuildState *accumArrayResult
*** 255,260 ****
--- 256,262 ----
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
  				 MemoryContext rcontext);
+ extern void shiftArrayResult(ArrayBuildState *astate, int count);
  extern Datum makeArrayResult(ArrayBuildState *astate,
  				MemoryContext rcontext);
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
*************** extern ArrayType *create_singleton_array
*** 290,295 ****
--- 292,298 ----
  					   int ndims);
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
  
  /*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..7d8e749 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 812,820 ****
--- 812,822 ----
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
  extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS);
  
  extern Datum text_concat(PG_FUNCTION_ARGS);
  extern Datum text_concat_ws(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index df676ad..81a4b74 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1288,1290 ****
--- 1288,1334 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
+      row     | str | str_del | str_vardel |      bin       |      bin_del       |      bin_vardel      |       ary        
+ -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------
+  1:1,0100,-  | -   | -       | -          | -              | -                  | -                    | 
+  2:-,0200,2  | 1   | 1       | 1          | \x0100         | \x0100             | \x0100               | {NULL}
+  3:3,----,3  | 1   | 1       | 1          | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2}
+  4:-,0400,4  | 13  | 1,3     | 13         | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2,3}
+  5:5,----,-  | 3   | 3       | 3          | \x02000400     | \x0200000400       | \x0200000400         | {2,3,4}
+  6:6,0600,6  | 35  | 3,5     | 3||5       | \x0400         | \x0400             | \x0400               | {3,4,NULL}
+  7:7,0700,-  | 56  | 5,6     | 56         | \x04000600     | \x0400000600       | \x04000600           | {4,NULL,6}
+  8:8,0800,8  | 567 | 5,6,7   | 56|7       | \x06000700     | \x0600000700       | \x0600000700         | {NULL,6,NULL}
+  9:-,----,-  | 678 | 6,7,8   | 6|7||8     | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8}
+  10:-,----,- | 78  | 7,8     | 7||8       | \x07000800     | \x0700000800       | \x070000000800       | {NULL,8,NULL}
+  11:-,----,- | 8   | 8       | 8          | \x0800         | \x0800             | \x0800               | {8,NULL,NULL}
+  12:-,----,- | -   | -       | -          | -              | -                  | -                    | {NULL,NULL,NULL}
+ (12 rows)
+ 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 65c1005..d7c3f27 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 476,478 ****
--- 476,507 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ 
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
invtrans_docs_0cb944.patchapplication/octet-stream; name=invtrans_docs_0cb944.patch; x-unix-mode=0644Download
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2230c93..44d547f 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 376,381 ****
--- 376,387 ----
        <entry>Transition function</entry>
       </row>
       <row>
+       <entry><structfield>agginvtransfn</structfield></entry>
+       <entry><type>regproc</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>Inverse transition function</entry>
+      </row>
+      <row>
        <entry><structfield>aggfinalfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c0a75de..51c493d 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT xmlagg(x) FROM (SELECT x FROM tes
*** 12885,12890 ****
--- 12885,13012 ----
     <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
     Other frame specifications can be used to obtain other effects.
    </para>
+   
+   <para>
+     Depending on the aggregate function, aggregating over frames starting
+     at a row relative to the current row can be drastically less efficient
+     than aggregating over frames aligned to the start of the partition. The
+     frame starts at a row relative to the current row if <literal>ORDER
+     BY</literal> is used together with any frame start clause other than
+     <literal>UNBOUNDED PRECEDING</literal> (which is the default). Then,
+     aggregates without a suitable <quote>inverse transition function
+     </quote> (see <xref linkend="SQL-CREATEAGGREGATE"> for details) will be
+     computed for each frame from scratch, instead of re-using the previous
+     frame's result, causing <emphasis>quadratic growth</emphasis> of the
+     execution time as the number of rows per partition increases. The table
+     <xref linkend="functions-aggregate-indframe"> list the built-in aggregate
+     functions affected by this. Note that quadratic growth is only a problem
+     if partitions contain many rows - for partitions with only a few rows,
+     even inefficient aggregates are unlikely to cause problems.
+   </para>
+ 
+   <table id="functions-aggregate-indframe">
+    <title>
+      Aggregate Function Behaviour for frames not starting at
+      <literal>UNBOUNDED PRECEDING</literal>.
+    </title>
+ 
+    <tgroup cols="3">
+      
+     <thead>
+      <row>
+       <entry>Aggregate Function</entry>
+       <entry>Input Type</entry>
+       <entry>Computed From Scratch</entry>
+      </row>
+     </thead>
+     
+     <tbody>
+       
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>float4</type>
+        or
+        <type>float8</type>
+       </entry>
+       <entry>always, to avoid error accumulation</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>numeric</type>
+       </entry>
+       <entry>if the maximum number of decimal digits within the inputs changes</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>min</function>
+       </entry>
+       <entry>
+        any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were minimal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>max</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were maximal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>bit_and</function>,<function>bit_or</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>string_agg</function>
+       </entry>
+       <entry>
+        <type>text</type> or
+        <type>bytea</type> or
+       </entry>
+       <entry>if the delimiter lengths vary</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>xmlagg</function>
+       </entry>
+       <entry>
+        <type>xml</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>json_agg</function>
+       </entry>
+       <entry>
+        <type>json</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
  
    <note>
     <para>
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index d15fcba..aaa09a7 100644
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
*************** CREATE AGGREGATE <replaceable class="par
*** 25,30 ****
--- 25,31 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 47,52 ****
--- 48,54 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 84,98 ****
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
!    a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
--- 86,103 ----
    </para>
  
    <para>
!    An aggregate function is made from one, two or three ordinary
     functions:
!    a (forward) state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
+    an optional inverse state transition function
+    <replaceable class="PARAMETER">invfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
+ <replaceable class="PARAMETER">invfunc</replaceable>( internal-state, data-values ) ---> internal-state-without-data-values
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 102,113 ****
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.  After all the rows have been processed,
!    the final function is invoked once to calculate the aggregate's return
!    value.  If there is no final function then the ending state value
!    is returned as-is.
    </para>
  
    <para>
--- 107,134 ----
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the forward state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.
!    If the aggregate is computed over a sliding frame, i.e. if it is used as a
!    <firstterm>window function</firstterm>, the inverse transition function is
!    used to undo the effect of a previous invocation of the forward transition
!    function once argument value(s) fall out of the sliding frame.
!    Conceptually, the forward transition functions thus adds some input
!    value(s) to the state, and the inverse transition functions removes them
!    again. Values are, if they are removed, always removed in the same order
!    they were added, without gaps. Whenever the inverse transition function is
!    invoked, it will thus receive the earliest added but not yet removed
!    argument value(s). If no inverse transition function is supplied, the
!    aggregate can still be used to aggregate over sliding frames, but with
!    reduced efficiency. <productname>PostgreSQL</productname> will then
!    recompute the whole aggregation whenever the start of the frame moves. To
!    calculate the aggregate's return value, the final function is invoked on
!    the ending state value. If there is no final function then ending state
!    value is returned as-is. Either way, the result is assumed to reflect the
!    aggregation of all values added but not yet removed from the state value.
!    Note that if the aggregate is used as a window function, the aggregation
!    may be continued after the final function has been called.
    </para>
  
    <para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 127,135 ****
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values.
!    This is handy for implementing aggregates like <function>max</function>.
!    Note that this behavior is only available when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
--- 148,164 ----
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values. Should inputs later need to be removed again, the
!    inverse transition function (if present) is used as long as some non-null
!    inputs remain part of the state value. In particular, even if the initial
!    state value is null, the inverse transition function might be used to remove
!    the first non-null input, even though that input was never passed to the
!    forward transition function, but instead just replaced the initial state!
!    The last non-null input, however, is not removed by invoking the inverse
!    transition function, but instead the state is simply reset to its initial
!    value. This is handy for implementing aggregates like <function>max</function>.
!    Note that turning the first non-null input into the initial state is only
!    possible when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
*************** CREATE AGGREGATE <replaceable class="PAR
*** 138,147 ****
    </para>
  
    <para>
!    If the state transition function is not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs
!    and null state values for itself.  This allows the aggregate
!    author to have full control over the aggregate's handling of null values.
    </para>
  
    <para>
--- 167,206 ----
    </para>
  
    <para>
!     If the forward transition function is not declared <quote>strict</quote>,
!     but the inverse transition function is, null input values are still not
!     passed to either the forward or the inverse transition function. The
!     system then behaves as it would for a strict forward transition function,
!     except that initial value null isn't handled specially, i.e. isn't
!     replaced by the first argument of the first non-null input. Instead the
!     forward transition function is simply invoked with a null state and
!     non-null argument values. So in effect, in this case the forward
!     transition function is being treated as non-strict in the state argument
!     but strict in all argument values.
!   </para> 
! 
!   <para>
!    If the state transition function is not <quote>strict</quote>, and either
!    no inverse was provided or is also not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs and null
!    state values for itself. The same goes for the inverse transition function.
!    This allows the aggregate author to have full control over the
!    aggregate's handling of null argument values.
!   </para>
!   
!   <para>
!     The inverse transition function can signal by returning null that it is
!     unable to remove a particular input value from a particular state.
!     <productname>PostgreSQL</productname> will then act as if no inverse
!     transition function had been supplied, i.e. it will recompute the whole
!     aggregation, starting with the first argument value that it would not have
!     removed. That allows aggregates like <function>max</function> to still
!     avoid redoing the whole aggregation in <emphasis>some</emphasis> cases,
!     without paying the overhead of tracking enough state to be able to avoid
!     them in <emphasis>all</emphasis> cases. This demands, however, that
!     null isn't used as a valid state value, except as the initial state. If
!     an aggregate provides an inverse transition function, it is therefore an
!     error for the forward transition function to return null.
    </para>
  
    <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 271,277 ****
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
--- 330,336 ----
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the (forward) state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
*************** SELECT col FROM tab ORDER BY col USING s
*** 281,287 ****
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value.
       </para>
  
       <para>
--- 340,348 ----
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value. Note that if an inverse
!       transition function is present, the forward transition function must
!       not return <literal>NULL</>
       </para>
  
       <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 294,299 ****
--- 355,384 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="PARAMETER">invfunc</replaceable></term>
+     <listitem>
+      <para>
+       The name of the inverse state transition function to be called for each
+       input row.  For a normal <replaceable class="PARAMETER">N</>-argument
+       aggregate function, the <replaceable class="PARAMETER">sfunc</>
+       must take <replaceable class="PARAMETER">N</>+1 arguments,
+       the first being of type <replaceable
+       class="PARAMETER">state_data_type</replaceable> and the rest
+       matching the declared input data type(s) of the aggregate.
+       The function must return a value of type <replaceable
+       class="PARAMETER">state_data_type</replaceable>. These are the same
+       demands placed on the forward transition function, meaning that the
+       signatures of the two functions must be identical. Their
+       <quote>strictness</quote> though may differ, but the only allowed
+       combination is a non-strict forward combined with a strict inverse
+       transition function. The inverse transition function may return
+       <literal>NULL</> to force the aggregation to be restarted from
+       scratch.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="PARAMETER">state_data_type</replaceable></term>
      <listitem>
       <para>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index e77ef12..397e398 100644
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 16,22 ****
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
--- 16,22 ----
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a forward state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
***************
*** 24,30 ****
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
--- 24,42 ----
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result. 
!    To enable efficient evaluation of an aggregate used as a window function
!    with a sliding frame (i.e. a frame that starts relative to the current row),
!    an aggregate can optionally provide an inverse state transition function.
!    The inverse transition function takes the the current state and the
!    aggregate's input value(s) for the <emphasis>earliest</emphasis> row passed
!    to the forward transition function, and returns a state equivalent to what
!    the current state had been had the forward transition function never been
!    invoked for that earliest row, only for all rows that followed it. Thus,
!    if an inverse transition function is provided, the rows that were part of
!    the previous row's frame but not of the current row's frame can simply be
!    removed from the state instead of having to redo the whole aggregation
!    over the new frame.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
*************** CREATE AGGREGATE avg (float8)
*** 132,137 ****
--- 144,188 ----
    </note>
  
    <para>
+    When providing an inverse transition function, care should be taken to
+    ensure that it doesn't introduce unexpected user-visible differences
+    between results obtained by reaggregating all inputs vs. using the inverse
+    transition function. An example for an aggregate where adding an inverse
+    transition function seems easy at first, yet were doing so would violate
+    this requirement is <function>sum</> over <type>float</> or
+    <type>double precision</>. A naive declaration of
+    <function>sum(<type>float</>)</function> could be
+    
+    <programlisting>
+    CREATE AGGREGATE unsafe_sum (float8)
+    (
+        stype = float8,
+        sfunc = float8pl,
+        invfunc = float8mi
+    );
+    </programlisting>
+    
+    This aggregate, howevery, can give wildly different results than it would
+    have without the inverse transition function. For example, consider
+    
+    <programlisting>
+    SELECT
+      unsafe_sum(x) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND
+                                                  1 FOLLOWING)
+    FROM (VALUES
+      (1, 1.0e20::float8),
+      (2, 1.0::float8)
+    ) AS v (n,x)
+    </programlisting>
+    
+    which returns 0 as it's second result, yet the expected answer is 1. The
+    reason for this is the limited precision of floating point types - adding
+    1 to 1e20 actually leaves the value unchanged, and so substracting 1e20
+    again yields 0, not 1. Note that this is a limitation of floating point
+    types in general and not a limitation of <productname>PostgreSQL</>.
+   </para>
+   
+   <para>
     Aggregate functions can use polymorphic
     state transition functions or final functions, so that the same functions
     can be used to implement multiple aggregates.
invtrans_minmax_613af3.patchapplication/octet-stream; name=invtrans_minmax_613af3.patch; x-unix-mode=0644Download
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 311d0c2..dd07f1a 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_larger(PG_FUNCTION_ARGS)
*** 4714,4719 ****
--- 4714,4737 ----
  }
  
  Datum
+ array_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) > 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ 
+ Datum
  array_smaller(PG_FUNCTION_ARGS)
  {
  	ArrayType  *v1,
*************** array_smaller(PG_FUNCTION_ARGS)
*** 4728,4733 ****
--- 4746,4767 ----
  	PG_RETURN_ARRAYTYPE_P(result);
  }
  
+ Datum
+ array_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) < 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
  
  typedef struct generate_subscripts_fctx
  {
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index d419b4a..52488cd 100644
*** a/src/backend/utils/adt/bool.c
--- b/src/backend/utils/adt/bool.c
*************** boolge(PG_FUNCTION_ARGS)
*** 285,290 ****
--- 285,292 ----
  
  /* function for standard EVERY aggregate implementation conforming to SQL 2003.
   * must be strict. It is also named bool_and for homogeneity.
+  * Note: this is no longer used for the bool_and() and every() aggregate
+  * functions.
   */
  Datum
  booland_statefunc(PG_FUNCTION_ARGS)
*************** booland_statefunc(PG_FUNCTION_ARGS)
*** 294,302 ****
--- 296,396 ----
  
  /* function for standard ANY/SOME aggregate conforming to SQL 2003.
   * must be strict. The name of the aggregate is bool_or. See the doc.
+  * Note: this is no longer used for the bool_or aggregate function.
   */
  Datum
  boolor_statefunc(PG_FUNCTION_ARGS)
  {
  	PG_RETURN_BOOL(PG_GETARG_BOOL(0) || PG_GETARG_BOOL(1));
  }
+ 
+ typedef struct BoolAggState
+ {
+ 	int64 aggcount; /* number of values aggregated */
+ 	int64 aggtrue; /* number of values aggregated which are true */
+ } BoolAggState;
+ 
+ static BoolAggState *
+ makeBoolAggState(FunctionCallInfo fcinfo)
+ {
+ 	BoolAggState *state;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 	state = (BoolAggState *) palloc(sizeof(BoolAggState));
+ 	state->aggcount = 0;
+ 	state->aggtrue = 0;
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 
+ 	return state;
+ }
+ 
+ Datum
+ bool_accum(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* Create the state data when we see the first non-null input. */
+ 	if (state == NULL)
+ 		state = makeBoolAggState(fcinfo);
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount++;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue++;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /* bool_accum_inv
+  * removes a bool from aggregation
+  * Note that this function should be declared as strict
+  */
+ Datum
+ bool_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state = (BoolAggState *) PG_GETARG_POINTER(0);
+ 	bool value = PG_GETARG_BOOL(1);
+ 
+ 	state->aggcount--;
+ 	if (value)
+ 		state->aggtrue--;
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_alltrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if all values are true */
+ 	PG_RETURN_BOOL(state->aggcount == state->aggtrue);
+ }
+ 
+ Datum
+ bool_anytrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if any value is true */
+ 	PG_RETURN_BOOL(state->aggtrue > 0);
+ }
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 0158758..cba17a3 100644
*** a/src/backend/utils/adt/cash.c
--- b/src/backend/utils/adt/cash.c
*************** cashlarger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,896 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 > c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cashsmaller()
   * Return smaller of two cash values.
   */
*************** cashsmaller(PG_FUNCTION_ARGS)
*** 892,897 ****
--- 906,925 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 < c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cash_words()
   * This converts a int4 as well but to a representation using words
   * Obviously way North American centric - sorry
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 06cc0cd..0cca0b0 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** date_larger(PG_FUNCTION_ARGS)
*** 396,401 ****
--- 396,415 ----
  }
  
  Datum
+ date_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 > dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  date_smaller(PG_FUNCTION_ARGS)
  {
  	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
*************** date_smaller(PG_FUNCTION_ARGS)
*** 404,409 ****
--- 418,437 ----
  	PG_RETURN_DATEADT((dateVal1 < dateVal2) ? dateVal1 : dateVal2);
  }
  
+ Datum
+ date_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 < dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* Compute difference between two dates in days.
   */
  Datum
*************** time_larger(PG_FUNCTION_ARGS)
*** 1463,1468 ****
--- 1491,1510 ----
  }
  
  Datum
+ time_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 > time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  time_smaller(PG_FUNCTION_ARGS)
  {
  	TimeADT		time1 = PG_GETARG_TIMEADT(0);
*************** time_smaller(PG_FUNCTION_ARGS)
*** 1471,1476 ****
--- 1513,1532 ----
  	PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
  }
  
+ Datum
+ time_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 < time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* overlaps_time() --- implements the SQL OVERLAPS operator.
   *
   * Algorithm is per SQL spec.  This is much harder than you'd think
*************** timetz_larger(PG_FUNCTION_ARGS)
*** 2262,2267 ****
--- 2318,2337 ----
  }
  
  Datum
+ timetz_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) > 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  timetz_smaller(PG_FUNCTION_ARGS)
  {
  	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
*************** timetz_smaller(PG_FUNCTION_ARGS)
*** 2275,2280 ****
--- 2345,2364 ----
  	PG_RETURN_TIMETZADT_P(result);
  }
  
+ Datum
+ timetz_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) < 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* timetz_pl_interval()
   * Add interval to timetz.
   */
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 83c3878..32035e0 100644
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
*************** enum_smaller(PG_FUNCTION_ARGS)
*** 273,278 ****
--- 273,292 ----
  }
  
  Datum
+ enum_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) < 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_larger(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
*************** enum_larger(PG_FUNCTION_ARGS)
*** 282,287 ****
--- 296,315 ----
  }
  
  Datum
+ enum_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) > 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_cmp(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index e2a0812..527eff6 100644
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4larger(PG_FUNCTION_ARGS)
*** 626,631 ****
--- 626,647 ----
  }
  
  Datum
+ float4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float4smaller(PG_FUNCTION_ARGS)
  {
  	float4		arg1 = PG_GETARG_FLOAT4(0);
*************** float4smaller(PG_FUNCTION_ARGS)
*** 639,644 ****
--- 655,676 ----
  	PG_RETURN_FLOAT4(result);
  }
  
+ Datum
+ float4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  /*
   *		======================
   *		FLOAT8 BASE OPERATIONS
*************** float8larger(PG_FUNCTION_ARGS)
*** 693,698 ****
--- 725,746 ----
  }
  
  Datum
+ float8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float8smaller(PG_FUNCTION_ARGS)
  {
  	float8		arg1 = PG_GETARG_FLOAT8(0);
*************** float8smaller(PG_FUNCTION_ARGS)
*** 706,711 ****
--- 754,774 ----
  	PG_RETURN_FLOAT8(result);
  }
  
+ Datum
+ float8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *		====================
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 669355e..fe9d736 100644
*** a/src/backend/utils/adt/int.c
--- b/src/backend/utils/adt/int.c
*************** int2larger(PG_FUNCTION_ARGS)
*** 1185,1190 ****
--- 1185,1204 ----
  }
  
  Datum
+ int2larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int2smaller(PG_FUNCTION_ARGS)
  {
  	int16		arg1 = PG_GETARG_INT16(0);
*************** int2smaller(PG_FUNCTION_ARGS)
*** 1194,1199 ****
--- 1208,1227 ----
  }
  
  Datum
+ int2smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4larger(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4larger(PG_FUNCTION_ARGS)
*** 1203,1208 ****
--- 1231,1250 ----
  }
  
  Datum
+ int4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4smaller(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4smaller(PG_FUNCTION_ARGS)
*** 1211,1216 ****
--- 1253,1272 ----
  	PG_RETURN_INT32((arg1 < arg2) ? arg1 : arg2);
  }
  
+ Datum
+ int4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /*
   * Bit-pushing operators
   *
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..820be68 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8larger(PG_FUNCTION_ARGS)
*** 752,757 ****
--- 752,771 ----
  }
  
  Datum
+ int8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int8smaller(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
*************** int8smaller(PG_FUNCTION_ARGS)
*** 764,769 ****
--- 778,797 ----
  }
  
  Datum
+ int8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int84pl(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451d..4749152 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_smaller(PG_FUNCTION_ARGS)
*** 1834,1839 ****
--- 1834,1854 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) < 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * numeric_larger() -
*************** numeric_larger(PG_FUNCTION_ARGS)
*** 1856,1861 ****
--- 1871,1892 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) > 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /* ----------------------------------------------------------------------
   *
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 8945ef4..745449a 100644
*** a/src/backend/utils/adt/oid.c
--- b/src/backend/utils/adt/oid.c
*************** oidlarger(PG_FUNCTION_ARGS)
*** 397,402 ****
--- 397,416 ----
  }
  
  Datum
+ oidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidsmaller(PG_FUNCTION_ARGS)
  {
  	Oid			arg1 = PG_GETARG_OID(0);
*************** oidsmaller(PG_FUNCTION_ARGS)
*** 406,411 ****
--- 420,439 ----
  }
  
  Datum
+ oidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidvectoreq(PG_FUNCTION_ARGS)
  {
  	int32		cmp = DatumGetInt32(btoidvectorcmp(fcinfo));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 6e2bbdc..57fcf80 100644
*** a/src/backend/utils/adt/tid.c
--- b/src/backend/utils/adt/tid.c
*************** tidlarger(PG_FUNCTION_ARGS)
*** 237,242 ****
--- 237,256 ----
  }
  
  Datum
+ tidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) > 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  tidsmaller(PG_FUNCTION_ARGS)
  {
  	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
*************** tidsmaller(PG_FUNCTION_ARGS)
*** 245,250 ****
--- 259,277 ----
  	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
  }
  
+ Datum
+ tidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) < 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *	Functions to get latest tid of a specified tuple.
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 4581862..5b03c9f 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** timestamp_smaller(PG_FUNCTION_ARGS)
*** 2393,2398 ****
--- 2393,2412 ----
  }
  
  Datum
+ timestamp_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) < 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  timestamp_larger(PG_FUNCTION_ARGS)
  {
  	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
*************** timestamp_larger(PG_FUNCTION_ARGS)
*** 2406,2411 ****
--- 2420,2438 ----
  	PG_RETURN_TIMESTAMP(result);
  }
  
+ Datum
+ timestamp_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) > 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
  
  Datum
  timestamp_mi(PG_FUNCTION_ARGS)
*************** interval_smaller(PG_FUNCTION_ARGS)
*** 2849,2854 ****
--- 2876,2895 ----
  }
  
  Datum
+ interval_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) < 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_larger(PG_FUNCTION_ARGS)
  {
  	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
*************** interval_larger(PG_FUNCTION_ARGS)
*** 2863,2868 ****
--- 2904,2923 ----
  }
  
  Datum
+ interval_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) > 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_pl(PG_FUNCTION_ARGS)
  {
  	Interval   *span1 = PG_GETARG_INTERVAL_P(0);
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 502ca44..536ca66 100644
*** a/src/backend/utils/adt/varchar.c
--- b/src/backend/utils/adt/varchar.c
*************** bpchar_larger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,905 ----
  }
  
  Datum
+ bpchar_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp > 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  bpchar_smaller(PG_FUNCTION_ARGS)
  {
  	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
*************** bpchar_smaller(PG_FUNCTION_ARGS)
*** 894,899 ****
--- 917,945 ----
  	PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2);
  }
  
+ Datum
+ bpchar_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp < 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /*
   * bpchar needs a specialized hash function because we want to ignore
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8ac402b..6b640cb 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** text_larger(PG_FUNCTION_ARGS)
*** 1697,1702 ****
--- 1697,1716 ----
  }
  
  Datum
+ text_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  text_smaller(PG_FUNCTION_ARGS)
  {
  	text	   *arg1 = PG_GETARG_TEXT_PP(0);
*************** text_smaller(PG_FUNCTION_ARGS)
*** 1708,1713 ****
--- 1722,1740 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ Datum
+ text_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * The following operators support character-by-character comparison
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 2fb3871..39f991e 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2113	n 0 interval_pl		-	-	
*** 122,169 ****
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
--- 122,169 ----
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger			int8larger_inv			-		413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger			int4larger_inv			-		521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger			int2larger_inv			-		520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger			oidlarger_inv			-		610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger		float4larger_inv		-		623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger		float8larger_inv		-		674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger			int4larger_inv			-		563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger			date_larger_inv			-		1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger			time_larger_inv			-		1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger		timetz_larger_inv		-		1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger			cashlarger_inv			-		903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	timestamp_larger_inv	-		2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	timestamptz_larger_inv	-		1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger		interval_larger_inv		-		1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger			text_larger_inv			-		666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger		numeric_larger_inv		-		1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger		array_larger_inv		-		1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger		bpchar_larger_inv		-		1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger			tidlarger_inv			-		2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger			enum_larger_inv			-		3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller			int8smaller_inv			-		412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller			int4smaller_inv			-		97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller			int2smaller_inv			-		95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller			oidsmaller_inv			-		609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller		float4smaller_inv		-		622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller		float8smaller_inv		-		672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller			int4smaller_inv			-		562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller		date_smaller_inv		-		1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller		time_smaller_inv		-		1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller		timetz_smaller_inv		-		1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller			cashsmaller_inv			-		902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	timestamp_smaller_inv	-		2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller timestamptz_smaller_inv	-		1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	interval_smaller_inv	-		1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller		text_smaller_inv		-		664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller		numeric_smaller_inv		-		1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller		array_smaller_inv		-		1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller		bpchar_smaller_inv		-		1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller			tidsmaller_inv			-		2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller		enum_smaller_inv		-		3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
*************** DATA(insert ( 2828	n 0 float8_regr_accum
*** 232,240 ****
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
--- 232,240 ----
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
! DATA(insert ( 2518	n 0 bool_accum		bool_accum_inv		bool_anytrue	59	2281		16	_null_ ));
! DATA(insert ( 2519	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ad9774c..386a219 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("larger of two");
*** 367,372 ****
--- 367,377 ----
  DATA(insert OID = 211 (  float4smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3200 (  float4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3201 (  float4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 212 (  int4um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "23" _null_ _null_ _null_ _null_ int4um _null_ _null_ _null_ ));
  DATA(insert OID = 213 (  int2um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "21" _null_ _null_ _null_ _null_ int2um _null_ _null_ _null_ ));
  
*************** DATA(insert OID = 223 (  float8larger	  
*** 386,391 ****
--- 391,400 ----
  DESCR("larger of two");
  DATA(insert OID = 224 (  float8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3202 (  float8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3203 (  float8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 225 (  lseg_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "601" _null_ _null_ _null_ _null_	lseg_center _null_ _null_ _null_ ));
  DATA(insert OID = 226 (  path_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "602" _null_ _null_ _null_ _null_	path_center _null_ _null_ _null_ ));
*************** DATA(insert OID = 458 (  text_larger	   
*** 711,716 ****
--- 720,729 ----
  DESCR("larger of two");
  DATA(insert OID = 459 (  text_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3204 (  text_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3205 (  text_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 460 (  int8in			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "2275" _null_ _null_ _null_ _null_ int8in _null_ _null_ _null_ ));
  DESCR("I/O");
*************** DATA(insert OID = 515 (  array_larger	  
*** 859,864 ****
--- 872,881 ----
  DESCR("larger of two");
  DATA(insert OID = 516 (  array_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3206 (  array_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3207 (  array_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1191 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 23 "2277 23 16" _null_ _null_ _null_ _null_ generate_subscripts _null_ _null_ _null_ ));
  DESCR("array subscripts generator");
  DATA(insert OID = 1192 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 23 "2277 23" _null_ _null_ _null_ _null_ generate_subscripts_nodir _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 913,918 ****
--- 930,945 ----
  DATA(insert OID = 771 (  int2smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3208 (  int4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3209 (  int4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3210 (  int2larger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3211 (  int2smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 774 (  gistgettuple	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_	gistgettuple _null_ _null_ _null_ ));
  DESCR("gist(internal)");
  DATA(insert OID = 638 (  gistgetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	gistgetbitmap _null_ _null_ _null_ ));
*************** DATA(insert OID =  898 (  cashlarger	   
*** 1003,1008 ****
--- 1030,1039 ----
  DESCR("larger of two");
  DATA(insert OID =  899 (  cashsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID =  3212 (  cashlarger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID =  3213 (  cashsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID =  919 (  flt8_mul_cash    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "701 790" _null_ _null_ _null_ _null_	flt8_mul_cash _null_ _null_ _null_ ));
  DATA(insert OID =  935 (  cash_words	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "790" _null_ _null_ _null_ _null_	cash_words _null_ _null_ _null_ ));
  DESCR("output money amount as words");
*************** DATA(insert OID = 1063 (  bpchar_larger 
*** 1157,1162 ****
--- 1188,1197 ----
  DESCR("larger of two");
  DATA(insert OID = 1064 (  bpchar_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3214 (  bpchar_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3215 (  bpchar_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1078 (  bpcharcmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1042 1042" _null_ _null_ _null_ _null_ bpcharcmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1080 (  hashbpchar	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "1042" _null_ _null_ _null_ _null_	hashbpchar _null_ _null_ _null_ ));
*************** DATA(insert OID = 1138 (  date_larger	  
*** 1191,1196 ****
--- 1226,1235 ----
  DESCR("larger of two");
  DATA(insert OID = 1139 (  date_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3216 (  date_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3217 (  date_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1140 (  date_mi		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1082 1082" _null_ _null_ _null_ _null_ date_mi _null_ _null_ _null_ ));
  DATA(insert OID = 1141 (  date_pli		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_pli _null_ _null_ _null_ ));
  DATA(insert OID = 1142 (  date_mii		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_mii _null_ _null_ _null_ ));
*************** DATA(insert OID = 1195 (  timestamptz_sm
*** 1281,1290 ****
--- 1320,1339 ----
  DESCR("smaller of two");
  DATA(insert OID = 1196 (  timestamptz_larger  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 3218 (  timestamptz_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3219 (  timestamptz_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1197 (  interval_smaller	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  DATA(insert OID = 1198 (  interval_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 3220 (  interval_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3221 (  interval_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1199 (  age				PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_	timestamptz_age _null_ _null_ _null_ ));
  DESCR("date difference preserving months and years");
  
*************** DESCR("larger of two");
*** 1318,1323 ****
--- 1367,1378 ----
  DATA(insert OID = 1237 (  int8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3223 (  int8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3224 (  int8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1238 (  texticregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexeq _null_ _null_ _null_ ));
  DATA(insert OID = 1239 (  texticregexne    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexne _null_ _null_ _null_ ));
  DATA(insert OID = 1240 (  nameicregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "19 25" _null_ _null_ _null_ _null_ nameicregexeq _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1374,1379 ****
--- 1429,1439 ----
  DATA(insert OID = 2796 ( tidsmaller		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3225 ( tidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3226 ( tidsmaller_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1296 (  timedate_pl	   PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1114 "1083 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
  DATA(insert OID = 1297 (  datetimetz_pl    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1082 1266" _null_ _null_ _null_ _null_ datetimetz_timestamptz _null_ _null_ _null_ ));
  DATA(insert OID = 1298 (  timetzdate_pl    PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1184 "1266 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1515,1520 ****
--- 1575,1590 ----
  DATA(insert OID = 1380 (  timetz_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3227 (  time_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3228 (  time_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3229 (  timetz_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3230 (  timetz_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1381 (  char_length	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "25" _null_ _null_ _null_ _null_ textlen _null_ _null_ _null_ ));
  DESCR("character length");
  
*************** DATA(insert OID = 1766 ( numeric_smaller
*** 2277,2282 ****
--- 2347,2357 ----
  DESCR("smaller of two");
  DATA(insert OID = 1767 ( numeric_larger			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 3231 ( numeric_smaller_inv		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3232 ( numeric_larger_inv			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1769 ( numeric_cmp			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1700 1700" _null_ _null_ _null_ _null_ numeric_cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1771 ( numeric_uminus			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ numeric_uminus _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 2801,2806 ****
--- 2876,2886 ----
  DATA(insert OID = 1966 (  oidsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3244 (  oidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3245 (  oidsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 timestamp_transform f f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
  DESCR("adjust timestamptz precision");
  DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 time_transform f f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
*************** DATA(insert OID = 2035 (  timestamp_smal
*** 2862,2867 ****
--- 2942,2953 ----
  DESCR("smaller of two");
  DATA(insert OID = 2036 (  timestamp_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 3246 (  timestamp_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3247 (  timestamp_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 2037 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 1266 "25 1266" _null_ _null_ _null_ _null_ timetz_zone _null_ _null_ _null_ ));
  DESCR("adjust time with time zone to new zone");
  DATA(insert OID = 2038 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1186 1266" _null_ _null_ _null_ _null_	timetz_izone _null_ _null_ _null_ ));
*************** DESCR("non-persistent series generator")
*** 3853,3863 ****
  DATA(insert OID = 939  (  generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t s 3 0 1184 "1184 1184 1186" _null_ _null_ _null_ _null_ generate_series_timestamptz _null_ _null_ _null_ ));
  DESCR("non-persistent series generator");
  
- /* boolean aggregates */
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
! DESCR("aggregate transition function");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
! DESCR("aggregate transition function");
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
--- 3939,3950 ----
  DATA(insert OID = 939  (  generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t s 3 0 1184 "1184 1184 1186" _null_ _null_ _null_ _null_ generate_series_timestamptz _null_ _null_ _null_ ));
  DESCR("non-persistent series generator");
  
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
! DESCR("true if both inputs are true");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
! DESCR("true if any inputs are true");
! 
! /* boolean aggregates */
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
*************** DATA(insert OID = 2518 ( bool_or					   
*** 3865,3871 ****
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
! 
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("bitwise-and smallint aggregate");
--- 3952,3965 ----
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
! DATA(insert OID = 8033 ( bool_accum			   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum _null_ _null_ _null_ ));
! DESCR("aggregate transition function");
! DATA(insert OID = 3248 ( bool_accum_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum_inv _null_ _null_ _null_ ));
! DESCR("inverse aggregate transition function");
! DATA(insert OID = 8035 ( bool_alltrue			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_alltrue _null_ _null_ _null_ ));
! DESCR("aggregate final function");
! DATA(insert OID = 8036 ( bool_anytrue			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_anytrue _null_ _null_ _null_ ));
! DESCR("aggregate final function");
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("bitwise-and smallint aggregate");
*************** DATA(insert OID = 3524 (  enum_smaller	P
*** 4205,4210 ****
--- 4299,4309 ----
  DESCR("smaller of two");
  DATA(insert OID = 3525 (  enum_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 3249 (  enum_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3250 (  enum_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 3526 (  max			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("maximum value of all enum input values");
  DATA(insert OID = 3527 (  min			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..43e4973 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern Datum array_upper(PG_FUNCTION_ARG
*** 206,212 ****
--- 206,214 ----
  extern Datum array_length(PG_FUNCTION_ARGS);
  extern Datum array_cardinality(PG_FUNCTION_ARGS);
  extern Datum array_larger(PG_FUNCTION_ARGS);
+ extern Datum array_larger_inv(PG_FUNCTION_ARGS);
  extern Datum array_smaller(PG_FUNCTION_ARGS);
+ extern Datum array_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
  extern Datum array_fill(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..c97c910 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum boolle(PG_FUNCTION_ARGS);
*** 121,126 ****
--- 121,130 ----
  extern Datum boolge(PG_FUNCTION_ARGS);
  extern Datum booland_statefunc(PG_FUNCTION_ARGS);
  extern Datum boolor_statefunc(PG_FUNCTION_ARGS);
+ extern Datum bool_accum(PG_FUNCTION_ARGS);
+ extern Datum bool_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum bool_alltrue(PG_FUNCTION_ARGS);
+ extern Datum bool_anytrue(PG_FUNCTION_ARGS);
  extern bool parse_bool(const char *value, bool *result);
  extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
  
*************** extern Datum enum_ge(PG_FUNCTION_ARGS);
*** 167,173 ****
--- 171,179 ----
  extern Datum enum_gt(PG_FUNCTION_ARGS);
  extern Datum enum_cmp(PG_FUNCTION_ARGS);
  extern Datum enum_smaller(PG_FUNCTION_ARGS);
+ extern Datum enum_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum enum_larger(PG_FUNCTION_ARGS);
+ extern Datum enum_larger_inv(PG_FUNCTION_ARGS);
  extern Datum enum_first(PG_FUNCTION_ARGS);
  extern Datum enum_last(PG_FUNCTION_ARGS);
  extern Datum enum_range_bounds(PG_FUNCTION_ARGS);
*************** extern Datum int42div(PG_FUNCTION_ARGS);
*** 241,249 ****
--- 247,259 ----
  extern Datum int4mod(PG_FUNCTION_ARGS);
  extern Datum int2mod(PG_FUNCTION_ARGS);
  extern Datum int2larger(PG_FUNCTION_ARGS);
+ extern Datum int2larger_inv(PG_FUNCTION_ARGS);
  extern Datum int2smaller(PG_FUNCTION_ARGS);
+ extern Datum int2smaller_inv(PG_FUNCTION_ARGS);
  extern Datum int4larger(PG_FUNCTION_ARGS);
+ extern Datum int4larger_inv(PG_FUNCTION_ARGS);
  extern Datum int4smaller(PG_FUNCTION_ARGS);
+ extern Datum int4smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int4and(PG_FUNCTION_ARGS);
  extern Datum int4or(PG_FUNCTION_ARGS);
*************** extern Datum float4abs(PG_FUNCTION_ARGS)
*** 347,358 ****
--- 357,372 ----
  extern Datum float4um(PG_FUNCTION_ARGS);
  extern Datum float4up(PG_FUNCTION_ARGS);
  extern Datum float4larger(PG_FUNCTION_ARGS);
+ extern Datum float4larger_inv(PG_FUNCTION_ARGS);
  extern Datum float4smaller(PG_FUNCTION_ARGS);
+ extern Datum float4smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float8abs(PG_FUNCTION_ARGS);
  extern Datum float8um(PG_FUNCTION_ARGS);
  extern Datum float8up(PG_FUNCTION_ARGS);
  extern Datum float8larger(PG_FUNCTION_ARGS);
+ extern Datum float8larger_inv(PG_FUNCTION_ARGS);
  extern Datum float8smaller(PG_FUNCTION_ARGS);
+ extern Datum float8smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float4pl(PG_FUNCTION_ARGS);
  extern Datum float4mi(PG_FUNCTION_ARGS);
  extern Datum float4mul(PG_FUNCTION_ARGS);
*************** extern Datum oidle(PG_FUNCTION_ARGS);
*** 501,507 ****
--- 515,523 ----
  extern Datum oidge(PG_FUNCTION_ARGS);
  extern Datum oidgt(PG_FUNCTION_ARGS);
  extern Datum oidlarger(PG_FUNCTION_ARGS);
+ extern Datum oidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum oidsmaller(PG_FUNCTION_ARGS);
+ extern Datum oidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum oidvectorin(PG_FUNCTION_ARGS);
  extern Datum oidvectorout(PG_FUNCTION_ARGS);
  extern Datum oidvectorrecv(PG_FUNCTION_ARGS);
*************** extern Datum tidgt(PG_FUNCTION_ARGS);
*** 707,713 ****
--- 723,731 ----
  extern Datum tidge(PG_FUNCTION_ARGS);
  extern Datum bttidcmp(PG_FUNCTION_ARGS);
  extern Datum tidlarger(PG_FUNCTION_ARGS);
+ extern Datum tidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum tidsmaller(PG_FUNCTION_ARGS);
+ extern Datum tidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum currtid_byreloid(PG_FUNCTION_ARGS);
  extern Datum currtid_byrelname(PG_FUNCTION_ARGS);
  
*************** extern Datum bpchargt(PG_FUNCTION_ARGS);
*** 730,736 ****
--- 748,756 ----
  extern Datum bpcharge(PG_FUNCTION_ARGS);
  extern Datum bpcharcmp(PG_FUNCTION_ARGS);
  extern Datum bpchar_larger(PG_FUNCTION_ARGS);
+ extern Datum bpchar_larger_inv(PG_FUNCTION_ARGS);
  extern Datum bpchar_smaller(PG_FUNCTION_ARGS);
+ extern Datum bpchar_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum bpcharlen(PG_FUNCTION_ARGS);
  extern Datum bpcharoctetlen(PG_FUNCTION_ARGS);
  extern Datum hashbpchar(PG_FUNCTION_ARGS);
*************** extern Datum text_le(PG_FUNCTION_ARGS);
*** 770,776 ****
--- 790,798 ----
  extern Datum text_gt(PG_FUNCTION_ARGS);
  extern Datum text_ge(PG_FUNCTION_ARGS);
  extern Datum text_larger(PG_FUNCTION_ARGS);
+ extern Datum text_larger_inv(PG_FUNCTION_ARGS);
  extern Datum text_smaller(PG_FUNCTION_ARGS);
+ extern Datum text_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum text_pattern_lt(PG_FUNCTION_ARGS);
  extern Datum text_pattern_le(PG_FUNCTION_ARGS);
  extern Datum text_pattern_gt(PG_FUNCTION_ARGS);
*************** extern Datum numeric_div_trunc(PG_FUNCTI
*** 980,986 ****
--- 1002,1010 ----
  extern Datum numeric_mod(PG_FUNCTION_ARGS);
  extern Datum numeric_inc(PG_FUNCTION_ARGS);
  extern Datum numeric_smaller(PG_FUNCTION_ARGS);
+ extern Datum numeric_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_larger(PG_FUNCTION_ARGS);
+ extern Datum numeric_larger_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_fac(PG_FUNCTION_ARGS);
  extern Datum numeric_sqrt(PG_FUNCTION_ARGS);
  extern Datum numeric_exp(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/cash.h b/src/include/utils/cash.h
index 3a491f9..4e184cc 100644
*** a/src/include/utils/cash.h
--- b/src/include/utils/cash.h
*************** extern Datum int2_mul_cash(PG_FUNCTION_A
*** 60,66 ****
--- 60,68 ----
  extern Datum cash_div_int2(PG_FUNCTION_ARGS);
  
  extern Datum cashlarger(PG_FUNCTION_ARGS);
+ extern Datum cashlarger_inv(PG_FUNCTION_ARGS);
  extern Datum cashsmaller(PG_FUNCTION_ARGS);
+ extern Datum cashsmaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum cash_words(PG_FUNCTION_ARGS);
  
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 622aa19..9f459ac 100644
*** a/src/include/utils/date.h
--- b/src/include/utils/date.h
*************** extern Datum date_cmp(PG_FUNCTION_ARGS);
*** 108,114 ****
--- 108,116 ----
  extern Datum date_sortsupport(PG_FUNCTION_ARGS);
  extern Datum date_finite(PG_FUNCTION_ARGS);
  extern Datum date_larger(PG_FUNCTION_ARGS);
+ extern Datum date_larger_inv(PG_FUNCTION_ARGS);
  extern Datum date_smaller(PG_FUNCTION_ARGS);
+ extern Datum date_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum date_mi(PG_FUNCTION_ARGS);
  extern Datum date_pli(PG_FUNCTION_ARGS);
  extern Datum date_mii(PG_FUNCTION_ARGS);
*************** extern Datum time_cmp(PG_FUNCTION_ARGS);
*** 168,174 ****
--- 170,178 ----
  extern Datum time_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_time(PG_FUNCTION_ARGS);
  extern Datum time_larger(PG_FUNCTION_ARGS);
+ extern Datum time_larger_inv(PG_FUNCTION_ARGS);
  extern Datum time_smaller(PG_FUNCTION_ARGS);
+ extern Datum time_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum time_mi_time(PG_FUNCTION_ARGS);
  extern Datum timestamp_time(PG_FUNCTION_ARGS);
  extern Datum timestamptz_time(PG_FUNCTION_ARGS);
*************** extern Datum timetz_cmp(PG_FUNCTION_ARGS
*** 195,201 ****
--- 199,207 ----
  extern Datum timetz_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_timetz(PG_FUNCTION_ARGS);
  extern Datum timetz_larger(PG_FUNCTION_ARGS);
+ extern Datum timetz_larger_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_smaller(PG_FUNCTION_ARGS);
+ extern Datum timetz_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_time(PG_FUNCTION_ARGS);
  extern Datum time_timetz(PG_FUNCTION_ARGS);
  extern Datum timestamptz_timetz(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..d102ccb 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8inc(PG_FUNCTION_ARGS);
*** 77,83 ****
--- 77,85 ----
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
+ extern Datum int8larger_inv(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
+ extern Datum int8smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int8and(PG_FUNCTION_ARGS);
  extern Datum int8or(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 47fb866..85c0283 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum timestamp_cmp(PG_FUNCTION_A
*** 111,117 ****
--- 111,119 ----
  extern Datum timestamp_sortsupport(PG_FUNCTION_ARGS);
  extern Datum timestamp_hash(PG_FUNCTION_ARGS);
  extern Datum timestamp_smaller(PG_FUNCTION_ARGS);
+ extern Datum timestamp_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timestamp_larger(PG_FUNCTION_ARGS);
+ extern Datum timestamp_larger_inv(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS);
  extern Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS);
*************** extern Datum interval_finite(PG_FUNCTION
*** 147,153 ****
--- 149,157 ----
  extern Datum interval_cmp(PG_FUNCTION_ARGS);
  extern Datum interval_hash(PG_FUNCTION_ARGS);
  extern Datum interval_smaller(PG_FUNCTION_ARGS);
+ extern Datum interval_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum interval_larger(PG_FUNCTION_ARGS);
+ extern Datum interval_larger_inv(PG_FUNCTION_ARGS);
  extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
  extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
  extern Datum interval_justify_days(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index df676ad..d227f3c 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1283,1288 ****
--- 1283,1393 ----
  -- Test the MIN, MAX and boolean inverse transition functions
  --
  --
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  40 |  30
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  20 |  20
+     |  40 |  40
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  30 |  30
+  40 |     |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |  20 |  10
+     |  10 |  10
+  10 |  10 |  10
+ (5 rows)
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |     |  10
+     |     |  10
+  10 |     |  10
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  |  p  | max | min 
+ ----+-----+-----+-----
+  ad | 100 | ae  | ab
+  ab | 100 | ae  | ab
+  ae | 100 | ae  | ae
+  ad | 200 | ad  | aa
+  aa | 200 | aa  | aa
+ (5 rows)
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  b | bool_and | bool_or 
+ ---+----------+---------
+  t | t        | t
+  t | f        | t
+  f | f        | f
+  f | f        | t
+  t | t        | t
+ (5 rows)
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 65c1005..82448ff 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 471,476 ****
--- 471,512 ----
  --
  --
  
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
#167Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#166)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 31 March 2014 01:58, Florian Pflug <fgp@phlo.org> wrote:

Attached are updated patches that include the EXPLAIN changes mentioned
above and updated docs.

These patches need re-basing --- they no longer apply to HEAD.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#168Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#167)
5 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr1, 2014, at 10:08 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 31 March 2014 01:58, Florian Pflug <fgp@phlo.org> wrote:

Attached are updated patches that include the EXPLAIN changes mentioned
above and updated docs.

These patches need re-basing --- they no longer apply to HEAD.

Rebased to head (554bb3beba27bf4a49edecc40f6c0f249974bc7c). I had to
re-assign OIDs in the dependent paches again (those which actually
add the various inverse transition functions).

best regards,
Florian Pflug

Attachments:

invtrans_strictstrict_arith_ecddfd.patchapplication/octet-stream; name=invtrans_strictstrict_arith_ecddfd.patch; x-unix-mode=0644Download
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..e62f2a3 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8inc(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 717,767 ----
  	}
  }
  
+ Datum
+ int8dec(PG_FUNCTION_ARGS)
+ {
+ 	/*
+ 	 * When int8 is pass-by-reference, we provide this special case to avoid
+ 	 * palloc overhead for COUNT(): when called as an inverse transition
+ 	 * aggregate, we know that the argument is modifiable local storage,
+ 	 * so just update it in-place. (If int8 is pass-by-value, then of course
+ 	 * this is useless as well as incorrect, so just ifdef it out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *arg = (int64 *) PG_GETARG_POINTER(0);
+ 		int64		result;
+ 
+ 		result = *arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && *arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		*arg = result;
+ 		PG_RETURN_POINTER(arg);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		/* Not called as an aggregate, so just do it the dumb way */
+ 		int64		arg = PG_GETARG_INT64(0);
+ 		int64		result;
+ 
+ 		result = arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		PG_RETURN_INT64(result);
+ 	}
+ }
+ 
+ 
  /*
   * These functions are exactly like int8inc but are used for aggregates that
   * count only non-null values.	Since the functions are declared strict,
*************** int8inc_any(PG_FUNCTION_ARGS)
*** 733,738 ****
--- 778,789 ----
  }
  
  Datum
+ int8inc_any_inv(PG_FUNCTION_ARGS)
+ {
+ 	return int8dec(fcinfo);
+ }
+ 
+ Datum
  int8inc_float8_float8(PG_FUNCTION_ARGS)
  {
  	return int8inc(fcinfo);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 64eb0f8..2a9e093 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_float4(PG_FUNCTION_ARGS)
*** 2517,2524 ****
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	bool		isNaN;			/* true if any processed number was NaN */
  	MemoryContext agg_context;	/* context we're calculating in */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
--- 2517,2528 ----
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	int			maxScale;		/* stores the maximum scale seen so far. */
! 	int64		maxScaleCount;  /* tracks the number of values we've
! 								 * seen with the maximum scale */
! 
  	MemoryContext agg_context;	/* context we're calculating in */
+ 	int64		NaNcount;		/* Count of NaN values that are aggregated */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
*************** makeNumericAggState(FunctionCallInfo fci
*** 2543,2548 ****
--- 2547,2556 ----
  	state = (NumericAggState *) palloc0(sizeof(NumericAggState));
  	state->calcSumX2 = calcSumX2;
  	state->agg_context = agg_context;
+ 	state->NaNcount = 0;
+ 
+ 	state->maxScale = 0;
+ 	state->maxScaleCount = 0;
  
  	MemoryContextSwitchTo(old_context);
  
*************** do_numeric_accum(NumericAggState *state,
*** 2560,2574 ****
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (state->isNaN || NUMERIC_IS_NAN(newval))
  	{
! 		state->isNaN = true;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
--- 2568,2596 ----
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (NUMERIC_IS_NAN(newval))
  	{
! 		state->NaNcount++;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
+ 	/*
+ 	 * Track the highest scale that we've seen as if we ever perform an inverse
+ 	 * transition and remove the last numeric with the highest scale that we've
+ 	 * seen then we can no longer perform inverse transitions without risking
+ 	 * having the wrong dscale in the result value.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 		state->maxScaleCount++;
+ 	else if (X.dscale > state->maxScale)
+ 	{
+ 		state->maxScale = X.dscale;
+ 		state->maxScaleCount = 1;
+ 	}
+ 
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2579,2591 ****
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N++ > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
  	}
  	else
  	{
--- 2601,2615 ----
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
+ 
+ 		state->N++;
  	}
  	else
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2594,2605 ****
--- 2618,2730 ----
  
  		if (state->calcSumX2)
  			set_var_from_var(&X2, &(state->sumX2));
+ 
+ 		state->N = 1;
  	}
  
  	MemoryContextSwitchTo(old_context);
  }
  
  /*
+  * do_numeric_discard
+  * Attempts to remove a value from the aggregated state.
+  * If the value cannot be removed then the function will return false, the
+  * possible reasons for failing are described below.
+  *
+  * If we aggregate the values 1.01 and 2 then the result will be 3.01. If we
+  * are then asked to un-aggregate the 1.01 then we must reject this case as we
+  * won't be able to tell what the new aggregated value's dscale should be.
+  * We can't return 2.00 (dscale = 2) as we really should return just 2, but
+  * since we're not tracking any previous highest scales then we must just fail
+  * to perform the inverse transition and just return false.
+  *
+  * Values that are no longer aggregated should not be able to effect the dscale
+  * of the result of the values that *are* still aggregated.
+  *
+  * Note it may be better to track the number of times we've aggregated a
+  * numeric with each scale, then if we ever remove final highest scaled value
+  * then we can step the result's dscale down to the next highest value. This is
+  * perhaps slightly more work than we can afford to do here, but doing it this
+  * way would mean that we could always perform the inverse transition.
+  */
+ static bool
+ do_numeric_discard(NumericAggState *state, Numeric newval)
+ {
+ 	NumericVar	X;
+ 	NumericVar	X2;
+ 	MemoryContext old_context;
+ 
+ 	/* result is NaN if any processed number is NaN */
+ 	if (NUMERIC_IS_NAN(newval))
+ 	{
+ 		state->NaNcount--;
+ 		return true;
+ 	}
+ 
+ 	/* load processed number in short-lived context */
+ 	init_var_from_num(newval, &X);
+ 
+ 	/*
+ 	 * state->sumX's dscale matches the maximum dscale of any of the inputs
+ 	 * Removing the last input with that dscale would require us to recompute
+ 	 * the maximum dscale of the *remaining* inputs, which we cannot do unless
+ 	 * no more non-NaN inputs remain at all. So we report a failure instead,
+ 	 * and force the aggregation to be redone from scratch.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 	{
+ 		if (state->maxScaleCount > 1)
+ 		{
+ 			/* Some remaining inputs have same dscale */
+ 			--state->maxScaleCount;
+ 		}
+ 		else if (state->N == 1)
+ 		{
+ 			/* No remaining non-NaN inputs at all */
+ 			state->maxScale = 0;
+ 			state->maxScaleCount = 0;
+ 		}
+ 		else
+ 		{
+ 			/* No remaining inputs have same dscale */
+ 			return false;
+ 		}
+ 	}
+ 
+ 	/* if we need X^2, calculate that in short-lived context */
+ 	if (state->calcSumX2)
+ 	{
+ 		init_var(&X2);
+ 		mul_var(&X, &X, &X2, X.dscale * 2);
+ 	}
+ 
+ 	/* The rest of this needs to work in the aggregate context */
+ 	old_context = MemoryContextSwitchTo(state->agg_context);
+ 
+ 	if (state->N > 1)
+ 	{
+ 		sub_var(&(state->sumX), &X, &(state->sumX));
+ 		if (state->calcSumX2)
+ 			sub_var(&(state->sumX2), &X2, &(state->sumX2));
+ 		state->N--;
+ 	}
+ 	else if (state->N == 1)
+ 	{
+ 		/* Sums will be reset by next call to do_numeric_accum */
+ 		state->N = 0;
+ 	}
+ 	else
+ 	{
+ 		MemoryContextSwitchTo(old_context);
+ 		elog(ERROR, "cannot discard more values than were accumulated");
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 	return true;
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that require sumX2.
   */
  Datum
*************** numeric_accum(PG_FUNCTION_ARGS)
*** 2609,2626 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
--- 2734,2772 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, true);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * Generic inverse transition function for numeric aggregates
+  */
+ Datum
+ numeric_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL)
+ 		elog(ERROR, "numeric_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		/* Can we perform an inverse transition? if not return NULL. */
+ 		if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 			PG_RETURN_NULL();
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
+ 
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
*************** numeric_avg_accum(PG_FUNCTION_ARGS)
*** 2631,2644 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, false);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
- 	}
  
  	PG_RETURN_POINTER(state);
  }
--- 2777,2788 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, false);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  
  	PG_RETURN_POINTER(state);
  }
*************** int2_accum(PG_FUNCTION_ARGS)
*** 2659,2675 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2803,2818 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int4_accum(PG_FUNCTION_ARGS)
*** 2683,2699 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2826,2841 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2707,2724 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
! 		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
--- 2849,2951 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
+ 		do_numeric_accum(state, newval);
+ 	}
  
! 	PG_RETURN_POINTER(state);
! }
  
! 
! /*
!  * int2_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int2_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int4_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int4_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int8_accum_inv
!  * aggregate inverse transition function.
!  * This function must be declared as strict.
!  */
! Datum
! int8_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
  	}
  
  	PG_RETURN_POINTER(state);
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2726,2731 ****
--- 2953,2959 ----
  
  /*
   * Transition function for int8 input when we don't need sumX2.
+  * For the inverse, we use int8_accum_inv.
   */
  Datum
  int8_avg_accum(PG_FUNCTION_ARGS)
*************** int8_avg_accum(PG_FUNCTION_ARGS)
*** 2734,2757 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, false);
- 
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
- 
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
--- 2962,2983 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, false);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
*************** numeric_avg(PG_FUNCTION_ARGS)
*** 2760,2769 ****
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
--- 2986,2996 ----
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
*************** numeric_sum(PG_FUNCTION_ARGS)
*** 2778,2787 ****
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
--- 3005,3016 ----
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)
! 		/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
*************** numeric_stddev_internal(NumericAggState 
*** 2812,2818 ****
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL)
  	{
  		*is_null = true;
  		return NULL;
--- 3041,3047 ----
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
  	{
  		*is_null = true;
  		return NULL;
*************** numeric_stddev_internal(NumericAggState 
*** 2820,2826 ****
  
  	*is_null = false;
  
! 	if (state->isNaN)
  		return make_result(&const_nan);
  
  	init_var(&vN);
--- 3049,3055 ----
  
  	*is_null = false;
  
! 	if (state->NaNcount > 0)
  		return make_result(&const_nan);
  
  	init_var(&vN);
*************** numeric_stddev_pop(PG_FUNCTION_ARGS)
*** 2951,2970 ****
  }
  
  /*
!  * SUM transition functions for integer datatypes.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   *
!  * Because SQL defines the SUM() of no values to be NULL, not zero,
!  * the initial condition of the transition data value needs to be NULL. This
!  * means we can't rely on ExecAgg to automatically insert the first non-null
!  * data value into the transition data: it doesn't know how to do the type
!  * conversion.	The upshot is that these routines have to be marked non-strict
!  * and handle substitution of the first non-null input themselves.
   */
  
  Datum
--- 3180,3193 ----
  }
  
  /*
!  * Obsolete SUM transition functions for integer datatypes.
   *
!  * These were used to implement SUM aggregates before inverse transition
!  * functions were added. For inverse transitions, we need to know the number
!  * of summands to be able to return NULL whenenver the number of non-NULL
!  * inputs becomes zero. We therefore now use the intX_avg_accum and
!  * intX_avg_accum_inv transition functions, which use int8[] is their
!  * transition type to be able to count the number of inputs.
   */
  
  Datum
*************** int8_sum(PG_FUNCTION_ARGS)
*** 3103,3112 ****
  										NumericGetDatum(oldsum), newval));
  }
  
- 
  /*
!  * Routines for avg(int2) and avg(int4).  The transition datatype
!  * is a two-element int8 array, holding count and sum.
   */
  
  typedef struct Int8TransTypeData
--- 3326,3340 ----
  										NumericGetDatum(oldsum), newval));
  }
  
  /*
!  * Routines for sum(int2), sum(int4), avg(int2) and avg(int4).  The transition
!  * datatype is a two-element int8 array, holding count and sum.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   */
  
  typedef struct Int8TransTypeData
*************** int2_avg_accum(PG_FUNCTION_ARGS)
*** 3144,3149 ****
--- 3372,3406 ----
  }
  
  Datum
+ int2_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int16		newval = PG_GETARG_INT16(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ 
+ Datum
  int4_avg_accum(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray;
*************** int4_avg_accum(PG_FUNCTION_ARGS)
*** 3172,3177 ****
--- 3429,3480 ----
  }
  
  Datum
+ int4_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int32		newval = PG_GETARG_INT32(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ Datum
+ int2int4_sum(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Int8TransTypeData *transdata;
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 
+ 	/* SQL defines SUM of no values to be NULL */
+ 	if (transdata->count == 0)
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_DATUM(Int64GetDatumFast(transdata->sum));
+ }
+ 
+ Datum
  int8_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ce30bb6..af0822a 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** interval_accum(PG_FUNCTION_ARGS)
*** 3429,3434 ****
--- 3429,3479 ----
  }
  
  Datum
+ interval_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Interval   *newval = PG_GETARG_INTERVAL_P(1);
+ 	Datum	   *transdatums;
+ 	int			ndatums;
+ 	Interval	sumX,
+ 				N;
+ 	Interval   *newsum;
+ 	ArrayType  *result;
+ 
+ 	deconstruct_array(transarray,
+ 					  INTERVALOID, sizeof(Interval), false, 'd',
+ 					  &transdatums, NULL, &ndatums);
+ 	if (ndatums != 2)
+ 		elog(ERROR, "expected 2-element interval array");
+ 
+ 	/*
+ 	 * XXX memcpy, instead of just extracting a pointer, to work around buggy
+ 	 * array code: it won't ensure proper alignment of Interval objects on
+ 	 * machines where double requires 8-byte alignment. That should be fixed,
+ 	 * but in the meantime...
+ 	 *
+ 	 * Note: must use DatumGetPointer here, not DatumGetIntervalP, else some
+ 	 * compilers optimize into double-aligned load/store anyway.
+ 	 */
+ 	memcpy((void *) &sumX, DatumGetPointer(transdatums[0]), sizeof(Interval));
+ 	memcpy((void *) &N, DatumGetPointer(transdatums[1]), sizeof(Interval));
+ 
+ 	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
+ 												   IntervalPGetDatum(&sumX),
+ 												 IntervalPGetDatum(newval)));
+ 	N.time -= 1;
+ 
+ 	transdatums[0] = IntervalPGetDatum(newsum);
+ 	transdatums[1] = IntervalPGetDatum(&N);
+ 
+ 	result = construct_array(transdatums, 2,
+ 							 INTERVALOID, sizeof(Interval), false, 'd');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ 
+ Datum
  interval_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..874aeec 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 103,125 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
--- 103,125 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		int8_accum_inv			numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		int4_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		int2_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_accum_inv		numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum		interval_accum_inv		interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		int8_accum_inv			numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_avg_accum		int4_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2109	n 0 int2_avg_accum		int2_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2110	n 0 float4pl			-						-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl			-						-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl				cash_mi					-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl			interval_mi				-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_accum_inv		numeric_sum		0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
*************** DATA(insert ( 2798	n 0 tidsmaller		-	-		
*** 166,221 ****
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
--- 166,221 ----
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		int8inc_any_inv	-		0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			int8dec			-		0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		int8_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		int4_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		int2_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_accum_inv	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		int8_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		int4_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		int2_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..59d8ac6 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("truncate interval to specified un
*** 1309,1316 ****
--- 1309,1320 ----
  
  DATA(insert OID = 1219 (  int8inc		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8inc _null_ _null_ _null_ ));
  DESCR("increment");
+ DATA(insert OID = 3546 (  int8dec		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8dec _null_ _null_ _null_ ));
+ DESCR("decrement");
  DATA(insert OID = 2804 (  int8inc_any	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any _null_ _null_ _null_ ));
  DESCR("increment, ignores second argument");
+ DATA(insert OID = 3547 (  int8inc_any_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any_inv _null_ _null_ _null_ ));
+ DESCR("decrement, ignores second argument");
  DATA(insert OID = 1230 (  int8abs		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8abs _null_ _null_ _null_ ));
  
  DATA(insert OID = 1236 (  int8larger	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger _null_ _null_ _null_ ));
*************** DATA(insert OID = 1832 (  float8_stddev_
*** 2396,2401 ****
--- 2400,2407 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3548 (  numeric_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
*************** DATA(insert OID = 1836 (  int8_accum	   
*** 2406,2411 ****
--- 2412,2423 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 2746 (  int8_avg_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3549 (  int2_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3550 (  int4_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3551 (  int8_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
*************** DATA(insert OID = 1842 (  int8_sum		   P
*** 2426,2439 ****
--- 2438,2459 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 1843 (  interval_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3552 (  interval_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1844 (  interval_avg	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1186 "1187" _null_ _null_ _null_ _null_ interval_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1962 (  int2_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1963 (  int4_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3553 (  int2_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 3218 (  int4_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 1964 (  int8_avg		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1016" _null_ _null_ _null_ _null_ int8_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
+ DATA(insert OID = 3219 (  int2int4_sum	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "1016" _null_ _null_ _null_ _null_ int2int4_sum _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  DATA(insert OID = 2805 (  int8inc_float8_float8		PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 20 "20 701 701" _null_ _null_ _null_ _null_ int8inc_float8_float8 _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2806 (  float8_regr_accum			PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 1022 "1022 701 701" _null_ _null_ _null_ _null_ float8_regr_accum _null_ _null_ _null_ ));
*************** DESCR("get value from jsonb with path el
*** 4529,4536 ****
  DATA(insert OID = 3939 (  jsonb_extract_path_op		PGNSP PGUID 12 1 0 0 0	f f f f t f i 2 0 3802 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ ));
  DATA(insert OID = 3940 (  jsonb_extract_path_text	PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 25 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
  DESCR("get value from jsonb as text with path elements");
! DATA(insert OID = 3218 (  jsonb_extract_path_text_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
! DATA(insert OID = 3219 (  jsonb_array_elements		PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 3802 "3802" "{3802,3802}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements _null_ _null_ _null_ ));
  DESCR("elements of a jsonb array");
  DATA(insert OID = 3465 (  jsonb_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" "{3802,25}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements_text _null_ _null_ _null_ ));
  DESCR("elements of jsonb array");
--- 4549,4556 ----
  DATA(insert OID = 3939 (  jsonb_extract_path_op		PGNSP PGUID 12 1 0 0 0	f f f f t f i 2 0 3802 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ ));
  DATA(insert OID = 3940 (  jsonb_extract_path_text	PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 25 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
  DESCR("get value from jsonb as text with path elements");
! DATA(insert OID = 3554 (  jsonb_extract_path_text_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
! DATA(insert OID = 3555 (  jsonb_array_elements		PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 3802 "3802" "{3802,3802}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements _null_ _null_ _null_ ));
  DESCR("elements of a jsonb array");
  DATA(insert OID = 3465 (  jsonb_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" "{3802,25}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements_text _null_ _null_ _null_ ));
  DESCR("elements of jsonb array");
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..2f77dc1 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum numeric_float8_no_overflow(
*** 999,1008 ****
--- 999,1012 ----
  extern Datum float4_numeric(PG_FUNCTION_ARGS);
  extern Datum numeric_float4(PG_FUNCTION_ARGS);
  extern Datum numeric_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg_accum(PG_FUNCTION_ARGS);
  extern Datum int2_accum(PG_FUNCTION_ARGS);
  extern Datum int4_accum(PG_FUNCTION_ARGS);
  extern Datum int8_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int4_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int8_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg_accum(PG_FUNCTION_ARGS);
  extern Datum numeric_avg(PG_FUNCTION_ARGS);
  extern Datum numeric_sum(PG_FUNCTION_ARGS);
*************** extern Datum int2_sum(PG_FUNCTION_ARGS);
*** 1014,1020 ****
--- 1018,1027 ----
  extern Datum int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_sum(PG_FUNCTION_ARGS);
  extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int4_avg_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int2int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_avg(PG_FUNCTION_ARGS);
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  extern Datum hash_numeric(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..5078e4a 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8div(PG_FUNCTION_ARGS);
*** 74,80 ****
--- 74,82 ----
  extern Datum int8abs(PG_FUNCTION_ARGS);
  extern Datum int8mod(PG_FUNCTION_ARGS);
  extern Datum int8inc(PG_FUNCTION_ARGS);
+ extern Datum int8dec(PG_FUNCTION_ARGS);
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
+ extern Datum int8inc_any_inv(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 2731c6a..94328b3 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum interval_mul(PG_FUNCTION_AR
*** 184,189 ****
--- 184,190 ----
  extern Datum mul_d_interval(PG_FUNCTION_ARGS);
  extern Datum interval_div(PG_FUNCTION_ARGS);
  extern Datum interval_accum(PG_FUNCTION_ARGS);
+ extern Datum interval_accum_inv(PG_FUNCTION_ARGS);
  extern Datum interval_avg(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_mi(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 8af3d23..55c2b4c 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1295,1300 ****
--- 1295,1783 ----
  -- Test the arithmetic inverse transition functions
  --
  --
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 2.0000000000000000
+  2 | 2.5000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |    avg     
+ ---+------------
+  1 | @ 1.5 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+  i |  sum  
+ ---+-------
+  1 | $3.30
+  2 | $2.20
+  3 |      
+  4 |      
+ (4 rows)
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |   sum    
+ ---+----------
+  1 | @ 3 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 | 3.3
+  2 | 2.2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+  sum  
+ ------
+  6.01
+     5
+     3
+ (3 rows)
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     2
+  2 |     1
+  3 |     0
+  4 |     0
+ (4 rows)
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     4
+  2 |     3
+  3 |     2
+  4 |     1
+ (4 rows)
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   1
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   6
+  3 |   9
+  4 |   7
+ (4 rows)
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+          to_char          
+ --------------------------
+   100000000000000000000
+                       1.0
+ (2 rows)
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+  a |  b  | sum 
+ ---+-----+-----
+  1 |   1 |   1
+  2 |   2 |   3
+  3 | NaN | NaN
+  4 |   3 | NaN
+  5 |   4 |   7
+ (5 rows)
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 117bd6c..29c4226 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 476,481 ****
--- 476,622 ----
  --
  --
  
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+ 
  --
  --
  -- Test the MIN, MAX and boolean inverse transition functions
invtrans_strictstrict_base_2825a5.patchapplication/octet-stream; name=invtrans_strictstrict_base_2825a5.patch; x-unix-mode=0644Download
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..b7fc5f2 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 56,61 ****
--- 56,62 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
*************** AggregateCreate(const char *aggName,
*** 68,78 ****
--- 69,81 ----
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
  	Oid			transfn;
+ 	Oid			invtransfn = InvalidOid; /* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	bool		transIsStrict;
  	Oid			rettype;
  	Oid			finaltype;
  	Oid			fnArgs[FUNC_MAX_ARGS];
*************** AggregateCreate(const char *aggName,
*** 234,241 ****
--- 237,297 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
+ 
+ 	/*
+ 	 * Remember if trans function is strict as we need to validate this
+ 	 * later if when we're dealing with the inverse transition function
+ 	 */
+ 	transIsStrict = proc->proisstrict;
+ 
  	ReleaseSysCache(tup);
  
+ 	/* handle invtransfn, if supplied */
+ 	if (agginvtransfnName)
+ 	{
+ 		/*
+ 		 * This must have the same number of arguments with the same types as
+ 		 * the transition function. We can just borrow the argument details
+ 		 * from the transition function and try to find a function with
+ 		 * the name of the inverse transition function and with a signature
+ 		 * that matches the transition function's.
+ 		 */
+ 		invtransfn = lookup_agg_function(agginvtransfnName,
+ 					nargs_transfn, fnArgs, InvalidOid, &rettype);
+ 
+ 		/*
+ 		 * Ensure the return type of the inverse transition function matches
+ 		 * the expected type.
+ 		 */
+ 		if (rettype != aggTransType)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("return type of inverse transition function %s is not %s",
+ 							NameListToString(agginvtransfnName),
+ 							format_type_be(aggTransType))));
+ 
+ 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+ 		proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 		/*
+ 		 * We force the strictness settings of the forward and inverse
+ 		 * transition functions to agree. This allows places which only need
+ 		 * forward transitions to not look at the inverse transition function
+ 		 * at all.
+ 		 */
+ 		if (transIsStrict != proc->proisstrict)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 						errmsg("strictness of forward and inverse transition functions must match"
+ 						)));
+ 		}
+ 		ReleaseSysCache(tup);
+ 
+ 	}
+ 
  	/* handle finalfn, if supplied */
  	if (aggfinalfnName)
  	{
*************** AggregateCreate(const char *aggName,
*** 391,396 ****
--- 447,453 ----
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+ 	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
*************** AggregateCreate(const char *aggName,
*** 425,430 ****
--- 482,496 ----
  	referenced.objectSubId = 0;
  	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
+ 	/* Depends on inverse transition function, if any */
+ 	if (OidIsValid(invtransfn))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = invtransfn;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
  	{
*************** AggregateCreate(const char *aggName,
*** 447,453 ****
  }
  
  /*
!  * lookup_agg_function -- common code for finding both transfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
--- 513,520 ----
  }
  
  /*
!  * lookup_agg_function
!  * common code for finding transfn, invtransfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..c0ea1f7 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
*************** DefineAggregate(List *name, List *args, 
*** 60,65 ****
--- 60,66 ----
  	AclResult	aclresult;
  	char		aggKind = AGGKIND_NORMAL;
  	List	   *transfuncName = NIL;
+ 	List	   *invtransfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
*************** DefineAggregate(List *name, List *args, 
*** 112,117 ****
--- 113,120 ----
  			transfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
  			transfuncName = defGetQualifiedName(defel);
+ 		else if (pg_strcasecmp(defel->defname, "invsfunc") == 0)
+ 			invtransfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
  			finalfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
*************** DefineAggregate(List *name, List *args, 
*** 283,288 ****
--- 286,292 ----
  						   parameterDefaults,
  						   variadicArgType,
  						   transfuncName,		/* step function name */
+ 						   invtransfuncName,	/* inverse trans function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 08f3167..08cbcd5 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_group_keys(PlanSta
*** 85,90 ****
--- 85,91 ----
  					 List *ancestors, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
+ static void show_windowagg_info(PlanState *planstate, ExplainState *es);
  static void show_tidbitmap_info(BitmapHeapScanState *planstate,
  								ExplainState *es);
  static void show_instrumentation_count(const char *qlabel, int which,
*************** ExplainNode(PlanState *planstate, List *
*** 1420,1425 ****
--- 1421,1430 ----
  		case T_Hash:
  			show_hash_info((HashState *) planstate, es);
  			break;
+ 		case T_WindowAgg:
+ 			if (es->verbose && planstate->instrument)
+ 				show_windowagg_info(planstate, es);
+ 			break;
  		default:
  			break;
  	}
*************** show_hash_info(HashState *hashstate, Exp
*** 1918,1923 ****
--- 1923,1939 ----
  	}
  }
  
+ static void
+ show_windowagg_info(PlanState *planstate, ExplainState *es)
+ {
+ 	WindowAggState *winaggstate = (WindowAggState *) planstate;
+ 
+ 	if (winaggstate->aggfwdtrans > 0)
+ 		ExplainPropertyFloat("Forward Transitions", winaggstate->aggfwdtrans, 1, es);
+ 	if (winaggstate->agginvtrans > 0)
+ 		ExplainPropertyFloat("Inverse Transtions", winaggstate->agginvtrans, 1, es);
+ }
+ 
  /*
   * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
   */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..9a7ed93 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1798,1805 ****
--- 1798,1807 ----
  								aggref->aggtype,
  								aggref->inputcollid,
  								transfn_oid,
+ 								InvalidOid, /* invtrans is not needed here */
  								finalfn_oid,
  								&transfnexpr,
+ 								NULL,
  								&finalfnexpr);
  
  		/* set up infrastructure for calling the transfn and finalfn */
*************** ExecReScanAgg(AggState *node)
*** 2127,2168 ****
  }
  
  /*
-  * AggCheckCallContext - test if a SQL function is being called as an aggregate
-  *
-  * The transition and/or final functions of an aggregate may want to verify
-  * that they are being called as aggregates, rather than as plain SQL
-  * functions.  They should use this function to do so.	The return value
-  * is nonzero if being called as an aggregate, or zero if not.	(Specific
-  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
-  * values could conceivably appear in future.)
-  *
-  * If aggcontext isn't NULL, the function also stores at *aggcontext the
-  * identity of the memory context that aggregate transition values are
-  * being stored in.
-  */
- int
- AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
- {
- 	if (fcinfo->context && IsA(fcinfo->context, AggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_AGGREGATE;
- 	}
- 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_WINDOW;
- 	}
- 
- 	/* this is just to prevent "uninitialized variable" warnings */
- 	if (aggcontext)
- 		*aggcontext = NULL;
- 	return 0;
- }
- 
- /*
   * AggGetAggref - allow an aggregate support function to get its Aggref
   *
   * If the function is being called as an aggregate support function,
--- 2129,2134 ----
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..539c664 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** typedef struct WindowStatePerFuncData
*** 102,119 ****
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transfer functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transfer functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/*
  	 * initial value from pg_aggregate entry
  	 */
--- 102,125 ----
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transition functions */
  	Oid			transfn_oid;
+ 	Oid			invtransfn_oid; /* may be InvalidOid */
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transition functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
+ 	FmgrInfo	invtransfn;
  	FmgrInfo	finalfn;
  
+ 	/* Aggregate properties */
+ 	bool		use_invtransfn;			/* whether to use the invtransfn */
+ 	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+ 
  	/*
  	 * initial value from pg_aggregate entry
  	 */
*************** typedef struct WindowStatePerAggData
*** 140,149 ****
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	Datum		transValue;		/* current transition value */
! 	bool		transValueIsNull;
  
! 	bool		noTransValue;	/* true if transValue not set yet */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
--- 146,158 ----
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	MemoryContext	aggcontext;			/* context for transValue */
! 	int64			transValueCount;	/* Number of aggregated values*/
! 	Datum			transValue;			/* current transition value */
! 	bool			transValueIsNull;
  
! 	/* Data local to eval_windowaggregates() */
! 	bool			restart;			/* tmp marker that agg needs restart */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
*************** static void initialize_windowaggregate(W
*** 152,157 ****
--- 161,169 ----
  static void advance_windowaggregate(WindowAggState *winstate,
  						WindowStatePerFunc perfuncstate,
  						WindowStatePerAgg peraggstate);
+ static bool retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate);
  static void finalize_windowaggregate(WindowAggState *winstate,
  						 WindowStatePerFunc perfuncstate,
  						 WindowStatePerAgg peraggstate,
*************** static bool are_peers(WindowAggState *wi
*** 181,186 ****
--- 193,245 ----
  static bool window_gettupleslot(WindowObject winobj, int64 pos,
  					TupleTableSlot *slot);
  
+ /*
+  * AggCheckCallContext - test if a SQL function is being called as an aggregate
+  *
+  * The transition and/or final functions of an aggregate may want to verify
+  * that they are being called as aggregates, rather than as plain SQL
+  * functions.  They should use this function to do so.	The return value
+  * is nonzero if being called as an aggregate, or zero if not.	(Specific
+  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+  * values could conceivably appear in future.)
+  *
+  * If aggcontext isn't NULL, the function also stores at *aggcontext the
+  * identity of the memory context that aggregate transition values are
+  * being stored in.
+  *
+  * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+  * is private.
+  *
+  * Note that this function is only meant to be used by aggregate support
+  * functions, NOT by true window functions.
+  */
+ int
+ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggState))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ 		return AGG_CONTEXT_AGGREGATE;
+ 	}
+ 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ 	{
+ 		if (aggcontext)
+ 		{
+ 			/* Must lookup per-aggregate context */
+ 			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+ 			int				aggno = winstate->calledaggno;
+ 			Assert(0 <= aggno && aggno < winstate->numaggs);
+ 			*aggcontext = winstate->peragg[aggno].aggcontext;
+ 		}
+ 		return AGG_CONTEXT_WINDOW;
+ 	}
+ 
+ 	/* this is just to prevent "uninitialized variable" warnings */
+ 	if (aggcontext)
+ 		*aggcontext = NULL;
+ 	return 0;
+ }
  
  /*
   * initialize_windowaggregate
*************** initialize_windowaggregate(WindowAggStat
*** 193,210 ****
  {
  	MemoryContext oldContext;
  
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = peraggstate->initValue;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->noTransValue = peraggstate->initValueIsNull;
  	peraggstate->resultValueIsNull = true;
  }
  
--- 252,277 ----
  {
  	MemoryContext oldContext;
  
+ 	/* If we're using a private aggcontext, we may reset it here. But if the
+ 	 * context is shared, we don't know which other aggregates may still need
+ 	 * it, so we must leave it to the caller to reset at an appropriate time
+ 	 */
+ 	if (!peraggstate->aggcontext_is_shared)
+ 		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+ 
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = (Datum) 0;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
+ 	peraggstate->transValueCount = 0;
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->resultValue = (Datum) 0;
  	peraggstate->resultValueIsNull = true;
  }
  
*************** advance_windowaggregate(WindowAggState *
*** 256,265 ****
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/*
! 		 * For a strict transfn, nothing happens when there's a NULL input; we
! 		 * just keep the prior transValue.
! 		 */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
--- 323,329 ----
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/* Skip NULL inputs for aggregates which desire that. */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
*************** advance_windowaggregate(WindowAggState *
*** 268,308 ****
  				return;
  			}
  		}
! 		if (peraggstate->noTransValue)
  		{
! 			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
! 			 * already checked that the agg's input type is binary-compatible
! 			 * with its transtype, so straight copy here is OK.)
! 			 *
! 			 * We must copy the datum into aggcontext if it is pass-by-ref. We
! 			 * do not need to pfree the old transValue, since it's NULL.
! 			 */
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->noTransValue = false;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  		if (peraggstate->transValueIsNull)
  		{
- 			/*
- 			 * Don't call a strict function with NULL inputs.  Note it is
- 			 * possible to get here despite the above tests, if the transfn is
- 			 * strict *and* returned a NULL on a prior cycle. If that happens
- 			 * we will propagate the NULL all the way to the end.
- 			 */
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  	}
  
  	/*
  	 * OK to call the transition function
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
--- 332,391 ----
  				return;
  			}
  		}
! 
! 		/*
! 		 * For strict transfer functions with initial value NULL we use the
! 		 * first non-NULL input as the initial state. (We already checked that
! 		 * the agg's input type is binary-compatible with its transtype, so
! 		 * straight copy here is OK.)
! 		 *
! 		 * We must copy the datum into aggcontext if it is pass-by-ref. We do
! 		 * not need to pfree the old transValue, since it's NULL.
! 		 */
! 		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->transValueCount = 1;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
+ 
+ 		/*
+ 		 * Don't call a strict function with NULL inputs.  Note it is possible
+ 		 * to get here despite the above tests, if the transfn is strict *and*
+ 		 * returned a NULL on a prior cycle. If that happens we will propagate
+ 		 * the NULL all the way to the end. That can only happen if there's no
+ 		 * inverse transition function, though, since we disallow transitions
+ 		 * back to NULL if there is one below.
+ 		 */
  		if (peraggstate->transValueIsNull)
  		{
  			MemoryContextSwitchTo(oldContext);
+ 			Assert(peraggstate->invtransfn_oid == InvalidOid);
  			return;
  		}
  	}
  
  	/*
+ 	 * We must track the number of inputs that we add to transValue, since
+ 	 * to remove the last input, retreat_windowaggregate() musn't call the
+ 	 * inverse transition function, but simply reset transValue back to its
+ 	 * initial value.
+ 	 *
+ 	 * Also update statistics
+ 	 */
+ 	Assert(peraggstate->transValueCount >= 0);
+ 	peraggstate->transValueCount++;
+ 	winstate->aggfwdtrans++;
+ 
+ 	/*
  	 * OK to call the transition function
+ 	 * Transfer functions with an inverse MUST not return NULL, see
+ 	 * retreat_windowaggregate()
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
*************** advance_windowaggregate(WindowAggState *
*** 310,316 ****
--- 393,407 ----
  							 (void *) winstate, NULL);
  	fcinfo->arg[0] = peraggstate->transValue;
  	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
  	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("transition function with an inverse returned NULL")));
+ 	}
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
*************** advance_windowaggregate(WindowAggState *
*** 322,328 ****
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
--- 413,558 ----
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
! 			newVal = datumCopy(newVal,
! 							   peraggstate->transtypeByVal,
! 							   peraggstate->transtypeLen);
! 		}
! 		if (!peraggstate->transValueIsNull)
! 			pfree(DatumGetPointer(peraggstate->transValue));
! 	}
! 
! 	MemoryContextSwitchTo(oldContext);
! 	peraggstate->transValue = newVal;
! 	peraggstate->transValueIsNull = fcinfo->isnull;
! }
! 
! /*
!  * retreat_windowaggregate
!  * removes tuples from aggregation.
!  * The calling function must ensure that each aggregate has
!  * a valid inverse transition function.
!  */
! static bool
! retreat_windowaggregate(WindowAggState *winstate,
! 						WindowStatePerFunc perfuncstate,
! 						WindowStatePerAgg peraggstate)
! {
! 	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
! 	int			numArguments = perfuncstate->numArguments;
! 	FunctionCallInfoData fcinfodata;
! 	FunctionCallInfo fcinfo = &fcinfodata;
! 	Datum		newVal;
! 	ListCell   *arg;
! 	int			i;
! 	MemoryContext oldContext;
! 	ExprContext *econtext = winstate->tmpcontext;
! 	ExprState  *filter = wfuncstate->aggfilter;
! 
! 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
! 
! 	/* Skip anything FILTERed out */
! 	if (filter)
! 	{
! 		bool		isnull;
! 		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
! 
! 		if (isnull || !DatumGetBool(res))
! 		{
! 			MemoryContextSwitchTo(oldContext);
! 			return true;
! 		}
! 	}
! 
! 	/* We start from 1, since the 0th arg will be the transition value */
! 	i = 1;
! 	foreach(arg, wfuncstate->args)
! 	{
! 		ExprState  *argstate = (ExprState *) lfirst(arg);
! 
! 		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
! 									  &fcinfo->argnull[i], NULL);
! 		i++;
! 	}
! 
! 	/* Skip inputs containing NULLS for aggregates that require this */
! 	if (peraggstate->invtransfn.fn_strict)
! 	{
! 		for (i = 1; i <= numArguments; i++)
! 		{
! 			if (fcinfo->argnull[i])
! 			{
! 				MemoryContextSwitchTo(oldContext);
! 				return true;
! 			}
! 		}
! 	}
! 
! 	/* There should still be an added but not yet removed value */
! 	Assert(peraggstate->transValueCount >= 1);
! 
! 	/*
! 	 * We mustn't use the inverse transition function to remove the last
! 	 * input. Doing so would yield a non-NULL state, whereas we should be
! 	 * in the initial state afterwards which may very well be NULL. So
! 	 * instead, we simply re-initialize the aggregation in this case.
! 	 */
! 	if (peraggstate->transValueCount == 1)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		initialize_windowaggregate(winstate,
! 								&winstate->perfunc[peraggstate->wfuncno],
! 								peraggstate);
! 		return true;
! 	}
! 
! 	/*
! 	 * Perform the inverse transition.
! 	 *
! 	 * For pairs of forward and inverse transition functions, the state may
! 	 * never be NULL, except in the ignore_nulls case, and then only until
! 	 * until we see the first non-NULL input during which time should never
! 	 * attempt to invoke the inverse transition function. Excluding NULL
! 	 * as a possible state value allows us to make it mean "sorry, can't
! 	 * do an inverse transition in this case" when returned by the inverse
! 	 * transition function. In that case, we report the failure to the
! 	 * caller.
! 	 */
! 	if (peraggstate->transValueIsNull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		elog(ERROR, "transition value is NULL during inverse transition");
! 	}
! 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
! 							 numArguments + 1,
! 							 perfuncstate->winCollation,
! 							 (void *) winstate, NULL);
! 	fcinfo->arg[0] = peraggstate->transValue;
! 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
! 	winstate->calledaggno = perfuncstate->aggno;
! 	newVal = FunctionCallInvoke(fcinfo);
! 	winstate->calledaggno = -1;
! 	if (fcinfo->isnull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		return false;
! 	}
! 
! 	/* Update number of added but not yet removed values and statistics */
! 	peraggstate->transValueCount--;
! 	winstate->agginvtrans++;
! 
! 	/*
! 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
! 	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
! 	 * first input, we don't need to do anything.
! 	 */
! 	if (!peraggstate->transtypeByVal &&
! 		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
! 	{
! 		if (!fcinfo->isnull)
! 		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
*************** advance_windowaggregate(WindowAggState *
*** 334,341 ****
--- 564,574 ----
  	MemoryContextSwitchTo(oldContext);
  	peraggstate->transValue = newVal;
  	peraggstate->transValueIsNull = fcinfo->isnull;
+ 
+ 	return true;
  }
  
+ 
  /*
   * finalize_windowaggregate
   * parallel to finalize_aggregate in nodeAgg.c
*************** finalize_windowaggregate(WindowAggState 
*** 370,376 ****
--- 603,611 ----
  		}
  		else
  		{
+ 			winstate->calledaggno = perfuncstate->aggno;
  			*result = FunctionCallInvoke(&fcinfo);
+ 			winstate->calledaggno = -1;
  			*isnull = fcinfo.isnull;
  		}
  	}
*************** finalize_windowaggregate(WindowAggState 
*** 392,402 ****
  	MemoryContextSwitchTo(oldContext);
  }
  
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * Much of this is duplicated from nodeAgg.c.  But NOTE that we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
--- 627,640 ----
  	MemoryContextSwitchTo(oldContext);
  }
  
+ 
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * This differes from nodeAgg.c in two ways. First, if the window's frame
!  * start position moves, we use the inverse transfer function (if it exists)
!  * to remove values from the transition value. And second, we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
*************** eval_windowaggregates(WindowAggState *wi
*** 406,417 ****
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs;
! 	int			i;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
--- 644,658 ----
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs,
! 				numaggs_restart = 0,
! 				i;
! 	int64		aggregatedupto_nonrestarted;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
! 	TupleTableSlot *temp_slot = winstate->temp_slot_1;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
*************** eval_windowaggregates(WindowAggState *wi
*** 420,426 ****
  	/* final output execution is in ps_ExprContext */
  	econtext = winstate->ss.ps.ps_ExprContext;
  	agg_winobj = winstate->agg_winobj;
- 	agg_row_slot = winstate->agg_row_slot;
  
  	/*
  	 * Currently, we support only a subset of the SQL-standard window framing
--- 661,666 ----
*************** eval_windowaggregates(WindowAggState *wi
*** 438,446 ****
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * For other frame start rules, we discard the aggregate state and re-run
! 	 * the aggregates whenever the frame head row moves.  We can still
! 	 * optimize as above whenever successive rows share the same frame head.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
--- 678,697 ----
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * We can still optimize as above whenever successive rows share the same
! 	 * frame head, but if the frame head moves beyond the aggregated base point
! 	 * we use the aggregate function's inverse transition function. This
! 	 * removes the tuple from aggregation and restores the aggregate's current
! 	 * state to what it would be if the removed row had never been aggregated
! 	 * in the first place. Inverse transition functions may optionally return
! 	 * NULL, this indicates that the function was unable to remove the tuple
! 	 * from aggregation, when this happens we must perform the aggregation all
! 	 * over again for all tuples in the new frame boundary.
! 	 *
! 	 * If the aggregate function does not have a inverse transition function
! 	 * and the frame head moves beyond the aggregated position then we must
! 	 * discard the aggregated state and re-aggregate similar to how we would
! 	 * have to if the inverse transition function had returned NULL.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
*************** eval_windowaggregates(WindowAggState *wi
*** 452,526 ****
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
- 	 *
- 	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
- 	 * some aggregates like SUM and COUNT we could avoid that by implementing
- 	 * a "negative transition function" that would be called for each row as
- 	 * it exits the frame.	We'd have to think about avoiding recalculation of
- 	 * volatile arguments of aggregate functions, too.
  	 */
  
  	/*
  	 * First, update the frame head position.
  	 */
! 	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
  
  	/*
! 	 * Initialize aggregates on first call for partition, or if the frame head
! 	 * position moved since last time.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos != winstate->aggregatedbase)
  	{
- 		/*
- 		 * Discard transient aggregate values
- 		 */
- 		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
- 
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  
  		/*
! 		 * If we created a mark pointer for aggregates, keep it pushed up to
! 		 * frame head, so that tuplestore can discard unnecessary rows.
  		 */
! 		if (agg_winobj->markptr >= 0)
! 			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
  
  		/*
! 		 * Initialize for loop below
  		 */
! 		ExecClearTuple(agg_row_slot);
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 		winstate->aggregatedupto = winstate->frameheadpos;
  	}
  
  	/*
! 	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
! 	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
! 	 * have to recalculate when the frame head moves or currentpos has
! 	 * advanced past the place we'd aggregated up to.  Check for these cases
! 	 * and if so, reuse the saved result values.
  	 */
! 	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
! 		for (i = 0; i < numaggs; i++)
  		{
- 			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
! 		return;
  	}
  
  	/*
--- 703,889 ----
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
  	 */
  
  	/*
  	 * First, update the frame head position.
+ 	 *
+ 	 * The frame head should never move backwards, and the code below wouldn't
+ 	 * cope if it did, so for safety we complain if it does.
  	 */
! 	update_frameheadpos(agg_winobj, temp_slot);
! 	if (winstate->frameheadpos < winstate->aggregatedbase)
! 		elog(ERROR, "frame moved backwards unexpectedly");
  
  	/*
! 	 * If the frame didn't change compared to the previous row, we can re-use
! 	 * the cached result. Since we don't know the current frame's end yet, we
! 	 * cannot check that the obvious way. But we can exploit that if the frame
! 	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
! 	 * row lies within the previous row's frame, the two frame's ends must
! 	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
! 	 * so we don't need to check for that explicitly here.
  	 */
! 	if (winstate->aggregatedbase == winstate->frameheadpos &&
! 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
! 		}
! 		return;
! 	}
! 
! 	/* Initialize restart flags.
! 	 *
! 	 * We restart the aggregation
! 	 *   - if we're processing the first row in the partition, or
! 	 *   - if we the frame's head moved and we cannot use an inverse
! 	 *     transition function, or
! 	 *   - if the new frame doesn't overlap the old one
! 	 *
! 	 * Note that we don't strictly need to restart in the last case, but
! 	 * if we're going to remove *all* rows from the aggregation anyway, a
! 	 * restart surely is faster.
! 	 */
! 	for (i = 0; i < numaggs; i++)
! 	{
! 		peraggstate = &winstate->peragg[i];
! 		if (winstate->currentpos == 0 ||
! 			(winstate->aggregatedbase < winstate->frameheadpos &&
! 			!peraggstate->use_invtransfn) ||
! 			winstate->aggregatedupto <= winstate->frameheadpos)
! 		{
! 			peraggstate->restart = true;
! 			numaggs_restart++;
  		}
+ 		else
+ 			peraggstate->restart = false;
+ 	}
  
+ 	/*
+ 	 * Attempt to update aggregatedbase to match the frame's head by
+ 	 * removing those inputs from the aggregations which fell off the top
+ 	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+ 	 * return false, in which case we restart that aggregate below.
+ 	 *
+ 	 * Aftwards, aggregatedbase equals frameheadpos.
+ 	 */
+ 	while(winstate->aggregatedbase < winstate->frameheadpos)
+ 	{
  		/*
! 		 * Fetch the tuple where the current aggregation started from.
! 		 * This should never fail as we should have been here before.
  		 */
! 		if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
! 								 temp_slot))
! 			elog(ERROR, "Unable to find tuple in tuplestore");
! 
! 		/* Set tuple context for evaluation of aggregate arguments */
! 		winstate->tmpcontext->ecxt_outertuple = temp_slot;
  
  		/*
! 		 * Perform the inverse transition for each aggregate function in
! 		 * the window, unless it has already been marked as needing a
! 		 * restart.
  		 */
! 		for (i = 0; i < numaggs; i++)
! 		{
! 			bool	ok;
! 
! 			peraggstate = &winstate->peragg[i];
! 			if (peraggstate->restart)
! 				continue;
! 
! 			wfuncno = peraggstate->wfuncno;
! 			ok = retreat_windowaggregate(winstate,
! 										 &winstate->perfunc[wfuncno],
! 										 peraggstate);
! 			if (!ok)
! 			{
! 				/* Inverse transition function has failed, must restart */
! 				peraggstate->restart = true;
! 				numaggs_restart++;
! 			}
! 		}
! 
! 		/* Reset per-input-tuple context after each tuple */
! 		ResetExprContext(winstate->tmpcontext);
! 
! 		/* And advance the aggregated-row state */
! 		winstate->aggregatedbase++;
! 		ExecClearTuple(temp_slot);
! 
! 		/* If no more retreatable aggregates are left, we stop early */
! 		if (numaggs_restart == numaggs)
! 		{
! 			winstate->aggregatedbase = winstate->frameheadpos;
! 			break;
! 		}
  	}
  
  	/*
! 	 * If we created a mark pointer for aggregates, keep it pushed up to
! 	 * frame head, so that tuplestore can discard unnecessary rows.
  	 */
! 	if (agg_winobj->markptr >= 0)
! 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
! 
! 	/*
! 	 * Then restart the aggregates which require it.
! 	 *
! 	 * We assume that aggregates using the shared context always restart
! 	 * if *any* aggregate restarts, and we may thus cleanup the shared
! 	 * aggcontext if that is the case. The private aggcontexts are reset
! 	 * by initialize_windowaggregate() if their owning aggregate restarts,
! 	 * otherwise we just pfree() the cached result.
! 	 */
! 	if (numaggs_restart > 0)
! 		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < numaggs; i++)
  	{
! 		peraggstate = &winstate->peragg[i];
! 		/* Aggregates using the shared ctx must restart if *any* agg does */
! 		Assert(!peraggstate->aggcontext_is_shared ||
! 			   !numaggs_restart || peraggstate->restart);
! 		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
! 			!peraggstate->resulttypeByVal)
! 		{
! 			pfree(DatumGetPointer(peraggstate->resultValue));
! 			peraggstate->resultValue = (Datum) 0;
! 			peraggstate->resultValueIsNull = true;
! 		}
! 		else if (peraggstate->restart)
  		{
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
! 	}
! 
! 	/*
! 	 * Non-restarted aggregates now contain the rows between aggregatedbase
! 	 * (i.e. frameheadpos) and aggregatedupto, and restarted aggregates
! 	 * contain no rows. If there are any restarted aggregates, we must thus
! 	 * begin aggregating anew at frameheadpos, otherwise we may simply
! 	 * continue at aggregatedupto. Since we possibly reset aggregatedupto, we
! 	 * must remember the old value to know how long to skip non-restarted
! 	 * aggregates. If we modify aggregatedupto, we must also clear
! 	 * agg_row_slot, per the loop invariant below.
! 	 */
! 	aggregatedupto_nonrestarted = winstate->aggregatedupto;
! 	if (numaggs_restart > 0 &&
! 		winstate->aggregatedupto != winstate->frameheadpos)
! 	{
! 		winstate->aggregatedupto = winstate->frameheadpos;
! 		ExecClearTuple(agg_row_slot);
  	}
  
  	/*
*************** eval_windowaggregates(WindowAggState *wi
*** 551,556 ****
--- 914,924 ----
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
+ 			/* Non-restarted aggs skip until aggregatedupto_nonrestarted */
+ 			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+ 				!peraggstate->restart)
+ 				continue;
+ 
  			wfuncno = peraggstate->wfuncno;
  			advance_windowaggregate(winstate,
  									&winstate->perfunc[wfuncno],
*************** eval_windowaggregates(WindowAggState *wi
*** 565,570 ****
--- 933,941 ----
  		ExecClearTuple(agg_row_slot);
  	}
  
+ 	/* The frame's end is not supposed to move backwards, ever */
+ 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+ 
  	/*
  	 * finalize aggregates and fill result/isnull fields.
  	 */
*************** eval_windowaggregates(WindowAggState *wi
*** 589,616 ****
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal)
  		{
! 			/*
! 			 * clear old resultValue in order not to leak memory.  (Note: the
! 			 * new result can't possibly be the same datum as old resultValue,
! 			 * because we never passed it to the trans function.)
! 			 */
! 			if (!peraggstate->resultValueIsNull)
! 				pfree(DatumGetPointer(peraggstate->resultValue));
! 
! 			/*
! 			 * If pass-by-ref, copy it into our aggregate context.
! 			 */
! 			if (!*isnull)
! 			{
! 				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
! 				peraggstate->resultValue =
! 					datumCopy(*result,
! 							  peraggstate->resulttypeByVal,
! 							  peraggstate->resulttypeLen);
! 				MemoryContextSwitchTo(oldContext);
! 			}
  		}
  		else
  		{
--- 960,973 ----
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal && !*isnull)
  		{
! 			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
! 			peraggstate->resultValue =
! 				datumCopy(*result,
! 						  peraggstate->resulttypeByVal,
! 						  peraggstate->resulttypeLen);
! 			MemoryContextSwitchTo(oldContext);
  		}
  		else
  		{
*************** eval_windowfunction(WindowAggState *wins
*** 651,656 ****
--- 1008,1014 ----
  	/* Just in case, make all the regular argument slots be null */
  	memset(fcinfo.argnull, true, perfuncstate->numArguments);
  
+ 	winstate->calledaggno = -1;
  	*result = FunctionCallInvoke(&fcinfo);
  	*isnull = fcinfo.isnull;
  
*************** spool_tuples(WindowAggState *winstate, i
*** 794,800 ****
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kluge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
--- 1152,1158 ----
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kludge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
*************** release_partition(WindowAggState *winsta
*** 869,875 ****
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
--- 1227,1236 ----
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < winstate->numaggs; ++i)
! 		if (!winstate->peragg[i].aggcontext_is_shared)
! 			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1419,1425 ****
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno;
  	ListCell   *l;
  
  	/* check for unsupported flags */
--- 1780,1787 ----
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno,
! 				numaggs_invtrans;
  	ListCell   *l;
  
  	/* check for unsupported flags */
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1450,1457 ****
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived context for aggregate trans values etc */
! 	winstate->aggcontext =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
--- 1812,1821 ----
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived contexts for aggregate trans values etc
! 	 * Note that invertible aggregates use their own private context
! 	 */
! 	winstate->aggcontext_shared =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1535,1540 ****
--- 1899,1905 ----
  
  	wfuncno = -1;
  	aggno = -1;
+ 	numaggs_invtrans = 0;
  	foreach(l, winstate->funcs)
  	{
  		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1603,1608 ****
--- 1968,1975 ----
  			peraggstate = &winstate->peragg[aggno];
  			initialize_peragg(winstate, wfunc, peraggstate);
  			peraggstate->wfuncno = wfuncno;
+ 			if (peraggstate->use_invtransfn)
+ 				numaggs_invtrans++;
  		}
  		else
  		{
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1618,1623 ****
--- 1985,1991 ----
  	/* Update numfuncs, numaggs to match number of unique functions found */
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
+ 	winstate->numaggs_invtrans = numaggs_invtrans;
  
  	/* Set up WindowObject for aggregates, if needed */
  	if (winstate->numaggs > 0)
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1646,1651 ****
--- 2014,2026 ----
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	/* initialize temporary data */
+ 	winstate->calledaggno = -1;
+ 
+ 	/* initialize statistics */
+ 	winstate->aggfwdtrans = 0;
+ 	winstate->agginvtrans = 0;
+ 
  	return winstate;
  }
  
*************** void
*** 1657,1668 ****
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
  
  	release_partition(node);
  
- 	pfree(node->perfunc);
- 	pfree(node->peragg);
- 
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
--- 2032,2041 ----
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
+ 	int			i;
  
  	release_partition(node);
  
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
*************** ExecEndWindowAgg(WindowAggState *node)
*** 1677,1683 ****
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
--- 2050,2062 ----
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext_shared);
! 	for(i = 0; i < node->numaggs; i++)
! 		if (!node->peragg[i].aggcontext_is_shared)
! 			MemoryContextDelete(node->peragg[i].aggcontext);
! 
! 	pfree(node->perfunc);
! 	pfree(node->peragg);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
*************** initialize_peragg(WindowAggState *winsta
*** 1735,1742 ****
--- 2114,2123 ----
  	Oid			aggtranstype;
  	AclResult	aclresult;
  	Oid			transfn_oid,
+ 				invtransfn_oid,
  				finalfn_oid;
  	Expr	   *transfnexpr,
+ 			   *invtransfnexpr,
  			   *finalfnexpr;
  	Datum		textInitVal;
  	int			i;
*************** initialize_peragg(WindowAggState *winsta
*** 1762,1767 ****
--- 2143,2149 ----
  	 */
  
  	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+ 	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
  	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
  	/* Check that aggregate owner has permission to call component fns */
*************** initialize_peragg(WindowAggState *winsta
*** 1783,1788 ****
--- 2165,2181 ----
  			aclcheck_error(aclresult, ACL_KIND_PROC,
  						   get_func_name(transfn_oid));
  		InvokeFunctionExecuteHook(transfn_oid);
+ 
+ 		if (OidIsValid(invtransfn_oid))
+ 		{
+ 			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+ 										 ACL_EXECUTE);
+ 			if (aclresult != ACLCHECK_OK)
+ 				aclcheck_error(aclresult, ACL_KIND_PROC,
+ 				get_func_name(invtransfn_oid));
+ 			InvokeFunctionExecuteHook(invtransfn_oid);
+ 		}
+ 
  		if (OidIsValid(finalfn_oid))
  		{
  			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
*************** initialize_peragg(WindowAggState *winsta
*** 1810,1822 ****
--- 2203,2223 ----
  							wfunc->wintype,
  							wfunc->inputcollid,
  							transfn_oid,
+ 							invtransfn_oid,
  							finalfn_oid,
  							&transfnexpr,
+ 							&invtransfnexpr,
  							&finalfnexpr);
  
  	fmgr_info(transfn_oid, &peraggstate->transfn);
  	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+ 		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+ 	}
+ 
  	if (OidIsValid(finalfn_oid))
  	{
  		fmgr_info(finalfn_oid, &peraggstate->finalfn);
*************** initialize_peragg(WindowAggState *winsta
*** 1860,1867 ****
--- 2261,2334 ----
  							wfunc->winfnoid)));
  	}
  
+ 	/*
+ 	 * Allowing only the forward transition function to be strict would
+ 	 * require handling more special cases in advance_windowaggregate() and
+ 	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 	 * that if the forward transition function is strict that the inverse
+ 	 * transition function is also strict. This should have been checked at
+ 	 * the aggregate function's definition time, but it's better to be safe...
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 				errmsg("strictness of forward and inverse transition functions must match")));
+ 	}
+ 
  	ReleaseSysCache(aggTuple);
  
+ 	/*
+ 	 * We can use the inverse transition function only if the aggregate's
+ 	 * arguments don't contain calls to volatile functions. Otherwise,
+ 	 * the difference between restarting and not restarting the aggregation
+ 	 * would be user-visible. Note that this check also covers the case where
+ 	 * the FILTER's WHERE clause contains a volatile function. If the frame
+ 	 * head cannot move, we won't ever need the inverse transition function,
+ 	 * so we also mark as "don't use" in that case.
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		!contain_volatile_functions((Node *) wfunc) &&
+ 		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+ 	{
+ 		peraggstate->use_invtransfn = true;
+ 	}
+ 	else
+ 	{
+ 		peraggstate->use_invtransfn = false;
+ 	}
+ 
+ 	/*
+ 	 * Invertible aggregates use their own aggcontext.
+ 	 *
+ 	 * This is necessary because they might all restart at different times,
+ 	 * so we might never be able to reset the shared context otherwise. We
+ 	 * can't make it the aggregate's responsibility to clean up after
+ 	 * themselves, because strict aggregates must be restarted whenever we
+ 	 * remove their last non-NULL input, which the aggregate won't be aware
+ 	 * is happening. Also, just pfree()ing the transValue upon restarting
+ 	 * wouldn't help, since we'd miss any indirectly referenced data. We
+ 	 * could, in theory, declare that aggregates with a state type other then
+ 	 * "internal" musn't allocate anything in the aggcontext themselves, that
+ 	 * non-strict aggregates with state type internal must clean up after
+ 	 * themselves when their inverse transfer function returns NULL, and then
+ 	 * only use private aggcontexts for strict aggregates with state type
+ 	 * internal. But that'd be a rather grotty set of requirements.
+ 	 */
+ 	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+ 	if (!peraggstate->aggcontext_is_shared)
+ 	{
+ 		peraggstate->aggcontext =
+ 			AllocSetContextCreate(CurrentMemoryContext,
+ 								  "WindowAgg_AggregatePrivate",
+ 								  ALLOCSET_DEFAULT_MINSIZE,
+ 								  ALLOCSET_DEFAULT_INITSIZE,
+ 								  ALLOCSET_DEFAULT_MAXSIZE);
+ 	}
+ 	else
+ 		peraggstate->aggcontext = winstate->aggcontext_shared;
+ 
  	return peraggstate;
  }
  
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..ef84e4c 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** resolve_aggregate_transtype(Oid aggfunci
*** 1187,1197 ****
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid and finalfn_oid identify the funcs to be called; the latter
!  * may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr and
!  * *finalfnexpr.  The latter is set to NULL if there's no finalfn.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
--- 1187,1199 ----
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid, invtransfn_oid and finalfn_oid identify the funcs to be
!  * called; the latter two may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr,
!  * *invtransfnexpr and *finalfnexpr. If there is invtransfn or finalfn, the
!  * respective pointers are set to NULL. Since use of the invtransfn is
!  * optional, NULL may be passed for invtransfnexpr.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1203,1210 ****
--- 1205,1214 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr)
  {
  	Param	   *argp;
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1249,1254 ****
--- 1253,1270 ----
  	fexpr->funcvariadic = agg_variadic;
  	*transfnexpr = (Expr *) fexpr;
  
+ 	if (OidIsValid(invtransfn_oid) && invtransfnexpr != NULL)
+ 	{
+ 		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+ 												agg_result_type,
+ 												args,
+ 												InvalidOid,
+ 												agg_input_collation,
+ 												COERCE_EXPLICIT_CALL);
+ 	}
+ 	else if (invtransfnexpr != NULL)
+ 		*invtransfnexpr	= NULL;
+ 
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
  	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2653ef0..c14209f 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11547,11552 ****
--- 11547,11553 ----
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
+ 	int			i_agginvtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
  	int			i_hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11555,11560 ****
--- 11556,11562 ----
  	int			i_agginitval;
  	int			i_convertok;
  	const char *aggtransfn;
+ 	const char *agginvtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
  	char	   *aggsortconvop;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11580,11586 ****
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
--- 11582,11588 ----
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11596,11601 ****
--- 11598,11604 ----
  	else if (fout->remoteVersion >= 80400)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11611,11616 ****
--- 11614,11620 ----
  	else if (fout->remoteVersion >= 80100)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11624,11629 ****
--- 11628,11634 ----
  	else if (fout->remoteVersion >= 70300)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11636,11642 ****
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
--- 11641,11649 ----
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
! 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
! 						  "aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11649,11654 ****
--- 11656,11662 ----
  	else
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn1 AS aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, "
  						  "(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
  						  "0 AS aggsortop, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11663,11668 ****
--- 11671,11677 ----
  	res = ExecuteSqlQueryForSingleRow(fout, query->data);
  
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
+ 	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
  	i_hypothetical = PQfnumber(res, "hypothetical");
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11672,11677 ****
--- 11681,11687 ----
  	i_convertok = PQfnumber(res, "convertok");
  
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ 	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11733,11738 ****
--- 11743,11754 ----
  						  fmtId(aggtranstype));
  	}
  
+ 	if (strcmp(agginvtransfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    INVSFUNC = %s",
+ 						  agginvtransfn);
+ 	}
+ 
  	if (strcmp(aggtransspace, "0") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    SSPACE = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index f189998..3412661 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 46,51 ****
--- 46,52 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		agginvtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 69,85 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_agginvtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,277 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 103,279 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	-	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
*************** extern Oid AggregateCreate(const char *a
*** 289,294 ****
--- 291,297 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index aed81cd..c4d4864 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** extern void **find_rendezvous_variable(c
*** 645,652 ****
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly with nodeAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
--- 645,654 ----
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
!  * which is in execute/nodeWindowAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly neither nodeAgg
!  * nor nodeWindowAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..03ee793 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WindowAggState
*** 1738,1743 ****
--- 1738,1744 ----
  	List	   *funcs;			/* all WindowFunc nodes in targetlist */
  	int			numfuncs;		/* total number of window functions */
  	int			numaggs;		/* number that are plain aggregates */
+ 	int			numaggs_invtrans;	/* number that are invertible aggregates */
  
  	WindowStatePerFunc perfunc; /* per-window-function information */
  	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
*************** typedef struct WindowAggState
*** 1762,1768 ****
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext;	/* context for each aggregate data */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
--- 1763,1769 ----
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext_shared;	/* shared context for agg states */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
*************** typedef struct WindowAggState
*** 1780,1789 ****
--- 1781,1797 ----
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
  
+ 	/* temporary data */
+ 	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+ 
  	/* temporary slots for tuples fetched back from tuplestore */
  	TupleTableSlot *agg_row_slot;
  	TupleTableSlot *temp_slot_1;
  	TupleTableSlot *temp_slot_2;
+ 
+ 	/* Statistics */
+ 	double	aggfwdtrans;	/* number of forward transitions */
+ 	double	agginvtrans;	/* number of inverse transitions */
  } WindowAggState;
  
  /* ----------------
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void build_aggregate_fnexprs(Oid 
*** 39,46 ****
--- 39,48 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr);
  
  #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..080cbd2 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select least_agg(variadic array[q1,q2]) 
*** 1580,1582 ****
--- 1580,1606 ----
   -4567890123456789
  (1 row)
  
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..1a52423 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** alter aggregate my_rank(VARIADIC "any" O
*** 90,92 ****
--- 90,129 ----
   public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
  (2 rows)
  
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ ERROR:  strictness of forward and inverse transition functions must match
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ ERROR:  function intminus(double precision, double precision) does not exist
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
+ ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..8af3d23 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT nth_value_def(ten) OVER (PARTITIO
*** 1071,1073 ****
--- 1071,1307 ----
               1 |   3 |    3
  (10 rows)
  
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+ ----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+  1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+  1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+  1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+  1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+  1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+  2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+  3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+ (9 rows)
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+ ----------+--------------+-------------------+-------------+------------------
+  1,1:NULL | +NULL        | I+NULL            |             | I
+  1,2:-    | +NULL        | I+NULL            |             | I
+  1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+  1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+  1,5:-    |              | I                 |             | I
+  1,6:-    |              | I                 |             | I
+  2,1:-    |              | I                 |             | I
+  2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+  3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+ (9 rows)
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+  logging_agg_strict 
+ --------------------
+  a
+  b
+  c
+ (3 rows)
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+  eq1 | eq2 | eq3 
+ -----+-----+-----
+  t   | t   | t
+ (1 row)
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..34f7004 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** drop view aggordview1;
*** 590,592 ****
--- 590,609 ----
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..130489c 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** alter aggregate my_rank(VARIADIC "any" O
*** 101,103 ****
--- 101,144 ----
    rename to test_rank;
  
  \da test_*
+ 
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ 
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ 
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ 
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..117bd6c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** SELECT nth_value_def(n := 2, val := ten)
*** 284,286 ****
--- 284,489 ----
  
  SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
    FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+ 
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ 
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ 
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ 
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ 
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ 
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ 
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ 
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the MIN, MAX and boolean inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
invtrans_strictstrict_collecting_7d3716.patchapplication/octet-stream; name=invtrans_strictstrict_collecting_7d3716.patch; x-unix-mode=0644Download
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index c62e3fb..5a3a31d 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** create_singleton_array(FunctionCallInfo 
*** 471,477 ****
  
  
  /*
!  * ARRAY_AGG aggregate function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
--- 471,477 ----
  
  
  /*
!  * ARRAY_AGG aggregate transition function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 508,513 ****
--- 508,537 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * ARRAY_AGG aggregate inverse transition function
+  */
+ Datum
+ array_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since NULLs
+ 	 * need to be removed just like any other value.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "array_agg_invtransfn called with NULL state");
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "array_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	shiftArrayResult(state, 1);
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..2dd7ecb 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** accumArrayResult(ArrayBuildState *astate
*** 4587,4592 ****
--- 4587,4593 ----
  		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
  		astate->mcontext = arr_context;
  		astate->alen = 64;		/* arbitrary starting array size */
+ 		astate->offset = 0;
  		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
  		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
  		astate->nelems = 0;
*************** accumArrayResult(ArrayBuildState *astate
*** 4600,4606 ****
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/* enlarge dvalues[]/dnulls[] if needed */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
--- 4601,4612 ----
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/*
! 		 * If the buffers are filled completely (offset must be zero then),
! 		 * we double their size. If they aren't, but the values extend to the
! 		 * end of the buffers, we reclaim wasted space at the beginning by
! 		 * moving the values to the front of the buffers.
! 		 */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
*************** accumArrayResult(ArrayBuildState *astate
*** 4609,4614 ****
--- 4615,4629 ----
  			astate->dnulls = (bool *)
  				repalloc(astate->dnulls, astate->alen * sizeof(bool));
  		}
+ 		else if (astate->offset + astate->nelems >= astate->alen)
+ 		{
+ 			memmove(astate->dvalues, astate->dvalues + astate->offset,
+ 					astate->alen * sizeof(Datum));
+ 			memmove(astate->dnulls, astate->dnulls + astate->offset,
+ 					astate->alen * sizeof(bool));
+ 			astate->offset = 0;
+ 		}
+ 		Assert(astate->offset + astate->nelems < astate->alen);
  	}
  
  	/*
*************** accumArrayResult(ArrayBuildState *astate
*** 4627,4634 ****
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->nelems] = dvalue;
! 	astate->dnulls[astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
--- 4642,4649 ----
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->offset + astate->nelems] = dvalue;
! 	astate->dnulls[astate->offset + astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
*************** accumArrayResult(ArrayBuildState *astate
*** 4637,4642 ****
--- 4652,4698 ----
  }
  
  /*
+  * shiftArrayResult - shift leading Datums out of an array result
+  *
+  *	astate is working state
+  *	count is the number of leading Datums to shift out
+  *
+  * If count is equal to or larger than the number of relements, the array
+  * result is empty afterwards. If astate is NULL, nothing is done.
+  */
+ void
+ shiftArrayResult(ArrayBuildState *astate, int count)
+ {
+ 	int		i;
+ 
+ 	if (astate == NULL)
+ 		return;
+ 
+ 	/* Limit shift count to number of elements for safety */
+ 	count = Min(count, astate->nelems);
+ 
+ 	/* For pass-by-ref types, free values we shift out */
+ 	if (!astate->typbyval) {
+ 		for(i = astate->offset; i < astate->offset + count; ++i) {
+ 			if (astate->dnulls[i])
+ 				continue;
+ 
+ 			pfree(DatumGetPointer(astate->dvalues[i]));
+ 
+ 			/* For cleanliness' sake */
+ 			astate->dnulls[i] = false;
+ 			astate->dvalues[i] = 0;
+ 		}
+ 
+ 	}
+ 
+ 	/* Update state */
+ 	astate->nelems -= count;
+ 	astate->offset += count;
+ }
+ 
+ 
+ /*
   * makeArrayResult - produce 1-D final result of accumArrayResult
   *
   *	astate is working state (not NULL)
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4679,4686 ****
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues,
! 								astate->dnulls,
  								ndims,
  								dims,
  								lbs,
--- 4735,4742 ----
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues + astate->offset,
! 								astate->dnulls + astate->offset,
  								ndims,
  								dims,
  								lbs,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index cb07a06..74fe7fc 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** typedef struct
*** 50,55 ****
--- 50,63 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct StringAggState
+ {
+ 	StringInfoData 	string; 	/* Contents */
+ 	int 			offset;		/* Offset into stringinfo's data */
+ 	int 			delimLen;	/* Delim length, -1 initially, -2 if multiple */
+ 	int64			notNullCount;/* Number of non-NULL inputs */
+ } StringAggState;
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
*************** static void appendStringInfoText(StringI
*** 78,84 ****
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
--- 86,95 ----
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo);
! static void prepareAppendStringAggState(StringAggState *state,
! 										int delimLen, int valueLen);
! static bool removeFromStringAggState(StringAggState *state, int valueLen);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
*************** static void text_format_string_conversio
*** 92,97 ****
--- 103,226 ----
  static void text_format_append_string(StringInfo buf, const char *str,
  						  int flags, int width);
  
+ /*****************************************************************************
+  *	 SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA)			 *
+  *****************************************************************************/
+ 
+ /*
+  * subroutine to initialize state
+  */
+ static StringAggState*
+ makeStringAggState(FunctionCallInfo fcinfo)
+ {
+ 	StringAggState*	state;
+ 	MemoryContext aggcontext;
+ 	MemoryContext oldcontext;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 	}
+ 
+ 	/*
+ 	 * Create state in aggregate context.  It'll stay there across subsequent
+ 	 * calls.
+ 	 */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = (StringAggState *) palloc(sizeof(StringAggState));
+ 	initStringInfo(&state->string);
+ 	state->offset = 0;
+ 	state->delimLen = -1;
+ 	state->notNullCount = 0;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * Prepare state for appending a value and a delimiter with specified lengths.
+  * pass -1 for delimLen if no delimiter will be added
+  */
+ void
+ prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen)
+ {
+ 	/*
+ 	 * Reclaim wasted space
+ 	 *
+ 	 * We move the contents to the left if the current contents fit into the
+ 	 * wasted space, i.e. if we waste more than we store. The limit is
+ 	 * somewhat arbitrary, but it's the smallest one that allows
+ 	 * memcpy to be used, because the source and destination don't overlap.
+ 	 * Note that we must check for <, not <=, because we include the trailing
+ 	 * '\0' in the copy.
+ 	 */
+ 	if (state->string.len - state->offset < state->offset)
+ 	{
+ 		state->string.len -= state->offset;
+ 		memcpy(state->string.data, state->string.data + state->offset,
+ 			   state->string.len + 1);
+ 		state->offset = 0;
+ 	}
+ 
+ 	/*
+ 	 * Enlarge StringInfo
+ 	 *
+ 	 * Not strictly necessary, but avoids potentially resizing twice when
+ 	 * the actual append... calls are done by the caller
+ 	 */
+ 	enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen);
+ 
+ 
+ 	/* Track delimiter length */
+ 	if (delimLen == -1)
+ 		{} /* Not specified, don't update */
+ 	else if (state->delimLen == -1)
+ 		state->delimLen = delimLen;
+ 	else if (state->delimLen != delimLen)
+ 		state->delimLen = -2;
+ }
+ 
+ /*
+  * Remove value with given length and the delimiter that follows
+  *
+  * Returns false if removal was impossible because delimiters varied
+  */
+ bool
+ removeFromStringAggState(StringAggState *state, int valueLen)
+ {
+ 	/* Remove the string */
+ 	state->offset += valueLen;
+ 
+ 	/*
+ 	 * Remove delimiter if necessary.
+ 	 *
+ 	 * The delimiter we need to remove isn't the delimiter we were passed, but
+ 	 * rather the delimiter passed when adding the input *after* this one. We
+ 	 * thus need the delimiter length to be all the same to be able to proceed.
+ 	 * If we're removing the last string, there will be no delimiter following
+ 	 * it. In that case, we may reset delimLen to its initial value.
+ 	 */
+ 	if (state->delimLen == -2)
+ 		return false;
+ 	if (state->offset < state->string.len)
+ 	{
+ 		Assert(state->delimLen >= 0);
+ 		state->offset += state->delimLen;
+ 	}
+ 	else
+ 		state->delimLen = -1;
+ 
+ 	/* Don't crash if we're ever asked to remove more than was added */
+ 	if (state->offset > state->string.len)
+ 	{
+ 		state->offset = state->string.len;
+ 		elog(ERROR, "tried to remove more data than was aggregated");
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /*****************************************************************************
   *	 CONVERSION ROUTINES EXPORTED FOR USE BY C CODE							 *
*************** byteasend(PG_FUNCTION_ARGS)
*** 408,435 ****
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		bytea	   *value = PG_GETARG_BYTEA_PP(1);
  
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 		{
! 			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim));
! 		}
  
! 		appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value));
  	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
--- 537,589 ----
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	bytea		   *value,
! 				   *delim;
! 	int				valueLen,
! 					delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
  
! 	value = PG_GETARG_BYTEA_PP(1);
! 	valueLen = VARSIZE_ANY_EXHDR(value);
! 	state->notNullCount++;
  
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
! 	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_BYTEA_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
! 	}
! 	else
! 	{
! 		/* Delimiter is NULL, treat as zero-length string */
! 		delim = NULL;
! 		delimLen = 0;
  	}
  
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, valueLen);
+ 	if (delim)
+ 		appendBinaryStringInfo(&state->string, VARDATA_ANY(delim),
+ 							   VARSIZE_ANY_EXHDR(delim));
+ 	appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen);
+ 
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 438,459 ****
  }
  
  Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
  	{
  		bytea	   *result;
  
! 		result = (bytea *) palloc(state->len + VARHDRSZ);
! 		SET_VARSIZE(result, state->len + VARHDRSZ);
! 		memcpy(VARDATA(result), state->data, state->len);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
--- 592,661 ----
  }
  
  Datum
+ bytea_string_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	int				valueLen;
+ 	StringAggState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since it
+ 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "string_agg_invtransfn called with NULL state");
+ 	else if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (StringAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* We append nothing if the string is NULL, so skip here as well */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_POINTER(state);
+ 
+ 	/* No need to de-toast value, need only the length */
+ 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
+ 	Assert(state->notNullCount >= 1);
+ 	state->notNullCount--;
+ 
+ 	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
+ 	 * The transition type for string_agg() is declared to be "internal",
+ 	 * which is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	if (removeFromStringAggState(state, valueLen))
+ 		PG_RETURN_POINTER(state);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
  	{
+ 		int			resultLen = state->string.len  - state->offset;
  		bytea	   *result;
  
! 		result = (bytea *) palloc(resultLen + VARHDRSZ);
! 		SET_VARSIZE(result, resultLen + VARHDRSZ);
! 		memcpy(VARDATA(result), state->string.data + state->offset, resultLen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3734,3802 ****
   * the associated value.
   */
  
! /* subroutine to initialize state */
! static StringInfo
! makeStringAggState(FunctionCallInfo fcinfo)
  {
! 	StringInfo	state;
! 	MemoryContext aggcontext;
! 	MemoryContext oldcontext;
  
! 	if (!AggCheckCallContext(fcinfo, &aggcontext))
  	{
! 		/* cannot be called directly because of internal-type argument */
! 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
  	}
  
  	/*
! 	 * Create state in aggregate context.  It'll stay there across subsequent
! 	 * calls.
  	 */
! 	oldcontext = MemoryContextSwitchTo(aggcontext);
! 	state = makeStringInfo();
! 	MemoryContextSwitchTo(oldcontext);
! 
! 	return state;
  }
  
  Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 			appendStringInfoText(state, PG_GETARG_TEXT_PP(2));	/* delimiter */
  
! 		appendStringInfoText(state, PG_GETARG_TEXT_PP(1));		/* value */
! 	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len));
  	else
  		PG_RETURN_NULL();
  }
--- 3936,4055 ----
   * the associated value.
   */
  
! Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	text		   *value,
! 				   *delim;
! 	int				delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
! 
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
! 
! 	value = PG_GETARG_TEXT_PP(1);
! 	state->notNullCount++;
! 
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
  	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_TEXT_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
  	}
+ 	else
+ 	{
+ 		/* Delimiter is NULL, treat as zero-length string */
+ 		delim = NULL;
+ 		delimLen = 0;
+ 	}
+ 
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value));
+ 	if (delim)
+ 		appendStringInfoText(&state->string, delim);
+ 	appendStringInfoText(&state->string, value);
  
  	/*
! 	 * The transition type for string_agg() is declared to be "internal",
! 	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
! string_agg_invtransfn(PG_FUNCTION_ARGS)
  {
! 	int				valueLen;
! 	StringAggState *state;
  
! 	/*
! 	 * Shouldn't happen, but we cannot mark this function strict, since it
! 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
! 	 * Must also prevent direct calls because of the "interal" argument
! 	 */
! 	if (PG_ARGISNULL(0))
! 		elog(ERROR, "string_agg_invtransfn called with NULL state");
! 	else if (!AggCheckCallContext(fcinfo, NULL))
! 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
  
! 	state = (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* We append nothing if the string is NULL, so skip here as well */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* No need to de-toast value, need only the length */
! 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
! 	Assert(state->notNullCount >= 1);
! 	state->notNullCount--;
  
  	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	if (removeFromStringAggState(state, valueLen))
! 		PG_RETURN_POINTER(state);
! 	else
! 		PG_RETURN_NULL();
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset,
! 												  state->string.len - state->offset));
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..403bb50 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-	-					0	
*** 250,262 ****
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
--- 250,262 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_invtransfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_invtransfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_invtransfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..efaa281 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 875,880 ****
--- 875,882 ----
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3585 (  array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2463,2474 ****
--- 2465,2480 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3586 (  string_agg_invtransfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3538 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into a string");
  DATA(insert OID = 3543 (  bytea_string_agg_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3587 (  bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3544 (  bytea_string_agg_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..9a6fc39 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 83,88 ****
--- 83,89 ----
  	Datum	   *dvalues;		/* array of accumulated Datums */
  	bool	   *dnulls;			/* array of is-null flags for Datums */
  	int			alen;			/* allocated length of above arrays */
+ 	int			offset;			/* offset of first element in above arrays */
  	int			nelems;			/* number of valid entries in above arrays */
  	Oid			element_type;	/* data type of the Datums */
  	int16		typlen;			/* needed info about datatype */
*************** extern ArrayBuildState *accumArrayResult
*** 255,260 ****
--- 256,262 ----
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
  				 MemoryContext rcontext);
+ extern void shiftArrayResult(ArrayBuildState *astate, int count);
  extern Datum makeArrayResult(ArrayBuildState *astate,
  				MemoryContext rcontext);
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
*************** extern ArrayType *create_singleton_array
*** 290,295 ****
--- 292,298 ----
  					   int ndims);
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
  
  /*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..173fa71 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 812,820 ****
--- 812,822 ----
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
  extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS);
  
  extern Datum text_concat(PG_FUNCTION_ARGS);
  extern Datum text_concat_ws(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 8af3d23..b33f241 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1305,1307 ****
--- 1305,1351 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
+      row     | str | str_del | str_vardel |      bin       |      bin_del       |      bin_vardel      |       ary        
+ -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------
+  1:1,0100,-  | -   | -       | -          | -              | -                  | -                    | 
+  2:-,0200,2  | 1   | 1       | 1          | \x0100         | \x0100             | \x0100               | {NULL}
+  3:3,----,3  | 1   | 1       | 1          | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2}
+  4:-,0400,4  | 13  | 1,3     | 13         | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2,3}
+  5:5,----,-  | 3   | 3       | 3          | \x02000400     | \x0200000400       | \x0200000400         | {2,3,4}
+  6:6,0600,6  | 35  | 3,5     | 3||5       | \x0400         | \x0400             | \x0400               | {3,4,NULL}
+  7:7,0700,-  | 56  | 5,6     | 56         | \x04000600     | \x0400000600       | \x04000600           | {4,NULL,6}
+  8:8,0800,8  | 567 | 5,6,7   | 56|7       | \x06000700     | \x0600000700       | \x0600000700         | {NULL,6,NULL}
+  9:-,----,-  | 678 | 6,7,8   | 6|7||8     | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8}
+  10:-,----,- | 78  | 7,8     | 7||8       | \x07000800     | \x0700000800       | \x070000000800       | {NULL,8,NULL}
+  11:-,----,- | 8   | 8       | 8          | \x0800         | \x0800             | \x0800               | {8,NULL,NULL}
+  12:-,----,- | -   | -       | -          | -              | -                  | -                    | {NULL,NULL,NULL}
+ (12 rows)
+ 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 117bd6c..9bb911c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 487,489 ****
--- 487,518 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ 
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
invtrans_strictstrict_docs_e918e4.patchapplication/octet-stream; name=invtrans_strictstrict_docs_e918e4.patch; x-unix-mode=0644Download
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index a12ee56..883fb50 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 381,386 ****
--- 381,392 ----
        <entry>Transition function</entry>
       </row>
       <row>
+       <entry><structfield>agginvtransfn</structfield></entry>
+       <entry><type>regproc</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>Inverse transition function</entry>
+      </row>
+      <row>
        <entry><structfield>aggfinalfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6e2fbda..59ce91e 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT xmlagg(x) FROM (SELECT x FROM tes
*** 13310,13315 ****
--- 13310,13437 ----
     <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
     Other frame specifications can be used to obtain other effects.
    </para>
+   
+   <para>
+     Depending on the aggregate function, aggregating over frames starting
+     at a row relative to the current row can be drastically less efficient
+     than aggregating over frames aligned to the start of the partition. The
+     frame starts at a row relative to the current row if <literal>ORDER
+     BY</literal> is used together with any frame start clause other than
+     <literal>UNBOUNDED PRECEDING</literal> (which is the default). Then,
+     aggregates without a suitable <quote>inverse transition function
+     </quote> (see <xref linkend="SQL-CREATEAGGREGATE"> for details) will be
+     computed for each frame from scratch, instead of re-using the previous
+     frame's result, causing <emphasis>quadratic growth</emphasis> of the
+     execution time as the number of rows per partition increases. The table
+     <xref linkend="functions-aggregate-indframe"> list the built-in aggregate
+     functions affected by this. Note that quadratic growth is only a problem
+     if partitions contain many rows - for partitions with only a few rows,
+     even inefficient aggregates are unlikely to cause problems.
+   </para>
+ 
+   <table id="functions-aggregate-indframe">
+    <title>
+      Aggregate Function Behaviour for frames not starting at
+      <literal>UNBOUNDED PRECEDING</literal>.
+    </title>
+ 
+    <tgroup cols="3">
+      
+     <thead>
+      <row>
+       <entry>Aggregate Function</entry>
+       <entry>Input Type</entry>
+       <entry>Computed From Scratch</entry>
+      </row>
+     </thead>
+     
+     <tbody>
+       
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>float4</type>
+        or
+        <type>float8</type>
+       </entry>
+       <entry>always, to avoid error accumulation</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>numeric</type>
+       </entry>
+       <entry>if the maximum number of decimal digits within the inputs changes</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>min</function>
+       </entry>
+       <entry>
+        any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were minimal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>max</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were maximal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>bit_and</function>,<function>bit_or</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>string_agg</function>
+       </entry>
+       <entry>
+        <type>text</type> or
+        <type>bytea</type> or
+       </entry>
+       <entry>if the delimiter lengths vary</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>xmlagg</function>
+       </entry>
+       <entry>
+        <type>xml</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>json_agg</function>
+       </entry>
+       <entry>
+        <type>json</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
  
    <note>
     <para>
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index e5fc718..9bdb2eb 100644
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
*************** CREATE AGGREGATE <replaceable class="par
*** 25,30 ****
--- 25,31 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVSFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 47,52 ****
--- 48,54 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVSFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 84,98 ****
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
!    a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
--- 86,103 ----
    </para>
  
    <para>
!    An aggregate function is made from one, two or three ordinary
     functions:
!    a (forward) state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
+    an optional inverse state transition function
+    <replaceable class="PARAMETER">invsfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
+ <replaceable class="PARAMETER">invsfunc</replaceable>( internal-state, data-values ) ---> internal-state-without-data-values
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 102,113 ****
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.  After all the rows have been processed,
!    the final function is invoked once to calculate the aggregate's return
!    value.  If there is no final function then the ending state value
!    is returned as-is.
    </para>
  
    <para>
--- 107,134 ----
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the forward state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.
!    If the aggregate is computed over a sliding frame, i.e. if it is used as a
!    <firstterm>window function</firstterm>, the inverse transition function is
!    used to undo the effect of a previous invocation of the forward transition
!    function once argument value(s) fall out of the sliding frame.
!    Conceptually, the forward transition functions thus adds some input
!    value(s) to the state, and the inverse transition functions removes them
!    again. Values are, if they are removed, always removed in the same order
!    they were added, without gaps. Whenever the inverse transition function is
!    invoked, it will thus receive the earliest added but not yet removed
!    argument value(s). If no inverse transition function is supplied, the
!    aggregate can still be used to aggregate over sliding frames, but with
!    reduced efficiency. <productname>PostgreSQL</productname> will then
!    recompute the whole aggregation whenever the start of the frame moves. To
!    calculate the aggregate's return value, the final function is invoked on
!    the ending state value. If there is no final function then ending state
!    value is returned as-is. Either way, the result is assumed to reflect the
!    aggregation of all values added but not yet removed from the state value.
!    Note that if the aggregate is used as a window function, the aggregation
!    may be continued after the final function has been called.
    </para>
  
    <para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 120,135 ****
    </para>
  
    <para>
!    If the state transition function is declared <quote>strict</quote>,
!    then it cannot be called with null inputs.  With such a transition
!    function, aggregate execution behaves as follows.  Rows with any null input
     values are ignored (the function is not called and the previous state value
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values.
!    This is handy for implementing aggregates like <function>max</function>.
!    Note that this behavior is only available when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
--- 141,164 ----
    </para>
  
    <para>
!    If the state transition functions are declared <quote>strict</quote>,
!    then it cannot be called with null inputs.  With such transition
!    functions, aggregate execution behaves as follows.  Rows with any null input
     values are ignored (the function is not called and the previous state value
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values. Should inputs later need to be removed again, the
!    inverse transition function (if present) is used as long as some non-null
!    inputs remain part of the state value. In particular, even if the initial
!    state value is null, the inverse transition function might be used to remove
!    the first non-null input, even though that input was never passed to the
!    forward transition function, but instead just replaced the initial state!
!    The last non-null input, however, is not removed by invoking the inverse
!    transition function, but instead the state is simply reset to its initial
!    value. This is handy for implementing aggregates like <function>max</function>.
!    Note that turning the first non-null input into the initial state is only
!    possible when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
*************** CREATE AGGREGATE <replaceable class="PAR
*** 138,147 ****
    </para>
  
    <para>
!    If the state transition function is not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs
!    and null state values for itself.  This allows the aggregate
!    author to have full control over the aggregate's handling of null values.
    </para>
  
    <para>
--- 167,192 ----
    </para>
  
    <para>
!    If the state transition functions are not <quote>strict</quote>, then they
!    will be called unconditionally at each input row, and must deal with null
!    inputs and null state values for itself. The same goes for the inverse
!    transition function. This allows the aggregate author to have full control
!    over the aggregate's handling of null argument values.
!   </para>
!   
!   <para>
!     The inverse transition function can signal by returning null that it is
!     unable to remove a particular input value from a particular state.
!     <productname>PostgreSQL</productname> will then act as if no inverse
!     transition function had been supplied, i.e. it will recompute the whole
!     aggregation, starting with the first argument value that it would not have
!     removed. That allows aggregates like <function>max</function> to still
!     avoid redoing the whole aggregation in <emphasis>some</emphasis> cases,
!     without paying the overhead of tracking enough state to be able to avoid
!     them in <emphasis>all</emphasis> cases. This demands, however, that
!     null isn't used as a valid state value, except as the initial state. If
!     an aggregate provides an inverse transition function, it is therefore an
!     error for the forward transition function to return null.
    </para>
  
    <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 271,277 ****
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
--- 316,322 ----
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the (forward) state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
*************** SELECT col FROM tab ORDER BY col USING s
*** 281,287 ****
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value.
       </para>
  
       <para>
--- 326,334 ----
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value. Note that if an inverse
!       transition function is present, the forward transition function must
!       not return <literal>NULL</>
       </para>
  
       <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 294,299 ****
--- 341,368 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="PARAMETER">invsfunc</replaceable></term>
+     <listitem>
+      <para>
+       The name of the inverse state transition function to be called for each
+       input row.  For a normal <replaceable class="PARAMETER">N</>-argument
+       aggregate function, the <replaceable class="PARAMETER">invsfunc</>
+       must take <replaceable class="PARAMETER">N</>+1 arguments,
+       the first being of type <replaceable
+       class="PARAMETER">state_data_type</replaceable> and the rest
+       matching the declared input data type(s) of the aggregate.
+       The function must return a value of type <replaceable
+       class="PARAMETER">state_data_type</replaceable>. These are the same
+       demands placed on the forward transition function, meaning that the
+       signatures of the two functions, including their
+       <quote>strictness</quote>, must be identical. The inverse transition
+       function may return <literal>NULL</> to force the aggregation to be
+       restarted from scratch.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="PARAMETER">state_data_type</replaceable></term>
      <listitem>
       <para>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index e77ef12..0c25b6f 100644
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 16,22 ****
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
--- 16,22 ----
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a forward state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
***************
*** 24,30 ****
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
--- 24,42 ----
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result. 
!    To enable efficient evaluation of an aggregate used as a window function
!    with a sliding frame (i.e. a frame that starts relative to the current row),
!    an aggregate can optionally provide an inverse state transition function.
!    The inverse transition function takes the the current state and the
!    aggregate's input value(s) for the <emphasis>earliest</emphasis> row passed
!    to the forward transition function, and returns a state equivalent to what
!    the current state had been had the forward transition function never been
!    invoked for that earliest row, only for all rows that followed it. Thus,
!    if an inverse transition function is provided, the rows that were part of
!    the previous row's frame but not of the current row's frame can simply be
!    removed from the state instead of having to redo the whole aggregation
!    over the new frame.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
*************** CREATE AGGREGATE avg (float8)
*** 132,137 ****
--- 144,188 ----
    </note>
  
    <para>
+    When providing an inverse transition function, care should be taken to
+    ensure that it doesn't introduce unexpected user-visible differences
+    between results obtained by reaggregating all inputs vs. using the inverse
+    transition function. An example for an aggregate where adding an inverse
+    transition function seems easy at first, yet were doing so would violate
+    this requirement is <function>sum</> over <type>float</> or
+    <type>double precision</>. A naive declaration of
+    <function>sum(<type>float</>)</function> could be
+    
+    <programlisting>
+    CREATE AGGREGATE unsafe_sum (float8)
+    (
+        stype = float8,
+        sfunc = float8pl,
+        invsfunc = float8mi
+    );
+    </programlisting>
+    
+    This aggregate, howevery, can give wildly different results than it would
+    have without the inverse transition function. For example, consider
+    
+    <programlisting>
+    SELECT
+      unsafe_sum(x) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND
+                                                  1 FOLLOWING)
+    FROM (VALUES
+      (1, 1.0e20::float8),
+      (2, 1.0::float8)
+    ) AS v (n,x)
+    </programlisting>
+    
+    which returns 0 as it's second result, yet the expected answer is 1. The
+    reason for this is the limited precision of floating point types - adding
+    1 to 1e20 actually leaves the value unchanged, and so substracting 1e20
+    again yields 0, not 1. Note that this is a limitation of floating point
+    types in general and not a limitation of <productname>PostgreSQL</>.
+   </para>
+   
+   <para>
     Aggregate functions can use polymorphic
     state transition functions or final functions, so that the same functions
     can be used to implement multiple aggregates.
invtrans_strictstrict_minmax_486bbc.patchapplication/octet-stream; name=invtrans_strictstrict_minmax_486bbc.patch; x-unix-mode=0644Download
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..4a4ca5d 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_larger(PG_FUNCTION_ARGS)
*** 4714,4719 ****
--- 4714,4737 ----
  }
  
  Datum
+ array_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) > 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ 
+ Datum
  array_smaller(PG_FUNCTION_ARGS)
  {
  	ArrayType  *v1,
*************** array_smaller(PG_FUNCTION_ARGS)
*** 4728,4733 ****
--- 4746,4767 ----
  	PG_RETURN_ARRAYTYPE_P(result);
  }
  
+ Datum
+ array_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) < 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
  
  typedef struct generate_subscripts_fctx
  {
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index d419b4a..593460d 100644
*** a/src/backend/utils/adt/bool.c
--- b/src/backend/utils/adt/bool.c
*************** boolge(PG_FUNCTION_ARGS)
*** 285,290 ****
--- 285,292 ----
  
  /* function for standard EVERY aggregate implementation conforming to SQL 2003.
   * must be strict. It is also named bool_and for homogeneity.
+  * Note: this is no longer used for the bool_and() and every() aggregate
+  * functions.
   */
  Datum
  booland_statefunc(PG_FUNCTION_ARGS)
*************** booland_statefunc(PG_FUNCTION_ARGS)
*** 294,302 ****
--- 296,400 ----
  
  /* function for standard ANY/SOME aggregate conforming to SQL 2003.
   * must be strict. The name of the aggregate is bool_or. See the doc.
+  * Note: this is no longer used for the bool_or aggregate function.
   */
  Datum
  boolor_statefunc(PG_FUNCTION_ARGS)
  {
  	PG_RETURN_BOOL(PG_GETARG_BOOL(0) || PG_GETARG_BOOL(1));
  }
+ 
+ typedef struct BoolAggState
+ {
+ 	int64 aggcount; /* number of values aggregated */
+ 	int64 aggtrue; /* number of values aggregated which are true */
+ } BoolAggState;
+ 
+ static BoolAggState *
+ makeBoolAggState(FunctionCallInfo fcinfo)
+ {
+ 	BoolAggState *state;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 	state = (BoolAggState *) palloc(sizeof(BoolAggState));
+ 	state->aggcount = 0;
+ 	state->aggtrue = 0;
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 
+ 	return state;
+ }
+ 
+ Datum
+ bool_accum(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* Create the state data on first call */
+ 	if (state == NULL)
+ 		state = makeBoolAggState(fcinfo);
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount++;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue++;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* bool_accum should have created the state data */
+ 	if (state == NULL)
+ 		elog(ERROR, "bool_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount--;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue--;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_alltrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if all values are true */
+ 	PG_RETURN_BOOL(state->aggcount == state->aggtrue);
+ }
+ 
+ Datum
+ bool_anytrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if any value is true */
+ 	PG_RETURN_BOOL(state->aggtrue > 0);
+ }
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 0158758..cba17a3 100644
*** a/src/backend/utils/adt/cash.c
--- b/src/backend/utils/adt/cash.c
*************** cashlarger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,896 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 > c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cashsmaller()
   * Return smaller of two cash values.
   */
*************** cashsmaller(PG_FUNCTION_ARGS)
*** 892,897 ****
--- 906,925 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 < c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cash_words()
   * This converts a int4 as well but to a representation using words
   * Obviously way North American centric - sorry
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 06cc0cd..0cca0b0 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** date_larger(PG_FUNCTION_ARGS)
*** 396,401 ****
--- 396,415 ----
  }
  
  Datum
+ date_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 > dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  date_smaller(PG_FUNCTION_ARGS)
  {
  	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
*************** date_smaller(PG_FUNCTION_ARGS)
*** 404,409 ****
--- 418,437 ----
  	PG_RETURN_DATEADT((dateVal1 < dateVal2) ? dateVal1 : dateVal2);
  }
  
+ Datum
+ date_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 < dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* Compute difference between two dates in days.
   */
  Datum
*************** time_larger(PG_FUNCTION_ARGS)
*** 1463,1468 ****
--- 1491,1510 ----
  }
  
  Datum
+ time_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 > time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  time_smaller(PG_FUNCTION_ARGS)
  {
  	TimeADT		time1 = PG_GETARG_TIMEADT(0);
*************** time_smaller(PG_FUNCTION_ARGS)
*** 1471,1476 ****
--- 1513,1532 ----
  	PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
  }
  
+ Datum
+ time_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 < time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* overlaps_time() --- implements the SQL OVERLAPS operator.
   *
   * Algorithm is per SQL spec.  This is much harder than you'd think
*************** timetz_larger(PG_FUNCTION_ARGS)
*** 2262,2267 ****
--- 2318,2337 ----
  }
  
  Datum
+ timetz_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) > 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  timetz_smaller(PG_FUNCTION_ARGS)
  {
  	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
*************** timetz_smaller(PG_FUNCTION_ARGS)
*** 2275,2280 ****
--- 2345,2364 ----
  	PG_RETURN_TIMETZADT_P(result);
  }
  
+ Datum
+ timetz_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) < 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* timetz_pl_interval()
   * Add interval to timetz.
   */
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 83c3878..32035e0 100644
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
*************** enum_smaller(PG_FUNCTION_ARGS)
*** 273,278 ****
--- 273,292 ----
  }
  
  Datum
+ enum_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) < 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_larger(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
*************** enum_larger(PG_FUNCTION_ARGS)
*** 282,287 ****
--- 296,315 ----
  }
  
  Datum
+ enum_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) > 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_cmp(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 774267e..04f89b0 100644
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4larger(PG_FUNCTION_ARGS)
*** 637,642 ****
--- 637,658 ----
  }
  
  Datum
+ float4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float4smaller(PG_FUNCTION_ARGS)
  {
  	float4		arg1 = PG_GETARG_FLOAT4(0);
*************** float4smaller(PG_FUNCTION_ARGS)
*** 650,655 ****
--- 666,687 ----
  	PG_RETURN_FLOAT4(result);
  }
  
+ Datum
+ float4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  /*
   *		======================
   *		FLOAT8 BASE OPERATIONS
*************** float8larger(PG_FUNCTION_ARGS)
*** 704,709 ****
--- 736,757 ----
  }
  
  Datum
+ float8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float8smaller(PG_FUNCTION_ARGS)
  {
  	float8		arg1 = PG_GETARG_FLOAT8(0);
*************** float8smaller(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 765,785 ----
  	PG_RETURN_FLOAT8(result);
  }
  
+ Datum
+ float8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *		====================
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 669355e..fe9d736 100644
*** a/src/backend/utils/adt/int.c
--- b/src/backend/utils/adt/int.c
*************** int2larger(PG_FUNCTION_ARGS)
*** 1185,1190 ****
--- 1185,1204 ----
  }
  
  Datum
+ int2larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int2smaller(PG_FUNCTION_ARGS)
  {
  	int16		arg1 = PG_GETARG_INT16(0);
*************** int2smaller(PG_FUNCTION_ARGS)
*** 1194,1199 ****
--- 1208,1227 ----
  }
  
  Datum
+ int2smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4larger(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4larger(PG_FUNCTION_ARGS)
*** 1203,1208 ****
--- 1231,1250 ----
  }
  
  Datum
+ int4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4smaller(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4smaller(PG_FUNCTION_ARGS)
*** 1211,1216 ****
--- 1253,1272 ----
  	PG_RETURN_INT32((arg1 < arg2) ? arg1 : arg2);
  }
  
+ Datum
+ int4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /*
   * Bit-pushing operators
   *
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..820be68 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8larger(PG_FUNCTION_ARGS)
*** 752,757 ****
--- 752,771 ----
  }
  
  Datum
+ int8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int8smaller(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
*************** int8smaller(PG_FUNCTION_ARGS)
*** 764,769 ****
--- 778,797 ----
  }
  
  Datum
+ int8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int84pl(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 64eb0f8..c9500bf 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_smaller(PG_FUNCTION_ARGS)
*** 1872,1877 ****
--- 1872,1892 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) < 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * numeric_larger() -
*************** numeric_larger(PG_FUNCTION_ARGS)
*** 1894,1899 ****
--- 1909,1930 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) > 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /* ----------------------------------------------------------------------
   *
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 8945ef4..745449a 100644
*** a/src/backend/utils/adt/oid.c
--- b/src/backend/utils/adt/oid.c
*************** oidlarger(PG_FUNCTION_ARGS)
*** 397,402 ****
--- 397,416 ----
  }
  
  Datum
+ oidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidsmaller(PG_FUNCTION_ARGS)
  {
  	Oid			arg1 = PG_GETARG_OID(0);
*************** oidsmaller(PG_FUNCTION_ARGS)
*** 406,411 ****
--- 420,439 ----
  }
  
  Datum
+ oidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidvectoreq(PG_FUNCTION_ARGS)
  {
  	int32		cmp = DatumGetInt32(btoidvectorcmp(fcinfo));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 6e2bbdc..57fcf80 100644
*** a/src/backend/utils/adt/tid.c
--- b/src/backend/utils/adt/tid.c
*************** tidlarger(PG_FUNCTION_ARGS)
*** 237,242 ****
--- 237,256 ----
  }
  
  Datum
+ tidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) > 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  tidsmaller(PG_FUNCTION_ARGS)
  {
  	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
*************** tidsmaller(PG_FUNCTION_ARGS)
*** 245,250 ****
--- 259,277 ----
  	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
  }
  
+ Datum
+ tidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) < 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *	Functions to get latest tid of a specified tuple.
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ce30bb6..e611983 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** timestamp_smaller(PG_FUNCTION_ARGS)
*** 2682,2687 ****
--- 2682,2701 ----
  }
  
  Datum
+ timestamp_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) < 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  timestamp_larger(PG_FUNCTION_ARGS)
  {
  	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
*************** timestamp_larger(PG_FUNCTION_ARGS)
*** 2695,2700 ****
--- 2709,2727 ----
  	PG_RETURN_TIMESTAMP(result);
  }
  
+ Datum
+ timestamp_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) > 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
  
  Datum
  timestamp_mi(PG_FUNCTION_ARGS)
*************** interval_smaller(PG_FUNCTION_ARGS)
*** 3151,3156 ****
--- 3178,3197 ----
  }
  
  Datum
+ interval_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) < 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_larger(PG_FUNCTION_ARGS)
  {
  	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
*************** interval_larger(PG_FUNCTION_ARGS)
*** 3165,3170 ****
--- 3206,3225 ----
  }
  
  Datum
+ interval_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) > 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_pl(PG_FUNCTION_ARGS)
  {
  	Interval   *span1 = PG_GETARG_INTERVAL_P(0);
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 502ca44..536ca66 100644
*** a/src/backend/utils/adt/varchar.c
--- b/src/backend/utils/adt/varchar.c
*************** bpchar_larger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,905 ----
  }
  
  Datum
+ bpchar_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp > 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  bpchar_smaller(PG_FUNCTION_ARGS)
  {
  	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
*************** bpchar_smaller(PG_FUNCTION_ARGS)
*** 894,899 ****
--- 917,945 ----
  	PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2);
  }
  
+ Datum
+ bpchar_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp < 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /*
   * bpchar needs a specialized hash function because we want to ignore
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index cb07a06..821cded 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** text_larger(PG_FUNCTION_ARGS)
*** 1697,1702 ****
--- 1697,1716 ----
  }
  
  Datum
+ text_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  text_smaller(PG_FUNCTION_ARGS)
  {
  	text	   *arg1 = PG_GETARG_TEXT_PP(0);
*************** text_smaller(PG_FUNCTION_ARGS)
*** 1708,1713 ****
--- 1722,1740 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ Datum
+ text_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * The following operators support character-by-character comparison
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..7d35559 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2113	n 0 interval_pl		-	-	
*** 122,169 ****
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
--- 122,169 ----
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger			int8larger_inv			-		413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger			int4larger_inv			-		521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger			int2larger_inv			-		520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger			oidlarger_inv			-		610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger		float4larger_inv		-		623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger		float8larger_inv		-		674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger			int4larger_inv			-		563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger			date_larger_inv			-		1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger			time_larger_inv			-		1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger		timetz_larger_inv		-		1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger			cashlarger_inv			-		903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	timestamp_larger_inv	-		2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	timestamptz_larger_inv	-		1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger		interval_larger_inv		-		1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger			text_larger_inv			-		666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger		numeric_larger_inv		-		1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger		array_larger_inv		-		1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger		bpchar_larger_inv		-		1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger			tidlarger_inv			-		2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger			enum_larger_inv			-		3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller			int8smaller_inv			-		412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller			int4smaller_inv			-		97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller			int2smaller_inv			-		95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller			oidsmaller_inv			-		609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller		float4smaller_inv		-		622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller		float8smaller_inv		-		672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller			int4smaller_inv			-		562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller		date_smaller_inv		-		1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller		time_smaller_inv		-		1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller		timetz_smaller_inv		-		1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller			cashsmaller_inv			-		902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	timestamp_smaller_inv	-		2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller timestamptz_smaller_inv	-		1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	interval_smaller_inv	-		1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller		text_smaller_inv		-		664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller		numeric_smaller_inv		-		1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller		array_smaller_inv		-		1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller		bpchar_smaller_inv		-		1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller			tidsmaller_inv			-		2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller		enum_smaller_inv		-		3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
*************** DATA(insert ( 2828	n 0 float8_regr_accum
*** 232,240 ****
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
--- 232,240 ----
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
! DATA(insert ( 2518	n 0 bool_accum		bool_accum_inv		bool_anytrue	59	2281		16	_null_ ));
! DATA(insert ( 2519	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..aff94b6 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("larger of two");
*** 367,372 ****
--- 367,377 ----
  DATA(insert OID = 211 (  float4smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3567 (  float4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3568 (  float4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 212 (  int4um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "23" _null_ _null_ _null_ _null_ int4um _null_ _null_ _null_ ));
  DATA(insert OID = 213 (  int2um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "21" _null_ _null_ _null_ _null_ int2um _null_ _null_ _null_ ));
  
*************** DATA(insert OID = 223 (  float8larger	  
*** 386,391 ****
--- 391,400 ----
  DESCR("larger of two");
  DATA(insert OID = 224 (  float8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3569 (  float8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3570 (  float8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 225 (  lseg_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "601" _null_ _null_ _null_ _null_	lseg_center _null_ _null_ _null_ ));
  DATA(insert OID = 226 (  path_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "602" _null_ _null_ _null_ _null_	path_center _null_ _null_ _null_ ));
*************** DATA(insert OID = 458 (  text_larger	   
*** 711,716 ****
--- 720,729 ----
  DESCR("larger of two");
  DATA(insert OID = 459 (  text_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3571 (  text_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3572 (  text_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 460 (  int8in			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "2275" _null_ _null_ _null_ _null_ int8in _null_ _null_ _null_ ));
  DESCR("I/O");
*************** DATA(insert OID = 515 (  array_larger	  
*** 859,864 ****
--- 872,881 ----
  DESCR("larger of two");
  DATA(insert OID = 516 (  array_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3573 (  array_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3574 (  array_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1191 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 23 "2277 23 16" _null_ _null_ _null_ _null_ generate_subscripts _null_ _null_ _null_ ));
  DESCR("array subscripts generator");
  DATA(insert OID = 1192 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 23 "2277 23" _null_ _null_ _null_ _null_ generate_subscripts_nodir _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 913,918 ****
--- 930,945 ----
  DATA(insert OID = 771 (  int2smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3575 (  int4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3576 (  int4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3577 (  int2larger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3578 (  int2smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 774 (  gistgettuple	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_	gistgettuple _null_ _null_ _null_ ));
  DESCR("gist(internal)");
  DATA(insert OID = 638 (  gistgetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	gistgetbitmap _null_ _null_ _null_ ));
*************** DATA(insert OID =  898 (  cashlarger	   
*** 1003,1008 ****
--- 1030,1039 ----
  DESCR("larger of two");
  DATA(insert OID =  899 (  cashsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID =  3579 (  cashlarger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID =  3580 (  cashsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID =  919 (  flt8_mul_cash    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "701 790" _null_ _null_ _null_ _null_	flt8_mul_cash _null_ _null_ _null_ ));
  DATA(insert OID =  935 (  cash_words	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "790" _null_ _null_ _null_ _null_	cash_words _null_ _null_ _null_ ));
  DESCR("output money amount as words");
*************** DATA(insert OID = 1063 (  bpchar_larger 
*** 1157,1162 ****
--- 1188,1197 ----
  DESCR("larger of two");
  DATA(insert OID = 1064 (  bpchar_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3581 (  bpchar_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3582 (  bpchar_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1078 (  bpcharcmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1042 1042" _null_ _null_ _null_ _null_ bpcharcmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1080 (  hashbpchar	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "1042" _null_ _null_ _null_ _null_	hashbpchar _null_ _null_ _null_ ));
*************** DATA(insert OID = 1138 (  date_larger	  
*** 1191,1196 ****
--- 1226,1235 ----
  DESCR("larger of two");
  DATA(insert OID = 1139 (  date_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3583 (  date_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3584 (  date_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1140 (  date_mi		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1082 1082" _null_ _null_ _null_ _null_ date_mi _null_ _null_ _null_ ));
  DATA(insert OID = 1141 (  date_pli		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_pli _null_ _null_ _null_ ));
  DATA(insert OID = 1142 (  date_mii		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_mii _null_ _null_ _null_ ));
*************** DATA(insert OID = 1195 (  timestamptz_sm
*** 1281,1290 ****
--- 1320,1339 ----
  DESCR("smaller of two");
  DATA(insert OID = 1196 (  timestamptz_larger  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4051 (  timestamptz_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4052 (  timestamptz_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1197 (  interval_smaller	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  DATA(insert OID = 1198 (  interval_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4053 (  interval_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4054 (  interval_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1199 (  age				PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_	timestamptz_age _null_ _null_ _null_ ));
  DESCR("date difference preserving months and years");
  
*************** DESCR("larger of two");
*** 1318,1323 ****
--- 1367,1378 ----
  DATA(insert OID = 1237 (  int8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4055 (  int8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4056 (  int8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1238 (  texticregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexeq _null_ _null_ _null_ ));
  DATA(insert OID = 1239 (  texticregexne    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexne _null_ _null_ _null_ ));
  DATA(insert OID = 1240 (  nameicregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "19 25" _null_ _null_ _null_ _null_ nameicregexeq _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1374,1379 ****
--- 1429,1439 ----
  DATA(insert OID = 2796 ( tidsmaller		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4057 ( tidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4058 ( tidsmaller_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1296 (  timedate_pl	   PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1114 "1083 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
  DATA(insert OID = 1297 (  datetimetz_pl    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1082 1266" _null_ _null_ _null_ _null_ datetimetz_timestamptz _null_ _null_ _null_ ));
  DATA(insert OID = 1298 (  timetzdate_pl    PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1184 "1266 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1515,1520 ****
--- 1575,1590 ----
  DATA(insert OID = 1380 (  timetz_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4059 (  time_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4060 (  time_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4061 (  timetz_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4062 (  timetz_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1381 (  char_length	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "25" _null_ _null_ _null_ _null_ textlen _null_ _null_ _null_ ));
  DESCR("character length");
  
*************** DATA(insert OID = 1766 ( numeric_smaller
*** 2277,2282 ****
--- 2347,2357 ----
  DESCR("smaller of two");
  DATA(insert OID = 1767 ( numeric_larger			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4063 ( numeric_smaller_inv		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4064 ( numeric_larger_inv			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1769 ( numeric_cmp			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1700 1700" _null_ _null_ _null_ _null_ numeric_cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1771 ( numeric_uminus			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ numeric_uminus _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 2803,2808 ****
--- 2878,2888 ----
  DATA(insert OID = 1966 (  oidsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4065 (  oidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4066 (  oidsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 timestamp_transform f f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
  DESCR("adjust timestamptz precision");
  DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 time_transform f f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
*************** DATA(insert OID = 2035 (  timestamp_smal
*** 2864,2869 ****
--- 2944,2955 ----
  DESCR("smaller of two");
  DATA(insert OID = 2036 (  timestamp_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4067 (  timestamp_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4068 (  timestamp_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 2037 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 1266 "25 1266" _null_ _null_ _null_ _null_ timetz_zone _null_ _null_ _null_ ));
  DESCR("adjust time with time zone to new zone");
  DATA(insert OID = 2038 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1186 1266" _null_ _null_ _null_ _null_	timetz_izone _null_ _null_ _null_ ));
*************** DATA(insert OID = 939  (  generate_serie
*** 3860,3869 ****
--- 3946,3957 ----
  DESCR("non-persistent series generator");
  
  /* boolean aggregates */
+ /* previous aggregate transition functions, unused now */
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ /* aggregates and new invertible transition functions */
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
*************** DATA(insert OID = 2518 ( bool_or					   
*** 3871,3876 ****
--- 3959,3972 ----
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
+ DATA(insert OID = 4069 ( bool_accum					   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 4070 ( bool_accum_inv				   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4071 ( bool_alltrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_alltrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
+ DATA(insert OID = 4072 ( bool_anytrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_anytrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DATA(insert OID = 3524 (  enum_smaller	P
*** 4254,4259 ****
--- 4350,4360 ----
  DESCR("smaller of two");
  DATA(insert OID = 3525 (  enum_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4073 (  enum_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4074 (  enum_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 3526 (  max			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("maximum value of all enum input values");
  DATA(insert OID = 3527 (  min			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..43e4973 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern Datum array_upper(PG_FUNCTION_ARG
*** 206,212 ****
--- 206,214 ----
  extern Datum array_length(PG_FUNCTION_ARGS);
  extern Datum array_cardinality(PG_FUNCTION_ARGS);
  extern Datum array_larger(PG_FUNCTION_ARGS);
+ extern Datum array_larger_inv(PG_FUNCTION_ARGS);
  extern Datum array_smaller(PG_FUNCTION_ARGS);
+ extern Datum array_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
  extern Datum array_fill(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..5e74e59 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum boolle(PG_FUNCTION_ARGS);
*** 121,126 ****
--- 121,130 ----
  extern Datum boolge(PG_FUNCTION_ARGS);
  extern Datum booland_statefunc(PG_FUNCTION_ARGS);
  extern Datum boolor_statefunc(PG_FUNCTION_ARGS);
+ extern Datum bool_accum(PG_FUNCTION_ARGS);
+ extern Datum bool_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum bool_alltrue(PG_FUNCTION_ARGS);
+ extern Datum bool_anytrue(PG_FUNCTION_ARGS);
  extern bool parse_bool(const char *value, bool *result);
  extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
  
*************** extern Datum enum_ge(PG_FUNCTION_ARGS);
*** 167,173 ****
--- 171,179 ----
  extern Datum enum_gt(PG_FUNCTION_ARGS);
  extern Datum enum_cmp(PG_FUNCTION_ARGS);
  extern Datum enum_smaller(PG_FUNCTION_ARGS);
+ extern Datum enum_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum enum_larger(PG_FUNCTION_ARGS);
+ extern Datum enum_larger_inv(PG_FUNCTION_ARGS);
  extern Datum enum_first(PG_FUNCTION_ARGS);
  extern Datum enum_last(PG_FUNCTION_ARGS);
  extern Datum enum_range_bounds(PG_FUNCTION_ARGS);
*************** extern Datum int42div(PG_FUNCTION_ARGS);
*** 241,249 ****
--- 247,259 ----
  extern Datum int4mod(PG_FUNCTION_ARGS);
  extern Datum int2mod(PG_FUNCTION_ARGS);
  extern Datum int2larger(PG_FUNCTION_ARGS);
+ extern Datum int2larger_inv(PG_FUNCTION_ARGS);
  extern Datum int2smaller(PG_FUNCTION_ARGS);
+ extern Datum int2smaller_inv(PG_FUNCTION_ARGS);
  extern Datum int4larger(PG_FUNCTION_ARGS);
+ extern Datum int4larger_inv(PG_FUNCTION_ARGS);
  extern Datum int4smaller(PG_FUNCTION_ARGS);
+ extern Datum int4smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int4and(PG_FUNCTION_ARGS);
  extern Datum int4or(PG_FUNCTION_ARGS);
*************** extern Datum float4abs(PG_FUNCTION_ARGS)
*** 347,358 ****
--- 357,372 ----
  extern Datum float4um(PG_FUNCTION_ARGS);
  extern Datum float4up(PG_FUNCTION_ARGS);
  extern Datum float4larger(PG_FUNCTION_ARGS);
+ extern Datum float4larger_inv(PG_FUNCTION_ARGS);
  extern Datum float4smaller(PG_FUNCTION_ARGS);
+ extern Datum float4smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float8abs(PG_FUNCTION_ARGS);
  extern Datum float8um(PG_FUNCTION_ARGS);
  extern Datum float8up(PG_FUNCTION_ARGS);
  extern Datum float8larger(PG_FUNCTION_ARGS);
+ extern Datum float8larger_inv(PG_FUNCTION_ARGS);
  extern Datum float8smaller(PG_FUNCTION_ARGS);
+ extern Datum float8smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float4pl(PG_FUNCTION_ARGS);
  extern Datum float4mi(PG_FUNCTION_ARGS);
  extern Datum float4mul(PG_FUNCTION_ARGS);
*************** extern Datum oidle(PG_FUNCTION_ARGS);
*** 501,507 ****
--- 515,523 ----
  extern Datum oidge(PG_FUNCTION_ARGS);
  extern Datum oidgt(PG_FUNCTION_ARGS);
  extern Datum oidlarger(PG_FUNCTION_ARGS);
+ extern Datum oidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum oidsmaller(PG_FUNCTION_ARGS);
+ extern Datum oidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum oidvectorin(PG_FUNCTION_ARGS);
  extern Datum oidvectorout(PG_FUNCTION_ARGS);
  extern Datum oidvectorrecv(PG_FUNCTION_ARGS);
*************** extern Datum tidgt(PG_FUNCTION_ARGS);
*** 707,713 ****
--- 723,731 ----
  extern Datum tidge(PG_FUNCTION_ARGS);
  extern Datum bttidcmp(PG_FUNCTION_ARGS);
  extern Datum tidlarger(PG_FUNCTION_ARGS);
+ extern Datum tidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum tidsmaller(PG_FUNCTION_ARGS);
+ extern Datum tidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum currtid_byreloid(PG_FUNCTION_ARGS);
  extern Datum currtid_byrelname(PG_FUNCTION_ARGS);
  
*************** extern Datum bpchargt(PG_FUNCTION_ARGS);
*** 730,736 ****
--- 748,756 ----
  extern Datum bpcharge(PG_FUNCTION_ARGS);
  extern Datum bpcharcmp(PG_FUNCTION_ARGS);
  extern Datum bpchar_larger(PG_FUNCTION_ARGS);
+ extern Datum bpchar_larger_inv(PG_FUNCTION_ARGS);
  extern Datum bpchar_smaller(PG_FUNCTION_ARGS);
+ extern Datum bpchar_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum bpcharlen(PG_FUNCTION_ARGS);
  extern Datum bpcharoctetlen(PG_FUNCTION_ARGS);
  extern Datum hashbpchar(PG_FUNCTION_ARGS);
*************** extern Datum text_le(PG_FUNCTION_ARGS);
*** 770,776 ****
--- 790,798 ----
  extern Datum text_gt(PG_FUNCTION_ARGS);
  extern Datum text_ge(PG_FUNCTION_ARGS);
  extern Datum text_larger(PG_FUNCTION_ARGS);
+ extern Datum text_larger_inv(PG_FUNCTION_ARGS);
  extern Datum text_smaller(PG_FUNCTION_ARGS);
+ extern Datum text_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum text_pattern_lt(PG_FUNCTION_ARGS);
  extern Datum text_pattern_le(PG_FUNCTION_ARGS);
  extern Datum text_pattern_gt(PG_FUNCTION_ARGS);
*************** extern Datum numeric_div_trunc(PG_FUNCTI
*** 980,986 ****
--- 1002,1010 ----
  extern Datum numeric_mod(PG_FUNCTION_ARGS);
  extern Datum numeric_inc(PG_FUNCTION_ARGS);
  extern Datum numeric_smaller(PG_FUNCTION_ARGS);
+ extern Datum numeric_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_larger(PG_FUNCTION_ARGS);
+ extern Datum numeric_larger_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_fac(PG_FUNCTION_ARGS);
  extern Datum numeric_sqrt(PG_FUNCTION_ARGS);
  extern Datum numeric_exp(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/cash.h b/src/include/utils/cash.h
index 3a491f9..4e184cc 100644
*** a/src/include/utils/cash.h
--- b/src/include/utils/cash.h
*************** extern Datum int2_mul_cash(PG_FUNCTION_A
*** 60,66 ****
--- 60,68 ----
  extern Datum cash_div_int2(PG_FUNCTION_ARGS);
  
  extern Datum cashlarger(PG_FUNCTION_ARGS);
+ extern Datum cashlarger_inv(PG_FUNCTION_ARGS);
  extern Datum cashsmaller(PG_FUNCTION_ARGS);
+ extern Datum cashsmaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum cash_words(PG_FUNCTION_ARGS);
  
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 622aa19..9f459ac 100644
*** a/src/include/utils/date.h
--- b/src/include/utils/date.h
*************** extern Datum date_cmp(PG_FUNCTION_ARGS);
*** 108,114 ****
--- 108,116 ----
  extern Datum date_sortsupport(PG_FUNCTION_ARGS);
  extern Datum date_finite(PG_FUNCTION_ARGS);
  extern Datum date_larger(PG_FUNCTION_ARGS);
+ extern Datum date_larger_inv(PG_FUNCTION_ARGS);
  extern Datum date_smaller(PG_FUNCTION_ARGS);
+ extern Datum date_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum date_mi(PG_FUNCTION_ARGS);
  extern Datum date_pli(PG_FUNCTION_ARGS);
  extern Datum date_mii(PG_FUNCTION_ARGS);
*************** extern Datum time_cmp(PG_FUNCTION_ARGS);
*** 168,174 ****
--- 170,178 ----
  extern Datum time_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_time(PG_FUNCTION_ARGS);
  extern Datum time_larger(PG_FUNCTION_ARGS);
+ extern Datum time_larger_inv(PG_FUNCTION_ARGS);
  extern Datum time_smaller(PG_FUNCTION_ARGS);
+ extern Datum time_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum time_mi_time(PG_FUNCTION_ARGS);
  extern Datum timestamp_time(PG_FUNCTION_ARGS);
  extern Datum timestamptz_time(PG_FUNCTION_ARGS);
*************** extern Datum timetz_cmp(PG_FUNCTION_ARGS
*** 195,201 ****
--- 199,207 ----
  extern Datum timetz_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_timetz(PG_FUNCTION_ARGS);
  extern Datum timetz_larger(PG_FUNCTION_ARGS);
+ extern Datum timetz_larger_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_smaller(PG_FUNCTION_ARGS);
+ extern Datum timetz_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_time(PG_FUNCTION_ARGS);
  extern Datum time_timetz(PG_FUNCTION_ARGS);
  extern Datum timestamptz_timetz(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..d102ccb 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8inc(PG_FUNCTION_ARGS);
*** 77,83 ****
--- 77,85 ----
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
+ extern Datum int8larger_inv(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
+ extern Datum int8smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int8and(PG_FUNCTION_ARGS);
  extern Datum int8or(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 2731c6a..4f6673c 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum timestamp_cmp(PG_FUNCTION_A
*** 111,117 ****
--- 111,119 ----
  extern Datum timestamp_sortsupport(PG_FUNCTION_ARGS);
  extern Datum timestamp_hash(PG_FUNCTION_ARGS);
  extern Datum timestamp_smaller(PG_FUNCTION_ARGS);
+ extern Datum timestamp_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timestamp_larger(PG_FUNCTION_ARGS);
+ extern Datum timestamp_larger_inv(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS);
  extern Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS);
*************** extern Datum interval_finite(PG_FUNCTION
*** 151,157 ****
--- 153,161 ----
  extern Datum interval_cmp(PG_FUNCTION_ARGS);
  extern Datum interval_hash(PG_FUNCTION_ARGS);
  extern Datum interval_smaller(PG_FUNCTION_ARGS);
+ extern Datum interval_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum interval_larger(PG_FUNCTION_ARGS);
+ extern Datum interval_larger_inv(PG_FUNCTION_ARGS);
  extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
  extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
  extern Datum interval_justify_days(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 8af3d23..4399f40 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1300,1305 ****
--- 1300,1410 ----
  -- Test the MIN, MAX and boolean inverse transition functions
  --
  --
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  40 |  30
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  20 |  20
+     |  40 |  40
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  30 |  30
+  40 |     |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |  20 |  10
+     |  10 |  10
+  10 |  10 |  10
+ (5 rows)
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |     |  10
+     |     |  10
+  10 |     |  10
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  |  p  | max | min 
+ ----+-----+-----+-----
+  ad | 100 | ae  | ab
+  ab | 100 | ae  | ab
+  ae | 100 | ae  | ae
+  ad | 200 | ad  | aa
+  aa | 200 | aa  | aa
+ (5 rows)
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  b | bool_and | bool_or 
+ ---+----------+---------
+  t | t        | t
+  t | f        | t
+  f | f        | f
+  f | f        | t
+  t | t        | t
+ (5 rows)
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 117bd6c..41065c9 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 482,487 ****
--- 482,523 ----
  --
  --
  
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
#169Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#168)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 1 April 2014 20:58, Florian Pflug <fgp@phlo.org> wrote:

On Apr1, 2014, at 10:08 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 31 March 2014 01:58, Florian Pflug <fgp@phlo.org> wrote:

Attached are updated patches that include the EXPLAIN changes mentioned
above and updated docs.

These patches need re-basing --- they no longer apply to HEAD.

Rebased to head (554bb3beba27bf4a49edecc40f6c0f249974bc7c). I had to
re-assign OIDs in the dependent paches again (those which actually
add the various inverse transition functions).

Looking at the new explain output, that is not exactly what I was
suggesting upthread. In particular, in cases where the WindowAgg node
is executed more than once, I think that the reported transition
counts should be the averages per-execution of the node. That way the
number of transition function calls reported for the node is directly
comparable with its "rows" value. Thus I think the output should be
divided by nloops, which would be more consistent with the way other
similar values are reported in explain output (c.f.
show_instrumentation_count).

I started looking at the "arith" patch, and I got as far as looking at
the changes to count(*) and count(val), which seem reasonable. But
then I started testing performance, and I found cases where the
improvement is not nearly what I expected.

The example cited at the start of this thread is indeed orders of
magnitude faster than HEAD:

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM generate_series(1,20000) g(n);

This executes in 20ms on my box, vs 30sec on HEAD, which reflects the
change from an O(n^2) to an O(n) algorithm.

However, if both ends of the frame move, the benefits are far less impressive:

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND 10 FOLLOWING)
FROM generate_series(1,20000) g(n);
-- 33ms

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND 100 FOLLOWING)
FROM generate_series(1,20000) g(n);
-- 173ms

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND 1000 FOLLOWING)
FROM generate_series(1,20000) g(n);
-- 1467ms

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND 10000 FOLLOWING)
FROM generate_series(1,20000) g(n);
-- 7709ms

This is still exhibiting the behaviour of an O(n^2) algorithm.

The problem lies in window_gettupleslot() which steps one row at a
time from the last position to the new required position. So if both
ends of the frame are moving, it needs to step forwards and backwards
through the entire window, for each row processed - hence the O(n^2)
behaviour.

Looking at window_gettupleslot, there is an obvious potential
mirco-optimisation that can be made if there are multiple rows to be
skipped --- instead of calling tuplestore_gettupleslot() multiple
times, call tuplestore_advance() multiple times, then call
tuplestore_gettupleslot() once to fetch the final required tuple, thus
avoiding a lot of unnecessary tuple copying. That of course won't
eliminate the O(n^2) behaviour, but it will reduce the overall factor,
and so is probably worth doing.

However, to fix the fundamental O(n^2) problem, I think there needs to
be separate tuplestore read pointers for the head and tail of the
frame. There may also be a case for another read pointer for the
current row too, and possibly one for general purpose user-triggered
fetches. One approach might be to have up to a small number N (say 3
or 4) of read pointers, with window_gettupleslot() automatically
choosing the one nearest to the requested row. Possibly there are
better approaches. I think a little more investigation is needed.

I'm not sure how much additional work is required to sort this out,
but to me it looks more realistic to target 9.5 than 9.4, so at this
point I tend to think that the patch ought to be marked as returned
with feedback.

Thoughts?

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#170Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#169)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

), which seem reasonable. But

then I started testing performance, and I found cases where the
improvement is not nearly what I expected.

The example cited at the start of this thread is indeed orders of
magnitude faster than HEAD:

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
F

I'm not sure how much additional work is required to sort this out,
but to me it looks more realistic to target 9.5 than 9.4, so at this
point I tend to think that the patch ought to be marked as returned
with feedback.

Thoughts?

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#171Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#169)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 04.04.2014, at 09:40, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

I'm not sure how much additional work is required to sort this out,
but to me it looks more realistic to target 9.5 than 9.4, so at this
point I tend to think that the patch ought to be marked as returned
with feedback.

I think the patch is worthwhile, even without this additional optimization. In fact, If the optimization was part of the patch, there would probably be calls to factor it out, on the ground that the patch is already rather large.

I don't see what bumping the whole thing to 9.5 buys, compared do applying what we have now, and optimizing in 9.5 further.

best regards,
Florian Pflug

PS: Sorry for the broken mail I sent earlier - miss-touched on my Phone ;-(

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#172Andres Freund
andres@2ndquadrant.com
In reply to: Florian Pflug (#171)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 2014-04-04 12:56:55 +0200, Florian Pflug wrote:

On 04.04.2014, at 09:40, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

I'm not sure how much additional work is required to sort this out,
but to me it looks more realistic to target 9.5 than 9.4, so at this
point I tend to think that the patch ought to be marked as returned
with feedback.

I think the patch is worthwhile, even without this additional
optimization. In fact, If the optimization was part of the patch,
there would probably be calls to factor it out, on the ground that the
patch is already rather large.

I don't see what bumping the whole thing to 9.5 buys, compared do
applying what we have now, and optimizing in 9.5 further.

From my POV applying this patch can't be considered a very high priority
for 9.4x. It came *really* late to the game for a relatively complex
patch. A significant portion of the development only happened *after*
the start of the last commitfest.

Greetings,

Andres Freund

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#173Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#171)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 4 April 2014 11:56, Florian Pflug <fgp@phlo.org> wrote:

On 04.04.2014, at 09:40, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

I'm not sure how much additional work is required to sort this out,
but to me it looks more realistic to target 9.5 than 9.4, so at this
point I tend to think that the patch ought to be marked as returned
with feedback.

Just doing the first optimisation I recommended (which I
pessimistically referred to as a "micro-optimisation") actually gives
up to a 4x performance boost relative to the current patch for the
queries above:

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND 10 FOLLOWING)
FROM generate_series(1,20000) g(n);
-- 20ms (was 33ms)

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND 100 FOLLOWING)
FROM generate_series(1,20000) g(n);
-- 54ms (was 173ms)

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND 1000 FOLLOWING)
FROM generate_series(1,20000) g(n);
-- 368ms (was 1467ms)

SELECT SUM(n::int) OVER (ROWS BETWEEN CURRENT ROW AND 10000 FOLLOWING)
FROM generate_series(1,20000) g(n);
-- 1866ms (was 7709ms)

See the attached patch, which applies on top of yours but is actually
independent of it.

IMO, this doesn't go far enough though. We should be aiming to
eliminate the O(n^2) behaviour completely and have all the above
queries take roughly the same amount of time.

I think the patch is worthwhile, even without this additional optimization. In fact, If the optimization was part of the patch, there would probably be calls to factor it out, on the ground that the patch is already rather large.

I don't see what bumping the whole thing to 9.5 buys, compared do applying what we have now, and optimizing in 9.5 further.

The problem with the current state of the patch is that we're going to
a lot of effort to add this new inverse aggregate function,
documenting it's benefits and revealing via EXPLAIN how it can reduce
by several orders of magnitude the number of transition function
calls, but then not giving a commensurate performance boost unless the
window is UNBOUNDED FOLLOWING. That's going to be awkward to explain
to users, and so releasing it in this state feels a little half-baked
to me.

Regards,
Dean

Attachments:

window_gettupleslot.patchtext/x-diff; charset=US-ASCII; name=window_gettupleslot.patchDownload
*** src/backend/executor/nodeWindowAgg.c.orig	2014-04-05 07:56:15.000000000 +0100
--- src/backend/executor/nodeWindowAgg.c	2014-04-05 08:03:32.000000000 +0100
*************** window_gettupleslot(WindowObject winobj,
*** 2412,2425 ****
  		winobj->seekpos++;
  	}
  
! 	while (winobj->seekpos > pos)
  	{
! 		if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot))
  			elog(ERROR, "unexpected end of tuplestore");
  		winobj->seekpos--;
  	}
  
! 	while (winobj->seekpos < pos)
  	{
  		if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot))
  			elog(ERROR, "unexpected end of tuplestore");
--- 2412,2443 ----
  		winobj->seekpos++;
  	}
  
! 	/* Advance or rewind until we are within one tuple of the one we want */
! 	while (winobj->seekpos < pos-1)
  	{
! 		if (!tuplestore_advance(winstate->buffer, true))
! 			elog(ERROR, "unexpected end of tuplestore");
! 		winobj->seekpos++;
! 	}
! 
! 	while (winobj->seekpos > pos+1)
! 	{
! 		if (!tuplestore_advance(winstate->buffer, false))
  			elog(ERROR, "unexpected end of tuplestore");
  		winobj->seekpos--;
  	}
  
! 	/*
! 	 * Now we should be on the tuple immediately before or after the one we
! 	 * want, so just fetch forwards or backwards as appropriate.
! 	 */
! 	if (winobj->seekpos > pos)
! 	{
! 		if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot))
! 			elog(ERROR, "unexpected end of tuplestore");
! 		winobj->seekpos--;
! 	}
! 	else
  	{
  		if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot))
  			elog(ERROR, "unexpected end of tuplestore");
#174Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Dean Rasheed (#173)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 5 April 2014 08:38, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

[snip] releasing it in this state feels a little half-baked
to me.

I regret writing that almost as soon as I sent it. The last of those
queries is now over 10 times faster than HEAD, which is certainly
worthwhile. What bugs me is that there is so much more potential in
this patch.

I think it's pretty close to being committable, but I fear that time
is running out for 9.4.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#175Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#174)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr5, 2014, at 09:55 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 5 April 2014 08:38, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

[snip] releasing it in this state feels a little half-baked
to me.

I regret writing that almost as soon as I sent it. The last of those
queries is now over 10 times faster than HEAD, which is certainly
worthwhile. What bugs me is that there is so much more potential in
this patch.

Well, the perfect is the enemy of the good as they say. By all means,
let's get rid of the O(n^2) behaviour in 9.5, but let's get a basic
version into 9.4.

I think it's pretty close to being committable, but I fear that time
is running out for 9.4.

I'm not aware of any open issues in the basic patch, other then
scaling the reported calling statistics by nloops, which should be
trivial. I'll post an updated patch later today.

I don't really expect all the add-on patches to make it into 9.4 -
they don't seem to have gotten much attention yet - but at least
the inverse transition functions for the basic arithmetic aggregates
should be doable I hope.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#176Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#175)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 7 April 2014 14:09, Florian Pflug <fgp@phlo.org> wrote:

On Apr5, 2014, at 09:55 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 5 April 2014 08:38, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

[snip] releasing it in this state feels a little half-baked
to me.

I regret writing that almost as soon as I sent it. The last of those
queries is now over 10 times faster than HEAD, which is certainly
worthwhile. What bugs me is that there is so much more potential in
this patch.

Well, the perfect is the enemy of the good as they say. By all means,
let's get rid of the O(n^2) behaviour in 9.5, but let's get a basic
version into 9.4.

I think it's pretty close to being committable, but I fear that time
is running out for 9.4.

I'm not aware of any open issues in the basic patch, other then
scaling the reported calling statistics by nloops, which should be
trivial. I'll post an updated patch later today.

I don't really expect all the add-on patches to make it into 9.4 -
they don't seem to have gotten much attention yet - but at least
the inverse transition functions for the basic arithmetic aggregates
should be doable I hope.

I've just finished reading through all the other patches, and they all
look OK to me. It's mostly straightforward stuff, so despite the size
it's hopefully all committable once the base patch goes in.

I think that you're probably right that optimising
window_gettupleslot() to eliminate the O(n^2) behaviour can be left to
a later patch --- the existing performance benefits of this patch are
enough to justify its inclusion IMO. It would be nice to include the
trivial optimisation to window_gettupleslot() that I posted upthread,
since it gives such a big improvement for only a few lines of code
changed.

If you post a new patch set, I'll mark it as ready for committer attention.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#177Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#176)
7 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr7, 2014, at 17:41 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

I've just finished reading through all the other patches, and they all
look OK to me. It's mostly straightforward stuff, so despite the size
it's hopefully all committable once the base patch goes in.

Hm, I'm starting to have second thoughts about the minmax patch. The
inverse transition functions for MIN and MAX have a non-trivial probability
of failure - they trigger a rescan whenever the value that is removed isn't
strictly smaller (respectively strictly larger) then the current maximum
(respectively minimum). Thus, whenever that happens, we both call the
inverse transition function *and* (since it fails) restart the aggregation.

For windows based on ROWS, this isn't too bad - even if we fail every second
time, we still avoid half the rescans, which should be a net win if the
average window size is > 2.

But for RANGE-based windows, more than one call of the inverse transition
function must succeed for us to save anything, since we must successfully
remove *all* peers to avoid one restart. This greatly increases the chance
that using the inverse transition function hurts rather then helps - the
situation is worse the larger the average number of peers is.

I've factored the BOOL_AND,BOOL_OR stuff out into a separate patch
invtrans_bool - it previously was part of invtrans_minmax. Given the
performance risk involved, I think that we probably shouldn't commit
invtrans_minmax at this time. I should have brought this up earlier, but
the issue had slipped my mind :-( Sorry for that.

I think that you're probably right that optimising
window_gettupleslot() to eliminate the O(n^2) behaviour can be left to
a later patch --- the existing performance benefits of this patch are
enough to justify its inclusion IMO. It would be nice to include the
trivial optimisation to window_gettupleslot() that I posted upthread,
since it gives such a big improvement for only a few lines of code
changed.

Agreed, but since it's independent from the rest of the base patch,
I kept it as a separate patch (invtrans_optimize) instead of merging it
into the base patch. It should probably be committed separately too.

If you post a new patch set, I'll mark it as ready for committer attention.

New patch set is attached. The only difference to the previous one is that
"Forward Transitions" and "Inverse Transitions" are now scaled with nloops,
and that it includes your window_gettupleslot patch under the name
invtrans_optimize.

Your nested loop query
explain (verbose, analyse)
select * from
(values (10), (20), (30), (40)) v(x),
lateral
(select sum(i) over (rows between 4 preceding and current row)
from generate_series(1, x) i) t
now produces
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..170.06 rows=4000 width=12) (actual time=0.092..0.257 rows=100 loops=1)
Output: "*VALUES*".column1, (sum(i.i) OVER (?))
-> Values Scan on "*VALUES*" (cost=0.00..0.05 rows=4 width=4) (actual time=0.003..0.007 rows=4 loops=1)
Output: "*VALUES*".column1
-> WindowAgg (cost=0.00..22.50 rows=1000 width=4) (actual time=0.027..0.055 rows=25 loops=4)
Output: sum(i.i) OVER (?)
Forward Transitions: 25.0
Inverse Transitions: 20.0
-> Function Scan on pg_catalog.generate_series i (cost=0.00..10.00 rows=1000 width=4) (actual time=0.013..0.015 rows=25 loops=4)
Output: i.i
Function Call: generate_series(1, "*VALUES*".column1)
Planning time: 0.359 ms
Total runtime: 0.544 ms

The patch dependencies are as follows:

invtrans_{optimize,docs) are independent from the rest

invtrans_{arith,bool,minmax,collecting} are pairwise independent and all
depend on invtrans_base.

As explain above, invtrans_bool is a bit problematic, since it carries
a real risk of performance regressions. It's included for completeness'
sake, and should probably not be committed at this time.

invtrans_optimize was authored by Dean Rasheed.

best regards,
Florian Pflug

Attachments:

invtrans_docs_267287.patchapplication/octet-stream; name=invtrans_docs_267287.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index a12ee56..883fb50 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 381,386 ****
--- 381,392 ----
        <entry>Transition function</entry>
       </row>
       <row>
+       <entry><structfield>agginvtransfn</structfield></entry>
+       <entry><type>regproc</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>Inverse transition function</entry>
+      </row>
+      <row>
        <entry><structfield>aggfinalfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6e2fbda..59ce91e 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT xmlagg(x) FROM (SELECT x FROM tes
*** 13310,13315 ****
--- 13310,13437 ----
     <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
     Other frame specifications can be used to obtain other effects.
    </para>
+   
+   <para>
+     Depending on the aggregate function, aggregating over frames starting
+     at a row relative to the current row can be drastically less efficient
+     than aggregating over frames aligned to the start of the partition. The
+     frame starts at a row relative to the current row if <literal>ORDER
+     BY</literal> is used together with any frame start clause other than
+     <literal>UNBOUNDED PRECEDING</literal> (which is the default). Then,
+     aggregates without a suitable <quote>inverse transition function
+     </quote> (see <xref linkend="SQL-CREATEAGGREGATE"> for details) will be
+     computed for each frame from scratch, instead of re-using the previous
+     frame's result, causing <emphasis>quadratic growth</emphasis> of the
+     execution time as the number of rows per partition increases. The table
+     <xref linkend="functions-aggregate-indframe"> list the built-in aggregate
+     functions affected by this. Note that quadratic growth is only a problem
+     if partitions contain many rows - for partitions with only a few rows,
+     even inefficient aggregates are unlikely to cause problems.
+   </para>
+ 
+   <table id="functions-aggregate-indframe">
+    <title>
+      Aggregate Function Behaviour for frames not starting at
+      <literal>UNBOUNDED PRECEDING</literal>.
+    </title>
+ 
+    <tgroup cols="3">
+      
+     <thead>
+      <row>
+       <entry>Aggregate Function</entry>
+       <entry>Input Type</entry>
+       <entry>Computed From Scratch</entry>
+      </row>
+     </thead>
+     
+     <tbody>
+       
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>float4</type>
+        or
+        <type>float8</type>
+       </entry>
+       <entry>always, to avoid error accumulation</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>numeric</type>
+       </entry>
+       <entry>if the maximum number of decimal digits within the inputs changes</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>min</function>
+       </entry>
+       <entry>
+        any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were minimal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>max</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were maximal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>bit_and</function>,<function>bit_or</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>string_agg</function>
+       </entry>
+       <entry>
+        <type>text</type> or
+        <type>bytea</type> or
+       </entry>
+       <entry>if the delimiter lengths vary</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>xmlagg</function>
+       </entry>
+       <entry>
+        <type>xml</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>json_agg</function>
+       </entry>
+       <entry>
+        <type>json</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
  
    <note>
     <para>
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index e5fc718..9bdb2eb 100644
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
*************** CREATE AGGREGATE <replaceable class="par
*** 25,30 ****
--- 25,31 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVSFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 47,52 ****
--- 48,54 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVSFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 84,98 ****
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
!    a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
--- 86,103 ----
    </para>
  
    <para>
!    An aggregate function is made from one, two or three ordinary
     functions:
!    a (forward) state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
+    an optional inverse state transition function
+    <replaceable class="PARAMETER">invsfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
+ <replaceable class="PARAMETER">invsfunc</replaceable>( internal-state, data-values ) ---> internal-state-without-data-values
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 102,113 ****
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.  After all the rows have been processed,
!    the final function is invoked once to calculate the aggregate's return
!    value.  If there is no final function then the ending state value
!    is returned as-is.
    </para>
  
    <para>
--- 107,134 ----
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the forward state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.
!    If the aggregate is computed over a sliding frame, i.e. if it is used as a
!    <firstterm>window function</firstterm>, the inverse transition function is
!    used to undo the effect of a previous invocation of the forward transition
!    function once argument value(s) fall out of the sliding frame.
!    Conceptually, the forward transition functions thus adds some input
!    value(s) to the state, and the inverse transition functions removes them
!    again. Values are, if they are removed, always removed in the same order
!    they were added, without gaps. Whenever the inverse transition function is
!    invoked, it will thus receive the earliest added but not yet removed
!    argument value(s). If no inverse transition function is supplied, the
!    aggregate can still be used to aggregate over sliding frames, but with
!    reduced efficiency. <productname>PostgreSQL</productname> will then
!    recompute the whole aggregation whenever the start of the frame moves. To
!    calculate the aggregate's return value, the final function is invoked on
!    the ending state value. If there is no final function then ending state
!    value is returned as-is. Either way, the result is assumed to reflect the
!    aggregation of all values added but not yet removed from the state value.
!    Note that if the aggregate is used as a window function, the aggregation
!    may be continued after the final function has been called.
    </para>
  
    <para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 120,135 ****
    </para>
  
    <para>
!    If the state transition function is declared <quote>strict</quote>,
!    then it cannot be called with null inputs.  With such a transition
!    function, aggregate execution behaves as follows.  Rows with any null input
     values are ignored (the function is not called and the previous state value
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values.
!    This is handy for implementing aggregates like <function>max</function>.
!    Note that this behavior is only available when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
--- 141,164 ----
    </para>
  
    <para>
!    If the state transition functions are declared <quote>strict</quote>,
!    then it cannot be called with null inputs.  With such transition
!    functions, aggregate execution behaves as follows.  Rows with any null input
     values are ignored (the function is not called and the previous state value
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values. Should inputs later need to be removed again, the
!    inverse transition function (if present) is used as long as some non-null
!    inputs remain part of the state value. In particular, even if the initial
!    state value is null, the inverse transition function might be used to remove
!    the first non-null input, even though that input was never passed to the
!    forward transition function, but instead just replaced the initial state!
!    The last non-null input, however, is not removed by invoking the inverse
!    transition function, but instead the state is simply reset to its initial
!    value. This is handy for implementing aggregates like <function>max</function>.
!    Note that turning the first non-null input into the initial state is only
!    possible when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
*************** CREATE AGGREGATE <replaceable class="PAR
*** 138,147 ****
    </para>
  
    <para>
!    If the state transition function is not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs
!    and null state values for itself.  This allows the aggregate
!    author to have full control over the aggregate's handling of null values.
    </para>
  
    <para>
--- 167,192 ----
    </para>
  
    <para>
!    If the state transition functions are not <quote>strict</quote>, then they
!    will be called unconditionally at each input row, and must deal with null
!    inputs and null state values for itself. The same goes for the inverse
!    transition function. This allows the aggregate author to have full control
!    over the aggregate's handling of null argument values.
!   </para>
!   
!   <para>
!     The inverse transition function can signal by returning null that it is
!     unable to remove a particular input value from a particular state.
!     <productname>PostgreSQL</productname> will then act as if no inverse
!     transition function had been supplied, i.e. it will recompute the whole
!     aggregation, starting with the first argument value that it would not have
!     removed. That allows aggregates like <function>max</function> to still
!     avoid redoing the whole aggregation in <emphasis>some</emphasis> cases,
!     without paying the overhead of tracking enough state to be able to avoid
!     them in <emphasis>all</emphasis> cases. This demands, however, that
!     null isn't used as a valid state value, except as the initial state. If
!     an aggregate provides an inverse transition function, it is therefore an
!     error for the forward transition function to return null.
    </para>
  
    <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 271,277 ****
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
--- 316,322 ----
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the (forward) state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
*************** SELECT col FROM tab ORDER BY col USING s
*** 281,287 ****
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value.
       </para>
  
       <para>
--- 326,334 ----
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value. Note that if an inverse
!       transition function is present, the forward transition function must
!       not return <literal>NULL</>
       </para>
  
       <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 294,299 ****
--- 341,368 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="PARAMETER">invsfunc</replaceable></term>
+     <listitem>
+      <para>
+       The name of the inverse state transition function to be called for each
+       input row.  For a normal <replaceable class="PARAMETER">N</>-argument
+       aggregate function, the <replaceable class="PARAMETER">invsfunc</>
+       must take <replaceable class="PARAMETER">N</>+1 arguments,
+       the first being of type <replaceable
+       class="PARAMETER">state_data_type</replaceable> and the rest
+       matching the declared input data type(s) of the aggregate.
+       The function must return a value of type <replaceable
+       class="PARAMETER">state_data_type</replaceable>. These are the same
+       demands placed on the forward transition function, meaning that the
+       signatures of the two functions, including their
+       <quote>strictness</quote>, must be identical. The inverse transition
+       function may return <literal>NULL</> to force the aggregation to be
+       restarted from scratch.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="PARAMETER">state_data_type</replaceable></term>
      <listitem>
       <para>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index e77ef12..0c25b6f 100644
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 16,22 ****
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
--- 16,22 ----
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a forward state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
***************
*** 24,30 ****
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
--- 24,42 ----
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result. 
!    To enable efficient evaluation of an aggregate used as a window function
!    with a sliding frame (i.e. a frame that starts relative to the current row),
!    an aggregate can optionally provide an inverse state transition function.
!    The inverse transition function takes the the current state and the
!    aggregate's input value(s) for the <emphasis>earliest</emphasis> row passed
!    to the forward transition function, and returns a state equivalent to what
!    the current state had been had the forward transition function never been
!    invoked for that earliest row, only for all rows that followed it. Thus,
!    if an inverse transition function is provided, the rows that were part of
!    the previous row's frame but not of the current row's frame can simply be
!    removed from the state instead of having to redo the whole aggregation
!    over the new frame.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
*************** CREATE AGGREGATE avg (float8)
*** 132,137 ****
--- 144,188 ----
    </note>
  
    <para>
+    When providing an inverse transition function, care should be taken to
+    ensure that it doesn't introduce unexpected user-visible differences
+    between results obtained by reaggregating all inputs vs. using the inverse
+    transition function. An example for an aggregate where adding an inverse
+    transition function seems easy at first, yet were doing so would violate
+    this requirement is <function>sum</> over <type>float</> or
+    <type>double precision</>. A naive declaration of
+    <function>sum(<type>float</>)</function> could be
+    
+    <programlisting>
+    CREATE AGGREGATE unsafe_sum (float8)
+    (
+        stype = float8,
+        sfunc = float8pl,
+        invsfunc = float8mi
+    );
+    </programlisting>
+    
+    This aggregate, howevery, can give wildly different results than it would
+    have without the inverse transition function. For example, consider
+    
+    <programlisting>
+    SELECT
+      unsafe_sum(x) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND
+                                                  1 FOLLOWING)
+    FROM (VALUES
+      (1, 1.0e20::float8),
+      (2, 1.0::float8)
+    ) AS v (n,x)
+    </programlisting>
+    
+    which returns 0 as it's second result, yet the expected answer is 1. The
+    reason for this is the limited precision of floating point types - adding
+    1 to 1e20 actually leaves the value unchanged, and so substracting 1e20
+    again yields 0, not 1. Note that this is a limitation of floating point
+    types in general and not a limitation of <productname>PostgreSQL</>.
+   </para>
+   
+   <para>
     Aggregate functions can use polymorphic
     state transition functions or final functions, so that the same functions
     can be used to implement multiple aggregates.
invtrans_minmax_174ac0.patchapplication/octet-stream; name=invtrans_minmax_174ac0.patchDownload
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..4a4ca5d 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_larger(PG_FUNCTION_ARGS)
*** 4714,4719 ****
--- 4714,4737 ----
  }
  
  Datum
+ array_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) > 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ 
+ Datum
  array_smaller(PG_FUNCTION_ARGS)
  {
  	ArrayType  *v1,
*************** array_smaller(PG_FUNCTION_ARGS)
*** 4728,4733 ****
--- 4746,4767 ----
  	PG_RETURN_ARRAYTYPE_P(result);
  }
  
+ Datum
+ array_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) < 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
  
  typedef struct generate_subscripts_fctx
  {
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 0158758..cba17a3 100644
*** a/src/backend/utils/adt/cash.c
--- b/src/backend/utils/adt/cash.c
*************** cashlarger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,896 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 > c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cashsmaller()
   * Return smaller of two cash values.
   */
*************** cashsmaller(PG_FUNCTION_ARGS)
*** 892,897 ****
--- 906,925 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 < c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cash_words()
   * This converts a int4 as well but to a representation using words
   * Obviously way North American centric - sorry
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 06cc0cd..0cca0b0 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** date_larger(PG_FUNCTION_ARGS)
*** 396,401 ****
--- 396,415 ----
  }
  
  Datum
+ date_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 > dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  date_smaller(PG_FUNCTION_ARGS)
  {
  	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
*************** date_smaller(PG_FUNCTION_ARGS)
*** 404,409 ****
--- 418,437 ----
  	PG_RETURN_DATEADT((dateVal1 < dateVal2) ? dateVal1 : dateVal2);
  }
  
+ Datum
+ date_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 < dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* Compute difference between two dates in days.
   */
  Datum
*************** time_larger(PG_FUNCTION_ARGS)
*** 1463,1468 ****
--- 1491,1510 ----
  }
  
  Datum
+ time_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 > time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  time_smaller(PG_FUNCTION_ARGS)
  {
  	TimeADT		time1 = PG_GETARG_TIMEADT(0);
*************** time_smaller(PG_FUNCTION_ARGS)
*** 1471,1476 ****
--- 1513,1532 ----
  	PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
  }
  
+ Datum
+ time_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 < time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* overlaps_time() --- implements the SQL OVERLAPS operator.
   *
   * Algorithm is per SQL spec.  This is much harder than you'd think
*************** timetz_larger(PG_FUNCTION_ARGS)
*** 2262,2267 ****
--- 2318,2337 ----
  }
  
  Datum
+ timetz_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) > 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  timetz_smaller(PG_FUNCTION_ARGS)
  {
  	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
*************** timetz_smaller(PG_FUNCTION_ARGS)
*** 2275,2280 ****
--- 2345,2364 ----
  	PG_RETURN_TIMETZADT_P(result);
  }
  
+ Datum
+ timetz_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) < 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* timetz_pl_interval()
   * Add interval to timetz.
   */
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 83c3878..32035e0 100644
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
*************** enum_smaller(PG_FUNCTION_ARGS)
*** 273,278 ****
--- 273,292 ----
  }
  
  Datum
+ enum_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) < 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_larger(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
*************** enum_larger(PG_FUNCTION_ARGS)
*** 282,287 ****
--- 296,315 ----
  }
  
  Datum
+ enum_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) > 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_cmp(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 774267e..04f89b0 100644
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4larger(PG_FUNCTION_ARGS)
*** 637,642 ****
--- 637,658 ----
  }
  
  Datum
+ float4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float4smaller(PG_FUNCTION_ARGS)
  {
  	float4		arg1 = PG_GETARG_FLOAT4(0);
*************** float4smaller(PG_FUNCTION_ARGS)
*** 650,655 ****
--- 666,687 ----
  	PG_RETURN_FLOAT4(result);
  }
  
+ Datum
+ float4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  /*
   *		======================
   *		FLOAT8 BASE OPERATIONS
*************** float8larger(PG_FUNCTION_ARGS)
*** 704,709 ****
--- 736,757 ----
  }
  
  Datum
+ float8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float8smaller(PG_FUNCTION_ARGS)
  {
  	float8		arg1 = PG_GETARG_FLOAT8(0);
*************** float8smaller(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 765,785 ----
  	PG_RETURN_FLOAT8(result);
  }
  
+ Datum
+ float8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *		====================
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 669355e..fe9d736 100644
*** a/src/backend/utils/adt/int.c
--- b/src/backend/utils/adt/int.c
*************** int2larger(PG_FUNCTION_ARGS)
*** 1185,1190 ****
--- 1185,1204 ----
  }
  
  Datum
+ int2larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int2smaller(PG_FUNCTION_ARGS)
  {
  	int16		arg1 = PG_GETARG_INT16(0);
*************** int2smaller(PG_FUNCTION_ARGS)
*** 1194,1199 ****
--- 1208,1227 ----
  }
  
  Datum
+ int2smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4larger(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4larger(PG_FUNCTION_ARGS)
*** 1203,1208 ****
--- 1231,1250 ----
  }
  
  Datum
+ int4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4smaller(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4smaller(PG_FUNCTION_ARGS)
*** 1211,1216 ****
--- 1253,1272 ----
  	PG_RETURN_INT32((arg1 < arg2) ? arg1 : arg2);
  }
  
+ Datum
+ int4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /*
   * Bit-pushing operators
   *
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..820be68 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8larger(PG_FUNCTION_ARGS)
*** 752,757 ****
--- 752,771 ----
  }
  
  Datum
+ int8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int8smaller(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
*************** int8smaller(PG_FUNCTION_ARGS)
*** 764,769 ****
--- 778,797 ----
  }
  
  Datum
+ int8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int84pl(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 64eb0f8..c9500bf 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_smaller(PG_FUNCTION_ARGS)
*** 1872,1877 ****
--- 1872,1892 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) < 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * numeric_larger() -
*************** numeric_larger(PG_FUNCTION_ARGS)
*** 1894,1899 ****
--- 1909,1930 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) > 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /* ----------------------------------------------------------------------
   *
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 8945ef4..745449a 100644
*** a/src/backend/utils/adt/oid.c
--- b/src/backend/utils/adt/oid.c
*************** oidlarger(PG_FUNCTION_ARGS)
*** 397,402 ****
--- 397,416 ----
  }
  
  Datum
+ oidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidsmaller(PG_FUNCTION_ARGS)
  {
  	Oid			arg1 = PG_GETARG_OID(0);
*************** oidsmaller(PG_FUNCTION_ARGS)
*** 406,411 ****
--- 420,439 ----
  }
  
  Datum
+ oidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidvectoreq(PG_FUNCTION_ARGS)
  {
  	int32		cmp = DatumGetInt32(btoidvectorcmp(fcinfo));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 6e2bbdc..57fcf80 100644
*** a/src/backend/utils/adt/tid.c
--- b/src/backend/utils/adt/tid.c
*************** tidlarger(PG_FUNCTION_ARGS)
*** 237,242 ****
--- 237,256 ----
  }
  
  Datum
+ tidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) > 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  tidsmaller(PG_FUNCTION_ARGS)
  {
  	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
*************** tidsmaller(PG_FUNCTION_ARGS)
*** 245,250 ****
--- 259,277 ----
  	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
  }
  
+ Datum
+ tidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) < 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *	Functions to get latest tid of a specified tuple.
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ce30bb6..e611983 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** timestamp_smaller(PG_FUNCTION_ARGS)
*** 2682,2687 ****
--- 2682,2701 ----
  }
  
  Datum
+ timestamp_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) < 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  timestamp_larger(PG_FUNCTION_ARGS)
  {
  	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
*************** timestamp_larger(PG_FUNCTION_ARGS)
*** 2695,2700 ****
--- 2709,2727 ----
  	PG_RETURN_TIMESTAMP(result);
  }
  
+ Datum
+ timestamp_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) > 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
  
  Datum
  timestamp_mi(PG_FUNCTION_ARGS)
*************** interval_smaller(PG_FUNCTION_ARGS)
*** 3151,3156 ****
--- 3178,3197 ----
  }
  
  Datum
+ interval_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) < 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_larger(PG_FUNCTION_ARGS)
  {
  	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
*************** interval_larger(PG_FUNCTION_ARGS)
*** 3165,3170 ****
--- 3206,3225 ----
  }
  
  Datum
+ interval_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) > 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_pl(PG_FUNCTION_ARGS)
  {
  	Interval   *span1 = PG_GETARG_INTERVAL_P(0);
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 502ca44..536ca66 100644
*** a/src/backend/utils/adt/varchar.c
--- b/src/backend/utils/adt/varchar.c
*************** bpchar_larger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,905 ----
  }
  
  Datum
+ bpchar_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp > 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  bpchar_smaller(PG_FUNCTION_ARGS)
  {
  	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
*************** bpchar_smaller(PG_FUNCTION_ARGS)
*** 894,899 ****
--- 917,945 ----
  	PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2);
  }
  
+ Datum
+ bpchar_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp < 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /*
   * bpchar needs a specialized hash function because we want to ignore
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index cb07a06..821cded 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** text_larger(PG_FUNCTION_ARGS)
*** 1697,1702 ****
--- 1697,1716 ----
  }
  
  Datum
+ text_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  text_smaller(PG_FUNCTION_ARGS)
  {
  	text	   *arg1 = PG_GETARG_TEXT_PP(0);
*************** text_smaller(PG_FUNCTION_ARGS)
*** 1708,1713 ****
--- 1722,1740 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ Datum
+ text_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * The following operators support character-by-character comparison
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..f1a602d 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2113	n 0 interval_pl		-	-	
*** 122,169 ****
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
--- 122,169 ----
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger			int8larger_inv			-		413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger			int4larger_inv			-		521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger			int2larger_inv			-		520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger			oidlarger_inv			-		610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger		float4larger_inv		-		623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger		float8larger_inv		-		674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger			int4larger_inv			-		563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger			date_larger_inv			-		1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger			time_larger_inv			-		1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger		timetz_larger_inv		-		1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger			cashlarger_inv			-		903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	timestamp_larger_inv	-		2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	timestamptz_larger_inv	-		1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger		interval_larger_inv		-		1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger			text_larger_inv			-		666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger		numeric_larger_inv		-		1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger		array_larger_inv		-		1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger		bpchar_larger_inv		-		1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger			tidlarger_inv			-		2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger			enum_larger_inv			-		3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller			int8smaller_inv			-		412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller			int4smaller_inv			-		97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller			int2smaller_inv			-		95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller			oidsmaller_inv			-		609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller		float4smaller_inv		-		622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller		float8smaller_inv		-		672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller			int4smaller_inv			-		562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller		date_smaller_inv		-		1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller		time_smaller_inv		-		1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller		timetz_smaller_inv		-		1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller			cashsmaller_inv			-		902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	timestamp_smaller_inv	-		2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller timestamptz_smaller_inv	-		1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	interval_smaller_inv	-		1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller		text_smaller_inv		-		664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller		numeric_smaller_inv		-		1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller		array_smaller_inv		-		1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller		bpchar_smaller_inv		-		1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller			tidsmaller_inv			-		2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller		enum_smaller_inv		-		3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..42dcb27 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("larger of two");
*** 367,372 ****
--- 367,377 ----
  DATA(insert OID = 211 (  float4smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3567 (  float4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3568 (  float4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 212 (  int4um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "23" _null_ _null_ _null_ _null_ int4um _null_ _null_ _null_ ));
  DATA(insert OID = 213 (  int2um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "21" _null_ _null_ _null_ _null_ int2um _null_ _null_ _null_ ));
  
*************** DATA(insert OID = 223 (  float8larger	  
*** 386,391 ****
--- 391,400 ----
  DESCR("larger of two");
  DATA(insert OID = 224 (  float8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3569 (  float8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3570 (  float8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 225 (  lseg_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "601" _null_ _null_ _null_ _null_	lseg_center _null_ _null_ _null_ ));
  DATA(insert OID = 226 (  path_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "602" _null_ _null_ _null_ _null_	path_center _null_ _null_ _null_ ));
*************** DATA(insert OID = 458 (  text_larger	   
*** 711,716 ****
--- 720,729 ----
  DESCR("larger of two");
  DATA(insert OID = 459 (  text_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3571 (  text_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3572 (  text_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 460 (  int8in			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "2275" _null_ _null_ _null_ _null_ int8in _null_ _null_ _null_ ));
  DESCR("I/O");
*************** DATA(insert OID = 515 (  array_larger	  
*** 859,864 ****
--- 872,881 ----
  DESCR("larger of two");
  DATA(insert OID = 516 (  array_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3573 (  array_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3574 (  array_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1191 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 23 "2277 23 16" _null_ _null_ _null_ _null_ generate_subscripts _null_ _null_ _null_ ));
  DESCR("array subscripts generator");
  DATA(insert OID = 1192 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 23 "2277 23" _null_ _null_ _null_ _null_ generate_subscripts_nodir _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 913,918 ****
--- 930,945 ----
  DATA(insert OID = 771 (  int2smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3575 (  int4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3576 (  int4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3577 (  int2larger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3578 (  int2smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 774 (  gistgettuple	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_	gistgettuple _null_ _null_ _null_ ));
  DESCR("gist(internal)");
  DATA(insert OID = 638 (  gistgetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	gistgetbitmap _null_ _null_ _null_ ));
*************** DATA(insert OID =  898 (  cashlarger	   
*** 1003,1008 ****
--- 1030,1039 ----
  DESCR("larger of two");
  DATA(insert OID =  899 (  cashsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID =  3579 (  cashlarger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID =  3580 (  cashsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID =  919 (  flt8_mul_cash    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "701 790" _null_ _null_ _null_ _null_	flt8_mul_cash _null_ _null_ _null_ ));
  DATA(insert OID =  935 (  cash_words	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "790" _null_ _null_ _null_ _null_	cash_words _null_ _null_ _null_ ));
  DESCR("output money amount as words");
*************** DATA(insert OID = 1063 (  bpchar_larger 
*** 1157,1162 ****
--- 1188,1197 ----
  DESCR("larger of two");
  DATA(insert OID = 1064 (  bpchar_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3581 (  bpchar_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3582 (  bpchar_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1078 (  bpcharcmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1042 1042" _null_ _null_ _null_ _null_ bpcharcmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1080 (  hashbpchar	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "1042" _null_ _null_ _null_ _null_	hashbpchar _null_ _null_ _null_ ));
*************** DATA(insert OID = 1138 (  date_larger	  
*** 1191,1196 ****
--- 1226,1235 ----
  DESCR("larger of two");
  DATA(insert OID = 1139 (  date_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3583 (  date_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3584 (  date_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1140 (  date_mi		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1082 1082" _null_ _null_ _null_ _null_ date_mi _null_ _null_ _null_ ));
  DATA(insert OID = 1141 (  date_pli		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_pli _null_ _null_ _null_ ));
  DATA(insert OID = 1142 (  date_mii		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_mii _null_ _null_ _null_ ));
*************** DATA(insert OID = 1195 (  timestamptz_sm
*** 1281,1290 ****
--- 1320,1339 ----
  DESCR("smaller of two");
  DATA(insert OID = 1196 (  timestamptz_larger  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4051 (  timestamptz_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4052 (  timestamptz_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1197 (  interval_smaller	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  DATA(insert OID = 1198 (  interval_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4053 (  interval_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4054 (  interval_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1199 (  age				PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_	timestamptz_age _null_ _null_ _null_ ));
  DESCR("date difference preserving months and years");
  
*************** DESCR("larger of two");
*** 1318,1323 ****
--- 1367,1378 ----
  DATA(insert OID = 1237 (  int8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4055 (  int8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4056 (  int8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1238 (  texticregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexeq _null_ _null_ _null_ ));
  DATA(insert OID = 1239 (  texticregexne    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexne _null_ _null_ _null_ ));
  DATA(insert OID = 1240 (  nameicregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "19 25" _null_ _null_ _null_ _null_ nameicregexeq _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1374,1379 ****
--- 1429,1439 ----
  DATA(insert OID = 2796 ( tidsmaller		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4057 ( tidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4058 ( tidsmaller_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1296 (  timedate_pl	   PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1114 "1083 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
  DATA(insert OID = 1297 (  datetimetz_pl    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1082 1266" _null_ _null_ _null_ _null_ datetimetz_timestamptz _null_ _null_ _null_ ));
  DATA(insert OID = 1298 (  timetzdate_pl    PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1184 "1266 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1515,1520 ****
--- 1575,1590 ----
  DATA(insert OID = 1380 (  timetz_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4059 (  time_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4060 (  time_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4061 (  timetz_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4062 (  timetz_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1381 (  char_length	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "25" _null_ _null_ _null_ _null_ textlen _null_ _null_ _null_ ));
  DESCR("character length");
  
*************** DATA(insert OID = 1766 ( numeric_smaller
*** 2277,2282 ****
--- 2347,2357 ----
  DESCR("smaller of two");
  DATA(insert OID = 1767 ( numeric_larger			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4063 ( numeric_smaller_inv		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4064 ( numeric_larger_inv			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1769 ( numeric_cmp			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1700 1700" _null_ _null_ _null_ _null_ numeric_cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1771 ( numeric_uminus			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ numeric_uminus _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 2803,2808 ****
--- 2878,2888 ----
  DATA(insert OID = 1966 (  oidsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4065 (  oidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4066 (  oidsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 timestamp_transform f f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
  DESCR("adjust timestamptz precision");
  DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 time_transform f f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
*************** DATA(insert OID = 2035 (  timestamp_smal
*** 2864,2869 ****
--- 2944,2955 ----
  DESCR("smaller of two");
  DATA(insert OID = 2036 (  timestamp_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4067 (  timestamp_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4068 (  timestamp_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 2037 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 1266 "25 1266" _null_ _null_ _null_ _null_ timetz_zone _null_ _null_ _null_ ));
  DESCR("adjust time with time zone to new zone");
  DATA(insert OID = 2038 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1186 1266" _null_ _null_ _null_ _null_	timetz_izone _null_ _null_ _null_ ));
*************** DATA(insert OID = 3524 (  enum_smaller	P
*** 4254,4259 ****
--- 4340,4350 ----
  DESCR("smaller of two");
  DATA(insert OID = 3525 (  enum_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4073 (  enum_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4074 (  enum_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 3526 (  max			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("maximum value of all enum input values");
  DATA(insert OID = 3527 (  min			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..43e4973 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern Datum array_upper(PG_FUNCTION_ARG
*** 206,212 ****
--- 206,214 ----
  extern Datum array_length(PG_FUNCTION_ARGS);
  extern Datum array_cardinality(PG_FUNCTION_ARGS);
  extern Datum array_larger(PG_FUNCTION_ARGS);
+ extern Datum array_larger_inv(PG_FUNCTION_ARGS);
  extern Datum array_smaller(PG_FUNCTION_ARGS);
+ extern Datum array_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
  extern Datum array_fill(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..b869743 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum enum_ge(PG_FUNCTION_ARGS);
*** 167,173 ****
--- 167,175 ----
  extern Datum enum_gt(PG_FUNCTION_ARGS);
  extern Datum enum_cmp(PG_FUNCTION_ARGS);
  extern Datum enum_smaller(PG_FUNCTION_ARGS);
+ extern Datum enum_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum enum_larger(PG_FUNCTION_ARGS);
+ extern Datum enum_larger_inv(PG_FUNCTION_ARGS);
  extern Datum enum_first(PG_FUNCTION_ARGS);
  extern Datum enum_last(PG_FUNCTION_ARGS);
  extern Datum enum_range_bounds(PG_FUNCTION_ARGS);
*************** extern Datum int42div(PG_FUNCTION_ARGS);
*** 241,249 ****
--- 243,255 ----
  extern Datum int4mod(PG_FUNCTION_ARGS);
  extern Datum int2mod(PG_FUNCTION_ARGS);
  extern Datum int2larger(PG_FUNCTION_ARGS);
+ extern Datum int2larger_inv(PG_FUNCTION_ARGS);
  extern Datum int2smaller(PG_FUNCTION_ARGS);
+ extern Datum int2smaller_inv(PG_FUNCTION_ARGS);
  extern Datum int4larger(PG_FUNCTION_ARGS);
+ extern Datum int4larger_inv(PG_FUNCTION_ARGS);
  extern Datum int4smaller(PG_FUNCTION_ARGS);
+ extern Datum int4smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int4and(PG_FUNCTION_ARGS);
  extern Datum int4or(PG_FUNCTION_ARGS);
*************** extern Datum float4abs(PG_FUNCTION_ARGS)
*** 347,358 ****
--- 353,368 ----
  extern Datum float4um(PG_FUNCTION_ARGS);
  extern Datum float4up(PG_FUNCTION_ARGS);
  extern Datum float4larger(PG_FUNCTION_ARGS);
+ extern Datum float4larger_inv(PG_FUNCTION_ARGS);
  extern Datum float4smaller(PG_FUNCTION_ARGS);
+ extern Datum float4smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float8abs(PG_FUNCTION_ARGS);
  extern Datum float8um(PG_FUNCTION_ARGS);
  extern Datum float8up(PG_FUNCTION_ARGS);
  extern Datum float8larger(PG_FUNCTION_ARGS);
+ extern Datum float8larger_inv(PG_FUNCTION_ARGS);
  extern Datum float8smaller(PG_FUNCTION_ARGS);
+ extern Datum float8smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float4pl(PG_FUNCTION_ARGS);
  extern Datum float4mi(PG_FUNCTION_ARGS);
  extern Datum float4mul(PG_FUNCTION_ARGS);
*************** extern Datum oidle(PG_FUNCTION_ARGS);
*** 501,507 ****
--- 511,519 ----
  extern Datum oidge(PG_FUNCTION_ARGS);
  extern Datum oidgt(PG_FUNCTION_ARGS);
  extern Datum oidlarger(PG_FUNCTION_ARGS);
+ extern Datum oidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum oidsmaller(PG_FUNCTION_ARGS);
+ extern Datum oidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum oidvectorin(PG_FUNCTION_ARGS);
  extern Datum oidvectorout(PG_FUNCTION_ARGS);
  extern Datum oidvectorrecv(PG_FUNCTION_ARGS);
*************** extern Datum tidgt(PG_FUNCTION_ARGS);
*** 707,713 ****
--- 719,727 ----
  extern Datum tidge(PG_FUNCTION_ARGS);
  extern Datum bttidcmp(PG_FUNCTION_ARGS);
  extern Datum tidlarger(PG_FUNCTION_ARGS);
+ extern Datum tidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum tidsmaller(PG_FUNCTION_ARGS);
+ extern Datum tidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum currtid_byreloid(PG_FUNCTION_ARGS);
  extern Datum currtid_byrelname(PG_FUNCTION_ARGS);
  
*************** extern Datum bpchargt(PG_FUNCTION_ARGS);
*** 730,736 ****
--- 744,752 ----
  extern Datum bpcharge(PG_FUNCTION_ARGS);
  extern Datum bpcharcmp(PG_FUNCTION_ARGS);
  extern Datum bpchar_larger(PG_FUNCTION_ARGS);
+ extern Datum bpchar_larger_inv(PG_FUNCTION_ARGS);
  extern Datum bpchar_smaller(PG_FUNCTION_ARGS);
+ extern Datum bpchar_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum bpcharlen(PG_FUNCTION_ARGS);
  extern Datum bpcharoctetlen(PG_FUNCTION_ARGS);
  extern Datum hashbpchar(PG_FUNCTION_ARGS);
*************** extern Datum text_le(PG_FUNCTION_ARGS);
*** 770,776 ****
--- 786,794 ----
  extern Datum text_gt(PG_FUNCTION_ARGS);
  extern Datum text_ge(PG_FUNCTION_ARGS);
  extern Datum text_larger(PG_FUNCTION_ARGS);
+ extern Datum text_larger_inv(PG_FUNCTION_ARGS);
  extern Datum text_smaller(PG_FUNCTION_ARGS);
+ extern Datum text_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum text_pattern_lt(PG_FUNCTION_ARGS);
  extern Datum text_pattern_le(PG_FUNCTION_ARGS);
  extern Datum text_pattern_gt(PG_FUNCTION_ARGS);
*************** extern Datum numeric_div_trunc(PG_FUNCTI
*** 980,986 ****
--- 998,1006 ----
  extern Datum numeric_mod(PG_FUNCTION_ARGS);
  extern Datum numeric_inc(PG_FUNCTION_ARGS);
  extern Datum numeric_smaller(PG_FUNCTION_ARGS);
+ extern Datum numeric_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_larger(PG_FUNCTION_ARGS);
+ extern Datum numeric_larger_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_fac(PG_FUNCTION_ARGS);
  extern Datum numeric_sqrt(PG_FUNCTION_ARGS);
  extern Datum numeric_exp(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/cash.h b/src/include/utils/cash.h
index 3a491f9..4e184cc 100644
*** a/src/include/utils/cash.h
--- b/src/include/utils/cash.h
*************** extern Datum int2_mul_cash(PG_FUNCTION_A
*** 60,66 ****
--- 60,68 ----
  extern Datum cash_div_int2(PG_FUNCTION_ARGS);
  
  extern Datum cashlarger(PG_FUNCTION_ARGS);
+ extern Datum cashlarger_inv(PG_FUNCTION_ARGS);
  extern Datum cashsmaller(PG_FUNCTION_ARGS);
+ extern Datum cashsmaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum cash_words(PG_FUNCTION_ARGS);
  
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 622aa19..9f459ac 100644
*** a/src/include/utils/date.h
--- b/src/include/utils/date.h
*************** extern Datum date_cmp(PG_FUNCTION_ARGS);
*** 108,114 ****
--- 108,116 ----
  extern Datum date_sortsupport(PG_FUNCTION_ARGS);
  extern Datum date_finite(PG_FUNCTION_ARGS);
  extern Datum date_larger(PG_FUNCTION_ARGS);
+ extern Datum date_larger_inv(PG_FUNCTION_ARGS);
  extern Datum date_smaller(PG_FUNCTION_ARGS);
+ extern Datum date_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum date_mi(PG_FUNCTION_ARGS);
  extern Datum date_pli(PG_FUNCTION_ARGS);
  extern Datum date_mii(PG_FUNCTION_ARGS);
*************** extern Datum time_cmp(PG_FUNCTION_ARGS);
*** 168,174 ****
--- 170,178 ----
  extern Datum time_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_time(PG_FUNCTION_ARGS);
  extern Datum time_larger(PG_FUNCTION_ARGS);
+ extern Datum time_larger_inv(PG_FUNCTION_ARGS);
  extern Datum time_smaller(PG_FUNCTION_ARGS);
+ extern Datum time_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum time_mi_time(PG_FUNCTION_ARGS);
  extern Datum timestamp_time(PG_FUNCTION_ARGS);
  extern Datum timestamptz_time(PG_FUNCTION_ARGS);
*************** extern Datum timetz_cmp(PG_FUNCTION_ARGS
*** 195,201 ****
--- 199,207 ----
  extern Datum timetz_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_timetz(PG_FUNCTION_ARGS);
  extern Datum timetz_larger(PG_FUNCTION_ARGS);
+ extern Datum timetz_larger_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_smaller(PG_FUNCTION_ARGS);
+ extern Datum timetz_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_time(PG_FUNCTION_ARGS);
  extern Datum time_timetz(PG_FUNCTION_ARGS);
  extern Datum timestamptz_timetz(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..d102ccb 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8inc(PG_FUNCTION_ARGS);
*** 77,83 ****
--- 77,85 ----
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
+ extern Datum int8larger_inv(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
+ extern Datum int8smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int8and(PG_FUNCTION_ARGS);
  extern Datum int8or(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 2731c6a..4f6673c 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum timestamp_cmp(PG_FUNCTION_A
*** 111,117 ****
--- 111,119 ----
  extern Datum timestamp_sortsupport(PG_FUNCTION_ARGS);
  extern Datum timestamp_hash(PG_FUNCTION_ARGS);
  extern Datum timestamp_smaller(PG_FUNCTION_ARGS);
+ extern Datum timestamp_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timestamp_larger(PG_FUNCTION_ARGS);
+ extern Datum timestamp_larger_inv(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS);
  extern Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS);
*************** extern Datum interval_finite(PG_FUNCTION
*** 151,157 ****
--- 153,161 ----
  extern Datum interval_cmp(PG_FUNCTION_ARGS);
  extern Datum interval_hash(PG_FUNCTION_ARGS);
  extern Datum interval_smaller(PG_FUNCTION_ARGS);
+ extern Datum interval_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum interval_larger(PG_FUNCTION_ARGS);
+ extern Datum interval_larger_inv(PG_FUNCTION_ARGS);
  extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
  extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
  extern Datum interval_justify_days(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index e3eba47..3c4ad7f 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1305,1310 ****
--- 1305,1403 ----
  -- Test the MIN and MAX inverse transition functions
  --
  --
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  40 |  30
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  20 |  20
+     |  40 |  40
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  30 |  30
+  40 |     |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |  20 |  10
+     |  10 |  10
+  10 |  10 |  10
+ (5 rows)
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |     |  10
+     |     |  10
+  10 |     |  10
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  |  p  | max | min 
+ ----+-----+-----+-----
+  ad | 100 | ae  | ab
+  ab | 100 | ae  | ab
+  ae | 100 | ae  | ae
+  ad | 200 | ad  | aa
+  aa | 200 | aa  | aa
+ (5 rows)
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index bd8dbdc..bde2a49 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 488,493 ****
--- 488,525 ----
  --
  --
  
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
invtrans_arith_3194a9.patchapplication/octet-stream; name=invtrans_arith_3194a9.patchDownload
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..e62f2a3 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8inc(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 717,767 ----
  	}
  }
  
+ Datum
+ int8dec(PG_FUNCTION_ARGS)
+ {
+ 	/*
+ 	 * When int8 is pass-by-reference, we provide this special case to avoid
+ 	 * palloc overhead for COUNT(): when called as an inverse transition
+ 	 * aggregate, we know that the argument is modifiable local storage,
+ 	 * so just update it in-place. (If int8 is pass-by-value, then of course
+ 	 * this is useless as well as incorrect, so just ifdef it out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *arg = (int64 *) PG_GETARG_POINTER(0);
+ 		int64		result;
+ 
+ 		result = *arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && *arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		*arg = result;
+ 		PG_RETURN_POINTER(arg);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		/* Not called as an aggregate, so just do it the dumb way */
+ 		int64		arg = PG_GETARG_INT64(0);
+ 		int64		result;
+ 
+ 		result = arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		PG_RETURN_INT64(result);
+ 	}
+ }
+ 
+ 
  /*
   * These functions are exactly like int8inc but are used for aggregates that
   * count only non-null values.	Since the functions are declared strict,
*************** int8inc_any(PG_FUNCTION_ARGS)
*** 733,738 ****
--- 778,789 ----
  }
  
  Datum
+ int8inc_any_inv(PG_FUNCTION_ARGS)
+ {
+ 	return int8dec(fcinfo);
+ }
+ 
+ Datum
  int8inc_float8_float8(PG_FUNCTION_ARGS)
  {
  	return int8inc(fcinfo);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 64eb0f8..2a9e093 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_float4(PG_FUNCTION_ARGS)
*** 2517,2524 ****
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	bool		isNaN;			/* true if any processed number was NaN */
  	MemoryContext agg_context;	/* context we're calculating in */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
--- 2517,2528 ----
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	int			maxScale;		/* stores the maximum scale seen so far. */
! 	int64		maxScaleCount;  /* tracks the number of values we've
! 								 * seen with the maximum scale */
! 
  	MemoryContext agg_context;	/* context we're calculating in */
+ 	int64		NaNcount;		/* Count of NaN values that are aggregated */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
*************** makeNumericAggState(FunctionCallInfo fci
*** 2543,2548 ****
--- 2547,2556 ----
  	state = (NumericAggState *) palloc0(sizeof(NumericAggState));
  	state->calcSumX2 = calcSumX2;
  	state->agg_context = agg_context;
+ 	state->NaNcount = 0;
+ 
+ 	state->maxScale = 0;
+ 	state->maxScaleCount = 0;
  
  	MemoryContextSwitchTo(old_context);
  
*************** do_numeric_accum(NumericAggState *state,
*** 2560,2574 ****
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (state->isNaN || NUMERIC_IS_NAN(newval))
  	{
! 		state->isNaN = true;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
--- 2568,2596 ----
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (NUMERIC_IS_NAN(newval))
  	{
! 		state->NaNcount++;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
+ 	/*
+ 	 * Track the highest scale that we've seen as if we ever perform an inverse
+ 	 * transition and remove the last numeric with the highest scale that we've
+ 	 * seen then we can no longer perform inverse transitions without risking
+ 	 * having the wrong dscale in the result value.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 		state->maxScaleCount++;
+ 	else if (X.dscale > state->maxScale)
+ 	{
+ 		state->maxScale = X.dscale;
+ 		state->maxScaleCount = 1;
+ 	}
+ 
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2579,2591 ****
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N++ > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
  	}
  	else
  	{
--- 2601,2615 ----
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
+ 
+ 		state->N++;
  	}
  	else
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2594,2605 ****
--- 2618,2730 ----
  
  		if (state->calcSumX2)
  			set_var_from_var(&X2, &(state->sumX2));
+ 
+ 		state->N = 1;
  	}
  
  	MemoryContextSwitchTo(old_context);
  }
  
  /*
+  * do_numeric_discard
+  * Attempts to remove a value from the aggregated state.
+  * If the value cannot be removed then the function will return false, the
+  * possible reasons for failing are described below.
+  *
+  * If we aggregate the values 1.01 and 2 then the result will be 3.01. If we
+  * are then asked to un-aggregate the 1.01 then we must reject this case as we
+  * won't be able to tell what the new aggregated value's dscale should be.
+  * We can't return 2.00 (dscale = 2) as we really should return just 2, but
+  * since we're not tracking any previous highest scales then we must just fail
+  * to perform the inverse transition and just return false.
+  *
+  * Values that are no longer aggregated should not be able to effect the dscale
+  * of the result of the values that *are* still aggregated.
+  *
+  * Note it may be better to track the number of times we've aggregated a
+  * numeric with each scale, then if we ever remove final highest scaled value
+  * then we can step the result's dscale down to the next highest value. This is
+  * perhaps slightly more work than we can afford to do here, but doing it this
+  * way would mean that we could always perform the inverse transition.
+  */
+ static bool
+ do_numeric_discard(NumericAggState *state, Numeric newval)
+ {
+ 	NumericVar	X;
+ 	NumericVar	X2;
+ 	MemoryContext old_context;
+ 
+ 	/* result is NaN if any processed number is NaN */
+ 	if (NUMERIC_IS_NAN(newval))
+ 	{
+ 		state->NaNcount--;
+ 		return true;
+ 	}
+ 
+ 	/* load processed number in short-lived context */
+ 	init_var_from_num(newval, &X);
+ 
+ 	/*
+ 	 * state->sumX's dscale matches the maximum dscale of any of the inputs
+ 	 * Removing the last input with that dscale would require us to recompute
+ 	 * the maximum dscale of the *remaining* inputs, which we cannot do unless
+ 	 * no more non-NaN inputs remain at all. So we report a failure instead,
+ 	 * and force the aggregation to be redone from scratch.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 	{
+ 		if (state->maxScaleCount > 1)
+ 		{
+ 			/* Some remaining inputs have same dscale */
+ 			--state->maxScaleCount;
+ 		}
+ 		else if (state->N == 1)
+ 		{
+ 			/* No remaining non-NaN inputs at all */
+ 			state->maxScale = 0;
+ 			state->maxScaleCount = 0;
+ 		}
+ 		else
+ 		{
+ 			/* No remaining inputs have same dscale */
+ 			return false;
+ 		}
+ 	}
+ 
+ 	/* if we need X^2, calculate that in short-lived context */
+ 	if (state->calcSumX2)
+ 	{
+ 		init_var(&X2);
+ 		mul_var(&X, &X, &X2, X.dscale * 2);
+ 	}
+ 
+ 	/* The rest of this needs to work in the aggregate context */
+ 	old_context = MemoryContextSwitchTo(state->agg_context);
+ 
+ 	if (state->N > 1)
+ 	{
+ 		sub_var(&(state->sumX), &X, &(state->sumX));
+ 		if (state->calcSumX2)
+ 			sub_var(&(state->sumX2), &X2, &(state->sumX2));
+ 		state->N--;
+ 	}
+ 	else if (state->N == 1)
+ 	{
+ 		/* Sums will be reset by next call to do_numeric_accum */
+ 		state->N = 0;
+ 	}
+ 	else
+ 	{
+ 		MemoryContextSwitchTo(old_context);
+ 		elog(ERROR, "cannot discard more values than were accumulated");
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 	return true;
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that require sumX2.
   */
  Datum
*************** numeric_accum(PG_FUNCTION_ARGS)
*** 2609,2626 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
--- 2734,2772 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, true);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * Generic inverse transition function for numeric aggregates
+  */
+ Datum
+ numeric_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL)
+ 		elog(ERROR, "numeric_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		/* Can we perform an inverse transition? if not return NULL. */
+ 		if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 			PG_RETURN_NULL();
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
+ 
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
*************** numeric_avg_accum(PG_FUNCTION_ARGS)
*** 2631,2644 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, false);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
- 	}
  
  	PG_RETURN_POINTER(state);
  }
--- 2777,2788 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, false);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  
  	PG_RETURN_POINTER(state);
  }
*************** int2_accum(PG_FUNCTION_ARGS)
*** 2659,2675 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2803,2818 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int4_accum(PG_FUNCTION_ARGS)
*** 2683,2699 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2826,2841 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2707,2724 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
! 		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
--- 2849,2951 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
+ 		do_numeric_accum(state, newval);
+ 	}
  
! 	PG_RETURN_POINTER(state);
! }
  
! 
! /*
!  * int2_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int2_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int4_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int4_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int8_accum_inv
!  * aggregate inverse transition function.
!  * This function must be declared as strict.
!  */
! Datum
! int8_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
  	}
  
  	PG_RETURN_POINTER(state);
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2726,2731 ****
--- 2953,2959 ----
  
  /*
   * Transition function for int8 input when we don't need sumX2.
+  * For the inverse, we use int8_accum_inv.
   */
  Datum
  int8_avg_accum(PG_FUNCTION_ARGS)
*************** int8_avg_accum(PG_FUNCTION_ARGS)
*** 2734,2757 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, false);
- 
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
- 
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
--- 2962,2983 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, false);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
*************** numeric_avg(PG_FUNCTION_ARGS)
*** 2760,2769 ****
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
--- 2986,2996 ----
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
*************** numeric_sum(PG_FUNCTION_ARGS)
*** 2778,2787 ****
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
--- 3005,3016 ----
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)
! 		/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
*************** numeric_stddev_internal(NumericAggState 
*** 2812,2818 ****
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL)
  	{
  		*is_null = true;
  		return NULL;
--- 3041,3047 ----
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
  	{
  		*is_null = true;
  		return NULL;
*************** numeric_stddev_internal(NumericAggState 
*** 2820,2826 ****
  
  	*is_null = false;
  
! 	if (state->isNaN)
  		return make_result(&const_nan);
  
  	init_var(&vN);
--- 3049,3055 ----
  
  	*is_null = false;
  
! 	if (state->NaNcount > 0)
  		return make_result(&const_nan);
  
  	init_var(&vN);
*************** numeric_stddev_pop(PG_FUNCTION_ARGS)
*** 2951,2970 ****
  }
  
  /*
!  * SUM transition functions for integer datatypes.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   *
!  * Because SQL defines the SUM() of no values to be NULL, not zero,
!  * the initial condition of the transition data value needs to be NULL. This
!  * means we can't rely on ExecAgg to automatically insert the first non-null
!  * data value into the transition data: it doesn't know how to do the type
!  * conversion.	The upshot is that these routines have to be marked non-strict
!  * and handle substitution of the first non-null input themselves.
   */
  
  Datum
--- 3180,3193 ----
  }
  
  /*
!  * Obsolete SUM transition functions for integer datatypes.
   *
!  * These were used to implement SUM aggregates before inverse transition
!  * functions were added. For inverse transitions, we need to know the number
!  * of summands to be able to return NULL whenenver the number of non-NULL
!  * inputs becomes zero. We therefore now use the intX_avg_accum and
!  * intX_avg_accum_inv transition functions, which use int8[] is their
!  * transition type to be able to count the number of inputs.
   */
  
  Datum
*************** int8_sum(PG_FUNCTION_ARGS)
*** 3103,3112 ****
  										NumericGetDatum(oldsum), newval));
  }
  
- 
  /*
!  * Routines for avg(int2) and avg(int4).  The transition datatype
!  * is a two-element int8 array, holding count and sum.
   */
  
  typedef struct Int8TransTypeData
--- 3326,3340 ----
  										NumericGetDatum(oldsum), newval));
  }
  
  /*
!  * Routines for sum(int2), sum(int4), avg(int2) and avg(int4).  The transition
!  * datatype is a two-element int8 array, holding count and sum.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   */
  
  typedef struct Int8TransTypeData
*************** int2_avg_accum(PG_FUNCTION_ARGS)
*** 3144,3149 ****
--- 3372,3406 ----
  }
  
  Datum
+ int2_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int16		newval = PG_GETARG_INT16(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ 
+ Datum
  int4_avg_accum(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray;
*************** int4_avg_accum(PG_FUNCTION_ARGS)
*** 3172,3177 ****
--- 3429,3480 ----
  }
  
  Datum
+ int4_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int32		newval = PG_GETARG_INT32(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ Datum
+ int2int4_sum(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Int8TransTypeData *transdata;
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 
+ 	/* SQL defines SUM of no values to be NULL */
+ 	if (transdata->count == 0)
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_DATUM(Int64GetDatumFast(transdata->sum));
+ }
+ 
+ Datum
  int8_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ce30bb6..af0822a 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** interval_accum(PG_FUNCTION_ARGS)
*** 3429,3434 ****
--- 3429,3479 ----
  }
  
  Datum
+ interval_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Interval   *newval = PG_GETARG_INTERVAL_P(1);
+ 	Datum	   *transdatums;
+ 	int			ndatums;
+ 	Interval	sumX,
+ 				N;
+ 	Interval   *newsum;
+ 	ArrayType  *result;
+ 
+ 	deconstruct_array(transarray,
+ 					  INTERVALOID, sizeof(Interval), false, 'd',
+ 					  &transdatums, NULL, &ndatums);
+ 	if (ndatums != 2)
+ 		elog(ERROR, "expected 2-element interval array");
+ 
+ 	/*
+ 	 * XXX memcpy, instead of just extracting a pointer, to work around buggy
+ 	 * array code: it won't ensure proper alignment of Interval objects on
+ 	 * machines where double requires 8-byte alignment. That should be fixed,
+ 	 * but in the meantime...
+ 	 *
+ 	 * Note: must use DatumGetPointer here, not DatumGetIntervalP, else some
+ 	 * compilers optimize into double-aligned load/store anyway.
+ 	 */
+ 	memcpy((void *) &sumX, DatumGetPointer(transdatums[0]), sizeof(Interval));
+ 	memcpy((void *) &N, DatumGetPointer(transdatums[1]), sizeof(Interval));
+ 
+ 	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
+ 												   IntervalPGetDatum(&sumX),
+ 												 IntervalPGetDatum(newval)));
+ 	N.time -= 1;
+ 
+ 	transdatums[0] = IntervalPGetDatum(newsum);
+ 	transdatums[1] = IntervalPGetDatum(&N);
+ 
+ 	result = construct_array(transdatums, 2,
+ 							 INTERVALOID, sizeof(Interval), false, 'd');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ 
+ Datum
  interval_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..874aeec 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 103,125 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
--- 103,125 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		int8_accum_inv			numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		int4_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		int2_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_accum_inv		numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum		interval_accum_inv		interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		int8_accum_inv			numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_avg_accum		int4_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2109	n 0 int2_avg_accum		int2_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2110	n 0 float4pl			-						-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl			-						-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl				cash_mi					-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl			interval_mi				-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_accum_inv		numeric_sum		0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
*************** DATA(insert ( 2798	n 0 tidsmaller		-	-		
*** 166,221 ****
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
--- 166,221 ----
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		int8inc_any_inv	-		0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			int8dec			-		0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		int8_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		int4_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		int2_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_accum_inv	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		int8_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		int4_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		int2_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..59d8ac6 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("truncate interval to specified un
*** 1309,1316 ****
--- 1309,1320 ----
  
  DATA(insert OID = 1219 (  int8inc		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8inc _null_ _null_ _null_ ));
  DESCR("increment");
+ DATA(insert OID = 3546 (  int8dec		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8dec _null_ _null_ _null_ ));
+ DESCR("decrement");
  DATA(insert OID = 2804 (  int8inc_any	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any _null_ _null_ _null_ ));
  DESCR("increment, ignores second argument");
+ DATA(insert OID = 3547 (  int8inc_any_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any_inv _null_ _null_ _null_ ));
+ DESCR("decrement, ignores second argument");
  DATA(insert OID = 1230 (  int8abs		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8abs _null_ _null_ _null_ ));
  
  DATA(insert OID = 1236 (  int8larger	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger _null_ _null_ _null_ ));
*************** DATA(insert OID = 1832 (  float8_stddev_
*** 2396,2401 ****
--- 2400,2407 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3548 (  numeric_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
*************** DATA(insert OID = 1836 (  int8_accum	   
*** 2406,2411 ****
--- 2412,2423 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 2746 (  int8_avg_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3549 (  int2_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3550 (  int4_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3551 (  int8_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
*************** DATA(insert OID = 1842 (  int8_sum		   P
*** 2426,2439 ****
--- 2438,2459 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 1843 (  interval_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3552 (  interval_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1844 (  interval_avg	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1186 "1187" _null_ _null_ _null_ _null_ interval_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1962 (  int2_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1963 (  int4_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3553 (  int2_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 3218 (  int4_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 1964 (  int8_avg		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1016" _null_ _null_ _null_ _null_ int8_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
+ DATA(insert OID = 3219 (  int2int4_sum	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "1016" _null_ _null_ _null_ _null_ int2int4_sum _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  DATA(insert OID = 2805 (  int8inc_float8_float8		PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 20 "20 701 701" _null_ _null_ _null_ _null_ int8inc_float8_float8 _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2806 (  float8_regr_accum			PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 1022 "1022 701 701" _null_ _null_ _null_ _null_ float8_regr_accum _null_ _null_ _null_ ));
*************** DESCR("get value from jsonb with path el
*** 4529,4536 ****
  DATA(insert OID = 3939 (  jsonb_extract_path_op		PGNSP PGUID 12 1 0 0 0	f f f f t f i 2 0 3802 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ ));
  DATA(insert OID = 3940 (  jsonb_extract_path_text	PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 25 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
  DESCR("get value from jsonb as text with path elements");
! DATA(insert OID = 3218 (  jsonb_extract_path_text_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
! DATA(insert OID = 3219 (  jsonb_array_elements		PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 3802 "3802" "{3802,3802}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements _null_ _null_ _null_ ));
  DESCR("elements of a jsonb array");
  DATA(insert OID = 3465 (  jsonb_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" "{3802,25}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements_text _null_ _null_ _null_ ));
  DESCR("elements of jsonb array");
--- 4549,4556 ----
  DATA(insert OID = 3939 (  jsonb_extract_path_op		PGNSP PGUID 12 1 0 0 0	f f f f t f i 2 0 3802 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ ));
  DATA(insert OID = 3940 (  jsonb_extract_path_text	PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 25 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
  DESCR("get value from jsonb as text with path elements");
! DATA(insert OID = 3554 (  jsonb_extract_path_text_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
! DATA(insert OID = 3555 (  jsonb_array_elements		PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 3802 "3802" "{3802,3802}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements _null_ _null_ _null_ ));
  DESCR("elements of a jsonb array");
  DATA(insert OID = 3465 (  jsonb_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" "{3802,25}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements_text _null_ _null_ _null_ ));
  DESCR("elements of jsonb array");
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..2f77dc1 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum numeric_float8_no_overflow(
*** 999,1008 ****
--- 999,1012 ----
  extern Datum float4_numeric(PG_FUNCTION_ARGS);
  extern Datum numeric_float4(PG_FUNCTION_ARGS);
  extern Datum numeric_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg_accum(PG_FUNCTION_ARGS);
  extern Datum int2_accum(PG_FUNCTION_ARGS);
  extern Datum int4_accum(PG_FUNCTION_ARGS);
  extern Datum int8_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int4_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int8_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg_accum(PG_FUNCTION_ARGS);
  extern Datum numeric_avg(PG_FUNCTION_ARGS);
  extern Datum numeric_sum(PG_FUNCTION_ARGS);
*************** extern Datum int2_sum(PG_FUNCTION_ARGS);
*** 1014,1020 ****
--- 1018,1027 ----
  extern Datum int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_sum(PG_FUNCTION_ARGS);
  extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int4_avg_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int2int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_avg(PG_FUNCTION_ARGS);
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  extern Datum hash_numeric(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..5078e4a 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8div(PG_FUNCTION_ARGS);
*** 74,80 ****
--- 74,82 ----
  extern Datum int8abs(PG_FUNCTION_ARGS);
  extern Datum int8mod(PG_FUNCTION_ARGS);
  extern Datum int8inc(PG_FUNCTION_ARGS);
+ extern Datum int8dec(PG_FUNCTION_ARGS);
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
+ extern Datum int8inc_any_inv(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 2731c6a..94328b3 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum interval_mul(PG_FUNCTION_AR
*** 184,189 ****
--- 184,190 ----
  extern Datum mul_d_interval(PG_FUNCTION_ARGS);
  extern Datum interval_div(PG_FUNCTION_ARGS);
  extern Datum interval_accum(PG_FUNCTION_ARGS);
+ extern Datum interval_accum_inv(PG_FUNCTION_ARGS);
  extern Datum interval_avg(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_mi(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index e3eba47..394e3d5 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1295,1300 ****
--- 1295,1783 ----
  -- Test the arithmetic inverse transition functions
  --
  --
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 2.0000000000000000
+  2 | 2.5000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |    avg     
+ ---+------------
+  1 | @ 1.5 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+  i |  sum  
+ ---+-------
+  1 | $3.30
+  2 | $2.20
+  3 |      
+  4 |      
+ (4 rows)
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |   sum    
+ ---+----------
+  1 | @ 3 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 | 3.3
+  2 | 2.2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+  sum  
+ ------
+  6.01
+     5
+     3
+ (3 rows)
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     2
+  2 |     1
+  3 |     0
+  4 |     0
+ (4 rows)
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     4
+  2 |     3
+  3 |     2
+  4 |     1
+ (4 rows)
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   1
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   6
+  3 |   9
+  4 |   7
+ (4 rows)
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+          to_char          
+ --------------------------
+   100000000000000000000
+                       1.0
+ (2 rows)
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+  a |  b  | sum 
+ ---+-----+-----
+  1 |   1 |   1
+  2 |   2 |   3
+  3 | NaN | NaN
+  4 |   3 | NaN
+  5 |   4 |   7
+ (5 rows)
+ 
  --
  --
  -- Test the boolean inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index bd8dbdc..3cbd888 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 476,481 ****
--- 476,622 ----
  --
  --
  
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+ 
  --
  --
  -- Test the boolean inverse transition functions
invtrans_base_edb69c.patchapplication/octet-stream; name=invtrans_base_edb69c.patchDownload
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..b7fc5f2 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 56,61 ****
--- 56,62 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
*************** AggregateCreate(const char *aggName,
*** 68,78 ****
--- 69,81 ----
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
  	Oid			transfn;
+ 	Oid			invtransfn = InvalidOid; /* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	bool		transIsStrict;
  	Oid			rettype;
  	Oid			finaltype;
  	Oid			fnArgs[FUNC_MAX_ARGS];
*************** AggregateCreate(const char *aggName,
*** 234,241 ****
--- 237,297 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
+ 
+ 	/*
+ 	 * Remember if trans function is strict as we need to validate this
+ 	 * later if when we're dealing with the inverse transition function
+ 	 */
+ 	transIsStrict = proc->proisstrict;
+ 
  	ReleaseSysCache(tup);
  
+ 	/* handle invtransfn, if supplied */
+ 	if (agginvtransfnName)
+ 	{
+ 		/*
+ 		 * This must have the same number of arguments with the same types as
+ 		 * the transition function. We can just borrow the argument details
+ 		 * from the transition function and try to find a function with
+ 		 * the name of the inverse transition function and with a signature
+ 		 * that matches the transition function's.
+ 		 */
+ 		invtransfn = lookup_agg_function(agginvtransfnName,
+ 					nargs_transfn, fnArgs, InvalidOid, &rettype);
+ 
+ 		/*
+ 		 * Ensure the return type of the inverse transition function matches
+ 		 * the expected type.
+ 		 */
+ 		if (rettype != aggTransType)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("return type of inverse transition function %s is not %s",
+ 							NameListToString(agginvtransfnName),
+ 							format_type_be(aggTransType))));
+ 
+ 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+ 		proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 		/*
+ 		 * We force the strictness settings of the forward and inverse
+ 		 * transition functions to agree. This allows places which only need
+ 		 * forward transitions to not look at the inverse transition function
+ 		 * at all.
+ 		 */
+ 		if (transIsStrict != proc->proisstrict)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 						errmsg("strictness of forward and inverse transition functions must match"
+ 						)));
+ 		}
+ 		ReleaseSysCache(tup);
+ 
+ 	}
+ 
  	/* handle finalfn, if supplied */
  	if (aggfinalfnName)
  	{
*************** AggregateCreate(const char *aggName,
*** 391,396 ****
--- 447,453 ----
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+ 	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
*************** AggregateCreate(const char *aggName,
*** 425,430 ****
--- 482,496 ----
  	referenced.objectSubId = 0;
  	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
+ 	/* Depends on inverse transition function, if any */
+ 	if (OidIsValid(invtransfn))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = invtransfn;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
  	{
*************** AggregateCreate(const char *aggName,
*** 447,453 ****
  }
  
  /*
!  * lookup_agg_function -- common code for finding both transfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
--- 513,520 ----
  }
  
  /*
!  * lookup_agg_function
!  * common code for finding transfn, invtransfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..c0ea1f7 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
*************** DefineAggregate(List *name, List *args, 
*** 60,65 ****
--- 60,66 ----
  	AclResult	aclresult;
  	char		aggKind = AGGKIND_NORMAL;
  	List	   *transfuncName = NIL;
+ 	List	   *invtransfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
*************** DefineAggregate(List *name, List *args, 
*** 112,117 ****
--- 113,120 ----
  			transfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
  			transfuncName = defGetQualifiedName(defel);
+ 		else if (pg_strcasecmp(defel->defname, "invsfunc") == 0)
+ 			invtransfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
  			finalfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
*************** DefineAggregate(List *name, List *args, 
*** 283,288 ****
--- 286,292 ----
  						   parameterDefaults,
  						   variadicArgType,
  						   transfuncName,		/* step function name */
+ 						   invtransfuncName,	/* inverse trans function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 08f3167..af36690 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_group_keys(PlanSta
*** 85,90 ****
--- 85,91 ----
  					 List *ancestors, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
+ static void show_windowagg_info(PlanState *planstate, ExplainState *es);
  static void show_tidbitmap_info(BitmapHeapScanState *planstate,
  								ExplainState *es);
  static void show_instrumentation_count(const char *qlabel, int which,
*************** ExplainNode(PlanState *planstate, List *
*** 1420,1425 ****
--- 1421,1430 ----
  		case T_Hash:
  			show_hash_info((HashState *) planstate, es);
  			break;
+ 		case T_WindowAgg:
+ 			if (es->verbose && planstate->instrument)
+ 				show_windowagg_info(planstate, es);
+ 			break;
  		default:
  			break;
  	}
*************** show_hash_info(HashState *hashstate, Exp
*** 1918,1923 ****
--- 1923,1943 ----
  	}
  }
  
+ static void
+ show_windowagg_info(PlanState *planstate, ExplainState *es)
+ {
+ 	WindowAggState *winaggstate = (WindowAggState *) planstate;
+ 
+ 	if (winaggstate->aggfwdtrans > 0 && planstate->instrument->nloops > 0)
+ 		ExplainPropertyFloat("Forward Transitions",
+ 							 (double)winaggstate->aggfwdtrans /
+ 							 planstate->instrument->nloops, 1, es);
+ 	if (winaggstate->agginvtrans > 0 && planstate->instrument->nloops > 0)
+ 		ExplainPropertyFloat("Inverse Transitions",
+ 							 (double)winaggstate->agginvtrans /
+ 							 planstate->instrument->nloops, 1, es);
+ }
+ 
  /*
   * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
   */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..9a7ed93 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1798,1805 ****
--- 1798,1807 ----
  								aggref->aggtype,
  								aggref->inputcollid,
  								transfn_oid,
+ 								InvalidOid, /* invtrans is not needed here */
  								finalfn_oid,
  								&transfnexpr,
+ 								NULL,
  								&finalfnexpr);
  
  		/* set up infrastructure for calling the transfn and finalfn */
*************** ExecReScanAgg(AggState *node)
*** 2127,2168 ****
  }
  
  /*
-  * AggCheckCallContext - test if a SQL function is being called as an aggregate
-  *
-  * The transition and/or final functions of an aggregate may want to verify
-  * that they are being called as aggregates, rather than as plain SQL
-  * functions.  They should use this function to do so.	The return value
-  * is nonzero if being called as an aggregate, or zero if not.	(Specific
-  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
-  * values could conceivably appear in future.)
-  *
-  * If aggcontext isn't NULL, the function also stores at *aggcontext the
-  * identity of the memory context that aggregate transition values are
-  * being stored in.
-  */
- int
- AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
- {
- 	if (fcinfo->context && IsA(fcinfo->context, AggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_AGGREGATE;
- 	}
- 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_WINDOW;
- 	}
- 
- 	/* this is just to prevent "uninitialized variable" warnings */
- 	if (aggcontext)
- 		*aggcontext = NULL;
- 	return 0;
- }
- 
- /*
   * AggGetAggref - allow an aggregate support function to get its Aggref
   *
   * If the function is being called as an aggregate support function,
--- 2129,2134 ----
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 8afe3d0..3ceb0f7 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** typedef struct WindowStatePerFuncData
*** 102,119 ****
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transfer functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transfer functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/*
  	 * initial value from pg_aggregate entry
  	 */
--- 102,125 ----
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transition functions */
  	Oid			transfn_oid;
+ 	Oid			invtransfn_oid; /* may be InvalidOid */
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transition functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
+ 	FmgrInfo	invtransfn;
  	FmgrInfo	finalfn;
  
+ 	/* Aggregate properties */
+ 	bool		use_invtransfn;			/* whether to use the invtransfn */
+ 	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+ 
  	/*
  	 * initial value from pg_aggregate entry
  	 */
*************** typedef struct WindowStatePerAggData
*** 140,149 ****
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	Datum		transValue;		/* current transition value */
! 	bool		transValueIsNull;
  
! 	bool		noTransValue;	/* true if transValue not set yet */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
--- 146,158 ----
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	MemoryContext	aggcontext;			/* context for transValue */
! 	int64			transValueCount;	/* Number of aggregated values*/
! 	Datum			transValue;			/* current transition value */
! 	bool			transValueIsNull;
  
! 	/* Data local to eval_windowaggregates() */
! 	bool			restart;			/* tmp marker that agg needs restart */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
*************** static void initialize_windowaggregate(W
*** 152,157 ****
--- 161,169 ----
  static void advance_windowaggregate(WindowAggState *winstate,
  						WindowStatePerFunc perfuncstate,
  						WindowStatePerAgg peraggstate);
+ static bool retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate);
  static void finalize_windowaggregate(WindowAggState *winstate,
  						 WindowStatePerFunc perfuncstate,
  						 WindowStatePerAgg peraggstate,
*************** static bool are_peers(WindowAggState *wi
*** 181,186 ****
--- 193,245 ----
  static bool window_gettupleslot(WindowObject winobj, int64 pos,
  					TupleTableSlot *slot);
  
+ /*
+  * AggCheckCallContext - test if a SQL function is being called as an aggregate
+  *
+  * The transition and/or final functions of an aggregate may want to verify
+  * that they are being called as aggregates, rather than as plain SQL
+  * functions.  They should use this function to do so.	The return value
+  * is nonzero if being called as an aggregate, or zero if not.	(Specific
+  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+  * values could conceivably appear in future.)
+  *
+  * If aggcontext isn't NULL, the function also stores at *aggcontext the
+  * identity of the memory context that aggregate transition values are
+  * being stored in.
+  *
+  * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+  * is private.
+  *
+  * Note that this function is only meant to be used by aggregate support
+  * functions, NOT by true window functions.
+  */
+ int
+ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggState))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ 		return AGG_CONTEXT_AGGREGATE;
+ 	}
+ 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ 	{
+ 		if (aggcontext)
+ 		{
+ 			/* Must lookup per-aggregate context */
+ 			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+ 			int				aggno = winstate->calledaggno;
+ 			Assert(0 <= aggno && aggno < winstate->numaggs);
+ 			*aggcontext = winstate->peragg[aggno].aggcontext;
+ 		}
+ 		return AGG_CONTEXT_WINDOW;
+ 	}
+ 
+ 	/* this is just to prevent "uninitialized variable" warnings */
+ 	if (aggcontext)
+ 		*aggcontext = NULL;
+ 	return 0;
+ }
  
  /*
   * initialize_windowaggregate
*************** initialize_windowaggregate(WindowAggStat
*** 193,210 ****
  {
  	MemoryContext oldContext;
  
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = peraggstate->initValue;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->noTransValue = peraggstate->initValueIsNull;
  	peraggstate->resultValueIsNull = true;
  }
  
--- 252,277 ----
  {
  	MemoryContext oldContext;
  
+ 	/* If we're using a private aggcontext, we may reset it here. But if the
+ 	 * context is shared, we don't know which other aggregates may still need
+ 	 * it, so we must leave it to the caller to reset at an appropriate time
+ 	 */
+ 	if (!peraggstate->aggcontext_is_shared)
+ 		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+ 
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = (Datum) 0;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
+ 	peraggstate->transValueCount = 0;
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->resultValue = (Datum) 0;
  	peraggstate->resultValueIsNull = true;
  }
  
*************** advance_windowaggregate(WindowAggState *
*** 256,265 ****
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/*
! 		 * For a strict transfn, nothing happens when there's a NULL input; we
! 		 * just keep the prior transValue.
! 		 */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
--- 323,329 ----
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/* Skip NULL inputs for aggregates which desire that. */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
*************** advance_windowaggregate(WindowAggState *
*** 268,308 ****
  				return;
  			}
  		}
! 		if (peraggstate->noTransValue)
  		{
! 			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
! 			 * already checked that the agg's input type is binary-compatible
! 			 * with its transtype, so straight copy here is OK.)
! 			 *
! 			 * We must copy the datum into aggcontext if it is pass-by-ref. We
! 			 * do not need to pfree the old transValue, since it's NULL.
! 			 */
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->noTransValue = false;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  		if (peraggstate->transValueIsNull)
  		{
- 			/*
- 			 * Don't call a strict function with NULL inputs.  Note it is
- 			 * possible to get here despite the above tests, if the transfn is
- 			 * strict *and* returned a NULL on a prior cycle. If that happens
- 			 * we will propagate the NULL all the way to the end.
- 			 */
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  	}
  
  	/*
  	 * OK to call the transition function
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
--- 332,391 ----
  				return;
  			}
  		}
! 
! 		/*
! 		 * For strict transfer functions with initial value NULL we use the
! 		 * first non-NULL input as the initial state. (We already checked that
! 		 * the agg's input type is binary-compatible with its transtype, so
! 		 * straight copy here is OK.)
! 		 *
! 		 * We must copy the datum into aggcontext if it is pass-by-ref. We do
! 		 * not need to pfree the old transValue, since it's NULL.
! 		 */
! 		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->transValueCount = 1;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
+ 
+ 		/*
+ 		 * Don't call a strict function with NULL inputs.  Note it is possible
+ 		 * to get here despite the above tests, if the transfn is strict *and*
+ 		 * returned a NULL on a prior cycle. If that happens we will propagate
+ 		 * the NULL all the way to the end. That can only happen if there's no
+ 		 * inverse transition function, though, since we disallow transitions
+ 		 * back to NULL if there is one below.
+ 		 */
  		if (peraggstate->transValueIsNull)
  		{
  			MemoryContextSwitchTo(oldContext);
+ 			Assert(peraggstate->invtransfn_oid == InvalidOid);
  			return;
  		}
  	}
  
  	/*
+ 	 * We must track the number of inputs that we add to transValue, since
+ 	 * to remove the last input, retreat_windowaggregate() musn't call the
+ 	 * inverse transition function, but simply reset transValue back to its
+ 	 * initial value.
+ 	 *
+ 	 * Also update statistics
+ 	 */
+ 	Assert(peraggstate->transValueCount >= 0);
+ 	peraggstate->transValueCount++;
+ 	winstate->aggfwdtrans++;
+ 
+ 	/*
  	 * OK to call the transition function
+ 	 * Transfer functions with an inverse MUST not return NULL, see
+ 	 * retreat_windowaggregate()
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
*************** advance_windowaggregate(WindowAggState *
*** 310,316 ****
--- 393,407 ----
  							 (void *) winstate, NULL);
  	fcinfo->arg[0] = peraggstate->transValue;
  	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
  	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("transition function with an inverse returned NULL")));
+ 	}
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
*************** advance_windowaggregate(WindowAggState *
*** 322,328 ****
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
--- 413,558 ----
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
! 			newVal = datumCopy(newVal,
! 							   peraggstate->transtypeByVal,
! 							   peraggstate->transtypeLen);
! 		}
! 		if (!peraggstate->transValueIsNull)
! 			pfree(DatumGetPointer(peraggstate->transValue));
! 	}
! 
! 	MemoryContextSwitchTo(oldContext);
! 	peraggstate->transValue = newVal;
! 	peraggstate->transValueIsNull = fcinfo->isnull;
! }
! 
! /*
!  * retreat_windowaggregate
!  * removes tuples from aggregation.
!  * The calling function must ensure that each aggregate has
!  * a valid inverse transition function.
!  */
! static bool
! retreat_windowaggregate(WindowAggState *winstate,
! 						WindowStatePerFunc perfuncstate,
! 						WindowStatePerAgg peraggstate)
! {
! 	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
! 	int			numArguments = perfuncstate->numArguments;
! 	FunctionCallInfoData fcinfodata;
! 	FunctionCallInfo fcinfo = &fcinfodata;
! 	Datum		newVal;
! 	ListCell   *arg;
! 	int			i;
! 	MemoryContext oldContext;
! 	ExprContext *econtext = winstate->tmpcontext;
! 	ExprState  *filter = wfuncstate->aggfilter;
! 
! 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
! 
! 	/* Skip anything FILTERed out */
! 	if (filter)
! 	{
! 		bool		isnull;
! 		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
! 
! 		if (isnull || !DatumGetBool(res))
! 		{
! 			MemoryContextSwitchTo(oldContext);
! 			return true;
! 		}
! 	}
! 
! 	/* We start from 1, since the 0th arg will be the transition value */
! 	i = 1;
! 	foreach(arg, wfuncstate->args)
! 	{
! 		ExprState  *argstate = (ExprState *) lfirst(arg);
! 
! 		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
! 									  &fcinfo->argnull[i], NULL);
! 		i++;
! 	}
! 
! 	/* Skip inputs containing NULLS for aggregates that require this */
! 	if (peraggstate->invtransfn.fn_strict)
! 	{
! 		for (i = 1; i <= numArguments; i++)
! 		{
! 			if (fcinfo->argnull[i])
! 			{
! 				MemoryContextSwitchTo(oldContext);
! 				return true;
! 			}
! 		}
! 	}
! 
! 	/* There should still be an added but not yet removed value */
! 	Assert(peraggstate->transValueCount >= 1);
! 
! 	/*
! 	 * We mustn't use the inverse transition function to remove the last
! 	 * input. Doing so would yield a non-NULL state, whereas we should be
! 	 * in the initial state afterwards which may very well be NULL. So
! 	 * instead, we simply re-initialize the aggregation in this case.
! 	 */
! 	if (peraggstate->transValueCount == 1)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		initialize_windowaggregate(winstate,
! 								&winstate->perfunc[peraggstate->wfuncno],
! 								peraggstate);
! 		return true;
! 	}
! 
! 	/*
! 	 * Perform the inverse transition.
! 	 *
! 	 * For pairs of forward and inverse transition functions, the state may
! 	 * never be NULL, except in the ignore_nulls case, and then only until
! 	 * until we see the first non-NULL input during which time should never
! 	 * attempt to invoke the inverse transition function. Excluding NULL
! 	 * as a possible state value allows us to make it mean "sorry, can't
! 	 * do an inverse transition in this case" when returned by the inverse
! 	 * transition function. In that case, we report the failure to the
! 	 * caller.
! 	 */
! 	if (peraggstate->transValueIsNull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		elog(ERROR, "transition value is NULL during inverse transition");
! 	}
! 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
! 							 numArguments + 1,
! 							 perfuncstate->winCollation,
! 							 (void *) winstate, NULL);
! 	fcinfo->arg[0] = peraggstate->transValue;
! 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
! 	winstate->calledaggno = perfuncstate->aggno;
! 	newVal = FunctionCallInvoke(fcinfo);
! 	winstate->calledaggno = -1;
! 	if (fcinfo->isnull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		return false;
! 	}
! 
! 	/* Update number of added but not yet removed values and statistics */
! 	peraggstate->transValueCount--;
! 	winstate->agginvtrans++;
! 
! 	/*
! 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
! 	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
! 	 * first input, we don't need to do anything.
! 	 */
! 	if (!peraggstate->transtypeByVal &&
! 		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
! 	{
! 		if (!fcinfo->isnull)
! 		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
*************** advance_windowaggregate(WindowAggState *
*** 334,341 ****
--- 564,574 ----
  	MemoryContextSwitchTo(oldContext);
  	peraggstate->transValue = newVal;
  	peraggstate->transValueIsNull = fcinfo->isnull;
+ 
+ 	return true;
  }
  
+ 
  /*
   * finalize_windowaggregate
   * parallel to finalize_aggregate in nodeAgg.c
*************** finalize_windowaggregate(WindowAggState 
*** 370,376 ****
--- 603,611 ----
  		}
  		else
  		{
+ 			winstate->calledaggno = perfuncstate->aggno;
  			*result = FunctionCallInvoke(&fcinfo);
+ 			winstate->calledaggno = -1;
  			*isnull = fcinfo.isnull;
  		}
  	}
*************** finalize_windowaggregate(WindowAggState 
*** 392,402 ****
  	MemoryContextSwitchTo(oldContext);
  }
  
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * Much of this is duplicated from nodeAgg.c.  But NOTE that we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
--- 627,640 ----
  	MemoryContextSwitchTo(oldContext);
  }
  
+ 
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * This differes from nodeAgg.c in two ways. First, if the window's frame
!  * start position moves, we use the inverse transfer function (if it exists)
!  * to remove values from the transition value. And second, we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
*************** eval_windowaggregates(WindowAggState *wi
*** 406,417 ****
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs;
! 	int			i;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
--- 644,658 ----
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs,
! 				numaggs_restart = 0,
! 				i;
! 	int64		aggregatedupto_nonrestarted;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
! 	TupleTableSlot *temp_slot = winstate->temp_slot_1;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
*************** eval_windowaggregates(WindowAggState *wi
*** 420,426 ****
  	/* final output execution is in ps_ExprContext */
  	econtext = winstate->ss.ps.ps_ExprContext;
  	agg_winobj = winstate->agg_winobj;
- 	agg_row_slot = winstate->agg_row_slot;
  
  	/*
  	 * Currently, we support only a subset of the SQL-standard window framing
--- 661,666 ----
*************** eval_windowaggregates(WindowAggState *wi
*** 438,446 ****
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * For other frame start rules, we discard the aggregate state and re-run
! 	 * the aggregates whenever the frame head row moves.  We can still
! 	 * optimize as above whenever successive rows share the same frame head.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
--- 678,697 ----
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * We can still optimize as above whenever successive rows share the same
! 	 * frame head, but if the frame head moves beyond the aggregated base point
! 	 * we use the aggregate function's inverse transition function. This
! 	 * removes the tuple from aggregation and restores the aggregate's current
! 	 * state to what it would be if the removed row had never been aggregated
! 	 * in the first place. Inverse transition functions may optionally return
! 	 * NULL, this indicates that the function was unable to remove the tuple
! 	 * from aggregation, when this happens we must perform the aggregation all
! 	 * over again for all tuples in the new frame boundary.
! 	 *
! 	 * If the aggregate function does not have a inverse transition function
! 	 * and the frame head moves beyond the aggregated position then we must
! 	 * discard the aggregated state and re-aggregate similar to how we would
! 	 * have to if the inverse transition function had returned NULL.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
*************** eval_windowaggregates(WindowAggState *wi
*** 452,526 ****
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
- 	 *
- 	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
- 	 * some aggregates like SUM and COUNT we could avoid that by implementing
- 	 * a "negative transition function" that would be called for each row as
- 	 * it exits the frame.	We'd have to think about avoiding recalculation of
- 	 * volatile arguments of aggregate functions, too.
  	 */
  
  	/*
  	 * First, update the frame head position.
  	 */
! 	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
  
  	/*
! 	 * Initialize aggregates on first call for partition, or if the frame head
! 	 * position moved since last time.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos != winstate->aggregatedbase)
  	{
- 		/*
- 		 * Discard transient aggregate values
- 		 */
- 		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
- 
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  
  		/*
! 		 * If we created a mark pointer for aggregates, keep it pushed up to
! 		 * frame head, so that tuplestore can discard unnecessary rows.
  		 */
! 		if (agg_winobj->markptr >= 0)
! 			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
  
  		/*
! 		 * Initialize for loop below
  		 */
! 		ExecClearTuple(agg_row_slot);
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 		winstate->aggregatedupto = winstate->frameheadpos;
  	}
  
  	/*
! 	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
! 	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
! 	 * have to recalculate when the frame head moves or currentpos has
! 	 * advanced past the place we'd aggregated up to.  Check for these cases
! 	 * and if so, reuse the saved result values.
  	 */
! 	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
! 		for (i = 0; i < numaggs; i++)
  		{
- 			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
! 		return;
  	}
  
  	/*
--- 703,889 ----
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
  	 */
  
  	/*
  	 * First, update the frame head position.
+ 	 *
+ 	 * The frame head should never move backwards, and the code below wouldn't
+ 	 * cope if it did, so for safety we complain if it does.
  	 */
! 	update_frameheadpos(agg_winobj, temp_slot);
! 	if (winstate->frameheadpos < winstate->aggregatedbase)
! 		elog(ERROR, "frame moved backwards unexpectedly");
  
  	/*
! 	 * If the frame didn't change compared to the previous row, we can re-use
! 	 * the cached result. Since we don't know the current frame's end yet, we
! 	 * cannot check that the obvious way. But we can exploit that if the frame
! 	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
! 	 * row lies within the previous row's frame, the two frame's ends must
! 	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
! 	 * so we don't need to check for that explicitly here.
  	 */
! 	if (winstate->aggregatedbase == winstate->frameheadpos &&
! 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
! 		}
! 		return;
! 	}
! 
! 	/* Initialize restart flags.
! 	 *
! 	 * We restart the aggregation
! 	 *   - if we're processing the first row in the partition, or
! 	 *   - if we the frame's head moved and we cannot use an inverse
! 	 *     transition function, or
! 	 *   - if the new frame doesn't overlap the old one
! 	 *
! 	 * Note that we don't strictly need to restart in the last case, but
! 	 * if we're going to remove *all* rows from the aggregation anyway, a
! 	 * restart surely is faster.
! 	 */
! 	for (i = 0; i < numaggs; i++)
! 	{
! 		peraggstate = &winstate->peragg[i];
! 		if (winstate->currentpos == 0 ||
! 			(winstate->aggregatedbase < winstate->frameheadpos &&
! 			!peraggstate->use_invtransfn) ||
! 			winstate->aggregatedupto <= winstate->frameheadpos)
! 		{
! 			peraggstate->restart = true;
! 			numaggs_restart++;
  		}
+ 		else
+ 			peraggstate->restart = false;
+ 	}
  
+ 	/*
+ 	 * Attempt to update aggregatedbase to match the frame's head by
+ 	 * removing those inputs from the aggregations which fell off the top
+ 	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+ 	 * return false, in which case we restart that aggregate below.
+ 	 *
+ 	 * Aftwards, aggregatedbase equals frameheadpos.
+ 	 */
+ 	while(winstate->aggregatedbase < winstate->frameheadpos)
+ 	{
  		/*
! 		 * Fetch the tuple where the current aggregation started from.
! 		 * This should never fail as we should have been here before.
  		 */
! 		if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
! 								 temp_slot))
! 			elog(ERROR, "Unable to find tuple in tuplestore");
! 
! 		/* Set tuple context for evaluation of aggregate arguments */
! 		winstate->tmpcontext->ecxt_outertuple = temp_slot;
  
  		/*
! 		 * Perform the inverse transition for each aggregate function in
! 		 * the window, unless it has already been marked as needing a
! 		 * restart.
  		 */
! 		for (i = 0; i < numaggs; i++)
! 		{
! 			bool	ok;
! 
! 			peraggstate = &winstate->peragg[i];
! 			if (peraggstate->restart)
! 				continue;
! 
! 			wfuncno = peraggstate->wfuncno;
! 			ok = retreat_windowaggregate(winstate,
! 										 &winstate->perfunc[wfuncno],
! 										 peraggstate);
! 			if (!ok)
! 			{
! 				/* Inverse transition function has failed, must restart */
! 				peraggstate->restart = true;
! 				numaggs_restart++;
! 			}
! 		}
! 
! 		/* Reset per-input-tuple context after each tuple */
! 		ResetExprContext(winstate->tmpcontext);
! 
! 		/* And advance the aggregated-row state */
! 		winstate->aggregatedbase++;
! 		ExecClearTuple(temp_slot);
! 
! 		/* If no more retreatable aggregates are left, we stop early */
! 		if (numaggs_restart == numaggs)
! 		{
! 			winstate->aggregatedbase = winstate->frameheadpos;
! 			break;
! 		}
  	}
  
  	/*
! 	 * If we created a mark pointer for aggregates, keep it pushed up to
! 	 * frame head, so that tuplestore can discard unnecessary rows.
  	 */
! 	if (agg_winobj->markptr >= 0)
! 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
! 
! 	/*
! 	 * Then restart the aggregates which require it.
! 	 *
! 	 * We assume that aggregates using the shared context always restart
! 	 * if *any* aggregate restarts, and we may thus cleanup the shared
! 	 * aggcontext if that is the case. The private aggcontexts are reset
! 	 * by initialize_windowaggregate() if their owning aggregate restarts,
! 	 * otherwise we just pfree() the cached result.
! 	 */
! 	if (numaggs_restart > 0)
! 		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < numaggs; i++)
  	{
! 		peraggstate = &winstate->peragg[i];
! 		/* Aggregates using the shared ctx must restart if *any* agg does */
! 		Assert(!peraggstate->aggcontext_is_shared ||
! 			   !numaggs_restart || peraggstate->restart);
! 		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
! 			!peraggstate->resulttypeByVal)
! 		{
! 			pfree(DatumGetPointer(peraggstate->resultValue));
! 			peraggstate->resultValue = (Datum) 0;
! 			peraggstate->resultValueIsNull = true;
! 		}
! 		else if (peraggstate->restart)
  		{
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
! 	}
! 
! 	/*
! 	 * Non-restarted aggregates now contain the rows between aggregatedbase
! 	 * (i.e. frameheadpos) and aggregatedupto, and restarted aggregates
! 	 * contain no rows. If there are any restarted aggregates, we must thus
! 	 * begin aggregating anew at frameheadpos, otherwise we may simply
! 	 * continue at aggregatedupto. Since we possibly reset aggregatedupto, we
! 	 * must remember the old value to know how long to skip non-restarted
! 	 * aggregates. If we modify aggregatedupto, we must also clear
! 	 * agg_row_slot, per the loop invariant below.
! 	 */
! 	aggregatedupto_nonrestarted = winstate->aggregatedupto;
! 	if (numaggs_restart > 0 &&
! 		winstate->aggregatedupto != winstate->frameheadpos)
! 	{
! 		winstate->aggregatedupto = winstate->frameheadpos;
! 		ExecClearTuple(agg_row_slot);
  	}
  
  	/*
*************** eval_windowaggregates(WindowAggState *wi
*** 551,556 ****
--- 914,924 ----
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
+ 			/* Non-restarted aggs skip until aggregatedupto_nonrestarted */
+ 			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+ 				!peraggstate->restart)
+ 				continue;
+ 
  			wfuncno = peraggstate->wfuncno;
  			advance_windowaggregate(winstate,
  									&winstate->perfunc[wfuncno],
*************** eval_windowaggregates(WindowAggState *wi
*** 565,570 ****
--- 933,941 ----
  		ExecClearTuple(agg_row_slot);
  	}
  
+ 	/* The frame's end is not supposed to move backwards, ever */
+ 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+ 
  	/*
  	 * finalize aggregates and fill result/isnull fields.
  	 */
*************** eval_windowaggregates(WindowAggState *wi
*** 589,616 ****
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal)
  		{
! 			/*
! 			 * clear old resultValue in order not to leak memory.  (Note: the
! 			 * new result can't possibly be the same datum as old resultValue,
! 			 * because we never passed it to the trans function.)
! 			 */
! 			if (!peraggstate->resultValueIsNull)
! 				pfree(DatumGetPointer(peraggstate->resultValue));
! 
! 			/*
! 			 * If pass-by-ref, copy it into our aggregate context.
! 			 */
! 			if (!*isnull)
! 			{
! 				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
! 				peraggstate->resultValue =
! 					datumCopy(*result,
! 							  peraggstate->resulttypeByVal,
! 							  peraggstate->resulttypeLen);
! 				MemoryContextSwitchTo(oldContext);
! 			}
  		}
  		else
  		{
--- 960,973 ----
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal && !*isnull)
  		{
! 			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
! 			peraggstate->resultValue =
! 				datumCopy(*result,
! 						  peraggstate->resulttypeByVal,
! 						  peraggstate->resulttypeLen);
! 			MemoryContextSwitchTo(oldContext);
  		}
  		else
  		{
*************** eval_windowfunction(WindowAggState *wins
*** 651,656 ****
--- 1008,1014 ----
  	/* Just in case, make all the regular argument slots be null */
  	memset(fcinfo.argnull, true, perfuncstate->numArguments);
  
+ 	winstate->calledaggno = -1;
  	*result = FunctionCallInvoke(&fcinfo);
  	*isnull = fcinfo.isnull;
  
*************** spool_tuples(WindowAggState *winstate, i
*** 794,800 ****
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kluge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
--- 1152,1158 ----
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kludge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
*************** release_partition(WindowAggState *winsta
*** 869,875 ****
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
--- 1227,1236 ----
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < winstate->numaggs; ++i)
! 		if (!winstate->peragg[i].aggcontext_is_shared)
! 			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1419,1425 ****
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno;
  	ListCell   *l;
  
  	/* check for unsupported flags */
--- 1780,1787 ----
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno,
! 				numaggs_invtrans;
  	ListCell   *l;
  
  	/* check for unsupported flags */
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1450,1457 ****
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived context for aggregate trans values etc */
! 	winstate->aggcontext =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
--- 1812,1821 ----
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived contexts for aggregate trans values etc
! 	 * Note that invertible aggregates use their own private context
! 	 */
! 	winstate->aggcontext_shared =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1535,1540 ****
--- 1899,1905 ----
  
  	wfuncno = -1;
  	aggno = -1;
+ 	numaggs_invtrans = 0;
  	foreach(l, winstate->funcs)
  	{
  		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1603,1608 ****
--- 1968,1975 ----
  			peraggstate = &winstate->peragg[aggno];
  			initialize_peragg(winstate, wfunc, peraggstate);
  			peraggstate->wfuncno = wfuncno;
+ 			if (peraggstate->use_invtransfn)
+ 				numaggs_invtrans++;
  		}
  		else
  		{
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1618,1623 ****
--- 1985,1991 ----
  	/* Update numfuncs, numaggs to match number of unique functions found */
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
+ 	winstate->numaggs_invtrans = numaggs_invtrans;
  
  	/* Set up WindowObject for aggregates, if needed */
  	if (winstate->numaggs > 0)
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1646,1651 ****
--- 2014,2026 ----
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	/* initialize temporary data */
+ 	winstate->calledaggno = -1;
+ 
+ 	/* initialize statistics */
+ 	winstate->aggfwdtrans = 0;
+ 	winstate->agginvtrans = 0;
+ 
  	return winstate;
  }
  
*************** void
*** 1657,1668 ****
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
  
  	release_partition(node);
  
- 	pfree(node->perfunc);
- 	pfree(node->peragg);
- 
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
--- 2032,2041 ----
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
+ 	int			i;
  
  	release_partition(node);
  
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
*************** ExecEndWindowAgg(WindowAggState *node)
*** 1677,1683 ****
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
--- 2050,2062 ----
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext_shared);
! 	for(i = 0; i < node->numaggs; i++)
! 		if (!node->peragg[i].aggcontext_is_shared)
! 			MemoryContextDelete(node->peragg[i].aggcontext);
! 
! 	pfree(node->perfunc);
! 	pfree(node->peragg);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
*************** initialize_peragg(WindowAggState *winsta
*** 1735,1742 ****
--- 2114,2123 ----
  	Oid			aggtranstype;
  	AclResult	aclresult;
  	Oid			transfn_oid,
+ 				invtransfn_oid,
  				finalfn_oid;
  	Expr	   *transfnexpr,
+ 			   *invtransfnexpr,
  			   *finalfnexpr;
  	Datum		textInitVal;
  	int			i;
*************** initialize_peragg(WindowAggState *winsta
*** 1762,1767 ****
--- 2143,2149 ----
  	 */
  
  	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+ 	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
  	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
  	/* Check that aggregate owner has permission to call component fns */
*************** initialize_peragg(WindowAggState *winsta
*** 1783,1788 ****
--- 2165,2181 ----
  			aclcheck_error(aclresult, ACL_KIND_PROC,
  						   get_func_name(transfn_oid));
  		InvokeFunctionExecuteHook(transfn_oid);
+ 
+ 		if (OidIsValid(invtransfn_oid))
+ 		{
+ 			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+ 										 ACL_EXECUTE);
+ 			if (aclresult != ACLCHECK_OK)
+ 				aclcheck_error(aclresult, ACL_KIND_PROC,
+ 				get_func_name(invtransfn_oid));
+ 			InvokeFunctionExecuteHook(invtransfn_oid);
+ 		}
+ 
  		if (OidIsValid(finalfn_oid))
  		{
  			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
*************** initialize_peragg(WindowAggState *winsta
*** 1810,1822 ****
--- 2203,2223 ----
  							wfunc->wintype,
  							wfunc->inputcollid,
  							transfn_oid,
+ 							invtransfn_oid,
  							finalfn_oid,
  							&transfnexpr,
+ 							&invtransfnexpr,
  							&finalfnexpr);
  
  	fmgr_info(transfn_oid, &peraggstate->transfn);
  	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+ 		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+ 	}
+ 
  	if (OidIsValid(finalfn_oid))
  	{
  		fmgr_info(finalfn_oid, &peraggstate->finalfn);
*************** initialize_peragg(WindowAggState *winsta
*** 1860,1867 ****
--- 2261,2334 ----
  							wfunc->winfnoid)));
  	}
  
+ 	/*
+ 	 * Allowing only the forward transition function to be strict would
+ 	 * require handling more special cases in advance_windowaggregate() and
+ 	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 	 * that if the forward transition function is strict that the inverse
+ 	 * transition function is also strict. This should have been checked at
+ 	 * the aggregate function's definition time, but it's better to be safe...
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 				errmsg("strictness of forward and inverse transition functions must match")));
+ 	}
+ 
  	ReleaseSysCache(aggTuple);
  
+ 	/*
+ 	 * We can use the inverse transition function only if the aggregate's
+ 	 * arguments don't contain calls to volatile functions. Otherwise,
+ 	 * the difference between restarting and not restarting the aggregation
+ 	 * would be user-visible. Note that this check also covers the case where
+ 	 * the FILTER's WHERE clause contains a volatile function. If the frame
+ 	 * head cannot move, we won't ever need the inverse transition function,
+ 	 * so we also mark as "don't use" in that case.
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		!contain_volatile_functions((Node *) wfunc) &&
+ 		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+ 	{
+ 		peraggstate->use_invtransfn = true;
+ 	}
+ 	else
+ 	{
+ 		peraggstate->use_invtransfn = false;
+ 	}
+ 
+ 	/*
+ 	 * Invertible aggregates use their own aggcontext.
+ 	 *
+ 	 * This is necessary because they might all restart at different times,
+ 	 * so we might never be able to reset the shared context otherwise. We
+ 	 * can't make it the aggregate's responsibility to clean up after
+ 	 * themselves, because strict aggregates must be restarted whenever we
+ 	 * remove their last non-NULL input, which the aggregate won't be aware
+ 	 * is happening. Also, just pfree()ing the transValue upon restarting
+ 	 * wouldn't help, since we'd miss any indirectly referenced data. We
+ 	 * could, in theory, declare that aggregates with a state type other then
+ 	 * "internal" musn't allocate anything in the aggcontext themselves, that
+ 	 * non-strict aggregates with state type internal must clean up after
+ 	 * themselves when their inverse transfer function returns NULL, and then
+ 	 * only use private aggcontexts for strict aggregates with state type
+ 	 * internal. But that'd be a rather grotty set of requirements.
+ 	 */
+ 	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+ 	if (!peraggstate->aggcontext_is_shared)
+ 	{
+ 		peraggstate->aggcontext =
+ 			AllocSetContextCreate(CurrentMemoryContext,
+ 								  "WindowAgg_AggregatePrivate",
+ 								  ALLOCSET_DEFAULT_MINSIZE,
+ 								  ALLOCSET_DEFAULT_INITSIZE,
+ 								  ALLOCSET_DEFAULT_MAXSIZE);
+ 	}
+ 	else
+ 		peraggstate->aggcontext = winstate->aggcontext_shared;
+ 
  	return peraggstate;
  }
  
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..ef84e4c 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** resolve_aggregate_transtype(Oid aggfunci
*** 1187,1197 ****
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid and finalfn_oid identify the funcs to be called; the latter
!  * may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr and
!  * *finalfnexpr.  The latter is set to NULL if there's no finalfn.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
--- 1187,1199 ----
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid, invtransfn_oid and finalfn_oid identify the funcs to be
!  * called; the latter two may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr,
!  * *invtransfnexpr and *finalfnexpr. If there is invtransfn or finalfn, the
!  * respective pointers are set to NULL. Since use of the invtransfn is
!  * optional, NULL may be passed for invtransfnexpr.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1203,1210 ****
--- 1205,1214 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr)
  {
  	Param	   *argp;
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1249,1254 ****
--- 1253,1270 ----
  	fexpr->funcvariadic = agg_variadic;
  	*transfnexpr = (Expr *) fexpr;
  
+ 	if (OidIsValid(invtransfn_oid) && invtransfnexpr != NULL)
+ 	{
+ 		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+ 												agg_result_type,
+ 												args,
+ 												InvalidOid,
+ 												agg_input_collation,
+ 												COERCE_EXPLICIT_CALL);
+ 	}
+ 	else if (invtransfnexpr != NULL)
+ 		*invtransfnexpr	= NULL;
+ 
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
  	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2653ef0..c14209f 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11547,11552 ****
--- 11547,11553 ----
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
+ 	int			i_agginvtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
  	int			i_hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11555,11560 ****
--- 11556,11562 ----
  	int			i_agginitval;
  	int			i_convertok;
  	const char *aggtransfn;
+ 	const char *agginvtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
  	char	   *aggsortconvop;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11580,11586 ****
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
--- 11582,11588 ----
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11596,11601 ****
--- 11598,11604 ----
  	else if (fout->remoteVersion >= 80400)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11611,11616 ****
--- 11614,11620 ----
  	else if (fout->remoteVersion >= 80100)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11624,11629 ****
--- 11628,11634 ----
  	else if (fout->remoteVersion >= 70300)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11636,11642 ****
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
--- 11641,11649 ----
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
! 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
! 						  "aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11649,11654 ****
--- 11656,11662 ----
  	else
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn1 AS aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, "
  						  "(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
  						  "0 AS aggsortop, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11663,11668 ****
--- 11671,11677 ----
  	res = ExecuteSqlQueryForSingleRow(fout, query->data);
  
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
+ 	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
  	i_hypothetical = PQfnumber(res, "hypothetical");
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11672,11677 ****
--- 11681,11687 ----
  	i_convertok = PQfnumber(res, "convertok");
  
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ 	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11733,11738 ****
--- 11743,11754 ----
  						  fmtId(aggtranstype));
  	}
  
+ 	if (strcmp(agginvtransfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    INVSFUNC = %s",
+ 						  agginvtransfn);
+ 	}
+ 
  	if (strcmp(aggtransspace, "0") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    SSPACE = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index f189998..3412661 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 46,51 ****
--- 46,52 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		agginvtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 69,85 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_agginvtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,277 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 103,279 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	-	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
*************** extern Oid AggregateCreate(const char *a
*** 289,294 ****
--- 291,297 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index aed81cd..c4d4864 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** extern void **find_rendezvous_variable(c
*** 645,652 ****
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly with nodeAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
--- 645,654 ----
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
!  * which is in execute/nodeWindowAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly neither nodeAgg
!  * nor nodeWindowAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..03ee793 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WindowAggState
*** 1738,1743 ****
--- 1738,1744 ----
  	List	   *funcs;			/* all WindowFunc nodes in targetlist */
  	int			numfuncs;		/* total number of window functions */
  	int			numaggs;		/* number that are plain aggregates */
+ 	int			numaggs_invtrans;	/* number that are invertible aggregates */
  
  	WindowStatePerFunc perfunc; /* per-window-function information */
  	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
*************** typedef struct WindowAggState
*** 1762,1768 ****
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext;	/* context for each aggregate data */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
--- 1763,1769 ----
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext_shared;	/* shared context for agg states */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
*************** typedef struct WindowAggState
*** 1780,1789 ****
--- 1781,1797 ----
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
  
+ 	/* temporary data */
+ 	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+ 
  	/* temporary slots for tuples fetched back from tuplestore */
  	TupleTableSlot *agg_row_slot;
  	TupleTableSlot *temp_slot_1;
  	TupleTableSlot *temp_slot_2;
+ 
+ 	/* Statistics */
+ 	double	aggfwdtrans;	/* number of forward transitions */
+ 	double	agginvtrans;	/* number of inverse transitions */
  } WindowAggState;
  
  /* ----------------
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void build_aggregate_fnexprs(Oid 
*** 39,46 ****
--- 39,48 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr);
  
  #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..080cbd2 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select least_agg(variadic array[q1,q2]) 
*** 1580,1582 ****
--- 1580,1606 ----
   -4567890123456789
  (1 row)
  
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..1a52423 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** alter aggregate my_rank(VARIADIC "any" O
*** 90,92 ****
--- 90,129 ----
   public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
  (2 rows)
  
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ ERROR:  strictness of forward and inverse transition functions must match
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ ERROR:  function intminus(double precision, double precision) does not exist
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
+ ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..e3eba47 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT nth_value_def(ten) OVER (PARTITIO
*** 1071,1073 ****
--- 1071,1312 ----
               1 |   3 |    3
  (10 rows)
  
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+ ----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+  1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+  1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+  1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+  1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+  1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+  2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+  3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+ (9 rows)
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+ ----------+--------------+-------------------+-------------+------------------
+  1,1:NULL | +NULL        | I+NULL            |             | I
+  1,2:-    | +NULL        | I+NULL            |             | I
+  1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+  1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+  1,5:-    |              | I                 |             | I
+  1,6:-    |              | I                 |             | I
+  2,1:-    |              | I                 |             | I
+  2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+  3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+ (9 rows)
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+  logging_agg_strict 
+ --------------------
+  a
+  b
+  c
+ (3 rows)
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+  eq1 | eq2 | eq3 
+ -----+-----+-----
+  t   | t   | t
+ (1 row)
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the boolean inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the MIN and MAX inverse transition functions
+ --
+ --
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..34f7004 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** drop view aggordview1;
*** 590,592 ****
--- 590,609 ----
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..130489c 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** alter aggregate my_rank(VARIADIC "any" O
*** 101,103 ****
--- 101,144 ----
    rename to test_rank;
  
  \da test_*
+ 
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ 
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ 
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ 
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..bd8dbdc 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** SELECT nth_value_def(n := 2, val := ten)
*** 284,286 ****
--- 284,495 ----
  
  SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
    FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+ 
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ 
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ 
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ 
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ 
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ 
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ 
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ 
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the boolean inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the MIN and MAX inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
invtrans_bool_73bf630.patchapplication/octet-stream; name=invtrans_bool_73bf630.patchDownload
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index d419b4a..593460d 100644
*** a/src/backend/utils/adt/bool.c
--- b/src/backend/utils/adt/bool.c
*************** boolge(PG_FUNCTION_ARGS)
*** 285,290 ****
--- 285,292 ----
  
  /* function for standard EVERY aggregate implementation conforming to SQL 2003.
   * must be strict. It is also named bool_and for homogeneity.
+  * Note: this is no longer used for the bool_and() and every() aggregate
+  * functions.
   */
  Datum
  booland_statefunc(PG_FUNCTION_ARGS)
*************** booland_statefunc(PG_FUNCTION_ARGS)
*** 294,302 ****
--- 296,400 ----
  
  /* function for standard ANY/SOME aggregate conforming to SQL 2003.
   * must be strict. The name of the aggregate is bool_or. See the doc.
+  * Note: this is no longer used for the bool_or aggregate function.
   */
  Datum
  boolor_statefunc(PG_FUNCTION_ARGS)
  {
  	PG_RETURN_BOOL(PG_GETARG_BOOL(0) || PG_GETARG_BOOL(1));
  }
+ 
+ typedef struct BoolAggState
+ {
+ 	int64 aggcount; /* number of values aggregated */
+ 	int64 aggtrue; /* number of values aggregated which are true */
+ } BoolAggState;
+ 
+ static BoolAggState *
+ makeBoolAggState(FunctionCallInfo fcinfo)
+ {
+ 	BoolAggState *state;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 	state = (BoolAggState *) palloc(sizeof(BoolAggState));
+ 	state->aggcount = 0;
+ 	state->aggtrue = 0;
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 
+ 	return state;
+ }
+ 
+ Datum
+ bool_accum(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* Create the state data on first call */
+ 	if (state == NULL)
+ 		state = makeBoolAggState(fcinfo);
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount++;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue++;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* bool_accum should have created the state data */
+ 	if (state == NULL)
+ 		elog(ERROR, "bool_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount--;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue--;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_alltrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if all values are true */
+ 	PG_RETURN_BOOL(state->aggcount == state->aggtrue);
+ }
+ 
+ Datum
+ bool_anytrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if any value is true */
+ 	PG_RETURN_BOOL(state->aggtrue > 0);
+ }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..7289487 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2828	n 0 float8_regr_accum
*** 232,240 ****
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
--- 232,240 ----
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
! DATA(insert ( 2518	n 0 bool_accum		bool_accum_inv		bool_anytrue	59	2281		16	_null_ ));
! DATA(insert ( 2519	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..41031cd 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 939  (  generate_serie
*** 3860,3869 ****
--- 3860,3871 ----
  DESCR("non-persistent series generator");
  
  /* boolean aggregates */
+ /* previous aggregate transition functions, unused now */
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ /* aggregates and new invertible transition functions */
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
*************** DATA(insert OID = 2518 ( bool_or					   
*** 3871,3876 ****
--- 3873,3886 ----
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
+ DATA(insert OID = 4069 ( bool_accum					   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 4070 ( bool_accum_inv				   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4071 ( bool_alltrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_alltrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
+ DATA(insert OID = 4072 ( bool_anytrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_anytrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..d6f2e2f 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum boolle(PG_FUNCTION_ARGS);
*** 121,126 ****
--- 121,130 ----
  extern Datum boolge(PG_FUNCTION_ARGS);
  extern Datum booland_statefunc(PG_FUNCTION_ARGS);
  extern Datum boolor_statefunc(PG_FUNCTION_ARGS);
+ extern Datum bool_accum(PG_FUNCTION_ARGS);
+ extern Datum bool_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum bool_alltrue(PG_FUNCTION_ARGS);
+ extern Datum bool_anytrue(PG_FUNCTION_ARGS);
  extern bool parse_bool(const char *value, bool *result);
  extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
  
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index e3eba47..fcb07c8 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1300,1305 ****
--- 1300,1317 ----
  -- Test the boolean inverse transition functions
  --
  --
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  b | bool_and | bool_or 
+ ---+----------+---------
+  t | t        | t
+  t | f        | t
+  f | f        | f
+  f | f        | t
+  t | t        | t
+ (5 rows)
+ 
  --
  --
  -- Test the MIN and MAX inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index bd8dbdc..dcb410e 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 482,487 ****
--- 482,491 ----
  --
  --
  
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
  --
  --
  -- Test the MIN and MAX inverse transition functions
invtrans_collecting_2e5f19.patchapplication/octet-stream; name=invtrans_collecting_2e5f19.patchDownload
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index c62e3fb..5a3a31d 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** create_singleton_array(FunctionCallInfo 
*** 471,477 ****
  
  
  /*
!  * ARRAY_AGG aggregate function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
--- 471,477 ----
  
  
  /*
!  * ARRAY_AGG aggregate transition function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 508,513 ****
--- 508,537 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * ARRAY_AGG aggregate inverse transition function
+  */
+ Datum
+ array_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since NULLs
+ 	 * need to be removed just like any other value.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "array_agg_invtransfn called with NULL state");
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "array_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	shiftArrayResult(state, 1);
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..2dd7ecb 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** accumArrayResult(ArrayBuildState *astate
*** 4587,4592 ****
--- 4587,4593 ----
  		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
  		astate->mcontext = arr_context;
  		astate->alen = 64;		/* arbitrary starting array size */
+ 		astate->offset = 0;
  		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
  		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
  		astate->nelems = 0;
*************** accumArrayResult(ArrayBuildState *astate
*** 4600,4606 ****
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/* enlarge dvalues[]/dnulls[] if needed */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
--- 4601,4612 ----
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/*
! 		 * If the buffers are filled completely (offset must be zero then),
! 		 * we double their size. If they aren't, but the values extend to the
! 		 * end of the buffers, we reclaim wasted space at the beginning by
! 		 * moving the values to the front of the buffers.
! 		 */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
*************** accumArrayResult(ArrayBuildState *astate
*** 4609,4614 ****
--- 4615,4629 ----
  			astate->dnulls = (bool *)
  				repalloc(astate->dnulls, astate->alen * sizeof(bool));
  		}
+ 		else if (astate->offset + astate->nelems >= astate->alen)
+ 		{
+ 			memmove(astate->dvalues, astate->dvalues + astate->offset,
+ 					astate->alen * sizeof(Datum));
+ 			memmove(astate->dnulls, astate->dnulls + astate->offset,
+ 					astate->alen * sizeof(bool));
+ 			astate->offset = 0;
+ 		}
+ 		Assert(astate->offset + astate->nelems < astate->alen);
  	}
  
  	/*
*************** accumArrayResult(ArrayBuildState *astate
*** 4627,4634 ****
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->nelems] = dvalue;
! 	astate->dnulls[astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
--- 4642,4649 ----
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->offset + astate->nelems] = dvalue;
! 	astate->dnulls[astate->offset + astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
*************** accumArrayResult(ArrayBuildState *astate
*** 4637,4642 ****
--- 4652,4698 ----
  }
  
  /*
+  * shiftArrayResult - shift leading Datums out of an array result
+  *
+  *	astate is working state
+  *	count is the number of leading Datums to shift out
+  *
+  * If count is equal to or larger than the number of relements, the array
+  * result is empty afterwards. If astate is NULL, nothing is done.
+  */
+ void
+ shiftArrayResult(ArrayBuildState *astate, int count)
+ {
+ 	int		i;
+ 
+ 	if (astate == NULL)
+ 		return;
+ 
+ 	/* Limit shift count to number of elements for safety */
+ 	count = Min(count, astate->nelems);
+ 
+ 	/* For pass-by-ref types, free values we shift out */
+ 	if (!astate->typbyval) {
+ 		for(i = astate->offset; i < astate->offset + count; ++i) {
+ 			if (astate->dnulls[i])
+ 				continue;
+ 
+ 			pfree(DatumGetPointer(astate->dvalues[i]));
+ 
+ 			/* For cleanliness' sake */
+ 			astate->dnulls[i] = false;
+ 			astate->dvalues[i] = 0;
+ 		}
+ 
+ 	}
+ 
+ 	/* Update state */
+ 	astate->nelems -= count;
+ 	astate->offset += count;
+ }
+ 
+ 
+ /*
   * makeArrayResult - produce 1-D final result of accumArrayResult
   *
   *	astate is working state (not NULL)
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4679,4686 ****
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues,
! 								astate->dnulls,
  								ndims,
  								dims,
  								lbs,
--- 4735,4742 ----
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues + astate->offset,
! 								astate->dnulls + astate->offset,
  								ndims,
  								dims,
  								lbs,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index cb07a06..74fe7fc 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** typedef struct
*** 50,55 ****
--- 50,63 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct StringAggState
+ {
+ 	StringInfoData 	string; 	/* Contents */
+ 	int 			offset;		/* Offset into stringinfo's data */
+ 	int 			delimLen;	/* Delim length, -1 initially, -2 if multiple */
+ 	int64			notNullCount;/* Number of non-NULL inputs */
+ } StringAggState;
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
*************** static void appendStringInfoText(StringI
*** 78,84 ****
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
--- 86,95 ----
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo);
! static void prepareAppendStringAggState(StringAggState *state,
! 										int delimLen, int valueLen);
! static bool removeFromStringAggState(StringAggState *state, int valueLen);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
*************** static void text_format_string_conversio
*** 92,97 ****
--- 103,226 ----
  static void text_format_append_string(StringInfo buf, const char *str,
  						  int flags, int width);
  
+ /*****************************************************************************
+  *	 SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA)			 *
+  *****************************************************************************/
+ 
+ /*
+  * subroutine to initialize state
+  */
+ static StringAggState*
+ makeStringAggState(FunctionCallInfo fcinfo)
+ {
+ 	StringAggState*	state;
+ 	MemoryContext aggcontext;
+ 	MemoryContext oldcontext;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 	}
+ 
+ 	/*
+ 	 * Create state in aggregate context.  It'll stay there across subsequent
+ 	 * calls.
+ 	 */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = (StringAggState *) palloc(sizeof(StringAggState));
+ 	initStringInfo(&state->string);
+ 	state->offset = 0;
+ 	state->delimLen = -1;
+ 	state->notNullCount = 0;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * Prepare state for appending a value and a delimiter with specified lengths.
+  * pass -1 for delimLen if no delimiter will be added
+  */
+ void
+ prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen)
+ {
+ 	/*
+ 	 * Reclaim wasted space
+ 	 *
+ 	 * We move the contents to the left if the current contents fit into the
+ 	 * wasted space, i.e. if we waste more than we store. The limit is
+ 	 * somewhat arbitrary, but it's the smallest one that allows
+ 	 * memcpy to be used, because the source and destination don't overlap.
+ 	 * Note that we must check for <, not <=, because we include the trailing
+ 	 * '\0' in the copy.
+ 	 */
+ 	if (state->string.len - state->offset < state->offset)
+ 	{
+ 		state->string.len -= state->offset;
+ 		memcpy(state->string.data, state->string.data + state->offset,
+ 			   state->string.len + 1);
+ 		state->offset = 0;
+ 	}
+ 
+ 	/*
+ 	 * Enlarge StringInfo
+ 	 *
+ 	 * Not strictly necessary, but avoids potentially resizing twice when
+ 	 * the actual append... calls are done by the caller
+ 	 */
+ 	enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen);
+ 
+ 
+ 	/* Track delimiter length */
+ 	if (delimLen == -1)
+ 		{} /* Not specified, don't update */
+ 	else if (state->delimLen == -1)
+ 		state->delimLen = delimLen;
+ 	else if (state->delimLen != delimLen)
+ 		state->delimLen = -2;
+ }
+ 
+ /*
+  * Remove value with given length and the delimiter that follows
+  *
+  * Returns false if removal was impossible because delimiters varied
+  */
+ bool
+ removeFromStringAggState(StringAggState *state, int valueLen)
+ {
+ 	/* Remove the string */
+ 	state->offset += valueLen;
+ 
+ 	/*
+ 	 * Remove delimiter if necessary.
+ 	 *
+ 	 * The delimiter we need to remove isn't the delimiter we were passed, but
+ 	 * rather the delimiter passed when adding the input *after* this one. We
+ 	 * thus need the delimiter length to be all the same to be able to proceed.
+ 	 * If we're removing the last string, there will be no delimiter following
+ 	 * it. In that case, we may reset delimLen to its initial value.
+ 	 */
+ 	if (state->delimLen == -2)
+ 		return false;
+ 	if (state->offset < state->string.len)
+ 	{
+ 		Assert(state->delimLen >= 0);
+ 		state->offset += state->delimLen;
+ 	}
+ 	else
+ 		state->delimLen = -1;
+ 
+ 	/* Don't crash if we're ever asked to remove more than was added */
+ 	if (state->offset > state->string.len)
+ 	{
+ 		state->offset = state->string.len;
+ 		elog(ERROR, "tried to remove more data than was aggregated");
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /*****************************************************************************
   *	 CONVERSION ROUTINES EXPORTED FOR USE BY C CODE							 *
*************** byteasend(PG_FUNCTION_ARGS)
*** 408,435 ****
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		bytea	   *value = PG_GETARG_BYTEA_PP(1);
  
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 		{
! 			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim));
! 		}
  
! 		appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value));
  	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
--- 537,589 ----
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	bytea		   *value,
! 				   *delim;
! 	int				valueLen,
! 					delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
  
! 	value = PG_GETARG_BYTEA_PP(1);
! 	valueLen = VARSIZE_ANY_EXHDR(value);
! 	state->notNullCount++;
  
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
! 	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_BYTEA_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
! 	}
! 	else
! 	{
! 		/* Delimiter is NULL, treat as zero-length string */
! 		delim = NULL;
! 		delimLen = 0;
  	}
  
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, valueLen);
+ 	if (delim)
+ 		appendBinaryStringInfo(&state->string, VARDATA_ANY(delim),
+ 							   VARSIZE_ANY_EXHDR(delim));
+ 	appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen);
+ 
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 438,459 ****
  }
  
  Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
  	{
  		bytea	   *result;
  
! 		result = (bytea *) palloc(state->len + VARHDRSZ);
! 		SET_VARSIZE(result, state->len + VARHDRSZ);
! 		memcpy(VARDATA(result), state->data, state->len);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
--- 592,661 ----
  }
  
  Datum
+ bytea_string_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	int				valueLen;
+ 	StringAggState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since it
+ 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "string_agg_invtransfn called with NULL state");
+ 	else if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (StringAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* We append nothing if the string is NULL, so skip here as well */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_POINTER(state);
+ 
+ 	/* No need to de-toast value, need only the length */
+ 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
+ 	Assert(state->notNullCount >= 1);
+ 	state->notNullCount--;
+ 
+ 	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
+ 	 * The transition type for string_agg() is declared to be "internal",
+ 	 * which is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	if (removeFromStringAggState(state, valueLen))
+ 		PG_RETURN_POINTER(state);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
  	{
+ 		int			resultLen = state->string.len  - state->offset;
  		bytea	   *result;
  
! 		result = (bytea *) palloc(resultLen + VARHDRSZ);
! 		SET_VARSIZE(result, resultLen + VARHDRSZ);
! 		memcpy(VARDATA(result), state->string.data + state->offset, resultLen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3734,3802 ****
   * the associated value.
   */
  
! /* subroutine to initialize state */
! static StringInfo
! makeStringAggState(FunctionCallInfo fcinfo)
  {
! 	StringInfo	state;
! 	MemoryContext aggcontext;
! 	MemoryContext oldcontext;
  
! 	if (!AggCheckCallContext(fcinfo, &aggcontext))
  	{
! 		/* cannot be called directly because of internal-type argument */
! 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
  	}
  
  	/*
! 	 * Create state in aggregate context.  It'll stay there across subsequent
! 	 * calls.
  	 */
! 	oldcontext = MemoryContextSwitchTo(aggcontext);
! 	state = makeStringInfo();
! 	MemoryContextSwitchTo(oldcontext);
! 
! 	return state;
  }
  
  Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 			appendStringInfoText(state, PG_GETARG_TEXT_PP(2));	/* delimiter */
  
! 		appendStringInfoText(state, PG_GETARG_TEXT_PP(1));		/* value */
! 	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len));
  	else
  		PG_RETURN_NULL();
  }
--- 3936,4055 ----
   * the associated value.
   */
  
! Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	text		   *value,
! 				   *delim;
! 	int				delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
! 
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
! 
! 	value = PG_GETARG_TEXT_PP(1);
! 	state->notNullCount++;
! 
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
  	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_TEXT_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
  	}
+ 	else
+ 	{
+ 		/* Delimiter is NULL, treat as zero-length string */
+ 		delim = NULL;
+ 		delimLen = 0;
+ 	}
+ 
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value));
+ 	if (delim)
+ 		appendStringInfoText(&state->string, delim);
+ 	appendStringInfoText(&state->string, value);
  
  	/*
! 	 * The transition type for string_agg() is declared to be "internal",
! 	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
! string_agg_invtransfn(PG_FUNCTION_ARGS)
  {
! 	int				valueLen;
! 	StringAggState *state;
  
! 	/*
! 	 * Shouldn't happen, but we cannot mark this function strict, since it
! 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
! 	 * Must also prevent direct calls because of the "interal" argument
! 	 */
! 	if (PG_ARGISNULL(0))
! 		elog(ERROR, "string_agg_invtransfn called with NULL state");
! 	else if (!AggCheckCallContext(fcinfo, NULL))
! 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
  
! 	state = (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* We append nothing if the string is NULL, so skip here as well */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* No need to de-toast value, need only the length */
! 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
! 	Assert(state->notNullCount >= 1);
! 	state->notNullCount--;
  
  	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	if (removeFromStringAggState(state, valueLen))
! 		PG_RETURN_POINTER(state);
! 	else
! 		PG_RETURN_NULL();
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset,
! 												  state->string.len - state->offset));
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..403bb50 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-	-					0	
*** 250,262 ****
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
--- 250,262 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_invtransfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_invtransfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_invtransfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..efaa281 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 875,880 ****
--- 875,882 ----
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3585 (  array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2463,2474 ****
--- 2465,2480 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3586 (  string_agg_invtransfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3538 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into a string");
  DATA(insert OID = 3543 (  bytea_string_agg_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3587 (  bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3544 (  bytea_string_agg_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..9a6fc39 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 83,88 ****
--- 83,89 ----
  	Datum	   *dvalues;		/* array of accumulated Datums */
  	bool	   *dnulls;			/* array of is-null flags for Datums */
  	int			alen;			/* allocated length of above arrays */
+ 	int			offset;			/* offset of first element in above arrays */
  	int			nelems;			/* number of valid entries in above arrays */
  	Oid			element_type;	/* data type of the Datums */
  	int16		typlen;			/* needed info about datatype */
*************** extern ArrayBuildState *accumArrayResult
*** 255,260 ****
--- 256,262 ----
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
  				 MemoryContext rcontext);
+ extern void shiftArrayResult(ArrayBuildState *astate, int count);
  extern Datum makeArrayResult(ArrayBuildState *astate,
  				MemoryContext rcontext);
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
*************** extern ArrayType *create_singleton_array
*** 290,295 ****
--- 292,298 ----
  					   int ndims);
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
  
  /*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..173fa71 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 812,820 ****
--- 812,822 ----
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
  extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS);
  
  extern Datum text_concat(PG_FUNCTION_ARGS);
  extern Datum text_concat_ws(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index e3eba47..33587df 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1310,1312 ****
--- 1310,1356 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
+      row     | str | str_del | str_vardel |      bin       |      bin_del       |      bin_vardel      |       ary        
+ -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------
+  1:1,0100,-  | -   | -       | -          | -              | -                  | -                    | 
+  2:-,0200,2  | 1   | 1       | 1          | \x0100         | \x0100             | \x0100               | {NULL}
+  3:3,----,3  | 1   | 1       | 1          | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2}
+  4:-,0400,4  | 13  | 1,3     | 13         | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2,3}
+  5:5,----,-  | 3   | 3       | 3          | \x02000400     | \x0200000400       | \x0200000400         | {2,3,4}
+  6:6,0600,6  | 35  | 3,5     | 3||5       | \x0400         | \x0400             | \x0400               | {3,4,NULL}
+  7:7,0700,-  | 56  | 5,6     | 56         | \x04000600     | \x0400000600       | \x04000600           | {4,NULL,6}
+  8:8,0800,8  | 567 | 5,6,7   | 56|7       | \x06000700     | \x0600000700       | \x0600000700         | {NULL,6,NULL}
+  9:-,----,-  | 678 | 6,7,8   | 6|7||8     | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8}
+  10:-,----,- | 78  | 7,8     | 7||8       | \x07000800     | \x0700000800       | \x070000000800       | {NULL,8,NULL}
+  11:-,----,- | 8   | 8       | 8          | \x0800         | \x0800             | \x0800               | {8,NULL,NULL}
+  12:-,----,- | -   | -       | -          | -              | -                  | -                    | {NULL,NULL,NULL}
+ (12 rows)
+ 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index bd8dbdc..6761e0c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 493,495 ****
--- 493,524 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ 
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
invtrans_optimize_abf837.patchapplication/octet-stream; name=invtrans_optimize_abf837.patchDownload
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..8afe3d0 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** window_gettupleslot(WindowObject winobj,
*** 1945,1958 ****
  		winobj->seekpos++;
  	}
  
! 	while (winobj->seekpos > pos)
  	{
! 		if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot))
  			elog(ERROR, "unexpected end of tuplestore");
  		winobj->seekpos--;
  	}
  
! 	while (winobj->seekpos < pos)
  	{
  		if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot))
  			elog(ERROR, "unexpected end of tuplestore");
--- 1945,1976 ----
  		winobj->seekpos++;
  	}
  
! 	/* Advance or rewind until we are within one tuple of the one we want */
! 	while (winobj->seekpos < pos-1)
  	{
! 		if (!tuplestore_advance(winstate->buffer, true))
! 			elog(ERROR, "unexpected end of tuplestore");
! 		winobj->seekpos++;
! 	}
! 
! 	while (winobj->seekpos > pos+1)
! 	{
! 		if (!tuplestore_advance(winstate->buffer, false))
  			elog(ERROR, "unexpected end of tuplestore");
  		winobj->seekpos--;
  	}
  
! 	/*
! 	 * Now we should be on the tuple immediately before or after the one we
! 	 * want, so just fetch forwards or backwards as appropriate.
! 	 */
! 	if (winobj->seekpos > pos)
! 	{
! 		if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot))
! 			elog(ERROR, "unexpected end of tuplestore");
! 		winobj->seekpos--;
! 	}
! 	else
  	{
  		if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot))
  			elog(ERROR, "unexpected end of tuplestore");
#178Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#176)
7 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr7, 2014, at 17:41 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

I've just finished reading through all the other patches, and they all
look OK to me. It's mostly straightforward stuff, so despite the size
it's hopefully all committable once the base patch goes in.

Hm, I'm starting to have second thoughts about the minmax patch. The
inverse transition functions for MIN and MAX have a non-trivial probability
of failure - they trigger a rescan whenever the value that is removed isn't
strictly smaller (respectively strictly larger) then the current maximum
(respectively minimum). Thus, whenever that happens, we both call the
inverse transition function *and* (since it fails) restart the aggregation.

For windows based on ROWS, this isn't too bad - even if we fail every second
time, we still avoid half the rescans, which should be a net win if the
average window size is > 2.

But for RANGE-based windows, more than one call of the inverse transition
function must succeed for us to save anything, since we must successfully
remove *all* peers to avoid one restart. This greatly increases the chance
that using the inverse transition function hurts rather then helps - the
situation is worse the larger the average number of peers is.

I've factored the BOOL_AND,BOOL_OR stuff out into a separate patch
invtrans_bool - it previously was part of invtrans_minmax. Given the
performance risk involved, I think that we probably shouldn't commit
invtrans_minmax at this time. I should have brought this up earlier, but
the issue had slipped my mind :-( Sorry for that.

I think that you're probably right that optimising
window_gettupleslot() to eliminate the O(n^2) behaviour can be left to
a later patch --- the existing performance benefits of this patch are
enough to justify its inclusion IMO. It would be nice to include the
trivial optimisation to window_gettupleslot() that I posted upthread,
since it gives such a big improvement for only a few lines of code
changed.

Agreed, but since it's independent from the rest of the base patch,
I kept it as a separate patch (invtrans_optimize) instead of merging it
into the base patch. It should probably be committed separately too.

If you post a new patch set, I'll mark it as ready for committer attention.

New patch set is attached. The only difference to the previous one is that
"Forward Transitions" and "Inverse Transitions" are now scaled with nloops,
and that it includes your window_gettupleslot patch under the name
invtrans_optimize.

Your nested loop query
explain (verbose, analyse)
select * from
(values (10), (20), (30), (40)) v(x),
lateral
(select sum(i) over (rows between 4 preceding and current row)
from generate_series(1, x) i) t
now produces
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..170.06 rows=4000 width=12) (actual time=0.092..0.257 rows=100 loops=1)
Output: "*VALUES*".column1, (sum(i.i) OVER (?))
-> Values Scan on "*VALUES*" (cost=0.00..0.05 rows=4 width=4) (actual time=0.003..0.007 rows=4 loops=1)
Output: "*VALUES*".column1
-> WindowAgg (cost=0.00..22.50 rows=1000 width=4) (actual time=0.027..0.055 rows=25 loops=4)
Output: sum(i.i) OVER (?)
Forward Transitions: 25.0
Inverse Transitions: 20.0
-> Function Scan on pg_catalog.generate_series i (cost=0.00..10.00 rows=1000 width=4) (actual time=0.013..0.015 rows=25 loops=4)
Output: i.i
Function Call: generate_series(1, "*VALUES*".column1)
Planning time: 0.359 ms
Total runtime: 0.544 ms

The patch dependencies are as follows:

invtrans_{optimize,docs) are independent from the rest

invtrans_{arith,bool,minmax,collecting} are pairwise independent and all
depend on invtrans_base.

As explain above, invtrans_bool is a bit problematic, since it carries
a real risk of performance regressions. It's included for completeness'
sake, and should probably not be committed at this time.

invtrans_optimize was authored by Dean Rasheed.

best regards,
Florian Pflug

Attachments:

invtrans_arith_3194a9.patchapplication/octet-stream; name=invtrans_arith_3194a9.patchDownload
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..e62f2a3 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8inc(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 717,767 ----
  	}
  }
  
+ Datum
+ int8dec(PG_FUNCTION_ARGS)
+ {
+ 	/*
+ 	 * When int8 is pass-by-reference, we provide this special case to avoid
+ 	 * palloc overhead for COUNT(): when called as an inverse transition
+ 	 * aggregate, we know that the argument is modifiable local storage,
+ 	 * so just update it in-place. (If int8 is pass-by-value, then of course
+ 	 * this is useless as well as incorrect, so just ifdef it out.)
+ 	 */
+ #ifndef USE_FLOAT8_BYVAL		/* controls int8 too */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 	{
+ 		int64	   *arg = (int64 *) PG_GETARG_POINTER(0);
+ 		int64		result;
+ 
+ 		result = *arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && *arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		*arg = result;
+ 		PG_RETURN_POINTER(arg);
+ 	}
+ 	else
+ #endif
+ 	{
+ 		/* Not called as an aggregate, so just do it the dumb way */
+ 		int64		arg = PG_GETARG_INT64(0);
+ 		int64		result;
+ 
+ 		result = arg - 1;
+ 		/* Underflow check */
+ 		if (result > 0 && arg < 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("bigint out of range")));
+ 
+ 		PG_RETURN_INT64(result);
+ 	}
+ }
+ 
+ 
  /*
   * These functions are exactly like int8inc but are used for aggregates that
   * count only non-null values.	Since the functions are declared strict,
*************** int8inc_any(PG_FUNCTION_ARGS)
*** 733,738 ****
--- 778,789 ----
  }
  
  Datum
+ int8inc_any_inv(PG_FUNCTION_ARGS)
+ {
+ 	return int8dec(fcinfo);
+ }
+ 
+ Datum
  int8inc_float8_float8(PG_FUNCTION_ARGS)
  {
  	return int8inc(fcinfo);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 64eb0f8..2a9e093 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_float4(PG_FUNCTION_ARGS)
*** 2517,2524 ****
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	bool		isNaN;			/* true if any processed number was NaN */
  	MemoryContext agg_context;	/* context we're calculating in */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
--- 2517,2528 ----
  typedef struct NumericAggState
  {
  	bool		calcSumX2;		/* if true, calculate sumX2 */
! 	int			maxScale;		/* stores the maximum scale seen so far. */
! 	int64		maxScaleCount;  /* tracks the number of values we've
! 								 * seen with the maximum scale */
! 
  	MemoryContext agg_context;	/* context we're calculating in */
+ 	int64		NaNcount;		/* Count of NaN values that are aggregated */
  	int64		N;				/* count of processed numbers */
  	NumericVar	sumX;			/* sum of processed numbers */
  	NumericVar	sumX2;			/* sum of squares of processed numbers */
*************** makeNumericAggState(FunctionCallInfo fci
*** 2543,2548 ****
--- 2547,2556 ----
  	state = (NumericAggState *) palloc0(sizeof(NumericAggState));
  	state->calcSumX2 = calcSumX2;
  	state->agg_context = agg_context;
+ 	state->NaNcount = 0;
+ 
+ 	state->maxScale = 0;
+ 	state->maxScaleCount = 0;
  
  	MemoryContextSwitchTo(old_context);
  
*************** do_numeric_accum(NumericAggState *state,
*** 2560,2574 ****
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (state->isNaN || NUMERIC_IS_NAN(newval))
  	{
! 		state->isNaN = true;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
--- 2568,2596 ----
  	MemoryContext old_context;
  
  	/* result is NaN if any processed number is NaN */
! 	if (NUMERIC_IS_NAN(newval))
  	{
! 		state->NaNcount++;
  		return;
  	}
  
  	/* load processed number in short-lived context */
  	init_var_from_num(newval, &X);
  
+ 	/*
+ 	 * Track the highest scale that we've seen as if we ever perform an inverse
+ 	 * transition and remove the last numeric with the highest scale that we've
+ 	 * seen then we can no longer perform inverse transitions without risking
+ 	 * having the wrong dscale in the result value.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 		state->maxScaleCount++;
+ 	else if (X.dscale > state->maxScale)
+ 	{
+ 		state->maxScale = X.dscale;
+ 		state->maxScaleCount = 1;
+ 	}
+ 
  	/* if we need X^2, calculate that in short-lived context */
  	if (state->calcSumX2)
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2579,2591 ****
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N++ > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
  	}
  	else
  	{
--- 2601,2615 ----
  	/* The rest of this needs to work in the aggregate context */
  	old_context = MemoryContextSwitchTo(state->agg_context);
  
! 	if (state->N > 0)
  	{
  		/* Accumulate sums */
  		add_var(&X, &(state->sumX), &(state->sumX));
  
  		if (state->calcSumX2)
  			add_var(&X2, &(state->sumX2), &(state->sumX2));
+ 
+ 		state->N++;
  	}
  	else
  	{
*************** do_numeric_accum(NumericAggState *state,
*** 2594,2605 ****
--- 2618,2730 ----
  
  		if (state->calcSumX2)
  			set_var_from_var(&X2, &(state->sumX2));
+ 
+ 		state->N = 1;
  	}
  
  	MemoryContextSwitchTo(old_context);
  }
  
  /*
+  * do_numeric_discard
+  * Attempts to remove a value from the aggregated state.
+  * If the value cannot be removed then the function will return false, the
+  * possible reasons for failing are described below.
+  *
+  * If we aggregate the values 1.01 and 2 then the result will be 3.01. If we
+  * are then asked to un-aggregate the 1.01 then we must reject this case as we
+  * won't be able to tell what the new aggregated value's dscale should be.
+  * We can't return 2.00 (dscale = 2) as we really should return just 2, but
+  * since we're not tracking any previous highest scales then we must just fail
+  * to perform the inverse transition and just return false.
+  *
+  * Values that are no longer aggregated should not be able to effect the dscale
+  * of the result of the values that *are* still aggregated.
+  *
+  * Note it may be better to track the number of times we've aggregated a
+  * numeric with each scale, then if we ever remove final highest scaled value
+  * then we can step the result's dscale down to the next highest value. This is
+  * perhaps slightly more work than we can afford to do here, but doing it this
+  * way would mean that we could always perform the inverse transition.
+  */
+ static bool
+ do_numeric_discard(NumericAggState *state, Numeric newval)
+ {
+ 	NumericVar	X;
+ 	NumericVar	X2;
+ 	MemoryContext old_context;
+ 
+ 	/* result is NaN if any processed number is NaN */
+ 	if (NUMERIC_IS_NAN(newval))
+ 	{
+ 		state->NaNcount--;
+ 		return true;
+ 	}
+ 
+ 	/* load processed number in short-lived context */
+ 	init_var_from_num(newval, &X);
+ 
+ 	/*
+ 	 * state->sumX's dscale matches the maximum dscale of any of the inputs
+ 	 * Removing the last input with that dscale would require us to recompute
+ 	 * the maximum dscale of the *remaining* inputs, which we cannot do unless
+ 	 * no more non-NaN inputs remain at all. So we report a failure instead,
+ 	 * and force the aggregation to be redone from scratch.
+ 	 */
+ 	if (state->maxScale == X.dscale)
+ 	{
+ 		if (state->maxScaleCount > 1)
+ 		{
+ 			/* Some remaining inputs have same dscale */
+ 			--state->maxScaleCount;
+ 		}
+ 		else if (state->N == 1)
+ 		{
+ 			/* No remaining non-NaN inputs at all */
+ 			state->maxScale = 0;
+ 			state->maxScaleCount = 0;
+ 		}
+ 		else
+ 		{
+ 			/* No remaining inputs have same dscale */
+ 			return false;
+ 		}
+ 	}
+ 
+ 	/* if we need X^2, calculate that in short-lived context */
+ 	if (state->calcSumX2)
+ 	{
+ 		init_var(&X2);
+ 		mul_var(&X, &X, &X2, X.dscale * 2);
+ 	}
+ 
+ 	/* The rest of this needs to work in the aggregate context */
+ 	old_context = MemoryContextSwitchTo(state->agg_context);
+ 
+ 	if (state->N > 1)
+ 	{
+ 		sub_var(&(state->sumX), &X, &(state->sumX));
+ 		if (state->calcSumX2)
+ 			sub_var(&(state->sumX2), &X2, &(state->sumX2));
+ 		state->N--;
+ 	}
+ 	else if (state->N == 1)
+ 	{
+ 		/* Sums will be reset by next call to do_numeric_accum */
+ 		state->N = 0;
+ 	}
+ 	else
+ 	{
+ 		MemoryContextSwitchTo(old_context);
+ 		elog(ERROR, "cannot discard more values than were accumulated");
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 	return true;
+ }
+ 
+ 
+ /*
   * Generic transition function for numeric aggregates that require sumX2.
   */
  Datum
*************** numeric_accum(PG_FUNCTION_ARGS)
*** 2609,2626 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
--- 2734,2772 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, true);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * Generic inverse transition function for numeric aggregates
+  */
+ Datum
+ numeric_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	NumericAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL)
+ 		elog(ERROR, "numeric_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		/* Can we perform an inverse transition? if not return NULL. */
+ 		if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
+ 			PG_RETURN_NULL();
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
+ 
  /*
   * Generic transition function for numeric aggregates that don't require sumX2.
   */
*************** numeric_avg_accum(PG_FUNCTION_ARGS)
*** 2631,2644 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, false);
  
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
- 	}
  
  	PG_RETURN_POINTER(state);
  }
--- 2777,2788 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
! 	/* Create the state data on the first call */
! 	if (state == NULL)
! 		state = makeNumericAggState(fcinfo, false);
  
+ 	if (!PG_ARGISNULL(1))
  		do_numeric_accum(state, PG_GETARG_NUMERIC(1));
  
  	PG_RETURN_POINTER(state);
  }
*************** int2_accum(PG_FUNCTION_ARGS)
*** 2659,2675 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2803,2818 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int4_accum(PG_FUNCTION_ARGS)
*** 2683,2699 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, true);
- 
  		do_numeric_accum(state, newval);
  	}
  
--- 2826,2841 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2707,2724 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  
! 		/* Create the state data when we see the first non-null input. */
! 		if (state == NULL)
! 			state = makeNumericAggState(fcinfo, true);
  
! 		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
--- 2849,2951 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, true);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
+ 		do_numeric_accum(state, newval);
+ 	}
  
! 	PG_RETURN_POINTER(state);
! }
  
! 
! /*
!  * int2_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int2_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int4_accum_inv
!  * aggregate inverse transition function.
!  */
! Datum
! int4_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
! 	}
! 
! 	PG_RETURN_POINTER(state);
! }
! 
! /*
!  * int8_accum_inv
!  * aggregate inverse transition function.
!  * This function must be declared as strict.
!  */
! Datum
! int8_accum_inv(PG_FUNCTION_ARGS)
! {
! 	NumericAggState *state;
! 
! 	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)
! 		elog(ERROR, "numeric_accum_inv called with NULL state");
! 
! 	if (!PG_ARGISNULL(1))
! 	{
! 		Numeric		newval;
! 
! 		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
! 								 PG_GETARG_DATUM(1)));
! 
! 		/* Should never fail, all inputs have dscale 0 */
! 		if (!do_numeric_discard(state, newval))
! 			elog(ERROR, "do_numeric_discard failed unexpectedly");
  	}
  
  	PG_RETURN_POINTER(state);
*************** int8_accum(PG_FUNCTION_ARGS)
*** 2726,2731 ****
--- 2953,2959 ----
  
  /*
   * Transition function for int8 input when we don't need sumX2.
+  * For the inverse, we use int8_accum_inv.
   */
  Datum
  int8_avg_accum(PG_FUNCTION_ARGS)
*************** int8_avg_accum(PG_FUNCTION_ARGS)
*** 2734,2757 ****
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
- 
- 		/* Create the state data when we see the first non-null input. */
- 		if (state == NULL)
- 			state = makeNumericAggState(fcinfo, false);
- 
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
- 
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
--- 2962,2983 ----
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
  
+ 	/* Create the state data on the first call */
+ 	if (state == NULL)
+ 		state = makeNumericAggState(fcinfo, false);
+ 
  	if (!PG_ARGISNULL(1))
  	{
  		Numeric		newval;
  
  		newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
  													 PG_GETARG_DATUM(1)));
  		do_numeric_accum(state, newval);
  	}
  
  	PG_RETURN_POINTER(state);
  }
  
  Datum
  numeric_avg(PG_FUNCTION_ARGS)
  {
*************** numeric_avg(PG_FUNCTION_ARGS)
*** 2760,2769 ****
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
--- 2986,2996 ----
  	Datum		sumX_datum;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
*************** numeric_sum(PG_FUNCTION_ARGS)
*** 2778,2787 ****
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL)			/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->isNaN)			/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
--- 3005,3016 ----
  	NumericAggState *state;
  
  	state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
! 		/* there were no non-null inputs */
  		PG_RETURN_NULL();
  
! 	if (state->NaNcount > 0)
! 		/* there was at least one NaN input */
  		PG_RETURN_NUMERIC(make_result(&const_nan));
  
  	PG_RETURN_NUMERIC(make_result(&(state->sumX)));
*************** numeric_stddev_internal(NumericAggState 
*** 2812,2818 ****
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL)
  	{
  		*is_null = true;
  		return NULL;
--- 3041,3047 ----
  	int			rscale;
  
  	/* Deal with empty input and NaN-input cases */
! 	if (state == NULL || (state->N + state->NaNcount) == 0)
  	{
  		*is_null = true;
  		return NULL;
*************** numeric_stddev_internal(NumericAggState 
*** 2820,2826 ****
  
  	*is_null = false;
  
! 	if (state->isNaN)
  		return make_result(&const_nan);
  
  	init_var(&vN);
--- 3049,3055 ----
  
  	*is_null = false;
  
! 	if (state->NaNcount > 0)
  		return make_result(&const_nan);
  
  	init_var(&vN);
*************** numeric_stddev_pop(PG_FUNCTION_ARGS)
*** 2951,2970 ****
  }
  
  /*
!  * SUM transition functions for integer datatypes.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   *
!  * Because SQL defines the SUM() of no values to be NULL, not zero,
!  * the initial condition of the transition data value needs to be NULL. This
!  * means we can't rely on ExecAgg to automatically insert the first non-null
!  * data value into the transition data: it doesn't know how to do the type
!  * conversion.	The upshot is that these routines have to be marked non-strict
!  * and handle substitution of the first non-null input themselves.
   */
  
  Datum
--- 3180,3193 ----
  }
  
  /*
!  * Obsolete SUM transition functions for integer datatypes.
   *
!  * These were used to implement SUM aggregates before inverse transition
!  * functions were added. For inverse transitions, we need to know the number
!  * of summands to be able to return NULL whenenver the number of non-NULL
!  * inputs becomes zero. We therefore now use the intX_avg_accum and
!  * intX_avg_accum_inv transition functions, which use int8[] is their
!  * transition type to be able to count the number of inputs.
   */
  
  Datum
*************** int8_sum(PG_FUNCTION_ARGS)
*** 3103,3112 ****
  										NumericGetDatum(oldsum), newval));
  }
  
- 
  /*
!  * Routines for avg(int2) and avg(int4).  The transition datatype
!  * is a two-element int8 array, holding count and sum.
   */
  
  typedef struct Int8TransTypeData
--- 3326,3340 ----
  										NumericGetDatum(oldsum), newval));
  }
  
  /*
!  * Routines for sum(int2), sum(int4), avg(int2) and avg(int4).  The transition
!  * datatype is a two-element int8 array, holding count and sum.
!  *
!  * To avoid overflow, we use accumulators wider than the input datatype.
!  * A Numeric accumulator is needed for int8 input; for int4 and int2
!  * inputs, we use int8 accumulators which should be sufficient for practical
!  * purposes.  (The latter two therefore don't really belong in this file,
!  * but we keep them here anyway.)
   */
  
  typedef struct Int8TransTypeData
*************** int2_avg_accum(PG_FUNCTION_ARGS)
*** 3144,3149 ****
--- 3372,3406 ----
  }
  
  Datum
+ int2_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int16		newval = PG_GETARG_INT16(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ 
+ Datum
  int4_avg_accum(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray;
*************** int4_avg_accum(PG_FUNCTION_ARGS)
*** 3172,3177 ****
--- 3429,3480 ----
  }
  
  Datum
+ int4_avg_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray;
+ 	int32		newval = PG_GETARG_INT32(1);
+ 	Int8TransTypeData *transdata;
+ 
+ 	/*
+ 	 * If we're invoked as an aggregate, we can cheat and modify our first
+ 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
+ 	 * a copy of it before scribbling on it.
+ 	 */
+ 	if (AggCheckCallContext(fcinfo, NULL))
+ 		transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	else
+ 		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 	transdata->count--;
+ 	transdata->sum -= newval;
+ 
+ 	PG_RETURN_ARRAYTYPE_P(transarray);
+ }
+ 
+ Datum
+ int2int4_sum(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Int8TransTypeData *transdata;
+ 
+ 	if (ARR_HASNULL(transarray) ||
+ 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 		elog(ERROR, "expected 2-element int8 array");
+ 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
+ 
+ 	/* SQL defines SUM of no values to be NULL */
+ 	if (transdata->count == 0)
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_DATUM(Int64GetDatumFast(transdata->sum));
+ }
+ 
+ Datum
  int8_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ce30bb6..af0822a 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** interval_accum(PG_FUNCTION_ARGS)
*** 3429,3434 ****
--- 3429,3479 ----
  }
  
  Datum
+ interval_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
+ 	Interval   *newval = PG_GETARG_INTERVAL_P(1);
+ 	Datum	   *transdatums;
+ 	int			ndatums;
+ 	Interval	sumX,
+ 				N;
+ 	Interval   *newsum;
+ 	ArrayType  *result;
+ 
+ 	deconstruct_array(transarray,
+ 					  INTERVALOID, sizeof(Interval), false, 'd',
+ 					  &transdatums, NULL, &ndatums);
+ 	if (ndatums != 2)
+ 		elog(ERROR, "expected 2-element interval array");
+ 
+ 	/*
+ 	 * XXX memcpy, instead of just extracting a pointer, to work around buggy
+ 	 * array code: it won't ensure proper alignment of Interval objects on
+ 	 * machines where double requires 8-byte alignment. That should be fixed,
+ 	 * but in the meantime...
+ 	 *
+ 	 * Note: must use DatumGetPointer here, not DatumGetIntervalP, else some
+ 	 * compilers optimize into double-aligned load/store anyway.
+ 	 */
+ 	memcpy((void *) &sumX, DatumGetPointer(transdatums[0]), sizeof(Interval));
+ 	memcpy((void *) &N, DatumGetPointer(transdatums[1]), sizeof(Interval));
+ 
+ 	newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
+ 												   IntervalPGetDatum(&sumX),
+ 												 IntervalPGetDatum(newval)));
+ 	N.time -= 1;
+ 
+ 	transdatums[0] = IntervalPGetDatum(newsum);
+ 	transdatums[1] = IntervalPGetDatum(&N);
+ 
+ 	result = construct_array(transdatums, 2,
+ 							 INTERVALOID, sizeof(Interval), false, 'd');
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ 
+ Datum
  interval_avg(PG_FUNCTION_ARGS)
  {
  	ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..874aeec 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 103,125 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
--- 103,125 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		int8_accum_inv			numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		int4_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		int2_avg_accum_inv		int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_accum_inv		numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum		-						float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum		interval_accum_inv		interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		int8_accum_inv			numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_avg_accum		int4_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2109	n 0 int2_avg_accum		int2_avg_accum_inv		int2int4_sum	0	1016	0	"{0,0}" ));
! DATA(insert ( 2110	n 0 float4pl			-						-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl			-						-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl				cash_mi					-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl			interval_mi				-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_accum_inv		numeric_sum		0	2281	128 _null_ ));
  
  /* max */
  DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
*************** DATA(insert ( 2798	n 0 tidsmaller		-	-		
*** 166,221 ****
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
--- 166,221 ----
  DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		int8inc_any_inv	-		0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			int8dec			-		0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		int8_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		int4_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		int2_accum_inv		numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-					float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_accum_inv	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		int8_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		int4_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		int2_accum_inv		numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-					float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_accum_inv	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		int8_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		int4_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		int2_accum_inv		numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-					float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		int8_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		int4_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		int2_accum_inv		numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-					float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_accum_inv	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
  DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..59d8ac6 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("truncate interval to specified un
*** 1309,1316 ****
--- 1309,1320 ----
  
  DATA(insert OID = 1219 (  int8inc		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8inc _null_ _null_ _null_ ));
  DESCR("increment");
+ DATA(insert OID = 3546 (  int8dec		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8dec _null_ _null_ _null_ ));
+ DESCR("decrement");
  DATA(insert OID = 2804 (  int8inc_any	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any _null_ _null_ _null_ ));
  DESCR("increment, ignores second argument");
+ DATA(insert OID = 3547 (  int8inc_any_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 2276" _null_ _null_ _null_ _null_ int8inc_any_inv _null_ _null_ _null_ ));
+ DESCR("decrement, ignores second argument");
  DATA(insert OID = 1230 (  int8abs		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "20" _null_ _null_ _null_ _null_ int8abs _null_ _null_ _null_ ));
  
  DATA(insert OID = 1236 (  int8larger	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger _null_ _null_ _null_ ));
*************** DATA(insert OID = 1832 (  float8_stddev_
*** 2396,2401 ****
--- 2400,2407 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1833 (  numeric_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3548 (  numeric_accum_inv    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 2858 (  numeric_avg_accum    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ numeric_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1834 (  int2_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum _null_ _null_ _null_ ));
*************** DATA(insert OID = 1836 (  int8_accum	   
*** 2406,2411 ****
--- 2412,2423 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 2746 (  int8_avg_accum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3549 (  int2_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ int2_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3550 (  int4_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ int4_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3551 (  int8_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ int8_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 3178 (  numeric_sum	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_sum _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1837 (  numeric_avg	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ numeric_avg _null_ _null_ _null_ ));
*************** DATA(insert OID = 1842 (  int8_sum		   P
*** 2426,2439 ****
--- 2438,2459 ----
  DESCR("aggregate transition function");
  DATA(insert OID = 1843 (  interval_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3552 (  interval_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1187 "1187 1186" _null_ _null_ _null_ _null_ interval_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1844 (  interval_avg	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1186 "1187" _null_ _null_ _null_ _null_ interval_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 1962 (  int2_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1963 (  int4_avg_accum   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3553 (  int2_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 21" _null_ _null_ _null_ _null_ int2_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 3218 (  int4_avg_accum_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1016 "1016 23" _null_ _null_ _null_ _null_ int4_avg_accum_inv _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 1964 (  int8_avg		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1016" _null_ _null_ _null_ _null_ int8_avg _null_ _null_ _null_ ));
  DESCR("aggregate final function");
+ DATA(insert OID = 3219 (  int2int4_sum	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "1016" _null_ _null_ _null_ _null_ int2int4_sum _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  DATA(insert OID = 2805 (  int8inc_float8_float8		PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 20 "20 701 701" _null_ _null_ _null_ _null_ int8inc_float8_float8 _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2806 (  float8_regr_accum			PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 1022 "1022 701 701" _null_ _null_ _null_ _null_ float8_regr_accum _null_ _null_ _null_ ));
*************** DESCR("get value from jsonb with path el
*** 4529,4536 ****
  DATA(insert OID = 3939 (  jsonb_extract_path_op		PGNSP PGUID 12 1 0 0 0	f f f f t f i 2 0 3802 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ ));
  DATA(insert OID = 3940 (  jsonb_extract_path_text	PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 25 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
  DESCR("get value from jsonb as text with path elements");
! DATA(insert OID = 3218 (  jsonb_extract_path_text_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
! DATA(insert OID = 3219 (  jsonb_array_elements		PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 3802 "3802" "{3802,3802}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements _null_ _null_ _null_ ));
  DESCR("elements of a jsonb array");
  DATA(insert OID = 3465 (  jsonb_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" "{3802,25}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements_text _null_ _null_ _null_ ));
  DESCR("elements of jsonb array");
--- 4549,4556 ----
  DATA(insert OID = 3939 (  jsonb_extract_path_op		PGNSP PGUID 12 1 0 0 0	f f f f t f i 2 0 3802 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ ));
  DATA(insert OID = 3940 (  jsonb_extract_path_text	PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 25 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
  DESCR("get value from jsonb as text with path elements");
! DATA(insert OID = 3554 (  jsonb_extract_path_text_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ ));
! DATA(insert OID = 3555 (  jsonb_array_elements		PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 3802 "3802" "{3802,3802}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements _null_ _null_ _null_ ));
  DESCR("elements of a jsonb array");
  DATA(insert OID = 3465 (  jsonb_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" "{3802,25}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements_text _null_ _null_ _null_ ));
  DESCR("elements of jsonb array");
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..2f77dc1 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum numeric_float8_no_overflow(
*** 999,1008 ****
--- 999,1012 ----
  extern Datum float4_numeric(PG_FUNCTION_ARGS);
  extern Datum numeric_float4(PG_FUNCTION_ARGS);
  extern Datum numeric_accum(PG_FUNCTION_ARGS);
+ extern Datum numeric_accum_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_avg_accum(PG_FUNCTION_ARGS);
  extern Datum int2_accum(PG_FUNCTION_ARGS);
  extern Datum int4_accum(PG_FUNCTION_ARGS);
  extern Datum int8_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int4_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int8_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int8_avg_accum(PG_FUNCTION_ARGS);
  extern Datum numeric_avg(PG_FUNCTION_ARGS);
  extern Datum numeric_sum(PG_FUNCTION_ARGS);
*************** extern Datum int2_sum(PG_FUNCTION_ARGS);
*** 1014,1020 ****
--- 1018,1027 ----
  extern Datum int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_sum(PG_FUNCTION_ARGS);
  extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int2_avg_accum_inv(PG_FUNCTION_ARGS);
  extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
+ extern Datum int4_avg_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum int2int4_sum(PG_FUNCTION_ARGS);
  extern Datum int8_avg(PG_FUNCTION_ARGS);
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  extern Datum hash_numeric(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..5078e4a 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8div(PG_FUNCTION_ARGS);
*** 74,80 ****
--- 74,82 ----
  extern Datum int8abs(PG_FUNCTION_ARGS);
  extern Datum int8mod(PG_FUNCTION_ARGS);
  extern Datum int8inc(PG_FUNCTION_ARGS);
+ extern Datum int8dec(PG_FUNCTION_ARGS);
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
+ extern Datum int8inc_any_inv(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 2731c6a..94328b3 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum interval_mul(PG_FUNCTION_AR
*** 184,189 ****
--- 184,190 ----
  extern Datum mul_d_interval(PG_FUNCTION_ARGS);
  extern Datum interval_div(PG_FUNCTION_ARGS);
  extern Datum interval_accum(PG_FUNCTION_ARGS);
+ extern Datum interval_accum_inv(PG_FUNCTION_ARGS);
  extern Datum interval_avg(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_mi(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index e3eba47..394e3d5 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1295,1300 ****
--- 1295,1783 ----
  -- Test the arithmetic inverse transition functions
  --
  --
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 1.5000000000000000
+  2 | 2.0000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+  i |        avg         
+ ---+--------------------
+  1 | 2.0000000000000000
+  2 | 2.5000000000000000
+  3 |                   
+  4 |                   
+ (4 rows)
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |    avg     
+ ---+------------
+  1 | @ 1.5 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+  i |  sum  
+ ---+-------
+  1 | $3.30
+  2 | $2.20
+  3 |      
+  4 |      
+ (4 rows)
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+  i |   sum    
+ ---+----------
+  1 | @ 3 secs
+  2 | @ 2 secs
+  3 | 
+  4 | 
+ (4 rows)
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 | 3.3
+  2 | 2.2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+  sum  
+ ------
+  6.01
+     5
+     3
+ (3 rows)
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     2
+  2 |     1
+  3 |     0
+  4 |     0
+ (4 rows)
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | count 
+ ---+-------
+  1 |     4
+  2 |     3
+  3 |     2
+  4 |     1
+ (4 rows)
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+         var_pop        
+ -----------------------
+     21704.000000000000
+     13868.750000000000
+     11266.666666666667
+  4225.0000000000000000
+                      0
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        var_samp        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        variance        
+ -----------------------
+     27130.000000000000
+     18491.666666666667
+     16900.000000000000
+  8450.0000000000000000
+                       
+ (5 rows)
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_pop      
+ ---------------------
+     147.322774885623
+     147.322774885623
+     117.765657133139
+     106.144555520604
+  65.0000000000000000
+                    0
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+      stddev_samp     
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+        stddev        
+ ---------------------
+     164.711869639076
+     164.711869639076
+     135.984067694222
+     130.000000000000
+  91.9238815542511782
+                     
+ (6 rows)
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   1
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   6
+  3 |   9
+  4 |   7
+ (4 rows)
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+          to_char          
+ --------------------------
+   100000000000000000000
+                       1.0
+ (2 rows)
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+  i | sum 
+ ---+-----
+  1 |   3
+  2 |   2
+  3 |    
+  4 |    
+ (4 rows)
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+  a |  b  | sum 
+ ---+-----+-----
+  1 |   1 |   1
+  2 |   2 |   3
+  3 | NaN | NaN
+  4 |   3 | NaN
+  5 |   4 |   7
+ (5 rows)
+ 
  --
  --
  -- Test the boolean inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index bd8dbdc..3cbd888 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 476,481 ****
--- 476,622 ----
  --
  --
  
+ -- test inverse transition funtions handle NULLs properly
+ SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n);
+ 
+ SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n);
+ 
+ SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n);
+ 
+ -- test that inverse transition functions work with various frame options
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
+ 
+ SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v);
+ 
+ -- it might be tempting for someone to add an inverse trans function for
+ -- float and double precision. This should not be done as it  can give incorrect
+ -- results. This test should fail if anyone ever does this without thinking too
+ -- hard about it.
+ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9')
+   FROM (VALUES(1,1e20),(2,1)) n(i,n);
+ 
+ -- inverse transition function with filter
+ SELECT i,SUM(v::int) FILTER (WHERE i < 4) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   FROM (VALUES(1,1),(2,2),(3,NULL),(4,4)) t(i,v);
+ 
+ -- ensure aggregate context properly recovers from having NaN values
+ SELECT a, b,
+        SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b);
+ 
  --
  --
  -- Test the boolean inverse transition functions
invtrans_base_edb69c.patchapplication/octet-stream; name=invtrans_base_edb69c.patchDownload
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..b7fc5f2 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 56,61 ****
--- 56,62 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
*************** AggregateCreate(const char *aggName,
*** 68,78 ****
--- 69,81 ----
  	Datum		values[Natts_pg_aggregate];
  	Form_pg_proc proc;
  	Oid			transfn;
+ 	Oid			invtransfn = InvalidOid; /* can be omitted */
  	Oid			finalfn = InvalidOid;	/* can be omitted */
  	Oid			sortop = InvalidOid;	/* can be omitted */
  	Oid		   *aggArgTypes = parameterTypes->values;
  	bool		hasPolyArg;
  	bool		hasInternalArg;
+ 	bool		transIsStrict;
  	Oid			rettype;
  	Oid			finaltype;
  	Oid			fnArgs[FUNC_MAX_ARGS];
*************** AggregateCreate(const char *aggName,
*** 234,241 ****
--- 237,297 ----
  					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
  					 errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
  	}
+ 
+ 	/*
+ 	 * Remember if trans function is strict as we need to validate this
+ 	 * later if when we're dealing with the inverse transition function
+ 	 */
+ 	transIsStrict = proc->proisstrict;
+ 
  	ReleaseSysCache(tup);
  
+ 	/* handle invtransfn, if supplied */
+ 	if (agginvtransfnName)
+ 	{
+ 		/*
+ 		 * This must have the same number of arguments with the same types as
+ 		 * the transition function. We can just borrow the argument details
+ 		 * from the transition function and try to find a function with
+ 		 * the name of the inverse transition function and with a signature
+ 		 * that matches the transition function's.
+ 		 */
+ 		invtransfn = lookup_agg_function(agginvtransfnName,
+ 					nargs_transfn, fnArgs, InvalidOid, &rettype);
+ 
+ 		/*
+ 		 * Ensure the return type of the inverse transition function matches
+ 		 * the expected type.
+ 		 */
+ 		if (rettype != aggTransType)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("return type of inverse transition function %s is not %s",
+ 							NameListToString(agginvtransfnName),
+ 							format_type_be(aggTransType))));
+ 
+ 		tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(invtransfn));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for function %u", invtransfn);
+ 		proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 		/*
+ 		 * We force the strictness settings of the forward and inverse
+ 		 * transition functions to agree. This allows places which only need
+ 		 * forward transitions to not look at the inverse transition function
+ 		 * at all.
+ 		 */
+ 		if (transIsStrict != proc->proisstrict)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 						errmsg("strictness of forward and inverse transition functions must match"
+ 						)));
+ 		}
+ 		ReleaseSysCache(tup);
+ 
+ 	}
+ 
  	/* handle finalfn, if supplied */
  	if (aggfinalfnName)
  	{
*************** AggregateCreate(const char *aggName,
*** 391,396 ****
--- 447,453 ----
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
+ 	values[Anum_pg_aggregate_agginvtransfn - 1] = ObjectIdGetDatum(invtransfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
  	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
*************** AggregateCreate(const char *aggName,
*** 425,430 ****
--- 482,496 ----
  	referenced.objectSubId = 0;
  	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  
+ 	/* Depends on inverse transition function, if any */
+ 	if (OidIsValid(invtransfn))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = invtransfn;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Depends on final function, if any */
  	if (OidIsValid(finalfn))
  	{
*************** AggregateCreate(const char *aggName,
*** 447,453 ****
  }
  
  /*
!  * lookup_agg_function -- common code for finding both transfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
--- 513,520 ----
  }
  
  /*
!  * lookup_agg_function
!  * common code for finding transfn, invtransfn and finalfn
   */
  static Oid
  lookup_agg_function(List *fnName,
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 640e19c..c0ea1f7 100644
*** a/src/backend/commands/aggregatecmds.c
--- b/src/backend/commands/aggregatecmds.c
*************** DefineAggregate(List *name, List *args, 
*** 60,65 ****
--- 60,66 ----
  	AclResult	aclresult;
  	char		aggKind = AGGKIND_NORMAL;
  	List	   *transfuncName = NIL;
+ 	List	   *invtransfuncName = NIL;
  	List	   *finalfuncName = NIL;
  	List	   *sortoperatorName = NIL;
  	TypeName   *baseType = NULL;
*************** DefineAggregate(List *name, List *args, 
*** 112,117 ****
--- 113,120 ----
  			transfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sfunc1") == 0)
  			transfuncName = defGetQualifiedName(defel);
+ 		else if (pg_strcasecmp(defel->defname, "invsfunc") == 0)
+ 			invtransfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
  			finalfuncName = defGetQualifiedName(defel);
  		else if (pg_strcasecmp(defel->defname, "sortop") == 0)
*************** DefineAggregate(List *name, List *args, 
*** 283,288 ****
--- 286,292 ----
  						   parameterDefaults,
  						   variadicArgType,
  						   transfuncName,		/* step function name */
+ 						   invtransfuncName,	/* inverse trans function name */
  						   finalfuncName,		/* final function name */
  						   sortoperatorName,	/* sort operator name */
  						   transTypeId, /* transition data type */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 08f3167..af36690 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_sort_group_keys(PlanSta
*** 85,90 ****
--- 85,91 ----
  					 List *ancestors, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
+ static void show_windowagg_info(PlanState *planstate, ExplainState *es);
  static void show_tidbitmap_info(BitmapHeapScanState *planstate,
  								ExplainState *es);
  static void show_instrumentation_count(const char *qlabel, int which,
*************** ExplainNode(PlanState *planstate, List *
*** 1420,1425 ****
--- 1421,1430 ----
  		case T_Hash:
  			show_hash_info((HashState *) planstate, es);
  			break;
+ 		case T_WindowAgg:
+ 			if (es->verbose && planstate->instrument)
+ 				show_windowagg_info(planstate, es);
+ 			break;
  		default:
  			break;
  	}
*************** show_hash_info(HashState *hashstate, Exp
*** 1918,1923 ****
--- 1923,1943 ----
  	}
  }
  
+ static void
+ show_windowagg_info(PlanState *planstate, ExplainState *es)
+ {
+ 	WindowAggState *winaggstate = (WindowAggState *) planstate;
+ 
+ 	if (winaggstate->aggfwdtrans > 0 && planstate->instrument->nloops > 0)
+ 		ExplainPropertyFloat("Forward Transitions",
+ 							 (double)winaggstate->aggfwdtrans /
+ 							 planstate->instrument->nloops, 1, es);
+ 	if (winaggstate->agginvtrans > 0 && planstate->instrument->nloops > 0)
+ 		ExplainPropertyFloat("Inverse Transitions",
+ 							 (double)winaggstate->agginvtrans /
+ 							 planstate->instrument->nloops, 1, es);
+ }
+ 
  /*
   * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
   */
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..9a7ed93 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1798,1805 ****
--- 1798,1807 ----
  								aggref->aggtype,
  								aggref->inputcollid,
  								transfn_oid,
+ 								InvalidOid, /* invtrans is not needed here */
  								finalfn_oid,
  								&transfnexpr,
+ 								NULL,
  								&finalfnexpr);
  
  		/* set up infrastructure for calling the transfn and finalfn */
*************** ExecReScanAgg(AggState *node)
*** 2127,2168 ****
  }
  
  /*
-  * AggCheckCallContext - test if a SQL function is being called as an aggregate
-  *
-  * The transition and/or final functions of an aggregate may want to verify
-  * that they are being called as aggregates, rather than as plain SQL
-  * functions.  They should use this function to do so.	The return value
-  * is nonzero if being called as an aggregate, or zero if not.	(Specific
-  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
-  * values could conceivably appear in future.)
-  *
-  * If aggcontext isn't NULL, the function also stores at *aggcontext the
-  * identity of the memory context that aggregate transition values are
-  * being stored in.
-  */
- int
- AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
- {
- 	if (fcinfo->context && IsA(fcinfo->context, AggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_AGGREGATE;
- 	}
- 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
- 	{
- 		if (aggcontext)
- 			*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
- 		return AGG_CONTEXT_WINDOW;
- 	}
- 
- 	/* this is just to prevent "uninitialized variable" warnings */
- 	if (aggcontext)
- 		*aggcontext = NULL;
- 	return 0;
- }
- 
- /*
   * AggGetAggref - allow an aggregate support function to get its Aggref
   *
   * If the function is being called as an aggregate support function,
--- 2129,2134 ----
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 8afe3d0..3ceb0f7 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** typedef struct WindowStatePerFuncData
*** 102,119 ****
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transfer functions */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transfer functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
  	/*
  	 * initial value from pg_aggregate entry
  	 */
--- 102,125 ----
   */
  typedef struct WindowStatePerAggData
  {
! 	/* Oids of transition functions */
  	Oid			transfn_oid;
+ 	Oid			invtransfn_oid; /* may be InvalidOid */
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
  	/*
! 	 * fmgr lookup data for transition functions --- only valid when
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
  	FmgrInfo	transfn;
+ 	FmgrInfo	invtransfn;
  	FmgrInfo	finalfn;
  
+ 	/* Aggregate properties */
+ 	bool		use_invtransfn;			/* whether to use the invtransfn */
+ 	bool		aggcontext_is_shared;	/* aggcontext is winstate's aggcontext */
+ 
  	/*
  	 * initial value from pg_aggregate entry
  	 */
*************** typedef struct WindowStatePerAggData
*** 140,149 ****
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	Datum		transValue;		/* current transition value */
! 	bool		transValueIsNull;
  
! 	bool		noTransValue;	/* true if transValue not set yet */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
--- 146,158 ----
  	int			wfuncno;		/* index of associated PerFuncData */
  
  	/* Current transition value */
! 	MemoryContext	aggcontext;			/* context for transValue */
! 	int64			transValueCount;	/* Number of aggregated values*/
! 	Datum			transValue;			/* current transition value */
! 	bool			transValueIsNull;
  
! 	/* Data local to eval_windowaggregates() */
! 	bool			restart;			/* tmp marker that agg needs restart */
  } WindowStatePerAggData;
  
  static void initialize_windowaggregate(WindowAggState *winstate,
*************** static void initialize_windowaggregate(W
*** 152,157 ****
--- 161,169 ----
  static void advance_windowaggregate(WindowAggState *winstate,
  						WindowStatePerFunc perfuncstate,
  						WindowStatePerAgg peraggstate);
+ static bool retreat_windowaggregate(WindowAggState *winstate,
+ 						WindowStatePerFunc perfuncstate,
+ 						WindowStatePerAgg peraggstate);
  static void finalize_windowaggregate(WindowAggState *winstate,
  						 WindowStatePerFunc perfuncstate,
  						 WindowStatePerAgg peraggstate,
*************** static bool are_peers(WindowAggState *wi
*** 181,186 ****
--- 193,245 ----
  static bool window_gettupleslot(WindowObject winobj, int64 pos,
  					TupleTableSlot *slot);
  
+ /*
+  * AggCheckCallContext - test if a SQL function is being called as an aggregate
+  *
+  * The transition and/or final functions of an aggregate may want to verify
+  * that they are being called as aggregates, rather than as plain SQL
+  * functions.  They should use this function to do so.	The return value
+  * is nonzero if being called as an aggregate, or zero if not.	(Specific
+  * nonzero values are AGG_CONTEXT_AGGREGATE or AGG_CONTEXT_WINDOW, but more
+  * values could conceivably appear in future.)
+  *
+  * If aggcontext isn't NULL, the function also stores at *aggcontext the
+  * identity of the memory context that aggregate transition values are
+  * being stored in.
+  *
+  * This must live here, not in nodeAgg.c, because WindowStatePerAggData
+  * is private.
+  *
+  * Note that this function is only meant to be used by aggregate support
+  * functions, NOT by true window functions.
+  */
+ int
+ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
+ {
+ 	if (fcinfo->context && IsA(fcinfo->context, AggState))
+ 	{
+ 		if (aggcontext)
+ 			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ 		return AGG_CONTEXT_AGGREGATE;
+ 	}
+ 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ 	{
+ 		if (aggcontext)
+ 		{
+ 			/* Must lookup per-aggregate context */
+ 			WindowAggState *winstate = (WindowAggState *) fcinfo->context;
+ 			int				aggno = winstate->calledaggno;
+ 			Assert(0 <= aggno && aggno < winstate->numaggs);
+ 			*aggcontext = winstate->peragg[aggno].aggcontext;
+ 		}
+ 		return AGG_CONTEXT_WINDOW;
+ 	}
+ 
+ 	/* this is just to prevent "uninitialized variable" warnings */
+ 	if (aggcontext)
+ 		*aggcontext = NULL;
+ 	return 0;
+ }
  
  /*
   * initialize_windowaggregate
*************** initialize_windowaggregate(WindowAggStat
*** 193,210 ****
  {
  	MemoryContext oldContext;
  
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = peraggstate->initValue;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(winstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->noTransValue = peraggstate->initValueIsNull;
  	peraggstate->resultValueIsNull = true;
  }
  
--- 252,277 ----
  {
  	MemoryContext oldContext;
  
+ 	/* If we're using a private aggcontext, we may reset it here. But if the
+ 	 * context is shared, we don't know which other aggregates may still need
+ 	 * it, so we must leave it to the caller to reset at an appropriate time
+ 	 */
+ 	if (!peraggstate->aggcontext_is_shared)
+ 		MemoryContextResetAndDeleteChildren(peraggstate->aggcontext);
+ 
  	if (peraggstate->initValueIsNull)
! 		peraggstate->transValue = (Datum) 0;
  	else
  	{
! 		oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
  		peraggstate->transValue = datumCopy(peraggstate->initValue,
  											peraggstate->transtypeByVal,
  											peraggstate->transtypeLen);
  		MemoryContextSwitchTo(oldContext);
  	}
+ 	peraggstate->transValueCount = 0;
  	peraggstate->transValueIsNull = peraggstate->initValueIsNull;
! 	peraggstate->resultValue = (Datum) 0;
  	peraggstate->resultValueIsNull = true;
  }
  
*************** advance_windowaggregate(WindowAggState *
*** 256,265 ****
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/*
! 		 * For a strict transfn, nothing happens when there's a NULL input; we
! 		 * just keep the prior transValue.
! 		 */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
--- 323,329 ----
  
  	if (peraggstate->transfn.fn_strict)
  	{
! 		/* Skip NULL inputs for aggregates which desire that. */
  		for (i = 1; i <= numArguments; i++)
  		{
  			if (fcinfo->argnull[i])
*************** advance_windowaggregate(WindowAggState *
*** 268,308 ****
  				return;
  			}
  		}
! 		if (peraggstate->noTransValue)
  		{
! 			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
! 			 * already checked that the agg's input type is binary-compatible
! 			 * with its transtype, so straight copy here is OK.)
! 			 *
! 			 * We must copy the datum into aggcontext if it is pass-by-ref. We
! 			 * do not need to pfree the old transValue, since it's NULL.
! 			 */
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->noTransValue = false;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  		if (peraggstate->transValueIsNull)
  		{
- 			/*
- 			 * Don't call a strict function with NULL inputs.  Note it is
- 			 * possible to get here despite the above tests, if the transfn is
- 			 * strict *and* returned a NULL on a prior cycle. If that happens
- 			 * we will propagate the NULL all the way to the end.
- 			 */
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  	}
  
  	/*
  	 * OK to call the transition function
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
--- 332,391 ----
  				return;
  			}
  		}
! 
! 		/*
! 		 * For strict transfer functions with initial value NULL we use the
! 		 * first non-NULL input as the initial state. (We already checked that
! 		 * the agg's input type is binary-compatible with its transtype, so
! 		 * straight copy here is OK.)
! 		 *
! 		 * We must copy the datum into aggcontext if it is pass-by-ref. We do
! 		 * not need to pfree the old transValue, since it's NULL.
! 		 */
! 		if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			peraggstate->transValue = datumCopy(fcinfo->arg[1],
  												peraggstate->transtypeByVal,
  												peraggstate->transtypeLen);
  			peraggstate->transValueIsNull = false;
! 			peraggstate->transValueCount = 1;
  			MemoryContextSwitchTo(oldContext);
  			return;
  		}
+ 
+ 		/*
+ 		 * Don't call a strict function with NULL inputs.  Note it is possible
+ 		 * to get here despite the above tests, if the transfn is strict *and*
+ 		 * returned a NULL on a prior cycle. If that happens we will propagate
+ 		 * the NULL all the way to the end. That can only happen if there's no
+ 		 * inverse transition function, though, since we disallow transitions
+ 		 * back to NULL if there is one below.
+ 		 */
  		if (peraggstate->transValueIsNull)
  		{
  			MemoryContextSwitchTo(oldContext);
+ 			Assert(peraggstate->invtransfn_oid == InvalidOid);
  			return;
  		}
  	}
  
  	/*
+ 	 * We must track the number of inputs that we add to transValue, since
+ 	 * to remove the last input, retreat_windowaggregate() musn't call the
+ 	 * inverse transition function, but simply reset transValue back to its
+ 	 * initial value.
+ 	 *
+ 	 * Also update statistics
+ 	 */
+ 	Assert(peraggstate->transValueCount >= 0);
+ 	peraggstate->transValueCount++;
+ 	winstate->aggfwdtrans++;
+ 
+ 	/*
  	 * OK to call the transition function
+ 	 * Transfer functions with an inverse MUST not return NULL, see
+ 	 * retreat_windowaggregate()
  	 */
  	InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
  							 numArguments + 1,
*************** advance_windowaggregate(WindowAggState *
*** 310,316 ****
--- 393,407 ----
  							 (void *) winstate, NULL);
  	fcinfo->arg[0] = peraggstate->transValue;
  	fcinfo->argnull[0] = peraggstate->transValueIsNull;
+ 	winstate->calledaggno = perfuncstate->aggno;
  	newVal = FunctionCallInvoke(fcinfo);
+ 	winstate->calledaggno = -1;
+ 	if (peraggstate->invtransfn_oid != InvalidOid && fcinfo->isnull)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("transition function with an inverse returned NULL")));
+ 	}
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
*************** advance_windowaggregate(WindowAggState *
*** 322,328 ****
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(winstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
--- 413,558 ----
  	{
  		if (!fcinfo->isnull)
  		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
! 			newVal = datumCopy(newVal,
! 							   peraggstate->transtypeByVal,
! 							   peraggstate->transtypeLen);
! 		}
! 		if (!peraggstate->transValueIsNull)
! 			pfree(DatumGetPointer(peraggstate->transValue));
! 	}
! 
! 	MemoryContextSwitchTo(oldContext);
! 	peraggstate->transValue = newVal;
! 	peraggstate->transValueIsNull = fcinfo->isnull;
! }
! 
! /*
!  * retreat_windowaggregate
!  * removes tuples from aggregation.
!  * The calling function must ensure that each aggregate has
!  * a valid inverse transition function.
!  */
! static bool
! retreat_windowaggregate(WindowAggState *winstate,
! 						WindowStatePerFunc perfuncstate,
! 						WindowStatePerAgg peraggstate)
! {
! 	WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
! 	int			numArguments = perfuncstate->numArguments;
! 	FunctionCallInfoData fcinfodata;
! 	FunctionCallInfo fcinfo = &fcinfodata;
! 	Datum		newVal;
! 	ListCell   *arg;
! 	int			i;
! 	MemoryContext oldContext;
! 	ExprContext *econtext = winstate->tmpcontext;
! 	ExprState  *filter = wfuncstate->aggfilter;
! 
! 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
! 
! 	/* Skip anything FILTERed out */
! 	if (filter)
! 	{
! 		bool		isnull;
! 		Datum		res = ExecEvalExpr(filter, econtext, &isnull, NULL);
! 
! 		if (isnull || !DatumGetBool(res))
! 		{
! 			MemoryContextSwitchTo(oldContext);
! 			return true;
! 		}
! 	}
! 
! 	/* We start from 1, since the 0th arg will be the transition value */
! 	i = 1;
! 	foreach(arg, wfuncstate->args)
! 	{
! 		ExprState  *argstate = (ExprState *) lfirst(arg);
! 
! 		fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
! 									  &fcinfo->argnull[i], NULL);
! 		i++;
! 	}
! 
! 	/* Skip inputs containing NULLS for aggregates that require this */
! 	if (peraggstate->invtransfn.fn_strict)
! 	{
! 		for (i = 1; i <= numArguments; i++)
! 		{
! 			if (fcinfo->argnull[i])
! 			{
! 				MemoryContextSwitchTo(oldContext);
! 				return true;
! 			}
! 		}
! 	}
! 
! 	/* There should still be an added but not yet removed value */
! 	Assert(peraggstate->transValueCount >= 1);
! 
! 	/*
! 	 * We mustn't use the inverse transition function to remove the last
! 	 * input. Doing so would yield a non-NULL state, whereas we should be
! 	 * in the initial state afterwards which may very well be NULL. So
! 	 * instead, we simply re-initialize the aggregation in this case.
! 	 */
! 	if (peraggstate->transValueCount == 1)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		initialize_windowaggregate(winstate,
! 								&winstate->perfunc[peraggstate->wfuncno],
! 								peraggstate);
! 		return true;
! 	}
! 
! 	/*
! 	 * Perform the inverse transition.
! 	 *
! 	 * For pairs of forward and inverse transition functions, the state may
! 	 * never be NULL, except in the ignore_nulls case, and then only until
! 	 * until we see the first non-NULL input during which time should never
! 	 * attempt to invoke the inverse transition function. Excluding NULL
! 	 * as a possible state value allows us to make it mean "sorry, can't
! 	 * do an inverse transition in this case" when returned by the inverse
! 	 * transition function. In that case, we report the failure to the
! 	 * caller.
! 	 */
! 	if (peraggstate->transValueIsNull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		elog(ERROR, "transition value is NULL during inverse transition");
! 	}
! 	InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
! 							 numArguments + 1,
! 							 perfuncstate->winCollation,
! 							 (void *) winstate, NULL);
! 	fcinfo->arg[0] = peraggstate->transValue;
! 	fcinfo->argnull[0] = peraggstate->transValueIsNull;
! 	winstate->calledaggno = perfuncstate->aggno;
! 	newVal = FunctionCallInvoke(fcinfo);
! 	winstate->calledaggno = -1;
! 	if (fcinfo->isnull)
! 	{
! 		MemoryContextSwitchTo(oldContext);
! 		return false;
! 	}
! 
! 	/* Update number of added but not yet removed values and statistics */
! 	peraggstate->transValueCount--;
! 	winstate->agginvtrans++;
! 
! 	/*
! 	 * If pass-by-ref datatype, must copy the new value into aggcontext and
! 	 * pfree the prior transValue.	But if invtransfn returned a pointer to its
! 	 * first input, we don't need to do anything.
! 	 */
! 	if (!peraggstate->transtypeByVal &&
! 		DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
! 	{
! 		if (!fcinfo->isnull)
! 		{
! 			MemoryContextSwitchTo(peraggstate->aggcontext);
  			newVal = datumCopy(newVal,
  							   peraggstate->transtypeByVal,
  							   peraggstate->transtypeLen);
*************** advance_windowaggregate(WindowAggState *
*** 334,341 ****
--- 564,574 ----
  	MemoryContextSwitchTo(oldContext);
  	peraggstate->transValue = newVal;
  	peraggstate->transValueIsNull = fcinfo->isnull;
+ 
+ 	return true;
  }
  
+ 
  /*
   * finalize_windowaggregate
   * parallel to finalize_aggregate in nodeAgg.c
*************** finalize_windowaggregate(WindowAggState 
*** 370,376 ****
--- 603,611 ----
  		}
  		else
  		{
+ 			winstate->calledaggno = perfuncstate->aggno;
  			*result = FunctionCallInvoke(&fcinfo);
+ 			winstate->calledaggno = -1;
  			*isnull = fcinfo.isnull;
  		}
  	}
*************** finalize_windowaggregate(WindowAggState 
*** 392,402 ****
  	MemoryContextSwitchTo(oldContext);
  }
  
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * Much of this is duplicated from nodeAgg.c.  But NOTE that we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
--- 627,640 ----
  	MemoryContextSwitchTo(oldContext);
  }
  
+ 
  /*
   * eval_windowaggregates
   * evaluate plain aggregates being used as window functions
   *
!  * This differes from nodeAgg.c in two ways. First, if the window's frame
!  * start position moves, we use the inverse transfer function (if it exists)
!  * to remove values from the transition value. And second, we expect to be
   * able to call aggregate final functions repeatedly after aggregating more
   * data onto the same transition value.  This is not a behavior required by
   * nodeAgg.c.
*************** eval_windowaggregates(WindowAggState *wi
*** 406,417 ****
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs;
! 	int			i;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
--- 644,658 ----
  {
  	WindowStatePerAgg peraggstate;
  	int			wfuncno,
! 				numaggs,
! 				numaggs_restart = 0,
! 				i;
! 	int64		aggregatedupto_nonrestarted;
  	MemoryContext oldContext;
  	ExprContext *econtext;
  	WindowObject agg_winobj;
! 	TupleTableSlot *agg_row_slot = winstate->agg_row_slot;
! 	TupleTableSlot *temp_slot = winstate->temp_slot_1;
  
  	numaggs = winstate->numaggs;
  	if (numaggs == 0)
*************** eval_windowaggregates(WindowAggState *wi
*** 420,426 ****
  	/* final output execution is in ps_ExprContext */
  	econtext = winstate->ss.ps.ps_ExprContext;
  	agg_winobj = winstate->agg_winobj;
- 	agg_row_slot = winstate->agg_row_slot;
  
  	/*
  	 * Currently, we support only a subset of the SQL-standard window framing
--- 661,666 ----
*************** eval_windowaggregates(WindowAggState *wi
*** 438,446 ****
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * For other frame start rules, we discard the aggregate state and re-run
! 	 * the aggregates whenever the frame head row moves.  We can still
! 	 * optimize as above whenever successive rows share the same frame head.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
--- 678,697 ----
  	 * damage the running transition value, but we have the same assumption in
  	 * nodeAgg.c too (when it rescans an existing hash table).
  	 *
! 	 * We can still optimize as above whenever successive rows share the same
! 	 * frame head, but if the frame head moves beyond the aggregated base point
! 	 * we use the aggregate function's inverse transition function. This
! 	 * removes the tuple from aggregation and restores the aggregate's current
! 	 * state to what it would be if the removed row had never been aggregated
! 	 * in the first place. Inverse transition functions may optionally return
! 	 * NULL, this indicates that the function was unable to remove the tuple
! 	 * from aggregation, when this happens we must perform the aggregation all
! 	 * over again for all tuples in the new frame boundary.
! 	 *
! 	 * If the aggregate function does not have a inverse transition function
! 	 * and the frame head moves beyond the aggregated position then we must
! 	 * discard the aggregated state and re-aggregate similar to how we would
! 	 * have to if the inverse transition function had returned NULL.
  	 *
  	 * In many common cases, multiple rows share the same frame and hence the
  	 * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
*************** eval_windowaggregates(WindowAggState *wi
*** 452,526 ****
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
- 	 *
- 	 * TODO: Rerunning aggregates from the frame start can be pretty slow. For
- 	 * some aggregates like SUM and COUNT we could avoid that by implementing
- 	 * a "negative transition function" that would be called for each row as
- 	 * it exits the frame.	We'd have to think about avoiding recalculation of
- 	 * volatile arguments of aggregate functions, too.
  	 */
  
  	/*
  	 * First, update the frame head position.
  	 */
! 	update_frameheadpos(agg_winobj, winstate->temp_slot_1);
  
  	/*
! 	 * Initialize aggregates on first call for partition, or if the frame head
! 	 * position moved since last time.
  	 */
! 	if (winstate->currentpos == 0 ||
! 		winstate->frameheadpos != winstate->aggregatedbase)
  	{
- 		/*
- 		 * Discard transient aggregate values
- 		 */
- 		MemoryContextResetAndDeleteChildren(winstate->aggcontext);
- 
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
  
  		/*
! 		 * If we created a mark pointer for aggregates, keep it pushed up to
! 		 * frame head, so that tuplestore can discard unnecessary rows.
  		 */
! 		if (agg_winobj->markptr >= 0)
! 			WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
  
  		/*
! 		 * Initialize for loop below
  		 */
! 		ExecClearTuple(agg_row_slot);
! 		winstate->aggregatedbase = winstate->frameheadpos;
! 		winstate->aggregatedupto = winstate->frameheadpos;
  	}
  
  	/*
! 	 * In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
! 	 * except when the frame head moves.  In END_CURRENT_ROW mode, we only
! 	 * have to recalculate when the frame head moves or currentpos has
! 	 * advanced past the place we'd aggregated up to.  Check for these cases
! 	 * and if so, reuse the saved result values.
  	 */
! 	if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
! 		for (i = 0; i < numaggs; i++)
  		{
- 			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
  		}
! 		return;
  	}
  
  	/*
--- 703,889 ----
  	 * 'aggregatedupto' keeps track of the first row that has not yet been
  	 * accumulated into the aggregate transition values.  Whenever we start a
  	 * new peer group, we accumulate forward to the end of the peer group.
  	 */
  
  	/*
  	 * First, update the frame head position.
+ 	 *
+ 	 * The frame head should never move backwards, and the code below wouldn't
+ 	 * cope if it did, so for safety we complain if it does.
  	 */
! 	update_frameheadpos(agg_winobj, temp_slot);
! 	if (winstate->frameheadpos < winstate->aggregatedbase)
! 		elog(ERROR, "frame moved backwards unexpectedly");
  
  	/*
! 	 * If the frame didn't change compared to the previous row, we can re-use
! 	 * the cached result. Since we don't know the current frame's end yet, we
! 	 * cannot check that the obvious way. But we can exploit that if the frame
! 	 * end is UNBOUNDED FOLLOWING or CURRENT ROW, then whenever the current
! 	 * row lies within the previous row's frame, the two frame's ends must
! 	 * coincide. Note that for the first row, aggregatedbase = aggregatedupto,
! 	 * so we don't need to check for that explicitly here.
  	 */
! 	if (winstate->aggregatedbase == winstate->frameheadpos &&
! 		(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
! 								   FRAMEOPTION_END_CURRENT_ROW)) &&
! 		winstate->aggregatedbase <= winstate->currentpos &&
! 		winstate->aggregatedupto > winstate->currentpos)
  	{
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
  			wfuncno = peraggstate->wfuncno;
! 			econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
! 			econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
! 		}
! 		return;
! 	}
! 
! 	/* Initialize restart flags.
! 	 *
! 	 * We restart the aggregation
! 	 *   - if we're processing the first row in the partition, or
! 	 *   - if we the frame's head moved and we cannot use an inverse
! 	 *     transition function, or
! 	 *   - if the new frame doesn't overlap the old one
! 	 *
! 	 * Note that we don't strictly need to restart in the last case, but
! 	 * if we're going to remove *all* rows from the aggregation anyway, a
! 	 * restart surely is faster.
! 	 */
! 	for (i = 0; i < numaggs; i++)
! 	{
! 		peraggstate = &winstate->peragg[i];
! 		if (winstate->currentpos == 0 ||
! 			(winstate->aggregatedbase < winstate->frameheadpos &&
! 			!peraggstate->use_invtransfn) ||
! 			winstate->aggregatedupto <= winstate->frameheadpos)
! 		{
! 			peraggstate->restart = true;
! 			numaggs_restart++;
  		}
+ 		else
+ 			peraggstate->restart = false;
+ 	}
  
+ 	/*
+ 	 * Attempt to update aggregatedbase to match the frame's head by
+ 	 * removing those inputs from the aggregations which fell off the top
+ 	 * of the frame. This can fail, i.e. retreat_windowaggregate() can
+ 	 * return false, in which case we restart that aggregate below.
+ 	 *
+ 	 * Aftwards, aggregatedbase equals frameheadpos.
+ 	 */
+ 	while(winstate->aggregatedbase < winstate->frameheadpos)
+ 	{
  		/*
! 		 * Fetch the tuple where the current aggregation started from.
! 		 * This should never fail as we should have been here before.
  		 */
! 		if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
! 								 temp_slot))
! 			elog(ERROR, "Unable to find tuple in tuplestore");
! 
! 		/* Set tuple context for evaluation of aggregate arguments */
! 		winstate->tmpcontext->ecxt_outertuple = temp_slot;
  
  		/*
! 		 * Perform the inverse transition for each aggregate function in
! 		 * the window, unless it has already been marked as needing a
! 		 * restart.
  		 */
! 		for (i = 0; i < numaggs; i++)
! 		{
! 			bool	ok;
! 
! 			peraggstate = &winstate->peragg[i];
! 			if (peraggstate->restart)
! 				continue;
! 
! 			wfuncno = peraggstate->wfuncno;
! 			ok = retreat_windowaggregate(winstate,
! 										 &winstate->perfunc[wfuncno],
! 										 peraggstate);
! 			if (!ok)
! 			{
! 				/* Inverse transition function has failed, must restart */
! 				peraggstate->restart = true;
! 				numaggs_restart++;
! 			}
! 		}
! 
! 		/* Reset per-input-tuple context after each tuple */
! 		ResetExprContext(winstate->tmpcontext);
! 
! 		/* And advance the aggregated-row state */
! 		winstate->aggregatedbase++;
! 		ExecClearTuple(temp_slot);
! 
! 		/* If no more retreatable aggregates are left, we stop early */
! 		if (numaggs_restart == numaggs)
! 		{
! 			winstate->aggregatedbase = winstate->frameheadpos;
! 			break;
! 		}
  	}
  
  	/*
! 	 * If we created a mark pointer for aggregates, keep it pushed up to
! 	 * frame head, so that tuplestore can discard unnecessary rows.
  	 */
! 	if (agg_winobj->markptr >= 0)
! 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
! 
! 	/*
! 	 * Then restart the aggregates which require it.
! 	 *
! 	 * We assume that aggregates using the shared context always restart
! 	 * if *any* aggregate restarts, and we may thus cleanup the shared
! 	 * aggcontext if that is the case. The private aggcontexts are reset
! 	 * by initialize_windowaggregate() if their owning aggregate restarts,
! 	 * otherwise we just pfree() the cached result.
! 	 */
! 	if (numaggs_restart > 0)
! 		MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < numaggs; i++)
  	{
! 		peraggstate = &winstate->peragg[i];
! 		/* Aggregates using the shared ctx must restart if *any* agg does */
! 		Assert(!peraggstate->aggcontext_is_shared ||
! 			   !numaggs_restart || peraggstate->restart);
! 		if (!peraggstate->restart && !peraggstate->resultValueIsNull &&
! 			!peraggstate->resulttypeByVal)
! 		{
! 			pfree(DatumGetPointer(peraggstate->resultValue));
! 			peraggstate->resultValue = (Datum) 0;
! 			peraggstate->resultValueIsNull = true;
! 		}
! 		else if (peraggstate->restart)
  		{
  			wfuncno = peraggstate->wfuncno;
! 			initialize_windowaggregate(winstate,
! 									   &winstate->perfunc[wfuncno],
! 									   peraggstate);
  		}
! 	}
! 
! 	/*
! 	 * Non-restarted aggregates now contain the rows between aggregatedbase
! 	 * (i.e. frameheadpos) and aggregatedupto, and restarted aggregates
! 	 * contain no rows. If there are any restarted aggregates, we must thus
! 	 * begin aggregating anew at frameheadpos, otherwise we may simply
! 	 * continue at aggregatedupto. Since we possibly reset aggregatedupto, we
! 	 * must remember the old value to know how long to skip non-restarted
! 	 * aggregates. If we modify aggregatedupto, we must also clear
! 	 * agg_row_slot, per the loop invariant below.
! 	 */
! 	aggregatedupto_nonrestarted = winstate->aggregatedupto;
! 	if (numaggs_restart > 0 &&
! 		winstate->aggregatedupto != winstate->frameheadpos)
! 	{
! 		winstate->aggregatedupto = winstate->frameheadpos;
! 		ExecClearTuple(agg_row_slot);
  	}
  
  	/*
*************** eval_windowaggregates(WindowAggState *wi
*** 551,556 ****
--- 914,924 ----
  		for (i = 0; i < numaggs; i++)
  		{
  			peraggstate = &winstate->peragg[i];
+ 			/* Non-restarted aggs skip until aggregatedupto_nonrestarted */
+ 			if (winstate->aggregatedupto < aggregatedupto_nonrestarted &&
+ 				!peraggstate->restart)
+ 				continue;
+ 
  			wfuncno = peraggstate->wfuncno;
  			advance_windowaggregate(winstate,
  									&winstate->perfunc[wfuncno],
*************** eval_windowaggregates(WindowAggState *wi
*** 565,570 ****
--- 933,941 ----
  		ExecClearTuple(agg_row_slot);
  	}
  
+ 	/* The frame's end is not supposed to move backwards, ever */
+ 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
+ 
  	/*
  	 * finalize aggregates and fill result/isnull fields.
  	 */
*************** eval_windowaggregates(WindowAggState *wi
*** 589,616 ****
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal)
  		{
! 			/*
! 			 * clear old resultValue in order not to leak memory.  (Note: the
! 			 * new result can't possibly be the same datum as old resultValue,
! 			 * because we never passed it to the trans function.)
! 			 */
! 			if (!peraggstate->resultValueIsNull)
! 				pfree(DatumGetPointer(peraggstate->resultValue));
! 
! 			/*
! 			 * If pass-by-ref, copy it into our aggregate context.
! 			 */
! 			if (!*isnull)
! 			{
! 				oldContext = MemoryContextSwitchTo(winstate->aggcontext);
! 				peraggstate->resultValue =
! 					datumCopy(*result,
! 							  peraggstate->resulttypeByVal,
! 							  peraggstate->resulttypeLen);
! 				MemoryContextSwitchTo(oldContext);
! 			}
  		}
  		else
  		{
--- 960,973 ----
  		 * advance that the next row can't possibly share the same frame. Is
  		 * it worth detecting that and skipping this code?
  		 */
! 		if (!peraggstate->resulttypeByVal && !*isnull)
  		{
! 			oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
! 			peraggstate->resultValue =
! 				datumCopy(*result,
! 						  peraggstate->resulttypeByVal,
! 						  peraggstate->resulttypeLen);
! 			MemoryContextSwitchTo(oldContext);
  		}
  		else
  		{
*************** eval_windowfunction(WindowAggState *wins
*** 651,656 ****
--- 1008,1014 ----
  	/* Just in case, make all the regular argument slots be null */
  	memset(fcinfo.argnull, true, perfuncstate->numArguments);
  
+ 	winstate->calledaggno = -1;
  	*result = FunctionCallInvoke(&fcinfo);
  	*isnull = fcinfo.isnull;
  
*************** spool_tuples(WindowAggState *winstate, i
*** 794,800 ****
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kluge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
--- 1152,1158 ----
  	 * becomes quite expensive due to frequent buffer flushes.	It's cheaper
  	 * to force the entire partition to get spooled in one go.
  	 *
! 	 * XXX this is a horrid kludge --- it'd be better to fix the performance
  	 * problem inside tuplestore.  FIXME
  	 */
  	if (!tuplestore_in_memory(winstate->buffer))
*************** release_partition(WindowAggState *winsta
*** 869,875 ****
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
--- 1227,1236 ----
  	 * aggregates might have allocated data we don't have direct pointers to.
  	 */
  	MemoryContextResetAndDeleteChildren(winstate->partcontext);
! 	MemoryContextResetAndDeleteChildren(winstate->aggcontext_shared);
! 	for (i = 0; i < winstate->numaggs; ++i)
! 		if (!winstate->peragg[i].aggcontext_is_shared)
! 			MemoryContextResetAndDeleteChildren(winstate->peragg[i].aggcontext);
  
  	if (winstate->buffer)
  		tuplestore_end(winstate->buffer);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1419,1425 ****
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno;
  	ListCell   *l;
  
  	/* check for unsupported flags */
--- 1780,1787 ----
  	int			numfuncs,
  				wfuncno,
  				numaggs,
! 				aggno,
! 				numaggs_invtrans;
  	ListCell   *l;
  
  	/* check for unsupported flags */
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1450,1457 ****
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived context for aggregate trans values etc */
! 	winstate->aggcontext =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
--- 1812,1821 ----
  							  ALLOCSET_DEFAULT_INITSIZE,
  							  ALLOCSET_DEFAULT_MAXSIZE);
  
! 	/* Create mid-lived contexts for aggregate trans values etc
! 	 * Note that invertible aggregates use their own private context
! 	 */
! 	winstate->aggcontext_shared =
  		AllocSetContextCreate(CurrentMemoryContext,
  							  "WindowAgg_Aggregates",
  							  ALLOCSET_DEFAULT_MINSIZE,
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1535,1540 ****
--- 1899,1905 ----
  
  	wfuncno = -1;
  	aggno = -1;
+ 	numaggs_invtrans = 0;
  	foreach(l, winstate->funcs)
  	{
  		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1603,1608 ****
--- 1968,1975 ----
  			peraggstate = &winstate->peragg[aggno];
  			initialize_peragg(winstate, wfunc, peraggstate);
  			peraggstate->wfuncno = wfuncno;
+ 			if (peraggstate->use_invtransfn)
+ 				numaggs_invtrans++;
  		}
  		else
  		{
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1618,1623 ****
--- 1985,1991 ----
  	/* Update numfuncs, numaggs to match number of unique functions found */
  	winstate->numfuncs = wfuncno + 1;
  	winstate->numaggs = aggno + 1;
+ 	winstate->numaggs_invtrans = numaggs_invtrans;
  
  	/* Set up WindowObject for aggregates, if needed */
  	if (winstate->numaggs > 0)
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1646,1651 ****
--- 2014,2026 ----
  	winstate->partition_spooled = false;
  	winstate->more_partitions = false;
  
+ 	/* initialize temporary data */
+ 	winstate->calledaggno = -1;
+ 
+ 	/* initialize statistics */
+ 	winstate->aggfwdtrans = 0;
+ 	winstate->agginvtrans = 0;
+ 
  	return winstate;
  }
  
*************** void
*** 1657,1668 ****
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
  
  	release_partition(node);
  
- 	pfree(node->perfunc);
- 	pfree(node->peragg);
- 
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
--- 2032,2041 ----
  ExecEndWindowAgg(WindowAggState *node)
  {
  	PlanState  *outerPlan;
+ 	int			i;
  
  	release_partition(node);
  
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  	ExecClearTuple(node->first_part_slot);
  	ExecClearTuple(node->agg_row_slot);
*************** ExecEndWindowAgg(WindowAggState *node)
*** 1677,1683 ****
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
--- 2050,2062 ----
  	ExecFreeExprContext(&node->ss.ps);
  
  	MemoryContextDelete(node->partcontext);
! 	MemoryContextDelete(node->aggcontext_shared);
! 	for(i = 0; i < node->numaggs; i++)
! 		if (!node->peragg[i].aggcontext_is_shared)
! 			MemoryContextDelete(node->peragg[i].aggcontext);
! 
! 	pfree(node->perfunc);
! 	pfree(node->peragg);
  
  	outerPlan = outerPlanState(node);
  	ExecEndNode(outerPlan);
*************** initialize_peragg(WindowAggState *winsta
*** 1735,1742 ****
--- 2114,2123 ----
  	Oid			aggtranstype;
  	AclResult	aclresult;
  	Oid			transfn_oid,
+ 				invtransfn_oid,
  				finalfn_oid;
  	Expr	   *transfnexpr,
+ 			   *invtransfnexpr,
  			   *finalfnexpr;
  	Datum		textInitVal;
  	int			i;
*************** initialize_peragg(WindowAggState *winsta
*** 1762,1767 ****
--- 2143,2149 ----
  	 */
  
  	peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+ 	peraggstate->invtransfn_oid = invtransfn_oid = aggform->agginvtransfn;
  	peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
  	/* Check that aggregate owner has permission to call component fns */
*************** initialize_peragg(WindowAggState *winsta
*** 1783,1788 ****
--- 2165,2181 ----
  			aclcheck_error(aclresult, ACL_KIND_PROC,
  						   get_func_name(transfn_oid));
  		InvokeFunctionExecuteHook(transfn_oid);
+ 
+ 		if (OidIsValid(invtransfn_oid))
+ 		{
+ 			aclresult = pg_proc_aclcheck(invtransfn_oid, aggOwner,
+ 										 ACL_EXECUTE);
+ 			if (aclresult != ACLCHECK_OK)
+ 				aclcheck_error(aclresult, ACL_KIND_PROC,
+ 				get_func_name(invtransfn_oid));
+ 			InvokeFunctionExecuteHook(invtransfn_oid);
+ 		}
+ 
  		if (OidIsValid(finalfn_oid))
  		{
  			aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
*************** initialize_peragg(WindowAggState *winsta
*** 1810,1822 ****
--- 2203,2223 ----
  							wfunc->wintype,
  							wfunc->inputcollid,
  							transfn_oid,
+ 							invtransfn_oid,
  							finalfn_oid,
  							&transfnexpr,
+ 							&invtransfnexpr,
  							&finalfnexpr);
  
  	fmgr_info(transfn_oid, &peraggstate->transfn);
  	fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
+ 	if (OidIsValid(invtransfn_oid))
+ 	{
+ 		fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
+ 		fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
+ 	}
+ 
  	if (OidIsValid(finalfn_oid))
  	{
  		fmgr_info(finalfn_oid, &peraggstate->finalfn);
*************** initialize_peragg(WindowAggState *winsta
*** 1860,1867 ****
--- 2261,2334 ----
  							wfunc->winfnoid)));
  	}
  
+ 	/*
+ 	 * Allowing only the forward transition function to be strict would
+ 	 * require handling more special cases in advance_windowaggregate() and
+ 	 * retreat_windowaggregate(), for no discernible benefit. So we ensure
+ 	 * that if the forward transition function is strict that the inverse
+ 	 * transition function is also strict. This should have been checked at
+ 	 * the aggregate function's definition time, but it's better to be safe...
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ 				errmsg("strictness of forward and inverse transition functions must match")));
+ 	}
+ 
  	ReleaseSysCache(aggTuple);
  
+ 	/*
+ 	 * We can use the inverse transition function only if the aggregate's
+ 	 * arguments don't contain calls to volatile functions. Otherwise,
+ 	 * the difference between restarting and not restarting the aggregation
+ 	 * would be user-visible. Note that this check also covers the case where
+ 	 * the FILTER's WHERE clause contains a volatile function. If the frame
+ 	 * head cannot move, we won't ever need the inverse transition function,
+ 	 * so we also mark as "don't use" in that case.
+ 	 */
+ 	if (OidIsValid(invtransfn_oid) &&
+ 		!contain_volatile_functions((Node *) wfunc) &&
+ 		!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
+ 	{
+ 		peraggstate->use_invtransfn = true;
+ 	}
+ 	else
+ 	{
+ 		peraggstate->use_invtransfn = false;
+ 	}
+ 
+ 	/*
+ 	 * Invertible aggregates use their own aggcontext.
+ 	 *
+ 	 * This is necessary because they might all restart at different times,
+ 	 * so we might never be able to reset the shared context otherwise. We
+ 	 * can't make it the aggregate's responsibility to clean up after
+ 	 * themselves, because strict aggregates must be restarted whenever we
+ 	 * remove their last non-NULL input, which the aggregate won't be aware
+ 	 * is happening. Also, just pfree()ing the transValue upon restarting
+ 	 * wouldn't help, since we'd miss any indirectly referenced data. We
+ 	 * could, in theory, declare that aggregates with a state type other then
+ 	 * "internal" musn't allocate anything in the aggcontext themselves, that
+ 	 * non-strict aggregates with state type internal must clean up after
+ 	 * themselves when their inverse transfer function returns NULL, and then
+ 	 * only use private aggcontexts for strict aggregates with state type
+ 	 * internal. But that'd be a rather grotty set of requirements.
+ 	 */
+ 	peraggstate->aggcontext_is_shared = !peraggstate->use_invtransfn;
+ 	if (!peraggstate->aggcontext_is_shared)
+ 	{
+ 		peraggstate->aggcontext =
+ 			AllocSetContextCreate(CurrentMemoryContext,
+ 								  "WindowAgg_AggregatePrivate",
+ 								  ALLOCSET_DEFAULT_MINSIZE,
+ 								  ALLOCSET_DEFAULT_INITSIZE,
+ 								  ALLOCSET_DEFAULT_MAXSIZE);
+ 	}
+ 	else
+ 		peraggstate->aggcontext = winstate->aggcontext_shared;
+ 
  	return peraggstate;
  }
  
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..ef84e4c 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** resolve_aggregate_transtype(Oid aggfunci
*** 1187,1197 ****
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid and finalfn_oid identify the funcs to be called; the latter
!  * may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr and
!  * *finalfnexpr.  The latter is set to NULL if there's no finalfn.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
--- 1187,1199 ----
   * For an ordered-set aggregate, remember that agg_input_types describes
   * the direct arguments followed by the aggregated arguments.
   *
!  * transfn_oid, invtransfn_oid and finalfn_oid identify the funcs to be
!  * called; the latter two may be InvalidOid.
   *
!  * Pointers to the constructed trees are returned into *transfnexpr,
!  * *invtransfnexpr and *finalfnexpr. If there is invtransfn or finalfn, the
!  * respective pointers are set to NULL. Since use of the invtransfn is
!  * optional, NULL may be passed for invtransfnexpr.
   */
  void
  build_aggregate_fnexprs(Oid *agg_input_types,
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1203,1210 ****
--- 1205,1214 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr)
  {
  	Param	   *argp;
*************** build_aggregate_fnexprs(Oid *agg_input_t
*** 1249,1254 ****
--- 1253,1270 ----
  	fexpr->funcvariadic = agg_variadic;
  	*transfnexpr = (Expr *) fexpr;
  
+ 	if (OidIsValid(invtransfn_oid) && invtransfnexpr != NULL)
+ 	{
+ 		*invtransfnexpr = (Expr *) makeFuncExpr(invtransfn_oid,
+ 												agg_result_type,
+ 												args,
+ 												InvalidOid,
+ 												agg_input_collation,
+ 												COERCE_EXPLICIT_CALL);
+ 	}
+ 	else if (invtransfnexpr != NULL)
+ 		*invtransfnexpr	= NULL;
+ 
  	/* see if we have a final function */
  	if (!OidIsValid(finalfn_oid))
  	{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2653ef0..c14209f 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11547,11552 ****
--- 11547,11553 ----
  	char	   *aggsig_tag;
  	PGresult   *res;
  	int			i_aggtransfn;
+ 	int			i_agginvtransfn;
  	int			i_aggfinalfn;
  	int			i_aggsortop;
  	int			i_hypothetical;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11555,11560 ****
--- 11556,11562 ----
  	int			i_agginitval;
  	int			i_convertok;
  	const char *aggtransfn;
+ 	const char *agginvtransfn;
  	const char *aggfinalfn;
  	const char *aggsortop;
  	char	   *aggsortconvop;
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11580,11586 ****
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
--- 11582,11588 ----
  	/* Get aggregate-specific details */
  	if (fout->remoteVersion >= 90400)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "(aggkind = 'h') as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11596,11601 ****
--- 11598,11604 ----
  	else if (fout->remoteVersion >= 80400)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11611,11616 ****
--- 11614,11620 ----
  	else if (fout->remoteVersion >= 80100)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "aggsortop::pg_catalog.regoperator, "
  						  "false as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11624,11629 ****
--- 11628,11634 ----
  	else if (fout->remoteVersion >= 70300)
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11636,11642 ****
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
--- 11641,11649 ----
  	}
  	else if (fout->remoteVersion >= 70100)
  	{
! 		appendPQExpBuffer(query, "SELECT aggtransfn, "
! 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
! 						  "aggfinalfn, "
  						  "format_type(aggtranstype, NULL) AS aggtranstype, "
  						  "0 AS aggsortop, "
  						  "'f'::boolean as hypothetical, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11649,11654 ****
--- 11656,11662 ----
  	else
  	{
  		appendPQExpBuffer(query, "SELECT aggtransfn1 AS aggtransfn, "
+ 						  "'-'::pg_catalog.regproc AS agginvtransfn, "
  						  "aggfinalfn, "
  						  "(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
  						  "0 AS aggsortop, "
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11663,11668 ****
--- 11671,11677 ----
  	res = ExecuteSqlQueryForSingleRow(fout, query->data);
  
  	i_aggtransfn = PQfnumber(res, "aggtransfn");
+ 	i_agginvtransfn = PQfnumber(res, "agginvtransfn");
  	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
  	i_aggsortop = PQfnumber(res, "aggsortop");
  	i_hypothetical = PQfnumber(res, "hypothetical");
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11672,11677 ****
--- 11681,11687 ----
  	i_convertok = PQfnumber(res, "convertok");
  
  	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ 	agginvtransfn = PQgetvalue(res, 0, i_agginvtransfn);
  	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
  	aggsortop = PQgetvalue(res, 0, i_aggsortop);
  	hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
*************** dumpAgg(Archive *fout, AggInfo *agginfo)
*** 11733,11738 ****
--- 11743,11754 ----
  						  fmtId(aggtranstype));
  	}
  
+ 	if (strcmp(agginvtransfn, "-") != 0)
+ 	{
+ 		appendPQExpBuffer(details, ",\n    INVSFUNC = %s",
+ 						  agginvtransfn);
+ 	}
+ 
  	if (strcmp(aggtransspace, "0") != 0)
  	{
  		appendPQExpBuffer(details, ",\n    SSPACE = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index f189998..3412661 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 46,51 ****
--- 46,52 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		agginvtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
  	Oid			aggtranstype;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 69,85 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_agginvtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,277 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 103,279 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	-	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	-	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	-	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	-	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	-	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	-	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-	-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-	-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-	-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-	-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-	-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-	-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	-	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	-	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	-	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	-	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	-	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	-	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	-	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	-	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	-	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	-	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	-	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	-	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-	-					0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	-	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	-	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	-	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	-	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	-	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	-	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	-	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	-	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	-	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	-	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-	-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-	-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-	-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-	-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-	-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-	-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-	-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	-	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			-	percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			-	percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			-	percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			-	percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			-	percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			-	percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			-	mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	-	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	-	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	-	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	-	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
*************** extern Oid AggregateCreate(const char *a
*** 289,294 ****
--- 291,297 ----
  				List *parameterDefaults,
  				Oid variadicArgType,
  				List *aggtransfnName,
+ 				List *agginvtransfnName,
  				List *aggfinalfnName,
  				List *aggsortopName,
  				Oid aggTransType,
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index aed81cd..c4d4864 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** extern void **find_rendezvous_variable(c
*** 645,652 ****
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly with nodeAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
--- 645,654 ----
  /*
   * Support for aggregate functions
   *
!  * These are actually in executor/nodeAgg.c, except for AggCheckCallContext
!  * which is in execute/nodeWindowAgg.c, but we declare them here since
!  * the whole point is for callers to not be overly friendly neither nodeAgg
!  * nor nodeWindowAgg.
   */
  
  /* AggCheckCallContext can return one of the following codes, or 0: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..03ee793 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WindowAggState
*** 1738,1743 ****
--- 1738,1744 ----
  	List	   *funcs;			/* all WindowFunc nodes in targetlist */
  	int			numfuncs;		/* total number of window functions */
  	int			numaggs;		/* number that are plain aggregates */
+ 	int			numaggs_invtrans;	/* number that are invertible aggregates */
  
  	WindowStatePerFunc perfunc; /* per-window-function information */
  	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
*************** typedef struct WindowAggState
*** 1762,1768 ****
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext;	/* context for each aggregate data */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
--- 1763,1769 ----
  	Datum		endOffsetValue; /* result of endOffset evaluation */
  
  	MemoryContext partcontext;	/* context for partition-lifespan data */
! 	MemoryContext aggcontext_shared;	/* shared context for agg states */
  	ExprContext *tmpcontext;	/* short-term evaluation context */
  
  	bool		all_first;		/* true if the scan is starting */
*************** typedef struct WindowAggState
*** 1780,1789 ****
--- 1781,1797 ----
  	TupleTableSlot *first_part_slot;	/* first tuple of current or next
  										 * partition */
  
+ 	/* temporary data */
+ 	int			calledaggno;	/* called agg, used by AggCheckCallContext */
+ 
  	/* temporary slots for tuples fetched back from tuplestore */
  	TupleTableSlot *agg_row_slot;
  	TupleTableSlot *temp_slot_1;
  	TupleTableSlot *temp_slot_2;
+ 
+ 	/* Statistics */
+ 	double	aggfwdtrans;	/* number of forward transitions */
+ 	double	agginvtrans;	/* number of inverse transitions */
  } WindowAggState;
  
  /* ----------------
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 8faf991..938d408 100644
*** a/src/include/parser/parse_agg.h
--- b/src/include/parser/parse_agg.h
*************** extern void build_aggregate_fnexprs(Oid 
*** 39,46 ****
--- 39,48 ----
  						Oid agg_result_type,
  						Oid agg_input_collation,
  						Oid transfn_oid,
+ 						Oid invtransfn_oid,
  						Oid finalfn_oid,
  						Expr **transfnexpr,
+ 						Expr **invtransfnexpr,
  						Expr **finalfnexpr);
  
  #endif   /* PARSE_AGG_H */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 58df854..080cbd2 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** select least_agg(variadic array[q1,q2]) 
*** 1580,1582 ****
--- 1580,1606 ----
   -4567890123456789
  (1 row)
  
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
+  proname | proname 
+ ---------+---------
+ (0 rows)
+ 
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ca908d9..1a52423 100644
*** a/src/test/regress/expected/create_aggregate.out
--- b/src/test/regress/expected/create_aggregate.out
*************** alter aggregate my_rank(VARIADIC "any" O
*** 90,92 ****
--- 90,129 ----
   public | test_rank            | bigint           | VARIADIC "any" ORDER BY VARIADIC "any" | 
  (2 rows)
  
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ ERROR:  strictness of forward and inverse transition functions must match
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ ERROR:  function intminus(double precision, double precision) does not exist
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
+ ERROR:  return type of inverse transition function float8mi_int is not double precision
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 0f21fcb..e3eba47 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT nth_value_def(ten) OVER (PARTITIO
*** 1071,1073 ****
--- 1071,1312 ----
               1 |   3 |    3
  (10 rows)
  
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    |                    nstrict                    |                  nstrict_init                  |  strict   |  strict_init  
+ ----------+-----------------------------------------------+------------------------------------------------+-----------+---------------
+  1,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  1,2:a    | +NULL+'a'                                     | I+NULL+'a'                                     | a         | I+'a'
+  1,3:b    | +NULL+'a'-NULL+'b'                            | I+NULL+'a'-NULL+'b'                            | a+'b'     | I+'a'+'b'
+  1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL                   | I+NULL+'a'-NULL+'b'-'a'+NULL                   | a+'b'-'a' | I+'a'+'b'-'a'
+  1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL          |           | I
+  1,6:c    | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | I+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c         | I+'c'
+  2,1:NULL | +NULL                                         | I+NULL                                         |           | I
+  2,2:x    | +NULL+'x'                                     | I+NULL+'x'                                     | x         | I+'x'
+  3,1:z    | +'z'                                          | I+'z'                                          | z         | I+'z'
+ (9 rows)
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+    row    | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt 
+ ----------+--------------+-------------------+-------------+------------------
+  1,1:NULL | +NULL        | I+NULL            |             | I
+  1,2:-    | +NULL        | I+NULL            |             | I
+  1,3:b    | +'b'         | I+'b'             | b           | I+'b'
+  1,4:-    | +'b'         | I+'b'             | b           | I+'b'
+  1,5:-    |              | I                 |             | I
+  1,6:-    |              | I                 |             | I
+  2,1:-    |              | I                 |             | I
+  2,2:x    | +'x'         | I+'x'             | x           | I+'x'
+  3,1:z    | +'z'         | I+'z'             | z           | I+'z'
+ (9 rows)
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+  row |    inverse    | noinverse 
+ -----+---------------+-----------
+  1:a | a             | a
+  2:b | a+'b'         | a+'b'
+  3:c | a+'b'-'a'+'c' | b+'c'
+ (3 rows)
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+  logging_agg_strict 
+ --------------------
+  a
+  b
+  c
+ (3 rows)
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+  eq1 | eq2 | eq3 
+ -----+-----+-----
+  t   | t   | t
+ (1 row)
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the boolean inverse transition functions
+ --
+ --
+ --
+ --
+ -- Test the MIN and MAX inverse transition functions
+ --
+ --
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 8096a6f..34f7004 100644
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
*************** drop view aggordview1;
*** 590,592 ****
--- 590,609 ----
  -- variadic aggregates
  select least_agg(q1,q2) from int8_tbl;
  select least_agg(variadic array[q1,q2]) from int8_tbl;
+ -- built in aggregate function transition function args must match the inverse
+ -- transition function arguments.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proargtypes <> pinvtrns.proargtypes
+     AND agg.aggfnoid <= 9999;
+ 
+ -- built in aggregate function transition function may only be strict if
+ -- the inverse is also strict.
+ SELECT ptrns.proname, pinvtrns.proname
+   FROM pg_aggregate agg
+   INNER JOIN pg_proc As ptrns ON agg.aggtransfn = ptrns.oid
+   INNER JOIN pg_proc AS pinvtrns ON agg.agginvtransfn = pinvtrns.oid
+   WHERE agg.agginvtransfn <> 0 AND ptrns.proisstrict <> pinvtrns.proisstrict
+     AND agg.aggfnoid <= 9999;
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index c76882a..130489c 100644
*** a/src/test/regress/sql/create_aggregate.sql
--- b/src/test/regress/sql/create_aggregate.sql
*************** alter aggregate my_rank(VARIADIC "any" O
*** 101,103 ****
--- 101,144 ----
    rename to test_rank;
  
  \da test_*
+ 
+ -- inverse transition functions
+ CREATE AGGREGATE sumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi
+ );
+ 
+ CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
+ $$ SELECT $1 - $2; $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE invalidsumdouble (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_n
+ );
+ 
+ CREATE FUNCTION intminus(int, int) RETURNS float8 AS
+ $$ SELECT CAST($1 - $2 AS float8); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongtype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = intminus
+ );
+ 
+ CREATE FUNCTION float8mi_int(float8, float8) RETURNS int AS
+ $$ SELECT CAST($1 - $2 AS INT); $$
+ LANGUAGE SQL;
+ 
+ CREATE AGGREGATE wrongreturntype (float8)
+ (
+     stype = float8,
+     sfunc = float8pl,
+     invsfunc = float8mi_int
+ );
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 7297e62..bd8dbdc 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** SELECT nth_value_def(n := 2, val := ten)
*** 284,286 ****
--- 284,495 ----
  
  SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four
    FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s;
+ 
+ --
+ --
+ -- Test the basic inverse transition function machinery
+ --
+ --
+ 
+ -- create aggregates which log their calls
+ -- invsfunc is *not* a real inverse here!
+ CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT COALESCE($1, '') || '+' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_nonstrict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_nonstrict (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict
+ );
+ 
+ CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_nonstrict,
+ 	invsfunc = logging_invsfunc_nonstrict,
+ 	initcond = 'I'
+ );
+ 
+ CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '+' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE FUNCTION logging_invsfunc_strict(text, anyelement) RETURNS text AS
+ $$SELECT $1 || '-' || quote_nullable($2)$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE logging_agg_strict (text)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict
+ );
+ 
+ CREATE AGGREGATE logging_agg_strict_initcond (anyelement)
+ (
+ 	stype = text,
+ 	sfunc = logging_sfunc_strict,
+ 	invsfunc = logging_invsfunc_strict,
+ 	initcond = 'I'
+ );
+ 
+ -- test strict and non-strict cases
+ SELECT
+ 	p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row,
+ 	logging_agg_nonstrict(v) over wnd as nstrict,
+ 	logging_agg_nonstrict_initcond(v) over wnd as nstrict_init,
+ 	logging_agg_strict(v::text) over wnd as strict,
+ 	logging_agg_strict_initcond(v) over wnd as strict_init
+ FROM (VALUES
+ 	(1, 1, NULL),
+ 	(1, 2, 'a'),
+ 	(1, 3, 'b'),
+ 	(1, 4, NULL),
+ 	(1, 5, NULL),
+ 	(1, 6, 'c'),
+ 	(2, 1, NULL),
+ 	(2, 2, 'x'),
+ 	(3, 1, 'z')
+ ) AS t(p, i, v)
+ WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- and again, but with filter
+ SELECT
+ 	p::text || ',' || i::text || ':' ||
+ 		CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row,
+ 	logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt,
+ 	logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt,
+ 	logging_agg_strict(v::text) filter(where f) over wnd as strict_filt,
+ 	logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt
+ FROM (VALUES
+ 	(1, 1, true,  NULL),
+ 	(1, 2, false, 'a'),
+ 	(1, 3, true,  'b'),
+ 	(1, 4, false, NULL),
+ 	(1, 5, false, NULL),
+ 	(1, 6, false, 'c'),
+ 	(2, 1, false, NULL),
+ 	(2, 2, true,  'x'),
+ 	(3, 1, true,  'z')
+ ) AS t(p, i, f, v)
+ WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY p, i;
+ 
+ -- tests that volatile arguments disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- tests that volatile filters disable inverse transition functions
+ SELECT
+ 	i::text || ':' || COALESCE(v::text, 'NULL') as row,
+ 	logging_agg_strict(v::text) filter(where true)
+ 		over wnd as inverse,
+ 	logging_agg_strict(v::text) filter(where random() >= 0)
+ 		over wnd as noinverse
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that non-overlapping windows don't use inverse transitions
+ SELECT
+ 	logging_agg_strict(v::text) OVER wnd
+ FROM (VALUES
+ 	(1, 'a'),
+ 	(2, 'b'),
+ 	(3, 'c')
+ ) AS t(i, v)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW)
+ ORDER BY i;
+ 
+ -- test that returning NULL from the inverse transition functions
+ -- restarts the aggregation from scratch. The second aggregate is supposed
+ -- to test cases where only some aggregates restart, the third one checks
+ -- the one aggregate restarting doesn't cause others to restart
+ CREATE FUNCTION sum_int_randrestart_invsfunc(int4, int4) RETURNS int4 AS
+ $$SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END$$
+ LANGUAGE SQL STRICT IMMUTABLE;
+ 
+ CREATE AGGREGATE sum_int_randomrestart (int4)
+ (
+ 	stype = int4,
+ 	sfunc = int4pl,
+ 	invsfunc = sum_int_randrestart_invsfunc
+ );
+ 
+ WITH
+ vs AS (
+ 	SELECT i, (random() * 100)::int4 AS v
+ 	FROM generate_series(1, 100) AS i
+ ),
+ sum_following AS (
+ 	SELECT i, SUM(v) OVER
+ 		(ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s
+ 	FROM vs
+ )
+ SELECT DISTINCT
+ 	sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1,
+ 	-sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2,
+ 	100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3
+ FROM vs
+ JOIN sum_following ON sum_following.i = vs.i
+ WINDOW fwd AS (
+ 	ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ );
+ 
+ -- cleanup aggregates
+ DROP AGGREGATE sum_int_randomrestart (int4);
+ DROP FUNCTION sum_int_randrestart_invsfunc(int4, int4);
+ DROP AGGREGATE logging_agg_strict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_strict(text);
+ DROP FUNCTION logging_invsfunc_strict(text, anyelement);
+ DROP FUNCTION logging_sfunc_strict(text, anyelement);
+ DROP AGGREGATE logging_agg_nonstrict_initcond(anyelement);
+ DROP AGGREGATE logging_agg_nonstrict(anyelement);
+ DROP FUNCTION logging_invsfunc_nonstrict(text, anyelement);
+ DROP FUNCTION logging_sfunc_nonstrict(text, anyelement);
+ 
+ --
+ --
+ -- Test the arithmetic inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the boolean inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Test the MIN and MAX inverse transition functions
+ --
+ --
+ 
+ --
+ --
+ -- Tests of the collecting inverse transition functions
+ --
+ --
invtrans_bool_73bf630.patchapplication/octet-stream; name=invtrans_bool_73bf630.patchDownload
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index d419b4a..593460d 100644
*** a/src/backend/utils/adt/bool.c
--- b/src/backend/utils/adt/bool.c
*************** boolge(PG_FUNCTION_ARGS)
*** 285,290 ****
--- 285,292 ----
  
  /* function for standard EVERY aggregate implementation conforming to SQL 2003.
   * must be strict. It is also named bool_and for homogeneity.
+  * Note: this is no longer used for the bool_and() and every() aggregate
+  * functions.
   */
  Datum
  booland_statefunc(PG_FUNCTION_ARGS)
*************** booland_statefunc(PG_FUNCTION_ARGS)
*** 294,302 ****
--- 296,400 ----
  
  /* function for standard ANY/SOME aggregate conforming to SQL 2003.
   * must be strict. The name of the aggregate is bool_or. See the doc.
+  * Note: this is no longer used for the bool_or aggregate function.
   */
  Datum
  boolor_statefunc(PG_FUNCTION_ARGS)
  {
  	PG_RETURN_BOOL(PG_GETARG_BOOL(0) || PG_GETARG_BOOL(1));
  }
+ 
+ typedef struct BoolAggState
+ {
+ 	int64 aggcount; /* number of values aggregated */
+ 	int64 aggtrue; /* number of values aggregated which are true */
+ } BoolAggState;
+ 
+ static BoolAggState *
+ makeBoolAggState(FunctionCallInfo fcinfo)
+ {
+ 	BoolAggState *state;
+ 	MemoryContext agg_context;
+ 	MemoryContext old_context;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &agg_context))
+ 		elog(ERROR, "aggregate function called in non-aggregate context");
+ 
+ 	old_context = MemoryContextSwitchTo(agg_context);
+ 
+ 	state = (BoolAggState *) palloc(sizeof(BoolAggState));
+ 	state->aggcount = 0;
+ 	state->aggtrue = 0;
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 
+ 	return state;
+ }
+ 
+ Datum
+ bool_accum(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* Create the state data on first call */
+ 	if (state == NULL)
+ 		state = makeBoolAggState(fcinfo);
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount++;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue++;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_accum_inv(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* bool_accum should have created the state data */
+ 	if (state == NULL)
+ 		elog(ERROR, "bool_accum_inv called with NULL state");
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		state->aggcount--;
+ 		if (PG_GETARG_BOOL(1))
+ 			state->aggtrue--;
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ bool_alltrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if all values are true */
+ 	PG_RETURN_BOOL(state->aggcount == state->aggtrue);
+ }
+ 
+ Datum
+ bool_anytrue(PG_FUNCTION_ARGS)
+ {
+ 	BoolAggState *state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL || state->aggcount == 0)			/* there were no non-null inputs */
+ 		PG_RETURN_NULL();
+ 
+ 	/* true if any value is true */
+ 	PG_RETURN_BOOL(state->aggtrue > 0);
+ }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..7289487 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2828	n 0 float8_regr_accum
*** 232,240 ****
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	-			58	16		0	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
--- 232,240 ----
  DATA(insert ( 2829	n 0 float8_regr_accum	-	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
! DATA(insert ( 2518	n 0 bool_accum		bool_accum_inv		bool_anytrue	59	2281		16	_null_ ));
! DATA(insert ( 2519	n 0 bool_accum		bool_accum_inv		bool_alltrue	58	2281		16	_null_ ));
  
  /* bitwise integer */
  DATA(insert ( 2236	n 0 int2and		-	-					0	21		0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..41031cd 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 939  (  generate_serie
*** 3860,3869 ****
--- 3860,3871 ----
  DESCR("non-persistent series generator");
  
  /* boolean aggregates */
+ /* previous aggregate transition functions, unused now */
  DATA(insert OID = 2515 ( booland_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ booland_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 2516 ( boolor_statefunc			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolor_statefunc _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ /* aggregates and new invertible transition functions */
  DATA(insert OID = 2517 ( bool_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
  /* ANY, SOME? These names conflict with subquery operators. See doc. */
*************** DATA(insert OID = 2518 ( bool_or					   
*** 3871,3876 ****
--- 3873,3886 ----
  DESCR("boolean-or aggregate");
  DATA(insert OID = 2519 ( every						   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 16 "16" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("boolean-and aggregate");
+ DATA(insert OID = 4069 ( bool_accum					   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 4070 ( bool_accum_inv				   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ bool_accum_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4071 ( bool_alltrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_alltrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
+ DATA(insert OID = 4072 ( bool_anytrue				   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 16 "2281" _null_ _null_ _null_ _null_ bool_anytrue _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
  
  /* bitwise integer aggregates */
  DATA(insert OID = 2236 ( bit_and					   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 21 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..d6f2e2f 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum boolle(PG_FUNCTION_ARGS);
*** 121,126 ****
--- 121,130 ----
  extern Datum boolge(PG_FUNCTION_ARGS);
  extern Datum booland_statefunc(PG_FUNCTION_ARGS);
  extern Datum boolor_statefunc(PG_FUNCTION_ARGS);
+ extern Datum bool_accum(PG_FUNCTION_ARGS);
+ extern Datum bool_accum_inv(PG_FUNCTION_ARGS);
+ extern Datum bool_alltrue(PG_FUNCTION_ARGS);
+ extern Datum bool_anytrue(PG_FUNCTION_ARGS);
  extern bool parse_bool(const char *value, bool *result);
  extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
  
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index e3eba47..fcb07c8 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1300,1305 ****
--- 1300,1317 ----
  -- Test the boolean inverse transition functions
  --
  --
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  b | bool_and | bool_or 
+ ---+----------+---------
+  t | t        | t
+  t | f        | t
+  f | f        | f
+  f | f        | t
+  t | t        | t
+ (5 rows)
+ 
  --
  --
  -- Test the MIN and MAX inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index bd8dbdc..dcb410e 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 482,487 ****
--- 482,491 ----
  --
  --
  
+ SELECT b,bool_and(b) OVER w, bool_or(b) OVER w
+ FROM (VALUES(1,true),(2,true),(3,false),(4,false),(5,true)) v(i,b)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
  --
  --
  -- Test the MIN and MAX inverse transition functions
invtrans_collecting_2e5f19.patchapplication/octet-stream; name=invtrans_collecting_2e5f19.patchDownload
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index c62e3fb..5a3a31d 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** create_singleton_array(FunctionCallInfo 
*** 471,477 ****
  
  
  /*
!  * ARRAY_AGG aggregate function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
--- 471,477 ----
  
  
  /*
!  * ARRAY_AGG aggregate transition function
   */
  Datum
  array_agg_transfn(PG_FUNCTION_ARGS)
*************** array_agg_transfn(PG_FUNCTION_ARGS)
*** 508,513 ****
--- 508,537 ----
  	PG_RETURN_POINTER(state);
  }
  
+ /*
+  * ARRAY_AGG aggregate inverse transition function
+  */
+ Datum
+ array_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayBuildState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since NULLs
+ 	 * need to be removed just like any other value.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "array_agg_invtransfn called with NULL state");
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "array_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	shiftArrayResult(state, 1);
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
  Datum
  array_agg_finalfn(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..2dd7ecb 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** accumArrayResult(ArrayBuildState *astate
*** 4587,4592 ****
--- 4587,4593 ----
  		astate = (ArrayBuildState *) palloc(sizeof(ArrayBuildState));
  		astate->mcontext = arr_context;
  		astate->alen = 64;		/* arbitrary starting array size */
+ 		astate->offset = 0;
  		astate->dvalues = (Datum *) palloc(astate->alen * sizeof(Datum));
  		astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
  		astate->nelems = 0;
*************** accumArrayResult(ArrayBuildState *astate
*** 4600,4606 ****
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/* enlarge dvalues[]/dnulls[] if needed */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
--- 4601,4612 ----
  	{
  		oldcontext = MemoryContextSwitchTo(astate->mcontext);
  		Assert(astate->element_type == element_type);
! 		/*
! 		 * If the buffers are filled completely (offset must be zero then),
! 		 * we double their size. If they aren't, but the values extend to the
! 		 * end of the buffers, we reclaim wasted space at the beginning by
! 		 * moving the values to the front of the buffers.
! 		 */
  		if (astate->nelems >= astate->alen)
  		{
  			astate->alen *= 2;
*************** accumArrayResult(ArrayBuildState *astate
*** 4609,4614 ****
--- 4615,4629 ----
  			astate->dnulls = (bool *)
  				repalloc(astate->dnulls, astate->alen * sizeof(bool));
  		}
+ 		else if (astate->offset + astate->nelems >= astate->alen)
+ 		{
+ 			memmove(astate->dvalues, astate->dvalues + astate->offset,
+ 					astate->alen * sizeof(Datum));
+ 			memmove(astate->dnulls, astate->dnulls + astate->offset,
+ 					astate->alen * sizeof(bool));
+ 			astate->offset = 0;
+ 		}
+ 		Assert(astate->offset + astate->nelems < astate->alen);
  	}
  
  	/*
*************** accumArrayResult(ArrayBuildState *astate
*** 4627,4634 ****
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->nelems] = dvalue;
! 	astate->dnulls[astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
--- 4642,4649 ----
  			dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
  	}
  
! 	astate->dvalues[astate->offset + astate->nelems] = dvalue;
! 	astate->dnulls[astate->offset + astate->nelems] = disnull;
  	astate->nelems++;
  
  	MemoryContextSwitchTo(oldcontext);
*************** accumArrayResult(ArrayBuildState *astate
*** 4637,4642 ****
--- 4652,4698 ----
  }
  
  /*
+  * shiftArrayResult - shift leading Datums out of an array result
+  *
+  *	astate is working state
+  *	count is the number of leading Datums to shift out
+  *
+  * If count is equal to or larger than the number of relements, the array
+  * result is empty afterwards. If astate is NULL, nothing is done.
+  */
+ void
+ shiftArrayResult(ArrayBuildState *astate, int count)
+ {
+ 	int		i;
+ 
+ 	if (astate == NULL)
+ 		return;
+ 
+ 	/* Limit shift count to number of elements for safety */
+ 	count = Min(count, astate->nelems);
+ 
+ 	/* For pass-by-ref types, free values we shift out */
+ 	if (!astate->typbyval) {
+ 		for(i = astate->offset; i < astate->offset + count; ++i) {
+ 			if (astate->dnulls[i])
+ 				continue;
+ 
+ 			pfree(DatumGetPointer(astate->dvalues[i]));
+ 
+ 			/* For cleanliness' sake */
+ 			astate->dnulls[i] = false;
+ 			astate->dvalues[i] = 0;
+ 		}
+ 
+ 	}
+ 
+ 	/* Update state */
+ 	astate->nelems -= count;
+ 	astate->offset += count;
+ }
+ 
+ 
+ /*
   * makeArrayResult - produce 1-D final result of accumArrayResult
   *
   *	astate is working state (not NULL)
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4679,4686 ****
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues,
! 								astate->dnulls,
  								ndims,
  								dims,
  								lbs,
--- 4735,4742 ----
  	/* Build the final array result in rcontext */
  	oldcontext = MemoryContextSwitchTo(rcontext);
  
! 	result = construct_md_array(astate->dvalues + astate->offset,
! 								astate->dnulls + astate->offset,
  								ndims,
  								dims,
  								lbs,
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index cb07a06..74fe7fc 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** typedef struct
*** 50,55 ****
--- 50,63 ----
  	int			skiptable[256]; /* skip distance for given mismatched char */
  } TextPositionState;
  
+ typedef struct StringAggState
+ {
+ 	StringInfoData 	string; 	/* Contents */
+ 	int 			offset;		/* Offset into stringinfo's data */
+ 	int 			delimLen;	/* Delim length, -1 initially, -2 if multiple */
+ 	int64			notNullCount;/* Number of non-NULL inputs */
+ } StringAggState;
+ 
  #define DatumGetUnknownP(X)			((unknown *) PG_DETOAST_DATUM(X))
  #define DatumGetUnknownPCopy(X)		((unknown *) PG_DETOAST_DATUM_COPY(X))
  #define PG_GETARG_UNKNOWN_P(n)		DatumGetUnknownP(PG_GETARG_DATUM(n))
*************** static void appendStringInfoText(StringI
*** 78,84 ****
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
--- 86,95 ----
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
! static StringAggState* makeStringAggState(FunctionCallInfo fcinfo);
! static void prepareAppendStringAggState(StringAggState *state,
! 										int delimLen, int valueLen);
! static bool removeFromStringAggState(StringAggState *state, int valueLen);
  static bool text_format_parse_digits(const char **ptr, const char *end_ptr,
  						 int *value);
  static const char *text_format_parse_format(const char *start_ptr,
*************** static void text_format_string_conversio
*** 92,97 ****
--- 103,226 ----
  static void text_format_append_string(StringInfo buf, const char *str,
  						  int flags, int width);
  
+ /*****************************************************************************
+  *	 SUPPORT ROUTINES FOR STRING_AGG(TEXT) AND STRING_AGG(BYTEA)			 *
+  *****************************************************************************/
+ 
+ /*
+  * subroutine to initialize state
+  */
+ static StringAggState*
+ makeStringAggState(FunctionCallInfo fcinfo)
+ {
+ 	StringAggState*	state;
+ 	MemoryContext aggcontext;
+ 	MemoryContext oldcontext;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 	}
+ 
+ 	/*
+ 	 * Create state in aggregate context.  It'll stay there across subsequent
+ 	 * calls.
+ 	 */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = (StringAggState *) palloc(sizeof(StringAggState));
+ 	initStringInfo(&state->string);
+ 	state->offset = 0;
+ 	state->delimLen = -1;
+ 	state->notNullCount = 0;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * Prepare state for appending a value and a delimiter with specified lengths.
+  * pass -1 for delimLen if no delimiter will be added
+  */
+ void
+ prepareAppendStringAggState(StringAggState *state, int delimLen, int valueLen)
+ {
+ 	/*
+ 	 * Reclaim wasted space
+ 	 *
+ 	 * We move the contents to the left if the current contents fit into the
+ 	 * wasted space, i.e. if we waste more than we store. The limit is
+ 	 * somewhat arbitrary, but it's the smallest one that allows
+ 	 * memcpy to be used, because the source and destination don't overlap.
+ 	 * Note that we must check for <, not <=, because we include the trailing
+ 	 * '\0' in the copy.
+ 	 */
+ 	if (state->string.len - state->offset < state->offset)
+ 	{
+ 		state->string.len -= state->offset;
+ 		memcpy(state->string.data, state->string.data + state->offset,
+ 			   state->string.len + 1);
+ 		state->offset = 0;
+ 	}
+ 
+ 	/*
+ 	 * Enlarge StringInfo
+ 	 *
+ 	 * Not strictly necessary, but avoids potentially resizing twice when
+ 	 * the actual append... calls are done by the caller
+ 	 */
+ 	enlargeStringInfo(&state->string, Max(delimLen, 0) + valueLen);
+ 
+ 
+ 	/* Track delimiter length */
+ 	if (delimLen == -1)
+ 		{} /* Not specified, don't update */
+ 	else if (state->delimLen == -1)
+ 		state->delimLen = delimLen;
+ 	else if (state->delimLen != delimLen)
+ 		state->delimLen = -2;
+ }
+ 
+ /*
+  * Remove value with given length and the delimiter that follows
+  *
+  * Returns false if removal was impossible because delimiters varied
+  */
+ bool
+ removeFromStringAggState(StringAggState *state, int valueLen)
+ {
+ 	/* Remove the string */
+ 	state->offset += valueLen;
+ 
+ 	/*
+ 	 * Remove delimiter if necessary.
+ 	 *
+ 	 * The delimiter we need to remove isn't the delimiter we were passed, but
+ 	 * rather the delimiter passed when adding the input *after* this one. We
+ 	 * thus need the delimiter length to be all the same to be able to proceed.
+ 	 * If we're removing the last string, there will be no delimiter following
+ 	 * it. In that case, we may reset delimLen to its initial value.
+ 	 */
+ 	if (state->delimLen == -2)
+ 		return false;
+ 	if (state->offset < state->string.len)
+ 	{
+ 		Assert(state->delimLen >= 0);
+ 		state->offset += state->delimLen;
+ 	}
+ 	else
+ 		state->delimLen = -1;
+ 
+ 	/* Don't crash if we're ever asked to remove more than was added */
+ 	if (state->offset > state->string.len)
+ 	{
+ 		state->offset = state->string.len;
+ 		elog(ERROR, "tried to remove more data than was aggregated");
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /*****************************************************************************
   *	 CONVERSION ROUTINES EXPORTED FOR USE BY C CODE							 *
*************** byteasend(PG_FUNCTION_ARGS)
*** 408,435 ****
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		bytea	   *value = PG_GETARG_BYTEA_PP(1);
  
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 		{
! 			bytea	   *delim = PG_GETARG_BYTEA_PP(2);
  
! 			appendBinaryStringInfo(state, VARDATA_ANY(delim), VARSIZE_ANY_EXHDR(delim));
! 		}
  
! 		appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value));
  	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
--- 537,589 ----
  Datum
  bytea_string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	bytea		   *value,
! 				   *delim;
! 	int				valueLen,
! 					delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
  
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
  
! 	value = PG_GETARG_BYTEA_PP(1);
! 	valueLen = VARSIZE_ANY_EXHDR(value);
! 	state->notNullCount++;
  
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
! 	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_BYTEA_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
! 	}
! 	else
! 	{
! 		/* Delimiter is NULL, treat as zero-length string */
! 		delim = NULL;
! 		delimLen = 0;
  	}
  
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, valueLen);
+ 	if (delim)
+ 		appendBinaryStringInfo(&state->string, VARDATA_ANY(delim),
+ 							   VARSIZE_ANY_EXHDR(delim));
+ 	appendBinaryStringInfo(&state->string, VARDATA_ANY(value), valueLen);
+ 
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
*************** bytea_string_agg_transfn(PG_FUNCTION_ARG
*** 438,459 ****
  }
  
  Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
  	{
  		bytea	   *result;
  
! 		result = (bytea *) palloc(state->len + VARHDRSZ);
! 		SET_VARSIZE(result, state->len + VARHDRSZ);
! 		memcpy(VARDATA(result), state->data, state->len);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
--- 592,661 ----
  }
  
  Datum
+ bytea_string_agg_invtransfn(PG_FUNCTION_ARGS)
+ {
+ 	int				valueLen;
+ 	StringAggState *state;
+ 
+ 	/*
+ 	 * Shouldn't happen, but we cannot mark this function strict, since it
+ 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
+ 	 * Must also prevent direct calls because of the "interal" argument
+ 	 */
+ 	if (PG_ARGISNULL(0))
+ 		elog(ERROR, "string_agg_invtransfn called with NULL state");
+ 	else if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
+ 
+ 	state = (StringAggState *) PG_GETARG_POINTER(0);
+ 
+ 	/* We append nothing if the string is NULL, so skip here as well */
+ 	if (PG_ARGISNULL(1))
+ 		PG_RETURN_POINTER(state);
+ 
+ 	/* No need to de-toast value, need only the length */
+ 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
+ 	Assert(state->notNullCount >= 1);
+ 	state->notNullCount--;
+ 
+ 	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
+ 	 * The transition type for string_agg() is declared to be "internal",
+ 	 * which is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	if (removeFromStringAggState(state, valueLen))
+ 		PG_RETURN_POINTER(state);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ Datum
  bytea_string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
  	{
+ 		int			resultLen = state->string.len  - state->offset;
  		bytea	   *result;
  
! 		result = (bytea *) palloc(resultLen + VARHDRSZ);
! 		SET_VARSIZE(result, resultLen + VARHDRSZ);
! 		memcpy(VARDATA(result), state->string.data + state->offset, resultLen);
  		PG_RETURN_BYTEA_P(result);
  	}
  	else
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3734,3802 ****
   * the associated value.
   */
  
! /* subroutine to initialize state */
! static StringInfo
! makeStringAggState(FunctionCallInfo fcinfo)
  {
! 	StringInfo	state;
! 	MemoryContext aggcontext;
! 	MemoryContext oldcontext;
  
! 	if (!AggCheckCallContext(fcinfo, &aggcontext))
  	{
! 		/* cannot be called directly because of internal-type argument */
! 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
  	}
  
  	/*
! 	 * Create state in aggregate context.  It'll stay there across subsequent
! 	 * calls.
  	 */
! 	oldcontext = MemoryContextSwitchTo(aggcontext);
! 	state = makeStringInfo();
! 	MemoryContextSwitchTo(oldcontext);
! 
! 	return state;
  }
  
  Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	/* Append the value unless null. */
! 	if (!PG_ARGISNULL(1))
! 	{
! 		/* On the first time through, we ignore the delimiter. */
! 		if (state == NULL)
! 			state = makeStringAggState(fcinfo);
! 		else if (!PG_ARGISNULL(2))
! 			appendStringInfoText(state, PG_GETARG_TEXT_PP(2));	/* delimiter */
  
! 		appendStringInfoText(state, PG_GETARG_TEXT_PP(1));		/* value */
! 	}
  
  	/*
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
! 	if (state != NULL)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len));
  	else
  		PG_RETURN_NULL();
  }
--- 3936,4055 ----
   * the associated value.
   */
  
! Datum
! string_agg_transfn(PG_FUNCTION_ARGS)
  {
! 	text		   *value,
! 				   *delim;
! 	int				delimLen;
! 	StringAggState *state;
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
! 
! 	/* Ignore NULL values, and skip a possible delimiter too. */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* Allocate state on first non-NULL value */
! 	if (state == NULL)
! 		state = makeStringAggState(fcinfo);
! 
! 	value = PG_GETARG_TEXT_PP(1);
! 	state->notNullCount++;
! 
! 	Assert(state->offset <= state->string.len);
! 	if (state->offset == state->string.len)
  	{
! 		/* Buffer is empty, ignore delimiter */
! 		delim = NULL;
! 		delimLen = -1;
! 	}
! 	else if (!PG_ARGISNULL(2))
! 	{
! 		/* Delimiter is non-NULL */
! 		delim = PG_GETARG_TEXT_PP(2);
! 		delimLen = VARSIZE_ANY_EXHDR(delim);
  	}
+ 	else
+ 	{
+ 		/* Delimiter is NULL, treat as zero-length string */
+ 		delim = NULL;
+ 		delimLen = 0;
+ 	}
+ 
+ 	/* Append delimiter (if non-NULL) and value */
+ 	prepareAppendStringAggState(state, delimLen, VARSIZE_ANY_EXHDR(value));
+ 	if (delim)
+ 		appendStringInfoText(&state->string, delim);
+ 	appendStringInfoText(&state->string, value);
  
  	/*
! 	 * The transition type for string_agg() is declared to be "internal",
! 	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  Datum
! string_agg_invtransfn(PG_FUNCTION_ARGS)
  {
! 	int				valueLen;
! 	StringAggState *state;
  
! 	/*
! 	 * Shouldn't happen, but we cannot mark this function strict, since it
! 	 * needs to be able to receive a non-NULL string but a NULL delimiter.
! 	 * Must also prevent direct calls because of the "interal" argument
! 	 */
! 	if (PG_ARGISNULL(0))
! 		elog(ERROR, "string_agg_invtransfn called with NULL state");
! 	else if (!AggCheckCallContext(fcinfo, NULL))
! 		elog(ERROR, "string_agg_invtransfn called in non-aggregate context");
  
! 	state = (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/* We append nothing if the string is NULL, so skip here as well */
! 	if (PG_ARGISNULL(1))
! 		PG_RETURN_POINTER(state);
! 
! 	/* No need to de-toast value, need only the length */
! 	valueLen = VARSIZE_ANY_EXHDR(PG_GETARG_RAW_VARLENA_P(1));
! 	Assert(state->notNullCount >= 1);
! 	state->notNullCount--;
  
  	/*
+ 	 * Attempt to remove value plus following delimiter if possible, return
+ 	 * NULL otherwise to force an aggregation restart.
+ 	 *
  	 * The transition type for string_agg() is declared to be "internal",
  	 * which is a pass-by-value type the same size as a pointer.
  	 */
! 	if (removeFromStringAggState(state, valueLen))
! 		PG_RETURN_POINTER(state);
! 	else
! 		PG_RETURN_NULL();
  }
  
  Datum
  string_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringAggState *state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0);
  
! 	/*
! 	 * string_agg() is supposed to return NULL if all inputs were NULL
! 	 *
! 	 * That happens automatically for strict aggregates, but we can't make
! 	 * string_agg() strict because NULL is a valid delimiter value. So we
! 	 * need to keep track of the number of non-NULL 1st arguments ourselves.
! 	 */
! 	if (state != NULL && state->notNullCount > 0)
! 		PG_RETURN_TEXT_P(cstring_to_text_with_len(state->string.data + state->offset,
! 												  state->string.len - state->offset));
  	else
  		PG_RETURN_NULL();
  }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..403bb50 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2243	n 0 bitor		-	-					0	
*** 250,262 ****
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	-	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	-	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	-	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
--- 250,262 ----
  DATA(insert ( 2901	n 0 xmlconcat2	-	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_invtransfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_invtransfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_invtransfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
  DATA(insert ( 3175	n 0 json_agg_transfn		-	json_agg_finalfn		0	2281	0	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..efaa281 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3168 (  array_replace 
*** 875,880 ****
--- 875,882 ----
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3585 (  array_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
*************** DESCR("aggregate final function");
*** 2463,2474 ****
--- 2465,2480 ----
  
  DATA(insert OID = 3535 (  string_agg_transfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3586 (  string_agg_invtransfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3536 (  string_agg_finalfn		PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3538 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into a string");
  DATA(insert OID = 3543 (  bytea_string_agg_transfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
+ DATA(insert OID = 3587 (  bytea_string_agg_invtransfn PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2281 "2281 17 17" _null_ _null_ _null_ _null_ bytea_string_agg_invtransfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
  DATA(insert OID = 3544 (  bytea_string_agg_finalfn	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 17 "2281" _null_ _null_ _null_ _null_ bytea_string_agg_finalfn _null_ _null_ _null_ ));
  DESCR("aggregate final function");
  DATA(insert OID = 3545 (  string_agg				PGNSP PGUID 12 1 0 0 0 t f f f f f i 2 0 17 "17 17" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..9a6fc39 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 83,88 ****
--- 83,89 ----
  	Datum	   *dvalues;		/* array of accumulated Datums */
  	bool	   *dnulls;			/* array of is-null flags for Datums */
  	int			alen;			/* allocated length of above arrays */
+ 	int			offset;			/* offset of first element in above arrays */
  	int			nelems;			/* number of valid entries in above arrays */
  	Oid			element_type;	/* data type of the Datums */
  	int16		typlen;			/* needed info about datatype */
*************** extern ArrayBuildState *accumArrayResult
*** 255,260 ****
--- 256,262 ----
  				 Datum dvalue, bool disnull,
  				 Oid element_type,
  				 MemoryContext rcontext);
+ extern void shiftArrayResult(ArrayBuildState *astate, int count);
  extern Datum makeArrayResult(ArrayBuildState *astate,
  				MemoryContext rcontext);
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
*************** extern ArrayType *create_singleton_array
*** 290,295 ****
--- 292,298 ----
  					   int ndims);
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
  
  /*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..173fa71 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 812,820 ****
--- 812,822 ----
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
  extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum bytea_string_agg_invtransfn(PG_FUNCTION_ARGS);
  extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_invtransfn(PG_FUNCTION_ARGS);
  
  extern Datum text_concat(PG_FUNCTION_ARGS);
  extern Datum text_concat_ws(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index e3eba47..33587df 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1310,1312 ****
--- 1310,1356 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
+      row     | str | str_del | str_vardel |      bin       |      bin_del       |      bin_vardel      |       ary        
+ -------------+-----+---------+------------+----------------+--------------------+----------------------+------------------
+  1:1,0100,-  | -   | -       | -          | -              | -                  | -                    | 
+  2:-,0200,2  | 1   | 1       | 1          | \x0100         | \x0100             | \x0100               | {NULL}
+  3:3,----,3  | 1   | 1       | 1          | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2}
+  4:-,0400,4  | 13  | 1,3     | 13         | \x01000200     | \x0100000200       | \x010000000200       | {NULL,2,3}
+  5:5,----,-  | 3   | 3       | 3          | \x02000400     | \x0200000400       | \x0200000400         | {2,3,4}
+  6:6,0600,6  | 35  | 3,5     | 3||5       | \x0400         | \x0400             | \x0400               | {3,4,NULL}
+  7:7,0700,-  | 56  | 5,6     | 56         | \x04000600     | \x0400000600       | \x04000600           | {4,NULL,6}
+  8:8,0800,8  | 567 | 5,6,7   | 56|7       | \x06000700     | \x0600000700       | \x0600000700         | {NULL,6,NULL}
+  9:-,----,-  | 678 | 6,7,8   | 6|7||8     | \x060007000800 | \x0600000700000800 | \x060000070000000800 | {6,NULL,8}
+  10:-,----,- | 78  | 7,8     | 7||8       | \x07000800     | \x0700000800       | \x070000000800       | {NULL,8,NULL}
+  11:-,----,- | 8   | 8       | 8          | \x0800         | \x0800             | \x0800               | {8,NULL,NULL}
+  12:-,----,- | -   | -       | -          | -              | -                  | -                    | {NULL,NULL,NULL}
+ (12 rows)
+ 
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index bd8dbdc..6761e0c 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 493,495 ****
--- 493,524 ----
  -- Tests of the collecting inverse transition functions
  --
  --
+ 
+ SELECT
+ 	i::text || ':' || COALESCE(s, '-') || ',' ||
+ 					  COALESCE(encode(b, 'hex'), '----') || ',' ||
+ 					  COALESCE(n::text, '-') AS row,
+ 	coalesce(string_agg(s,'')        OVER wnd, '-') AS str,
+ 	coalesce(string_agg(s,',')       OVER wnd, '-') AS str_del,
+ 	coalesce(string_agg(s, nullif(repeat('|', i%3), ''))
+ 			 OVER wnd, '-') AS str_vardel,
+ 	coalesce((string_agg(b,'')        OVER wnd)::text, '-') AS bin,
+ 	coalesce((string_agg(b, E'\\x00') OVER wnd)::text, '-') AS bin_del,
+ 	coalesce((string_agg(b, nullif(decode(repeat('00', i%3), 'hex'), ''))
+ 			 OVER wnd)::text, '-') AS bin_vardel,
+ 	array_agg(n) OVER wnd AS ary
+ FROM (VALUES
+ 	(1,  '1',  E'\\x0100'::bytea, NULL::int8),
+ 	(2,  NULL, E'\\x0200'::bytea, 2::int8),
+ 	(3,  '3',  NULL,              3::int8),
+ 	(4,  NULL, E'\\x0400'::bytea, 4::int8),
+ 	(5,  '5',  NULL,              NULL::int8),
+ 	(6,  '6',  E'\\x0600'::bytea, 6::int8),
+ 	(7,  '7',  E'\\x0700'::bytea, NULL::int8),
+ 	(8,  '8',  E'\\x0800'::bytea, 8::int8),
+ 	(9,  NULL, NULL,              NULL),
+ 	(10, NULL, NULL,              NULL),
+ 	(11, NULL, NULL,              NULL),
+ 	(12, NULL, NULL,              NULL)
+ ) AS v(i, s, b, n)
+ WINDOW wnd AS (ORDER BY i ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING);
invtrans_docs_267287.patchapplication/octet-stream; name=invtrans_docs_267287.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index a12ee56..883fb50 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 381,386 ****
--- 381,392 ----
        <entry>Transition function</entry>
       </row>
       <row>
+       <entry><structfield>agginvtransfn</structfield></entry>
+       <entry><type>regproc</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>Inverse transition function</entry>
+      </row>
+      <row>
        <entry><structfield>aggfinalfn</structfield></entry>
        <entry><type>regproc</type></entry>
        <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6e2fbda..59ce91e 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT xmlagg(x) FROM (SELECT x FROM tes
*** 13310,13315 ****
--- 13310,13437 ----
     <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
     Other frame specifications can be used to obtain other effects.
    </para>
+   
+   <para>
+     Depending on the aggregate function, aggregating over frames starting
+     at a row relative to the current row can be drastically less efficient
+     than aggregating over frames aligned to the start of the partition. The
+     frame starts at a row relative to the current row if <literal>ORDER
+     BY</literal> is used together with any frame start clause other than
+     <literal>UNBOUNDED PRECEDING</literal> (which is the default). Then,
+     aggregates without a suitable <quote>inverse transition function
+     </quote> (see <xref linkend="SQL-CREATEAGGREGATE"> for details) will be
+     computed for each frame from scratch, instead of re-using the previous
+     frame's result, causing <emphasis>quadratic growth</emphasis> of the
+     execution time as the number of rows per partition increases. The table
+     <xref linkend="functions-aggregate-indframe"> list the built-in aggregate
+     functions affected by this. Note that quadratic growth is only a problem
+     if partitions contain many rows - for partitions with only a few rows,
+     even inefficient aggregates are unlikely to cause problems.
+   </para>
+ 
+   <table id="functions-aggregate-indframe">
+    <title>
+      Aggregate Function Behaviour for frames not starting at
+      <literal>UNBOUNDED PRECEDING</literal>.
+    </title>
+ 
+    <tgroup cols="3">
+      
+     <thead>
+      <row>
+       <entry>Aggregate Function</entry>
+       <entry>Input Type</entry>
+       <entry>Computed From Scratch</entry>
+      </row>
+     </thead>
+     
+     <tbody>
+       
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>float4</type>
+        or
+        <type>float8</type>
+       </entry>
+       <entry>always, to avoid error accumulation</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        any numerical aggregate
+       </entry>
+       <entry>
+        <type>numeric</type>
+       </entry>
+       <entry>if the maximum number of decimal digits within the inputs changes</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>min</function>
+       </entry>
+       <entry>
+        any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were minimal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>max</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>if some inputs in the old frame that aren't in the new frame were maximal</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>bit_and</function>,<function>bit_or</function>
+       </entry>
+       <entry>
+         any
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>string_agg</function>
+       </entry>
+       <entry>
+        <type>text</type> or
+        <type>bytea</type> or
+       </entry>
+       <entry>if the delimiter lengths vary</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>xmlagg</function>
+       </entry>
+       <entry>
+        <type>xml</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <function>json_agg</function>
+       </entry>
+       <entry>
+        <type>json</type>
+       </entry>
+       <entry>always</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
  
    <note>
     <para>
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index e5fc718..9bdb2eb 100644
*** a/doc/src/sgml/ref/create_aggregate.sgml
--- b/doc/src/sgml/ref/create_aggregate.sgml
*************** CREATE AGGREGATE <replaceable class="par
*** 25,30 ****
--- 25,31 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVSFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 47,52 ****
--- 48,54 ----
      SFUNC = <replaceable class="PARAMETER">sfunc</replaceable>,
      STYPE = <replaceable class="PARAMETER">state_data_type</replaceable>
      [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
+     [ , INVSFUNC = <replaceable class="PARAMETER">inv_trans_func</replaceable> ]
      [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
      [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
      [ , SORTOP = <replaceable class="PARAMETER">sort_operator</replaceable> ]
*************** CREATE AGGREGATE <replaceable class="PAR
*** 84,98 ****
    </para>
  
    <para>
!    An aggregate function is made from one or two ordinary
     functions:
!    a state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
--- 86,103 ----
    </para>
  
    <para>
!    An aggregate function is made from one, two or three ordinary
     functions:
!    a (forward) state transition function
     <replaceable class="PARAMETER">sfunc</replaceable>,
+    an optional inverse state transition function
+    <replaceable class="PARAMETER">invsfunc</replaceable>,
     and an optional final calculation function
     <replaceable class="PARAMETER">ffunc</replaceable>.
     These are used as follows:
  <programlisting>
  <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
+ <replaceable class="PARAMETER">invsfunc</replaceable>( internal-state, data-values ) ---> internal-state-without-data-values
  <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
  </programlisting>
    </para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 102,113 ****
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.  After all the rows have been processed,
!    the final function is invoked once to calculate the aggregate's return
!    value.  If there is no final function then the ending state value
!    is returned as-is.
    </para>
  
    <para>
--- 107,134 ----
     of data type <replaceable class="PARAMETER">stype</replaceable>
     to hold the current internal state of the aggregate.  At each input row,
     the aggregate argument value(s) are calculated and
!    the forward state transition function is invoked with the current state value
     and the new argument value(s) to calculate a new
!    internal state value.
!    If the aggregate is computed over a sliding frame, i.e. if it is used as a
!    <firstterm>window function</firstterm>, the inverse transition function is
!    used to undo the effect of a previous invocation of the forward transition
!    function once argument value(s) fall out of the sliding frame.
!    Conceptually, the forward transition functions thus adds some input
!    value(s) to the state, and the inverse transition functions removes them
!    again. Values are, if they are removed, always removed in the same order
!    they were added, without gaps. Whenever the inverse transition function is
!    invoked, it will thus receive the earliest added but not yet removed
!    argument value(s). If no inverse transition function is supplied, the
!    aggregate can still be used to aggregate over sliding frames, but with
!    reduced efficiency. <productname>PostgreSQL</productname> will then
!    recompute the whole aggregation whenever the start of the frame moves. To
!    calculate the aggregate's return value, the final function is invoked on
!    the ending state value. If there is no final function then ending state
!    value is returned as-is. Either way, the result is assumed to reflect the
!    aggregation of all values added but not yet removed from the state value.
!    Note that if the aggregate is used as a window function, the aggregation
!    may be continued after the final function has been called.
    </para>
  
    <para>
*************** CREATE AGGREGATE <replaceable class="PAR
*** 120,135 ****
    </para>
  
    <para>
!    If the state transition function is declared <quote>strict</quote>,
!    then it cannot be called with null inputs.  With such a transition
!    function, aggregate execution behaves as follows.  Rows with any null input
     values are ignored (the function is not called and the previous state value
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values.
!    This is handy for implementing aggregates like <function>max</function>.
!    Note that this behavior is only available when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
--- 141,164 ----
    </para>
  
    <para>
!    If the state transition functions are declared <quote>strict</quote>,
!    then it cannot be called with null inputs.  With such transition
!    functions, aggregate execution behaves as follows.  Rows with any null input
     values are ignored (the function is not called and the previous state value
     is retained).  If the initial state value is null, then at the first row
     with all-nonnull input values, the first argument value replaces the state
     value, and the transition function is invoked at subsequent rows with
!    all-nonnull input values. Should inputs later need to be removed again, the
!    inverse transition function (if present) is used as long as some non-null
!    inputs remain part of the state value. In particular, even if the initial
!    state value is null, the inverse transition function might be used to remove
!    the first non-null input, even though that input was never passed to the
!    forward transition function, but instead just replaced the initial state!
!    The last non-null input, however, is not removed by invoking the inverse
!    transition function, but instead the state is simply reset to its initial
!    value. This is handy for implementing aggregates like <function>max</function>.
!    Note that turning the first non-null input into the initial state is only
!    possible when
     <replaceable class="PARAMETER">state_data_type</replaceable>
     is the same as the first
     <replaceable class="PARAMETER">arg_data_type</replaceable>.
*************** CREATE AGGREGATE <replaceable class="PAR
*** 138,147 ****
    </para>
  
    <para>
!    If the state transition function is not strict, then it will be called
!    unconditionally at each input row, and must deal with null inputs
!    and null state values for itself.  This allows the aggregate
!    author to have full control over the aggregate's handling of null values.
    </para>
  
    <para>
--- 167,192 ----
    </para>
  
    <para>
!    If the state transition functions are not <quote>strict</quote>, then they
!    will be called unconditionally at each input row, and must deal with null
!    inputs and null state values for itself. The same goes for the inverse
!    transition function. This allows the aggregate author to have full control
!    over the aggregate's handling of null argument values.
!   </para>
!   
!   <para>
!     The inverse transition function can signal by returning null that it is
!     unable to remove a particular input value from a particular state.
!     <productname>PostgreSQL</productname> will then act as if no inverse
!     transition function had been supplied, i.e. it will recompute the whole
!     aggregation, starting with the first argument value that it would not have
!     removed. That allows aggregates like <function>max</function> to still
!     avoid redoing the whole aggregation in <emphasis>some</emphasis> cases,
!     without paying the overhead of tracking enough state to be able to avoid
!     them in <emphasis>all</emphasis> cases. This demands, however, that
!     null isn't used as a valid state value, except as the initial state. If
!     an aggregate provides an inverse transition function, it is therefore an
!     error for the forward transition function to return null.
    </para>
  
    <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 271,277 ****
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
--- 316,322 ----
      <term><replaceable class="PARAMETER">sfunc</replaceable></term>
      <listitem>
       <para>
!       The name of the (forward) state transition function to be called for each
        input row.  For a normal <replaceable class="PARAMETER">N</>-argument
        aggregate function, the <replaceable class="PARAMETER">sfunc</>
        must take <replaceable class="PARAMETER">N</>+1 arguments,
*************** SELECT col FROM tab ORDER BY col USING s
*** 281,287 ****
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value.
       </para>
  
       <para>
--- 326,334 ----
        The function must return a value of type <replaceable
        class="PARAMETER">state_data_type</replaceable>.  This function
        takes the current state value and the current input data value(s),
!       and returns the next state value. Note that if an inverse
!       transition function is present, the forward transition function must
!       not return <literal>NULL</>
       </para>
  
       <para>
*************** SELECT col FROM tab ORDER BY col USING s
*** 294,299 ****
--- 341,368 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="PARAMETER">invsfunc</replaceable></term>
+     <listitem>
+      <para>
+       The name of the inverse state transition function to be called for each
+       input row.  For a normal <replaceable class="PARAMETER">N</>-argument
+       aggregate function, the <replaceable class="PARAMETER">invsfunc</>
+       must take <replaceable class="PARAMETER">N</>+1 arguments,
+       the first being of type <replaceable
+       class="PARAMETER">state_data_type</replaceable> and the rest
+       matching the declared input data type(s) of the aggregate.
+       The function must return a value of type <replaceable
+       class="PARAMETER">state_data_type</replaceable>. These are the same
+       demands placed on the forward transition function, meaning that the
+       signatures of the two functions, including their
+       <quote>strictness</quote>, must be identical. The inverse transition
+       function may return <literal>NULL</> to force the aggregation to be
+       restarted from scratch.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="PARAMETER">state_data_type</replaceable></term>
      <listitem>
       <para>
diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml
index e77ef12..0c25b6f 100644
*** a/doc/src/sgml/xaggr.sgml
--- b/doc/src/sgml/xaggr.sgml
***************
*** 16,22 ****
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
--- 16,22 ----
     as each successive input row is processed.
     To define a new aggregate
     function, one selects a data type for the state value,
!    an initial value for the state, and a forward state transition
     function.  The state transition function takes the previous state
     value and the aggregate's input value(s) for the current row, and
     returns a new state value.
***************
*** 24,30 ****
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
--- 24,42 ----
     can also be specified, in case the desired result of the aggregate
     is different from the data that needs to be kept in the running
     state value.  The final function takes the last state value
!    and returns whatever is wanted as the aggregate result. 
!    To enable efficient evaluation of an aggregate used as a window function
!    with a sliding frame (i.e. a frame that starts relative to the current row),
!    an aggregate can optionally provide an inverse state transition function.
!    The inverse transition function takes the the current state and the
!    aggregate's input value(s) for the <emphasis>earliest</emphasis> row passed
!    to the forward transition function, and returns a state equivalent to what
!    the current state had been had the forward transition function never been
!    invoked for that earliest row, only for all rows that followed it. Thus,
!    if an inverse transition function is provided, the rows that were part of
!    the previous row's frame but not of the current row's frame can simply be
!    removed from the state instead of having to redo the whole aggregation
!    over the new frame.
     In principle, the transition and final functions are just ordinary
     functions that could also be used outside the context of the
     aggregate.  (In practice, it's often helpful for performance reasons
*************** CREATE AGGREGATE avg (float8)
*** 132,137 ****
--- 144,188 ----
    </note>
  
    <para>
+    When providing an inverse transition function, care should be taken to
+    ensure that it doesn't introduce unexpected user-visible differences
+    between results obtained by reaggregating all inputs vs. using the inverse
+    transition function. An example for an aggregate where adding an inverse
+    transition function seems easy at first, yet were doing so would violate
+    this requirement is <function>sum</> over <type>float</> or
+    <type>double precision</>. A naive declaration of
+    <function>sum(<type>float</>)</function> could be
+    
+    <programlisting>
+    CREATE AGGREGATE unsafe_sum (float8)
+    (
+        stype = float8,
+        sfunc = float8pl,
+        invsfunc = float8mi
+    );
+    </programlisting>
+    
+    This aggregate, howevery, can give wildly different results than it would
+    have without the inverse transition function. For example, consider
+    
+    <programlisting>
+    SELECT
+      unsafe_sum(x) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND
+                                                  1 FOLLOWING)
+    FROM (VALUES
+      (1, 1.0e20::float8),
+      (2, 1.0::float8)
+    ) AS v (n,x)
+    </programlisting>
+    
+    which returns 0 as it's second result, yet the expected answer is 1. The
+    reason for this is the limited precision of floating point types - adding
+    1 to 1e20 actually leaves the value unchanged, and so substracting 1e20
+    again yields 0, not 1. Note that this is a limitation of floating point
+    types in general and not a limitation of <productname>PostgreSQL</>.
+   </para>
+   
+   <para>
     Aggregate functions can use polymorphic
     state transition functions or final functions, so that the same functions
     can be used to implement multiple aggregates.
invtrans_minmax_174ac0.patchapplication/octet-stream; name=invtrans_minmax_174ac0.patchDownload
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..4a4ca5d 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_larger(PG_FUNCTION_ARGS)
*** 4714,4719 ****
--- 4714,4737 ----
  }
  
  Datum
+ array_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) > 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ 
+ Datum
  array_smaller(PG_FUNCTION_ARGS)
  {
  	ArrayType  *v1,
*************** array_smaller(PG_FUNCTION_ARGS)
*** 4728,4733 ****
--- 4746,4767 ----
  	PG_RETURN_ARRAYTYPE_P(result);
  }
  
+ Datum
+ array_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function function called in non-aggregate context");
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	if (array_cmp(fcinfo) < 0)
+ 		PG_RETURN_ARRAYTYPE_P(v1);
+ 	PG_RETURN_NULL();
+ }
  
  typedef struct generate_subscripts_fctx
  {
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 0158758..cba17a3 100644
*** a/src/backend/utils/adt/cash.c
--- b/src/backend/utils/adt/cash.c
*************** cashlarger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,896 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 > c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cashsmaller()
   * Return smaller of two cash values.
   */
*************** cashsmaller(PG_FUNCTION_ARGS)
*** 892,897 ****
--- 906,925 ----
  	PG_RETURN_CASH(result);
  }
  
+ Datum
+ cashsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Cash		c1 = PG_GETARG_CASH(0);
+ 	Cash		c2 = PG_GETARG_CASH(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (c1 < c2)
+ 		PG_RETURN_CASH(c1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* cash_words()
   * This converts a int4 as well but to a representation using words
   * Obviously way North American centric - sorry
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 06cc0cd..0cca0b0 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** date_larger(PG_FUNCTION_ARGS)
*** 396,401 ****
--- 396,415 ----
  }
  
  Datum
+ date_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 > dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  date_smaller(PG_FUNCTION_ARGS)
  {
  	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
*************** date_smaller(PG_FUNCTION_ARGS)
*** 404,409 ****
--- 418,437 ----
  	PG_RETURN_DATEADT((dateVal1 < dateVal2) ? dateVal1 : dateVal2);
  }
  
+ Datum
+ date_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	DateADT		dateVal1 = PG_GETARG_DATEADT(0);
+ 	DateADT		dateVal2 = PG_GETARG_DATEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (dateVal1 < dateVal2)
+ 		PG_RETURN_DATEADT(dateVal1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* Compute difference between two dates in days.
   */
  Datum
*************** time_larger(PG_FUNCTION_ARGS)
*** 1463,1468 ****
--- 1491,1510 ----
  }
  
  Datum
+ time_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 > time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  time_smaller(PG_FUNCTION_ARGS)
  {
  	TimeADT		time1 = PG_GETARG_TIMEADT(0);
*************** time_smaller(PG_FUNCTION_ARGS)
*** 1471,1476 ****
--- 1513,1532 ----
  	PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
  }
  
+ Datum
+ time_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeADT		time1 = PG_GETARG_TIMEADT(0);
+ 	TimeADT		time2 = PG_GETARG_TIMEADT(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (time1 < time2)
+ 		PG_RETURN_TIMEADT(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* overlaps_time() --- implements the SQL OVERLAPS operator.
   *
   * Algorithm is per SQL spec.  This is much harder than you'd think
*************** timetz_larger(PG_FUNCTION_ARGS)
*** 2262,2267 ****
--- 2318,2337 ----
  }
  
  Datum
+ timetz_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) > 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  timetz_smaller(PG_FUNCTION_ARGS)
  {
  	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
*************** timetz_smaller(PG_FUNCTION_ARGS)
*** 2275,2280 ****
--- 2345,2364 ----
  	PG_RETURN_TIMETZADT_P(result);
  }
  
+ Datum
+ timetz_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	TimeTzADT  *time1 = PG_GETARG_TIMETZADT_P(0);
+ 	TimeTzADT  *time2 = PG_GETARG_TIMETZADT_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timetz_cmp_internal(time1, time2) < 0)
+ 		PG_RETURN_TIMETZADT_P(time1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /* timetz_pl_interval()
   * Add interval to timetz.
   */
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index 83c3878..32035e0 100644
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
*************** enum_smaller(PG_FUNCTION_ARGS)
*** 273,278 ****
--- 273,292 ----
  }
  
  Datum
+ enum_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) < 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_larger(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
*************** enum_larger(PG_FUNCTION_ARGS)
*** 282,287 ****
--- 296,315 ----
  }
  
  Datum
+ enum_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			a = PG_GETARG_OID(0);
+ 	Oid			b = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (enum_cmp_internal(a, b, fcinfo) > 0)
+ 		PG_RETURN_OID(a);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  enum_cmp(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 774267e..04f89b0 100644
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4larger(PG_FUNCTION_ARGS)
*** 637,642 ****
--- 637,658 ----
  }
  
  Datum
+ float4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float4smaller(PG_FUNCTION_ARGS)
  {
  	float4		arg1 = PG_GETARG_FLOAT4(0);
*************** float4smaller(PG_FUNCTION_ARGS)
*** 650,655 ****
--- 666,687 ----
  	PG_RETURN_FLOAT4(result);
  }
  
+ Datum
+ float4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float4		arg1 = PG_GETARG_FLOAT4(0);
+ 	float4		arg2 = PG_GETARG_FLOAT4(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float4_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT4(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  /*
   *		======================
   *		FLOAT8 BASE OPERATIONS
*************** float8larger(PG_FUNCTION_ARGS)
*** 704,709 ****
--- 736,757 ----
  }
  
  Datum
+ float8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) > 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  float8smaller(PG_FUNCTION_ARGS)
  {
  	float8		arg1 = PG_GETARG_FLOAT8(0);
*************** float8smaller(PG_FUNCTION_ARGS)
*** 717,722 ****
--- 765,785 ----
  	PG_RETURN_FLOAT8(result);
  }
  
+ Datum
+ float8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_NULL();
+ 	else if (float8_cmp_internal(arg1, arg2) < 0)
+ 		PG_RETURN_FLOAT8(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *		====================
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 669355e..fe9d736 100644
*** a/src/backend/utils/adt/int.c
--- b/src/backend/utils/adt/int.c
*************** int2larger(PG_FUNCTION_ARGS)
*** 1185,1190 ****
--- 1185,1204 ----
  }
  
  Datum
+ int2larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int2smaller(PG_FUNCTION_ARGS)
  {
  	int16		arg1 = PG_GETARG_INT16(0);
*************** int2smaller(PG_FUNCTION_ARGS)
*** 1194,1199 ****
--- 1208,1227 ----
  }
  
  Datum
+ int2smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int16		arg1 = PG_GETARG_INT16(0);
+ 	int16		arg2 = PG_GETARG_INT16(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT16(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4larger(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4larger(PG_FUNCTION_ARGS)
*** 1203,1208 ****
--- 1231,1250 ----
  }
  
  Datum
+ int4larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int4smaller(PG_FUNCTION_ARGS)
  {
  	int32		arg1 = PG_GETARG_INT32(0);
*************** int4smaller(PG_FUNCTION_ARGS)
*** 1211,1216 ****
--- 1253,1272 ----
  	PG_RETURN_INT32((arg1 < arg2) ? arg1 : arg2);
  }
  
+ Datum
+ int4smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int32		arg1 = PG_GETARG_INT32(0);
+ 	int32		arg2 = PG_GETARG_INT32(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT32(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
  /*
   * Bit-pushing operators
   *
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5e1be90..820be68 100644
*** a/src/backend/utils/adt/int8.c
--- b/src/backend/utils/adt/int8.c
*************** int8larger(PG_FUNCTION_ARGS)
*** 752,757 ****
--- 752,771 ----
  }
  
  Datum
+ int8larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int8smaller(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
*************** int8smaller(PG_FUNCTION_ARGS)
*** 764,769 ****
--- 778,797 ----
  }
  
  Datum
+ int8smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	int64		arg1 = PG_GETARG_INT64(0);
+ 	int64		arg2 = PG_GETARG_INT64(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_INT64(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  int84pl(PG_FUNCTION_ARGS)
  {
  	int64		arg1 = PG_GETARG_INT64(0);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 64eb0f8..c9500bf 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** numeric_smaller(PG_FUNCTION_ARGS)
*** 1872,1877 ****
--- 1872,1892 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) < 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * numeric_larger() -
*************** numeric_larger(PG_FUNCTION_ARGS)
*** 1894,1899 ****
--- 1909,1930 ----
  		PG_RETURN_NUMERIC(num2);
  }
  
+ Datum
+ numeric_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		num1 = PG_GETARG_NUMERIC(0);
+ 	Numeric		num2 = PG_GETARG_NUMERIC(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ 		PG_RETURN_NULL();
+ 	else if (cmp_numerics(num1, num2) > 0)
+ 		PG_RETURN_NUMERIC(num1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /* ----------------------------------------------------------------------
   *
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 8945ef4..745449a 100644
*** a/src/backend/utils/adt/oid.c
--- b/src/backend/utils/adt/oid.c
*************** oidlarger(PG_FUNCTION_ARGS)
*** 397,402 ****
--- 397,416 ----
  }
  
  Datum
+ oidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 > arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidsmaller(PG_FUNCTION_ARGS)
  {
  	Oid			arg1 = PG_GETARG_OID(0);
*************** oidsmaller(PG_FUNCTION_ARGS)
*** 406,411 ****
--- 420,439 ----
  }
  
  Datum
+ oidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Oid			arg1 = PG_GETARG_OID(0);
+ 	Oid			arg2 = PG_GETARG_OID(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (arg1 < arg2)
+ 		PG_RETURN_OID(arg1);
+ 	PG_RETURN_NULL(); /* Unable to perform inverse transition */
+ }
+ 
+ Datum
  oidvectoreq(PG_FUNCTION_ARGS)
  {
  	int32		cmp = DatumGetInt32(btoidvectorcmp(fcinfo));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 6e2bbdc..57fcf80 100644
*** a/src/backend/utils/adt/tid.c
--- b/src/backend/utils/adt/tid.c
*************** tidlarger(PG_FUNCTION_ARGS)
*** 237,242 ****
--- 237,256 ----
  }
  
  Datum
+ tidlarger_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) > 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  tidsmaller(PG_FUNCTION_ARGS)
  {
  	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
*************** tidsmaller(PG_FUNCTION_ARGS)
*** 245,250 ****
--- 259,277 ----
  	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
  }
  
+ Datum
+ tidsmaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
+ 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (ItemPointerCompare(arg1, arg2) < 0)
+ 		PG_RETURN_ITEMPOINTER(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   *	Functions to get latest tid of a specified tuple.
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ce30bb6..e611983 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** timestamp_smaller(PG_FUNCTION_ARGS)
*** 2682,2687 ****
--- 2682,2701 ----
  }
  
  Datum
+ timestamp_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) < 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  timestamp_larger(PG_FUNCTION_ARGS)
  {
  	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
*************** timestamp_larger(PG_FUNCTION_ARGS)
*** 2695,2700 ****
--- 2709,2727 ----
  	PG_RETURN_TIMESTAMP(result);
  }
  
+ Datum
+ timestamp_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+ 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (timestamp_cmp_internal(dt1, dt2) > 0)
+ 		PG_RETURN_TIMESTAMP(dt1);
+ 	PG_RETURN_NULL();
+ }
  
  Datum
  timestamp_mi(PG_FUNCTION_ARGS)
*************** interval_smaller(PG_FUNCTION_ARGS)
*** 3151,3156 ****
--- 3178,3197 ----
  }
  
  Datum
+ interval_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) < 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_larger(PG_FUNCTION_ARGS)
  {
  	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
*************** interval_larger(PG_FUNCTION_ARGS)
*** 3165,3170 ****
--- 3206,3225 ----
  }
  
  Datum
+ interval_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	Interval   *interval1 = PG_GETARG_INTERVAL_P(0);
+ 	Interval   *interval2 = PG_GETARG_INTERVAL_P(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (interval_cmp_internal(interval1, interval2) > 0)
+ 		PG_RETURN_INTERVAL_P(interval1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  interval_pl(PG_FUNCTION_ARGS)
  {
  	Interval   *span1 = PG_GETARG_INTERVAL_P(0);
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 502ca44..536ca66 100644
*** a/src/backend/utils/adt/varchar.c
--- b/src/backend/utils/adt/varchar.c
*************** bpchar_larger(PG_FUNCTION_ARGS)
*** 877,882 ****
--- 877,905 ----
  }
  
  Datum
+ bpchar_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp > 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  bpchar_smaller(PG_FUNCTION_ARGS)
  {
  	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
*************** bpchar_smaller(PG_FUNCTION_ARGS)
*** 894,899 ****
--- 917,945 ----
  	PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2);
  }
  
+ Datum
+ bpchar_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	BpChar	   *arg1 = PG_GETARG_BPCHAR_PP(0);
+ 	BpChar	   *arg2 = PG_GETARG_BPCHAR_PP(1);
+ 	int			len1,
+ 				len2;
+ 	int			cmp;
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	len1 = bcTruelen(arg1);
+ 	len2 = bcTruelen(arg2);
+ 
+ 	cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2,
+ 		PG_GET_COLLATION());
+ 
+ 	if (cmp < 0)
+ 		PG_RETURN_BPCHAR_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
  
  /*
   * bpchar needs a specialized hash function because we want to ignore
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index cb07a06..821cded 100644
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
*************** text_larger(PG_FUNCTION_ARGS)
*** 1697,1702 ****
--- 1697,1716 ----
  }
  
  Datum
+ text_larger_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
+ 
+ Datum
  text_smaller(PG_FUNCTION_ARGS)
  {
  	text	   *arg1 = PG_GETARG_TEXT_PP(0);
*************** text_smaller(PG_FUNCTION_ARGS)
*** 1708,1713 ****
--- 1722,1740 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ Datum
+ text_smaller_inv(PG_FUNCTION_ARGS)
+ {
+ 	text	   *arg1 = PG_GETARG_TEXT_PP(0);
+ 	text	   *arg2 = PG_GETARG_TEXT_PP(1);
+ 
+ 	if (!AggCheckCallContext(fcinfo, NULL))
+ 		elog(ERROR, "aggregate inverse transition function called in non-aggregate context");
+ 
+ 	if (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0)
+ 		PG_RETURN_TEXT_P(arg1);
+ 	PG_RETURN_NULL();
+ }
  
  /*
   * The following operators support character-by-character comparison
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 3412661..f1a602d 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2113	n 0 interval_pl		-	-	
*** 122,169 ****
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-	-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-	-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-	-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-	-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-	-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-	-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-	-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-	-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -	-				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-	-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-	-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-	-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-	-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-	-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-	-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-	-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-	-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-	-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -	-			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -	-				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-	-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-	-				3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
--- 122,169 ----
  DATA(insert ( 2114	n 0 numeric_avg_accum	-	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger			int8larger_inv			-		413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger			int4larger_inv			-		521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger			int2larger_inv			-		520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger			oidlarger_inv			-		610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger		float4larger_inv		-		623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger		float8larger_inv		-		674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger			int4larger_inv			-		563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger			date_larger_inv			-		1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger			time_larger_inv			-		1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger		timetz_larger_inv		-		1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger			cashlarger_inv			-		903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	timestamp_larger_inv	-		2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	timestamptz_larger_inv	-		1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger		interval_larger_inv		-		1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger			text_larger_inv			-		666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger		numeric_larger_inv		-		1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger		array_larger_inv		-		1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger		bpchar_larger_inv		-		1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger			tidlarger_inv			-		2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger			enum_larger_inv			-		3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller			int8smaller_inv			-		412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller			int4smaller_inv			-		97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller			int2smaller_inv			-		95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller			oidsmaller_inv			-		609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller		float4smaller_inv		-		622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller		float8smaller_inv		-		672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller			int4smaller_inv			-		562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller		date_smaller_inv		-		1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller		time_smaller_inv		-		1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller		timetz_smaller_inv		-		1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller			cashsmaller_inv			-		902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	timestamp_smaller_inv	-		2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller timestamptz_smaller_inv	-		1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	interval_smaller_inv	-		1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller		text_smaller_inv		-		664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller		numeric_smaller_inv		-		1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller		array_smaller_inv		-		1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller		bpchar_smaller_inv		-		1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller			tidsmaller_inv			-		2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller		enum_smaller_inv		-		3518	3500	0	_null_ ));
  
  /* count */
  DATA(insert ( 2147	n 0 int8inc_any		-	-				0		20		0	"0" ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 334e6b8..42dcb27 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("larger of two");
*** 367,372 ****
--- 367,377 ----
  DATA(insert OID = 211 (  float4smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3567 (  float4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3568 (  float4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 700 "700 700" _null_ _null_ _null_ _null_	float4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 212 (  int4um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "23" _null_ _null_ _null_ _null_ int4um _null_ _null_ _null_ ));
  DATA(insert OID = 213 (  int2um			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 21 "21" _null_ _null_ _null_ _null_ int2um _null_ _null_ _null_ ));
  
*************** DATA(insert OID = 223 (  float8larger	  
*** 386,391 ****
--- 391,400 ----
  DESCR("larger of two");
  DATA(insert OID = 224 (  float8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3569 (  float8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3570 (  float8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_	float8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 225 (  lseg_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "601" _null_ _null_ _null_ _null_	lseg_center _null_ _null_ _null_ ));
  DATA(insert OID = 226 (  path_center	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 600 "602" _null_ _null_ _null_ _null_	path_center _null_ _null_ _null_ ));
*************** DATA(insert OID = 458 (  text_larger	   
*** 711,716 ****
--- 720,729 ----
  DESCR("larger of two");
  DATA(insert OID = 459 (  text_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3571 (  text_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3572 (  text_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ text_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  
  DATA(insert OID = 460 (  int8in			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "2275" _null_ _null_ _null_ _null_ int8in _null_ _null_ _null_ ));
  DESCR("I/O");
*************** DATA(insert OID = 515 (  array_larger	  
*** 859,864 ****
--- 872,881 ----
  DESCR("larger of two");
  DATA(insert OID = 516 (  array_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3573 (  array_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3574 (  array_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1191 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 23 "2277 23 16" _null_ _null_ _null_ _null_ generate_subscripts _null_ _null_ _null_ ));
  DESCR("array subscripts generator");
  DATA(insert OID = 1192 (  generate_subscripts PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 23 "2277 23" _null_ _null_ _null_ _null_ generate_subscripts_nodir _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 913,918 ****
--- 930,945 ----
  DATA(insert OID = 771 (  int2smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 3575 (  int4larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3576 (  int4smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ int4smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3577 (  int2larger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3578 (  int2smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 21 "21 21" _null_ _null_ _null_ _null_ int2smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 774 (  gistgettuple	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_	gistgettuple _null_ _null_ _null_ ));
  DESCR("gist(internal)");
  DATA(insert OID = 638 (  gistgetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	gistgetbitmap _null_ _null_ _null_ ));
*************** DATA(insert OID =  898 (  cashlarger	   
*** 1003,1008 ****
--- 1030,1039 ----
  DESCR("larger of two");
  DATA(insert OID =  899 (  cashsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID =  3579 (  cashlarger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID =  3580 (  cashsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "790 790" _null_ _null_ _null_ _null_	cashsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID =  919 (  flt8_mul_cash    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 790 "701 790" _null_ _null_ _null_ _null_	flt8_mul_cash _null_ _null_ _null_ ));
  DATA(insert OID =  935 (  cash_words	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "790" _null_ _null_ _null_ _null_	cash_words _null_ _null_ _null_ ));
  DESCR("output money amount as words");
*************** DATA(insert OID = 1063 (  bpchar_larger 
*** 1157,1162 ****
--- 1188,1197 ----
  DESCR("larger of two");
  DATA(insert OID = 1064 (  bpchar_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3581 (  bpchar_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3582 (  bpchar_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1042 "1042 1042" _null_ _null_ _null_ _null_ bpchar_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1078 (  bpcharcmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1042 1042" _null_ _null_ _null_ _null_ bpcharcmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1080 (  hashbpchar	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "1042" _null_ _null_ _null_ _null_	hashbpchar _null_ _null_ _null_ ));
*************** DATA(insert OID = 1138 (  date_larger	  
*** 1191,1196 ****
--- 1226,1235 ----
  DESCR("larger of two");
  DATA(insert OID = 1139 (  date_smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
+ DATA(insert OID = 3583 (  date_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 3584 (  date_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 1082" _null_ _null_ _null_ _null_ date_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1140 (  date_mi		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1082 1082" _null_ _null_ _null_ _null_ date_mi _null_ _null_ _null_ ));
  DATA(insert OID = 1141 (  date_pli		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_pli _null_ _null_ _null_ ));
  DATA(insert OID = 1142 (  date_mii		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ date_mii _null_ _null_ _null_ ));
*************** DATA(insert OID = 1195 (  timestamptz_sm
*** 1281,1290 ****
--- 1320,1339 ----
  DESCR("smaller of two");
  DATA(insert OID = 1196 (  timestamptz_larger  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4051 (  timestamptz_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4052 (  timestamptz_larger_inv  PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1184 1184" _null_ _null_ _null_ _null_ timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
  DATA(insert OID = 1197 (  interval_smaller	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  DATA(insert OID = 1198 (  interval_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4053 (  interval_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4054 (  interval_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_	interval_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1199 (  age				PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_	timestamptz_age _null_ _null_ _null_ ));
  DESCR("date difference preserving months and years");
  
*************** DESCR("larger of two");
*** 1318,1323 ****
--- 1367,1378 ----
  DATA(insert OID = 1237 (  int8smaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4055 (  int8larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4056 (  int8smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 20" _null_ _null_ _null_ _null_ int8smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1238 (  texticregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexeq _null_ _null_ _null_ ));
  DATA(insert OID = 1239 (  texticregexne    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "25 25" _null_ _null_ _null_ _null_ texticregexne _null_ _null_ _null_ ));
  DATA(insert OID = 1240 (  nameicregexeq    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "19 25" _null_ _null_ _null_ _null_ nameicregexeq _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1374,1379 ****
--- 1429,1439 ----
  DATA(insert OID = 2796 ( tidsmaller		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4057 ( tidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4058 ( tidsmaller_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 27 "27 27" _null_ _null_ _null_ _null_ tidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1296 (  timedate_pl	   PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1114 "1083 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
  DATA(insert OID = 1297 (  datetimetz_pl    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1184 "1082 1266" _null_ _null_ _null_ _null_ datetimetz_timestamptz _null_ _null_ _null_ ));
  DATA(insert OID = 1298 (  timetzdate_pl    PGNSP PGUID 14 1 0 0 0 f f f f t f i 2 0 1184 "1266 1082" _null_ _null_ _null_ _null_ "select ($2 + $1)" _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 1515,1520 ****
--- 1575,1590 ----
  DATA(insert OID = 1380 (  timetz_smaller   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4059 (  time_larger_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4060 (  time_smaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1083 "1083 1083" _null_ _null_ _null_ _null_ time_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4061 (  timetz_larger_inv    PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4062 (  timetz_smaller_inv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1266 1266" _null_ _null_ _null_ _null_ timetz_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
+ 
  DATA(insert OID = 1381 (  char_length	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "25" _null_ _null_ _null_ _null_ textlen _null_ _null_ _null_ ));
  DESCR("character length");
  
*************** DATA(insert OID = 1766 ( numeric_smaller
*** 2277,2282 ****
--- 2347,2357 ----
  DESCR("smaller of two");
  DATA(insert OID = 1767 ( numeric_larger			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4063 ( numeric_smaller_inv		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4064 ( numeric_larger_inv			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_	numeric_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1769 ( numeric_cmp			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1700 1700" _null_ _null_ _null_ _null_ numeric_cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 1771 ( numeric_uminus			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ numeric_uminus _null_ _null_ _null_ ));
*************** DESCR("larger of two");
*** 2803,2808 ****
--- 2878,2888 ----
  DATA(insert OID = 1966 (  oidsmaller	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller _null_ _null_ _null_ ));
  DESCR("smaller of two");
  
+ DATA(insert OID = 4065 (  oidlarger_inv		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidlarger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4066 (  oidsmaller_inv	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 timestamp_transform f f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
  DESCR("adjust timestamptz precision");
  DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 time_transform f f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
*************** DATA(insert OID = 2035 (  timestamp_smal
*** 2864,2869 ****
--- 2944,2955 ----
  DESCR("smaller of two");
  DATA(insert OID = 2036 (  timestamp_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ 
+ DATA(insert OID = 4067 (  timestamp_smaller_inv PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4068 (  timestamp_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1114 "1114 1114" _null_ _null_ _null_ _null_	timestamp_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 2037 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 1266 "25 1266" _null_ _null_ _null_ _null_ timetz_zone _null_ _null_ _null_ ));
  DESCR("adjust time with time zone to new zone");
  DATA(insert OID = 2038 (  timezone			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1266 "1186 1266" _null_ _null_ _null_ _null_	timetz_izone _null_ _null_ _null_ ));
*************** DATA(insert OID = 3524 (  enum_smaller	P
*** 4254,4259 ****
--- 4340,4350 ----
  DESCR("smaller of two");
  DATA(insert OID = 3525 (  enum_larger	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger _null_ _null_ _null_ ));
  DESCR("larger of two");
+ DATA(insert OID = 4073 (  enum_smaller_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_smaller_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ DATA(insert OID = 4074 (  enum_larger_inv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3500 "3500 3500" _null_ _null_ _null_ _null_ enum_larger_inv _null_ _null_ _null_ ));
+ DESCR("inverse aggregate transition function");
+ 
  DATA(insert OID = 3526 (  max			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("maximum value of all enum input values");
  DATA(insert OID = 3527 (  min			PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 3500 "3500" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..43e4973 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern Datum array_upper(PG_FUNCTION_ARG
*** 206,212 ****
--- 206,214 ----
  extern Datum array_length(PG_FUNCTION_ARGS);
  extern Datum array_cardinality(PG_FUNCTION_ARGS);
  extern Datum array_larger(PG_FUNCTION_ARGS);
+ extern Datum array_larger_inv(PG_FUNCTION_ARGS);
  extern Datum array_smaller(PG_FUNCTION_ARGS);
+ extern Datum array_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts(PG_FUNCTION_ARGS);
  extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
  extern Datum array_fill(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 031a43a..b869743 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum enum_ge(PG_FUNCTION_ARGS);
*** 167,173 ****
--- 167,175 ----
  extern Datum enum_gt(PG_FUNCTION_ARGS);
  extern Datum enum_cmp(PG_FUNCTION_ARGS);
  extern Datum enum_smaller(PG_FUNCTION_ARGS);
+ extern Datum enum_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum enum_larger(PG_FUNCTION_ARGS);
+ extern Datum enum_larger_inv(PG_FUNCTION_ARGS);
  extern Datum enum_first(PG_FUNCTION_ARGS);
  extern Datum enum_last(PG_FUNCTION_ARGS);
  extern Datum enum_range_bounds(PG_FUNCTION_ARGS);
*************** extern Datum int42div(PG_FUNCTION_ARGS);
*** 241,249 ****
--- 243,255 ----
  extern Datum int4mod(PG_FUNCTION_ARGS);
  extern Datum int2mod(PG_FUNCTION_ARGS);
  extern Datum int2larger(PG_FUNCTION_ARGS);
+ extern Datum int2larger_inv(PG_FUNCTION_ARGS);
  extern Datum int2smaller(PG_FUNCTION_ARGS);
+ extern Datum int2smaller_inv(PG_FUNCTION_ARGS);
  extern Datum int4larger(PG_FUNCTION_ARGS);
+ extern Datum int4larger_inv(PG_FUNCTION_ARGS);
  extern Datum int4smaller(PG_FUNCTION_ARGS);
+ extern Datum int4smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int4and(PG_FUNCTION_ARGS);
  extern Datum int4or(PG_FUNCTION_ARGS);
*************** extern Datum float4abs(PG_FUNCTION_ARGS)
*** 347,358 ****
--- 353,368 ----
  extern Datum float4um(PG_FUNCTION_ARGS);
  extern Datum float4up(PG_FUNCTION_ARGS);
  extern Datum float4larger(PG_FUNCTION_ARGS);
+ extern Datum float4larger_inv(PG_FUNCTION_ARGS);
  extern Datum float4smaller(PG_FUNCTION_ARGS);
+ extern Datum float4smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float8abs(PG_FUNCTION_ARGS);
  extern Datum float8um(PG_FUNCTION_ARGS);
  extern Datum float8up(PG_FUNCTION_ARGS);
  extern Datum float8larger(PG_FUNCTION_ARGS);
+ extern Datum float8larger_inv(PG_FUNCTION_ARGS);
  extern Datum float8smaller(PG_FUNCTION_ARGS);
+ extern Datum float8smaller_inv(PG_FUNCTION_ARGS);
  extern Datum float4pl(PG_FUNCTION_ARGS);
  extern Datum float4mi(PG_FUNCTION_ARGS);
  extern Datum float4mul(PG_FUNCTION_ARGS);
*************** extern Datum oidle(PG_FUNCTION_ARGS);
*** 501,507 ****
--- 511,519 ----
  extern Datum oidge(PG_FUNCTION_ARGS);
  extern Datum oidgt(PG_FUNCTION_ARGS);
  extern Datum oidlarger(PG_FUNCTION_ARGS);
+ extern Datum oidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum oidsmaller(PG_FUNCTION_ARGS);
+ extern Datum oidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum oidvectorin(PG_FUNCTION_ARGS);
  extern Datum oidvectorout(PG_FUNCTION_ARGS);
  extern Datum oidvectorrecv(PG_FUNCTION_ARGS);
*************** extern Datum tidgt(PG_FUNCTION_ARGS);
*** 707,713 ****
--- 719,727 ----
  extern Datum tidge(PG_FUNCTION_ARGS);
  extern Datum bttidcmp(PG_FUNCTION_ARGS);
  extern Datum tidlarger(PG_FUNCTION_ARGS);
+ extern Datum tidlarger_inv(PG_FUNCTION_ARGS);
  extern Datum tidsmaller(PG_FUNCTION_ARGS);
+ extern Datum tidsmaller_inv(PG_FUNCTION_ARGS);
  extern Datum currtid_byreloid(PG_FUNCTION_ARGS);
  extern Datum currtid_byrelname(PG_FUNCTION_ARGS);
  
*************** extern Datum bpchargt(PG_FUNCTION_ARGS);
*** 730,736 ****
--- 744,752 ----
  extern Datum bpcharge(PG_FUNCTION_ARGS);
  extern Datum bpcharcmp(PG_FUNCTION_ARGS);
  extern Datum bpchar_larger(PG_FUNCTION_ARGS);
+ extern Datum bpchar_larger_inv(PG_FUNCTION_ARGS);
  extern Datum bpchar_smaller(PG_FUNCTION_ARGS);
+ extern Datum bpchar_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum bpcharlen(PG_FUNCTION_ARGS);
  extern Datum bpcharoctetlen(PG_FUNCTION_ARGS);
  extern Datum hashbpchar(PG_FUNCTION_ARGS);
*************** extern Datum text_le(PG_FUNCTION_ARGS);
*** 770,776 ****
--- 786,794 ----
  extern Datum text_gt(PG_FUNCTION_ARGS);
  extern Datum text_ge(PG_FUNCTION_ARGS);
  extern Datum text_larger(PG_FUNCTION_ARGS);
+ extern Datum text_larger_inv(PG_FUNCTION_ARGS);
  extern Datum text_smaller(PG_FUNCTION_ARGS);
+ extern Datum text_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum text_pattern_lt(PG_FUNCTION_ARGS);
  extern Datum text_pattern_le(PG_FUNCTION_ARGS);
  extern Datum text_pattern_gt(PG_FUNCTION_ARGS);
*************** extern Datum numeric_div_trunc(PG_FUNCTI
*** 980,986 ****
--- 998,1006 ----
  extern Datum numeric_mod(PG_FUNCTION_ARGS);
  extern Datum numeric_inc(PG_FUNCTION_ARGS);
  extern Datum numeric_smaller(PG_FUNCTION_ARGS);
+ extern Datum numeric_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_larger(PG_FUNCTION_ARGS);
+ extern Datum numeric_larger_inv(PG_FUNCTION_ARGS);
  extern Datum numeric_fac(PG_FUNCTION_ARGS);
  extern Datum numeric_sqrt(PG_FUNCTION_ARGS);
  extern Datum numeric_exp(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/cash.h b/src/include/utils/cash.h
index 3a491f9..4e184cc 100644
*** a/src/include/utils/cash.h
--- b/src/include/utils/cash.h
*************** extern Datum int2_mul_cash(PG_FUNCTION_A
*** 60,66 ****
--- 60,68 ----
  extern Datum cash_div_int2(PG_FUNCTION_ARGS);
  
  extern Datum cashlarger(PG_FUNCTION_ARGS);
+ extern Datum cashlarger_inv(PG_FUNCTION_ARGS);
  extern Datum cashsmaller(PG_FUNCTION_ARGS);
+ extern Datum cashsmaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum cash_words(PG_FUNCTION_ARGS);
  
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 622aa19..9f459ac 100644
*** a/src/include/utils/date.h
--- b/src/include/utils/date.h
*************** extern Datum date_cmp(PG_FUNCTION_ARGS);
*** 108,114 ****
--- 108,116 ----
  extern Datum date_sortsupport(PG_FUNCTION_ARGS);
  extern Datum date_finite(PG_FUNCTION_ARGS);
  extern Datum date_larger(PG_FUNCTION_ARGS);
+ extern Datum date_larger_inv(PG_FUNCTION_ARGS);
  extern Datum date_smaller(PG_FUNCTION_ARGS);
+ extern Datum date_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum date_mi(PG_FUNCTION_ARGS);
  extern Datum date_pli(PG_FUNCTION_ARGS);
  extern Datum date_mii(PG_FUNCTION_ARGS);
*************** extern Datum time_cmp(PG_FUNCTION_ARGS);
*** 168,174 ****
--- 170,178 ----
  extern Datum time_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_time(PG_FUNCTION_ARGS);
  extern Datum time_larger(PG_FUNCTION_ARGS);
+ extern Datum time_larger_inv(PG_FUNCTION_ARGS);
  extern Datum time_smaller(PG_FUNCTION_ARGS);
+ extern Datum time_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum time_mi_time(PG_FUNCTION_ARGS);
  extern Datum timestamp_time(PG_FUNCTION_ARGS);
  extern Datum timestamptz_time(PG_FUNCTION_ARGS);
*************** extern Datum timetz_cmp(PG_FUNCTION_ARGS
*** 195,201 ****
--- 199,207 ----
  extern Datum timetz_hash(PG_FUNCTION_ARGS);
  extern Datum overlaps_timetz(PG_FUNCTION_ARGS);
  extern Datum timetz_larger(PG_FUNCTION_ARGS);
+ extern Datum timetz_larger_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_smaller(PG_FUNCTION_ARGS);
+ extern Datum timetz_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timetz_time(PG_FUNCTION_ARGS);
  extern Datum time_timetz(PG_FUNCTION_ARGS);
  extern Datum timestamptz_timetz(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index d63e3a9..d102ccb 100644
*** a/src/include/utils/int8.h
--- b/src/include/utils/int8.h
*************** extern Datum int8inc(PG_FUNCTION_ARGS);
*** 77,83 ****
--- 77,85 ----
  extern Datum int8inc_any(PG_FUNCTION_ARGS);
  extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS);
  extern Datum int8larger(PG_FUNCTION_ARGS);
+ extern Datum int8larger_inv(PG_FUNCTION_ARGS);
  extern Datum int8smaller(PG_FUNCTION_ARGS);
+ extern Datum int8smaller_inv(PG_FUNCTION_ARGS);
  
  extern Datum int8and(PG_FUNCTION_ARGS);
  extern Datum int8or(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 2731c6a..4f6673c 100644
*** a/src/include/utils/timestamp.h
--- b/src/include/utils/timestamp.h
*************** extern Datum timestamp_cmp(PG_FUNCTION_A
*** 111,117 ****
--- 111,119 ----
  extern Datum timestamp_sortsupport(PG_FUNCTION_ARGS);
  extern Datum timestamp_hash(PG_FUNCTION_ARGS);
  extern Datum timestamp_smaller(PG_FUNCTION_ARGS);
+ extern Datum timestamp_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum timestamp_larger(PG_FUNCTION_ARGS);
+ extern Datum timestamp_larger_inv(PG_FUNCTION_ARGS);
  
  extern Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS);
  extern Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS);
*************** extern Datum interval_finite(PG_FUNCTION
*** 151,157 ****
--- 153,161 ----
  extern Datum interval_cmp(PG_FUNCTION_ARGS);
  extern Datum interval_hash(PG_FUNCTION_ARGS);
  extern Datum interval_smaller(PG_FUNCTION_ARGS);
+ extern Datum interval_smaller_inv(PG_FUNCTION_ARGS);
  extern Datum interval_larger(PG_FUNCTION_ARGS);
+ extern Datum interval_larger_inv(PG_FUNCTION_ARGS);
  extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
  extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
  extern Datum interval_justify_days(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index e3eba47..3c4ad7f 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 1305,1310 ****
--- 1305,1403 ----
  -- Test the MIN and MAX inverse transition functions
  --
  --
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  40 |  30
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  20 |  20
+     |  40 |  40
+  40 |  40 |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  10 |  20 |  10
+  20 |  30 |  20
+  30 |  30 |  30
+  40 |     |  40
+ (4 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |  20 |  10
+     |  10 |  10
+  10 |  10 |  10
+ (5 rows)
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  40 |  40 |  10
+  40 |  40 |  10
+  20 |     |  10
+     |     |  10
+  10 |     |  10
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+  v  | max | min 
+ ----+-----+-----
+  ad | ad  | ab
+  ab | ad  | ab
+  ad | ad  | aa
+  aa | ae  | aa
+  ae | ae  | ae
+ (5 rows)
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+  v  |  p  | max | min 
+ ----+-----+-----+-----
+  ad | 100 | ae  | ab
+  ab | 100 | ae  | ab
+  ae | 100 | ae  | ae
+  ad | 200 | ad  | aa
+  aa | 200 | aa  | aa
+ (5 rows)
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index bd8dbdc..bde2a49 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** DROP FUNCTION logging_sfunc_nonstrict(te
*** 488,493 ****
--- 488,525 ----
  --
  --
  
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,NULL),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) FILTER(WHERE v <> 40) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,10::int),(2,20),(3,30),(4,40)) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(CASE WHEN v <= 20 THEN NULL ELSE v END) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,40::int),(2,40),(3,20),(4,NULL),(5,10)) v(i,v)
+ WINDOW w AS (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,'ad'),(2,'ab'),(3,'ad'),(4,'aa'),(5,'ae')) v(i,v)
+ WINDOW w AS (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING);
+ 
+ SELECT v,p,MAX(v) OVER w,MIN(v) OVER w
+ FROM (VALUES(1,100,'ad'),(2,100,'ab'),(3,200,'ad'),(4,200,'aa'),(5,100,'ae')) v(i,p,v)
+ WINDOW w AS (PARTITION BY p ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING);
+ 
  --
  --
  -- Tests of the collecting inverse transition functions
invtrans_optimize_abf837.patchapplication/octet-stream; name=invtrans_optimize_abf837.patchDownload
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 0b558e5..8afe3d0 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** window_gettupleslot(WindowObject winobj,
*** 1945,1958 ****
  		winobj->seekpos++;
  	}
  
! 	while (winobj->seekpos > pos)
  	{
! 		if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot))
  			elog(ERROR, "unexpected end of tuplestore");
  		winobj->seekpos--;
  	}
  
! 	while (winobj->seekpos < pos)
  	{
  		if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot))
  			elog(ERROR, "unexpected end of tuplestore");
--- 1945,1976 ----
  		winobj->seekpos++;
  	}
  
! 	/* Advance or rewind until we are within one tuple of the one we want */
! 	while (winobj->seekpos < pos-1)
  	{
! 		if (!tuplestore_advance(winstate->buffer, true))
! 			elog(ERROR, "unexpected end of tuplestore");
! 		winobj->seekpos++;
! 	}
! 
! 	while (winobj->seekpos > pos+1)
! 	{
! 		if (!tuplestore_advance(winstate->buffer, false))
  			elog(ERROR, "unexpected end of tuplestore");
  		winobj->seekpos--;
  	}
  
! 	/*
! 	 * Now we should be on the tuple immediately before or after the one we
! 	 * want, so just fetch forwards or backwards as appropriate.
! 	 */
! 	if (winobj->seekpos > pos)
! 	{
! 		if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot))
! 			elog(ERROR, "unexpected end of tuplestore");
! 		winobj->seekpos--;
! 	}
! 	else
  	{
  		if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot))
  			elog(ERROR, "unexpected end of tuplestore");
#179David Rowley
dgrowleyml@gmail.com
In reply to: Florian Pflug (#178)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Wed, Apr 9, 2014 at 8:48 AM, Florian Pflug <fgp@phlo.org> wrote:

As explain above, invtrans_bool is a bit problematic, since it carries
a real risk of performance regressions. It's included for completeness'
sake, and should probably not be committed at this time.

Did you mean to write invtrans_minmax? Otherwise you didn't explain about
you concerns with bool.

Regards

David Rowley

#180Florian Pflug
fgp@phlo.org
In reply to: David Rowley (#179)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr9, 2014, at 02:55 , David Rowley <dgrowleyml@gmail.com> wrote:

On Wed, Apr 9, 2014 at 8:48 AM, Florian Pflug <fgp@phlo.org> wrote:

As explain above, invtrans_bool is a bit problematic, since it carries
a real risk of performance regressions. It's included for completeness'
sake, and should probably not be committed at this time.

Did you mean to write invtrans_minmax? Otherwise you didn't explain about
you concerns with bool.

Grmpf. Should have re-read that once more before sending :-(

Yes, I meant invtrans_minmax is problematic! invtrans_bool is fine, the
inverse transition function never fails for BOOL_AND and BOOL_OR. This
is why I factored it out into a separate patch, to make it easy to not
apply the MIN/MAX stuff, while still applying the BOOL stuff. Sorry for
the confision.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#181Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#178)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 8 April 2014 21:48, Florian Pflug <fgp@phlo.org> wrote:

On Apr7, 2014, at 17:41 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

I've just finished reading through all the other patches, and they all
look OK to me. It's mostly straightforward stuff, so despite the size
it's hopefully all committable once the base patch goes in.

Hm, I'm starting to have second thoughts about the minmax patch. The
inverse transition functions for MIN and MAX have a non-trivial probability
of failure - they trigger a rescan whenever the value that is removed isn't
strictly smaller (respectively strictly larger) then the current maximum
(respectively minimum). Thus, whenever that happens, we both call the
inverse transition function *and* (since it fails) restart the aggregation.

For windows based on ROWS, this isn't too bad - even if we fail every second
time, we still avoid half the rescans, which should be a net win if the
average window size is > 2.

But for RANGE-based windows, more than one call of the inverse transition
function must succeed for us to save anything, since we must successfully
remove *all* peers to avoid one restart. This greatly increases the chance
that using the inverse transition function hurts rather then helps - the
situation is worse the larger the average number of peers is.

Argh, I hadn't really considered that case. I suppose any imperfect
inverse transition function has the potential to make performance
worse rather than better. But working out the likelihood of that isn't
necessarily straightforward.

It might be possible to include some sort of heuristic based on the
known information --- the number of rows P in the peer group about to
be removed vs the total number N of rows aggregated so far. If the
data were fairly random, then a quick back-of-the-envelope calculation
suggests that trying the inverse min/max functions would be worthwhile
on average if P were less than around 0.4N, but of course different
data distributions could make that much worse. Even a perfect inverse
transition function isn't going to be much use if P > N/2 (e.g.,
imagine a case where the peer groups decrease in size exponentially),
so perhaps we should be including such a check anyway.

That's also assuming that the cost of the inverse transition function
is about the same as the cost of the forward function, which is not
necessarily the case. Perhaps imperfect inverse transition functions
should be assigned a higher cost, and that should be factored into the
decision as to whether they are likely to be worth trying. All that
feels very speculative though, and I think it's too late to be
considering that for 9.4, so yes, let's leave out the min/max
aggregates for now.

I've factored the BOOL_AND,BOOL_OR stuff out into a separate patch
invtrans_bool - it previously was part of invtrans_minmax. Given the
performance risk involved, I think that we probably shouldn't commit
invtrans_minmax at this time. I should have brought this up earlier, but
the issue had slipped my mind :-( Sorry for that.

I think that you're probably right that optimising
window_gettupleslot() to eliminate the O(n^2) behaviour can be left to
a later patch --- the existing performance benefits of this patch are
enough to justify its inclusion IMO. It would be nice to include the
trivial optimisation to window_gettupleslot() that I posted upthread,
since it gives such a big improvement for only a few lines of code
changed.

Agreed, but since it's independent from the rest of the base patch,
I kept it as a separate patch (invtrans_optimize) instead of merging it
into the base patch. It should probably be committed separately too.

It would be good to commit at least the "base", "arith" and "optimize"
patches for 9.4. I think the "collecting" and "bool" patches are also
committable, but I also suspect that those aggregates are less well
used, so they could be considered lower priority.

If you post a new patch set, I'll mark it as ready for committer attention.

New patch set is attached. The only difference to the previous one is that
"Forward Transitions" and "Inverse Transitions" are now scaled with nloops,
and that it includes your window_gettupleslot patch under the name
invtrans_optimize.

OK, I'm marking this ready for committer attention, on the
understanding that that doesn't include the invtrans_minmax patch.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#182Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#181)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

OK, I'm marking this ready for committer attention, on the
understanding that that doesn't include the invtrans_minmax patch.

I've started to look at this patch set. After rereading the thread,
I'm thinking that it's a mistake to just add the inverse transition
function and require it to work with the standard forward transition
function for the aggregate. There was discussion upthread of providing
two separate forward transition functions, but Florian argued that that
would do nothing that you couldn't accomplish with a runtime check in
the forward function. I think that's nonsense though, because one of
the key points here is that an invertible aggregate may require a more
complex transition state data structure --- in particular, if you're
forced to go from a pass-by-value to a pass-by-reference data type, right
there you are going to take a big hit in aggregate performance, and there
is no way for the forward transition function to avoid it. The patch
has in fact already done that to a couple of basic aggregates like
sum(int4). Has anyone bothered to test what side-effects that has on
non-windowed aggregation performance?

I think what'd make sense is to have a separate forward function *and*
separate state datatype to be used when we want invertible aggregation
(hm, and I guess a separate final function too). So an aggregate
definition would include two entirely independent implementations.
If they chance to share sfunc and stype, fine, but they don't have to.

This would mean we'd need some new names for the doppelgangers of the
CREATE AGGREGATE parameters sfunc, stype, sspace, finalfunc, and initcond
(but not sortop). I guess that'd open up a chance to use a more uniform
naming scheme, but I'm not too sure what would be good.

Comments?

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

#183Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#182)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

I wrote:

... an invertible aggregate may require a more
complex transition state data structure --- in particular, if you're
forced to go from a pass-by-value to a pass-by-reference data type, right
there you are going to take a big hit in aggregate performance, and there
is no way for the forward transition function to avoid it. The patch
has in fact already done that to a couple of basic aggregates like
sum(int4). Has anyone bothered to test what side-effects that has on
non-windowed aggregation performance?

As a quick check, I compared aggregation performance in HEAD, non-assert
builds, with and without --disable-float8-byval on a 64-bit machine.
So this tests replacing a pass-by-val transition datatype with a
pass-by-ref one without any other changes. There's essentially no
difference in performance of sum(int4), AFAICT, but that's because
int4_sum goes out of its way to cheat and avoid palloc overhead.
I looked to the bit_and() aggregates to see what would happen to
an aggregate not thus optimized. As expected, int4 and int8 bit_and
are just about the same speed if int8 is pass by value ... but if it's
pass by ref, the int8 case is a good 60% slower.

So added palloc overhead, at least, is a no-go. I see that the patched
version of sum(int4) avoids that trap, but nonetheless it's replaced a
pretty cheap transition function with a less cheap function, namely the
function previously used for avg(int4). A quick test says that avg(int4)
is about five percent slower than sum(int4), so that's the kind of hit
we'd be taking on non-windowed aggregations if we do it like this.

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

#184Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#183)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr9, 2014, at 21:35 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

As a quick check, I compared aggregation performance in HEAD, non-assert
builds, with and without --disable-float8-byval on a 64-bit machine.
So this tests replacing a pass-by-val transition datatype with a
pass-by-ref one without any other changes. There's essentially no
difference in performance of sum(int4), AFAICT, but that's because
int4_sum goes out of its way to cheat and avoid palloc overhead.

I looked to the bit_and() aggregates to see what would happen to
an aggregate not thus optimized. As expected, int4 and int8 bit_and
are just about the same speed if int8 is pass by value ... but if it's
pass by ref, the int8 case is a good 60% slower.

True, but that just means that aggregate transition functions really
ought to update the state in-place, no?

So added palloc overhead, at least, is a no-go. I see that the patched
version of sum(int4) avoids that trap, but nonetheless it's replaced a
pretty cheap transition function with a less cheap function, namely the
function previously used for avg(int4). A quick test says that avg(int4)
is about five percent slower than sum(int4), so that's the kind of hit
we'd be taking on non-windowed aggregations if we do it like this.

That's rather surprising though. AFAICS, there's isn't much difference
between the two transfer functions int4_sum and int4_avg_accum at all.

The differences come down to (+ denoting things which ought to make
int4_avg_accum slower compared to int4_sum, - denoting the opposite)

1. +) int4_avg_accum calls AggCheckCallContext
2. -) int4_sum checks if the state is NULL (it never is for int4_avg_accum)
3. +) int4_avg_accum uses ARR_HASNULL, ARR_SIZE, ARR_OVERHEAD_NONULLS
to verify that the state is a 2-element array without NULL entries
4. -) int4_sum checks if the input is NULL

The number of conditional branches should be about the same (and all are
seldomly taken). The validity checks on the state array, i.e. (3), should
be rather cheap I think - not quite as cheap as PG_ARGISNULL maybe, but
not so much more expensive either. That leaves the AggCheckCallContext call.
If that call costs us 5%, maybe we can find a way to make it faster, or
get rid of it entirely? Still seems a lot of a call of a not-very-complex
function, though...

I'll go and check the disassembly - maybe something in int4_avg_accum turns
out to be more complex than is immediately obvious. I'll also try to create
a call profile, unless you already have one from your test runs.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#185Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#182)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr9, 2014, at 20:20 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

There was discussion upthread of providing
two separate forward transition functions, but Florian argued that that
would do nothing that you couldn't accomplish with a runtime check in
the forward function. I think that's nonsense though, because one of
the key points here is that an invertible aggregate may require a more
complex transition state data structure --- in particular, if you're
forced to go from a pass-by-value to a pass-by-reference data type, right
there you are going to take a big hit in aggregate performance, and there
is no way for the forward transition function to avoid it.

To be precise, my point was that *only* having a separate non-invertible
forward transition function is pointless, exactly because of the reason
you gave - that won't allow a cheaper state representation for the
non-invertible case. So all such a non-invertible forward transition
function can do is to skip some bookkeeping that's required by the
inverse transition function - and *that* can just as easily be accomplished
by a runtime check.

I was (and still am) not in favour of duplicating the whole quadruple of
(state, initialvalue, transferfunction, finalfunction) because it seems
excessive. In fact, I believed that doing this would probably be grounds for
outright rejection of the patch, on the base of catalog bloat. And your
initial response to this suggestion seemed to confirm this.

The patch has in fact already done that to a couple of basic aggregates like
sum(int4). Has anyone bothered to test what side-effects that has on
non-windowed aggregation performance?

I'm pretty sure David Rowley did some benchmarking. The results should be
in this thread somewhere I think, but they currently evade me... Maybe David
can re-post, if he's following this...

I think what'd make sense is to have a separate forward function *and*
separate state datatype to be used when we want invertible aggregation
(hm, and I guess a separate final function too). So an aggregate
definition would include two entirely independent implementations.
If they chance to share sfunc and stype, fine, but they don't have to.

This would mean we'd need some new names for the doppelgangers of the
CREATE AGGREGATE parameters sfunc, stype, sspace, finalfunc, and initcond
(but not sortop). I guess that'd open up a chance to use a more uniform
naming scheme, but I'm not too sure what would be good.

If we really go down that road (and I'm far from convinced), then maybe
instead of having a bunch of additional fields, we could have separate
entries in pg_aggregate for the two cases, with links between them.

The non-invertible aggregates would have something like "_noninv" or so
appended to their name, and we'd automatically use them if we know we
won't need to remove entries from the aggregation state. That would also
allow the users to *force* the non-invertible aggregate to be used by
simply saying "SUM_NONINV" instead of "SUM".

Then all we'd need would be an additional OID field that links the
invertible to the non-invertible definition.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#186Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#185)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

I was (and still am) not in favour of duplicating the whole quadruple of
(state, initialvalue, transferfunction, finalfunction) because it seems
excessive. In fact, I believed that doing this would probably be grounds for
outright rejection of the patch, on the base of catalog bloat. And your
initial response to this suggestion seemed to confirm this.

Well, I think it's much more likely that causing a performance penalty for
cases unrelated to window aggregates would lead to outright rejection :-(.
The majority of our users probably don't ever use window functions, but
for sure they've heard of SUM(). We can't penalize the non-window case.

Expanding pg_aggregate from 10 columns (as per patch) to 14 (as per this
suggestion) is a little annoying but it doesn't sound like a show stopper.
It seems reasonable to assume that the extra initval would be NULL in most
cases, so it's probably a net addition of 12 bytes per row.

On Apr9, 2014, at 20:20 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

The patch has in fact already done that to a couple of basic aggregates like
sum(int4). Has anyone bothered to test what side-effects that has on
non-windowed aggregation performance?

I'm pretty sure David Rowley did some benchmarking. The results should be
in this thread somewhere I think, but they currently evade me... Maybe David
can re-post, if he's following this...

I saw benchmarks addressing window aggregation, but none looking for
side-effects on plain aggregation.

If we really go down that road (and I'm far from convinced), then maybe
instead of having a bunch of additional fields, we could have separate
entries in pg_aggregate for the two cases, with links between them.

That seems like a complete mess; in particular it would break the primary
key for pg_aggregate (aggfnoid), and probably break every existing query
that looks at pg_aggregate. Some extra fields would not break such
expectations (in fact we've added fields to pg_aggregate in the past).

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

#187Florian Pflug
fgp@phlo.org
In reply to: Florian Pflug (#184)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr9, 2014, at 23:17 , Florian Pflug <fgp@phlo.org> wrote:

On Apr9, 2014, at 21:35 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

A quick test says that avg(int4)
is about five percent slower than sum(int4), so that's the kind of hit
we'd be taking on non-windowed aggregations if we do it like this.

That's rather surprising though. AFAICS, there's isn't much difference
between the two transfer functions int4_sum and int4_avg_accum at all.

The differences come down to (+ denoting things which ought to make
int4_avg_accum slower compared to int4_sum, - denoting the opposite)

1. +) int4_avg_accum calls AggCheckCallContext
2. -) int4_sum checks if the state is NULL (it never is for int4_avg_accum)
3. +) int4_avg_accum uses ARR_HASNULL, ARR_SIZE, ARR_OVERHEAD_NONULLS
to verify that the state is a 2-element array without NULL entries
4. -) int4_sum checks if the input is NULL

I've done a bit of profiling on this (using Instruments.app on OSX). One
thing I missed is that inv4_avg_accum also calls pg_detoast_datum - that
calls comes from the PG_GETARG_ARRAYTYPE_P macro. Doing that is a bit silly,
since we know that the datum cannot possibly be toasted I think (or if it
was, nodeAgg.c should do the de-toasting).

The profile also attributes a rather large percent of the total runtime of
int4_avg_accum (around 30%) to the call of AggCheckCallContext(). Getting
rid of that would help quite a few transition functions, invertible or not.
That certainly seems doable - we'd need a way to mark functions as internal
support functions, and prevent user-initiated calls of such functions.
Transition functions marked that way could then safely scribble over their
state arguments without having to consult AggCheckCallContext() first.

I've also compared the disassemblies of int4_sum and int4_avg_accum, and
apart from these differences, they are pretty similar.

Also, the *only* reason that SUM(int2|int4) cannot use int8 as it's
transition type is that it needs to return NULL, not 0, if zero rows
were aggregates. It might seems that it could just use int8 as state
with initial value NULL, but that only works if the transition functions
are non-strict (since the special case of state type == input type doesn't
apply here). And for non-strict transition functions need to deal with
NULL inputs themselves, which means counting the number of non-NULL inputs..

That problem would go away though if we had support for an initalfunction,
which would receive the first input value and initialize the state. In
the case of SUM(), the initial function would be strict, and thus would be
called on the first non-NULL input value, which it'd convert to int8 and
return as the new state.

However, I still believe the best approach at this point is to just work
on making int4_avg_accum faster. I still see no principal reason what it
has to be noticeably slower - the only additional work it absolutely *has*
to perform is *one* 64-bit increment.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#188Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#186)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 9 April 2014 22:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

I was (and still am) not in favour of duplicating the whole quadruple of
(state, initialvalue, transferfunction, finalfunction) because it seems
excessive. In fact, I believed that doing this would probably be grounds for
outright rejection of the patch, on the base of catalog bloat. And your
initial response to this suggestion seemed to confirm this.

Well, I think it's much more likely that causing a performance penalty for
cases unrelated to window aggregates would lead to outright rejection :-(.
The majority of our users probably don't ever use window functions, but
for sure they've heard of SUM(). We can't penalize the non-window case.

Expanding pg_aggregate from 10 columns (as per patch) to 14 (as per this
suggestion) is a little annoying but it doesn't sound like a show stopper.
It seems reasonable to assume that the extra initval would be NULL in most
cases, so it's probably a net addition of 12 bytes per row.

On Apr9, 2014, at 20:20 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

The patch has in fact already done that to a couple of basic aggregates like
sum(int4). Has anyone bothered to test what side-effects that has on
non-windowed aggregation performance?

I'm pretty sure David Rowley did some benchmarking. The results should be
in this thread somewhere I think, but they currently evade me... Maybe David
can re-post, if he's following this...

I saw benchmarks addressing window aggregation, but none looking for
side-effects on plain aggregation.

If we really go down that road (and I'm far from convinced), then maybe
instead of having a bunch of additional fields, we could have separate
entries in pg_aggregate for the two cases, with links between them.

That seems like a complete mess; in particular it would break the primary
key for pg_aggregate (aggfnoid), and probably break every existing query
that looks at pg_aggregate. Some extra fields would not break such
expectations (in fact we've added fields to pg_aggregate in the past).

This may initially sound unrelated, but I think it might address some
of these issues. Suppose we added a 'firsttrans' function, that took a
single argument (the first value to be aggregated) and was responsible
for creating the initial state from that first value.

This would apply to aggregates that ignore null values, but whose
transition function cannot currently be declared strict (either
because the state type is internal, or because it is not the same as
the aggregate's argument type).

I think quite a lot of the existing aggregates fall into this
category, and this would allow their transition functions to be made
strict and simplified --- no more testing if the state is null, and
then building it, and no more testing if the argument is null and
ignoring it. That might give a noticeable performance boost in the
regular aggregate case, especially over data containing nulls.

But in addition, it would help with writing inverse transition
functions because if the transition functions could be made strict,
they wouldn't need to do null-counting, which would mean that their
state types would not need to be expanded.

So for example sum(int4) could continue to have int8 as its state
type, it could use int8(int4) as its firsttrans function, and
int4_sum() could become strict and lose all its null-handling logic.
Then int4_sum_inv() would be the trivial to write - just doing the
same in reverse.

I'm not sure it helps for all aggregates, but there are certainly some
where it would seem to simplify things.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#189Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Florian Pflug (#187)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 10 April 2014 01:13, Florian Pflug <fgp@phlo.org> wrote:

Also, the *only* reason that SUM(int2|int4) cannot use int8 as it's
transition type is that it needs to return NULL, not 0, if zero rows
were aggregates. It might seems that it could just use int8 as state
with initial value NULL, but that only works if the transition functions
are non-strict (since the special case of state type == input type doesn't
apply here). And for non-strict transition functions need to deal with
NULL inputs themselves, which means counting the number of non-NULL inputs..

That problem would go away though if we had support for an initalfunction,
which would receive the first input value and initialize the state. In
the case of SUM(), the initial function would be strict, and thus would be
called on the first non-NULL input value, which it'd convert to int8 and
return as the new state.

Ah snap!

However, I still believe the best approach at this point is to just work
on making int4_avg_accum faster. I still see no principal reason what it
has to be noticeably slower - the only additional work it absolutely *has*
to perform is *one* 64-bit increment.

In the best case that would make sum() not noticeably slower than
avg(), whereas using a firsttrans/initialfunction would potentially
make both of them faster than they currently are, and not just in
window queries.

Also, IMO a first/initial function leads to a cleaner separation of
logic, allowing some of the transition functions to be simplified
rather than becoming more complex.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#190David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#186)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, Apr 10, 2014 at 9:55 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'm pretty sure David Rowley did some benchmarking. The results should be
in this thread somewhere I think, but they currently evade me... Maybe

David

can re-post, if he's following this...

I saw benchmarks addressing window aggregation, but none looking for
side-effects on plain aggregation.

These ones maybe?
/messages/by-id/CAApHDvr_oSpvM-XXz43eCMX8n0EfshJ=9j+rxvGqCy91YR-YQw@mail.gmail.com

I think it was only around SUM(numeric), and you'll need to scroll down a
bit to find it as it's mixed in with a load of window agg tests. At the
time it was the only forward transition function that I had added any
overhead to, so I think it was the only one I bothered to test at the time.
However, I do remember benchmarking the bool_and and bool_or changes after
I rewrote the way they worked and also found the same as you... no
difference, I just can't find the post with my results... Though it sounds
like Tom found the same as me so I don't think it's worth me looking any
more for them.

Regards

David Rowley

#191David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#186)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Thu, Apr 10, 2014 at 9:55 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

I was (and still am) not in favour of duplicating the whole quadruple of
(state, initialvalue, transferfunction, finalfunction) because it seems
excessive. In fact, I believed that doing this would probably be grounds

for

outright rejection of the patch, on the base of catalog bloat. And your
initial response to this suggestion seemed to confirm this.

Well, I think it's much more likely that causing a performance penalty for
cases unrelated to window aggregates would lead to outright rejection :-(.
The majority of our users probably don't ever use window functions, but
for sure they've heard of SUM(). We can't penalize the non-window case.

Expanding pg_aggregate from 10 columns (as per patch) to 14 (as per this
suggestion) is a little annoying but it doesn't sound like a show stopper.
It seems reasonable to assume that the extra initval would be NULL in most
cases, so it's probably a net addition of 12 bytes per row.

I also wouldn't imagine that the overhead of storing that would be too
great... And are there really any databases out there that have 1000's of
custom aggregate functions?

I'm actually quite glad to see someone agrees with me on this. I think it
opens up quite a bit of extra optimisation opportunities with things like
MAX and MIN... In these cases we could be tracking the number of values of
max found and reset it when we get a bigger value. That way we could report
the inverse transition as successful if maxcount is still above 0 after the
removal of a max value... Similar to how I implemented the inverse
transition for sum(numeric). In fact doing it this way would mean that
inverse transitions for sum(numeric) would never fail and retry. I just
thought we had gotten to a stage of not requiring this due to the overheads
being so low... I was quite surprised to see the count tracking account for
5% for sum int. What I don't quite understand yet is why we can't just
create a new function for int inverse transitions instead of borrowing the
inverse transition functions for avg...?

Regards

David Rowley

#192Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#189)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 10 April 2014 01:13, Florian Pflug <fgp@phlo.org> wrote:

However, I still believe the best approach at this point is to just work
on making int4_avg_accum faster. I still see no principal reason what it
has to be noticeably slower - the only additional work it absolutely *has*
to perform is *one* 64-bit increment.

In the best case that would make sum() not noticeably slower than
avg(), whereas using a firsttrans/initialfunction would potentially
make both of them faster than they currently are, and not just in
window queries.

I'm still of the opinion that we should separate the transfn for
invertible cases from the normal one, and allow for two separate
state types. One of the things that helps with is the strictness
consideration: you no longer have to have the same strictness
setting for the plain and invertible forward transfns.

This idea of a separate firsttrans function is interesting but perhaps
orthogonal to the current patch. Also, I don't quite understand how
it would work for aggregates with null initvalues; don't you end up
with exactly the same conflict about how you can't mark the transfn
strict? Or is the idea that firsttrans would *only* apply to aggregates
with null initvalue, and so you wouldn't even pass the previous state
value to it?

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

#193Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#192)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 10 April 2014 15:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 10 April 2014 01:13, Florian Pflug <fgp@phlo.org> wrote:

However, I still believe the best approach at this point is to just work
on making int4_avg_accum faster. I still see no principal reason what it
has to be noticeably slower - the only additional work it absolutely *has*
to perform is *one* 64-bit increment.

In the best case that would make sum() not noticeably slower than
avg(), whereas using a firsttrans/initialfunction would potentially
make both of them faster than they currently are, and not just in
window queries.

I'm still of the opinion that we should separate the transfn for
invertible cases from the normal one, and allow for two separate
state types. One of the things that helps with is the strictness
consideration: you no longer have to have the same strictness
setting for the plain and invertible forward transfns.

Yes, I can imagine that there would be some aggregates for which the
plain forwards transition function would be simpler than the
invertible one, with a simpler state type. You'd still be left with
quite a large number of existing aggregates having non-strict plain
transition functions, in addition to a bunch of new non-strict
invertible transition functions that had to do null counting.

This idea of a separate firsttrans function is interesting but perhaps
orthogonal to the current patch. Also, I don't quite understand how
it would work for aggregates with null initvalues; don't you end up
with exactly the same conflict about how you can't mark the transfn
strict? Or is the idea that firsttrans would *only* apply to aggregates
with null initvalue, and so you wouldn't even pass the previous state
value to it?

I was imagining that firsttrans would only be passed the first value
to be aggregated, not any previous state, and that it would be illegal
to specify both an initcond and a firsttrans function.

The forward transition function would only be called for values after
the first, by which point the state would be non-null, and so it could
be made strict in most cases. The same would apply to the invertible
transition functions, so they wouldn't have to do null counting, which
in turn would make their state types simpler.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#194Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#193)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 10 April 2014 15:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:

This idea of a separate firsttrans function is interesting but perhaps
orthogonal to the current patch. Also, I don't quite understand how
it would work for aggregates with null initvalues; don't you end up
with exactly the same conflict about how you can't mark the transfn
strict? Or is the idea that firsttrans would *only* apply to aggregates
with null initvalue, and so you wouldn't even pass the previous state
value to it?

I was imagining that firsttrans would only be passed the first value
to be aggregated, not any previous state, and that it would be illegal
to specify both an initcond and a firsttrans function.

Got it. So the existing behavior where we insert the first non-null
value could be seen as a degenerate case in which the firsttrans function
is an identity.

The forward transition function would only be called for values after
the first, by which point the state would be non-null, and so it could
be made strict in most cases. The same would apply to the invertible
transition functions, so they wouldn't have to do null counting, which
in turn would make their state types simpler.

Makes sense to me. We need names for these things though. I don't
think abbreviating to "ffunc" is a good idea because of the likelihood
of confusion with the finalfunc (indeed I see the CREATE AGGREGATE
ref page is already using "ffunc" as a short form for finalfunc).
Maybe "initfunc", which would parallel "initcond"?

What about names for the invertible-aggregate infrastructure?
I'm tempted to prefix "inv" to all the existing names, but then
"invsfunc" means the alternate forward function ... can we use
"invifunc" for the inverse transition function? Or maybe the
prefix should be just "i".

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

#195Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#194)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 10 April 2014 19:04, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 10 April 2014 15:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:

This idea of a separate firsttrans function is interesting but perhaps
orthogonal to the current patch. Also, I don't quite understand how
it would work for aggregates with null initvalues; don't you end up
with exactly the same conflict about how you can't mark the transfn
strict? Or is the idea that firsttrans would *only* apply to aggregates
with null initvalue, and so you wouldn't even pass the previous state
value to it?

I was imagining that firsttrans would only be passed the first value
to be aggregated, not any previous state, and that it would be illegal
to specify both an initcond and a firsttrans function.

Got it. So the existing behavior where we insert the first non-null
value could be seen as a degenerate case in which the firsttrans function
is an identity.

Right.

The forward transition function would only be called for values after
the first, by which point the state would be non-null, and so it could
be made strict in most cases. The same would apply to the invertible
transition functions, so they wouldn't have to do null counting, which
in turn would make their state types simpler.

Makes sense to me. We need names for these things though. I don't
think abbreviating to "ffunc" is a good idea because of the likelihood
of confusion with the finalfunc (indeed I see the CREATE AGGREGATE
ref page is already using "ffunc" as a short form for finalfunc).
Maybe "initfunc", which would parallel "initcond"?

Yes, I was already thinking that "initfunc" is actually a much better
name for that.

What about names for the invertible-aggregate infrastructure?
I'm tempted to prefix "inv" to all the existing names, but then
"invsfunc" means the alternate forward function ... can we use
"invifunc" for the inverse transition function? Or maybe the
prefix should be just "i".

Hmm, I'm not a fan of any of those names. Perhaps "win" as a prefix to
denote a sliding window? Or just "m" for "moving aggregate".

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#196Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#195)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 10 April 2014 19:04, Tom Lane <tgl@sss.pgh.pa.us> wrote:

What about names for the invertible-aggregate infrastructure?
I'm tempted to prefix "inv" to all the existing names, but then
"invsfunc" means the alternate forward function ... can we use
"invifunc" for the inverse transition function? Or maybe the
prefix should be just "i".

Hmm, I'm not a fan of any of those names. Perhaps "win" as a prefix to
denote a sliding window? Or just "m" for "moving aggregate".

Hmm ... "moving aggregate" is actually a pretty good name for this
whole feature -- better than "invertible aggregate" anyway. I can
feel a global-search-and-replace coming on.

So if we go with that terminology, perhaps these names for the
new CREATE AGGREGATE parameters:

initfunc applies to plain aggregation, mutually exclusive with initcond
msfunc (or just mfunc?) forward transition for moving-agg mode
mifunc inverse transition for moving-agg mode
mstype state datatype for moving-agg mode
msspace space estimate for mstype
mfinalfunc final function for moving-agg mode
minitfunc "firsttrans" for moving-agg mode
minitcond mutually exclusive with minitfunc

That takes us up to 16 columns in pg_aggregate, but it's still not going
to be a very voluminous catalog --- there's only 171 rows there today.
So I'm not particularly concerned about space, and if there's a chance
of squeezing out cycles, I think we should seize it.

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

#197Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#196)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 10 April 2014 19:54, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 10 April 2014 19:04, Tom Lane <tgl@sss.pgh.pa.us> wrote:

What about names for the invertible-aggregate infrastructure?
I'm tempted to prefix "inv" to all the existing names, but then
"invsfunc" means the alternate forward function ... can we use
"invifunc" for the inverse transition function? Or maybe the
prefix should be just "i".

Hmm, I'm not a fan of any of those names. Perhaps "win" as a prefix to
denote a sliding window? Or just "m" for "moving aggregate".

Hmm ... "moving aggregate" is actually a pretty good name for this
whole feature -- better than "invertible aggregate" anyway. I can
feel a global-search-and-replace coming on.

So if we go with that terminology, perhaps these names for the
new CREATE AGGREGATE parameters:

initfunc applies to plain aggregation, mutually exclusive with initcond
msfunc (or just mfunc?) forward transition for moving-agg mode
mifunc inverse transition for moving-agg mode
mstype state datatype for moving-agg mode
msspace space estimate for mstype
mfinalfunc final function for moving-agg mode
minitfunc "firsttrans" for moving-agg mode
minitcond mutually exclusive with minitfunc

Yeah, those work for me.

I think I prefer "mfunc" to "msfunc", but perhaps that's just my
natural aversion to the "ms" prefix :-)

Also, perhaps "minvfunc" rather than "mifunc" because "i" by itself
could mean "initial".

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#198Florian Pflug
fgp@phlo.org
In reply to: Florian Pflug (#187)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr10, 2014, at 02:13 , Florian Pflug <fgp@phlo.org> wrote:

On Apr9, 2014, at 23:17 , Florian Pflug <fgp@phlo.org> wrote:

On Apr9, 2014, at 21:35 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

A quick test says that avg(int4)
is about five percent slower than sum(int4), so that's the kind of hit
we'd be taking on non-windowed aggregations if we do it like this.

That's rather surprising though. AFAICS, there's isn't much difference
between the two transfer functions int4_sum and int4_avg_accum at all.

The differences come down to (+ denoting things which ought to make
int4_avg_accum slower compared to int4_sum, - denoting the opposite)

1. +) int4_avg_accum calls AggCheckCallContext
2. -) int4_sum checks if the state is NULL (it never is for int4_avg_accum)
3. +) int4_avg_accum uses ARR_HASNULL, ARR_SIZE, ARR_OVERHEAD_NONULLS
to verify that the state is a 2-element array without NULL entries
4. -) int4_sum checks if the input is NULL

I've done a bit of profiling on this (using Instruments.app on OSX). One
thing I missed is that inv4_avg_accum also calls pg_detoast_datum - that
calls comes from the PG_GETARG_ARRAYTYPE_P macro. Doing that is a bit silly,
since we know that the datum cannot possibly be toasted I think (or if it
was, nodeAgg.c should do the de-toasting).

The profile also attributes a rather large percent of the total runtime of
int4_avg_accum (around 30%) to the call of AggCheckCallContext(). Getting
rid of that would help quite a few transition functions, invertible or not.
That certainly seems doable - we'd need a way to mark functions as internal
support functions, and prevent user-initiated calls of such functions.
Transition functions marked that way could then safely scribble over their
state arguments without having to consult AggCheckCallContext() first.

...

However, I still believe the best approach at this point is to just work
on making int4_avg_accum faster. I still see no principal reason what it
has to be noticeably slower - the only additional work it absolutely *has*
to perform is *one* 64-bit increment.

I played with this a bit.

Currently, int4_avg_accum invokes AggCheckCallContext every time, and also
repeatedly checks whether the array has the required dimension - which,
incidentally, is the only big difference between int4_avg_accum and int4_sum.

To avoid that, I added a flags field to fcinfo which nodeAgg uses to tell
transition functions whether they're called for the first time, or are being
called with whatever state they themselves returned last time, i.e the
n-th time.

If the n-th time flag is set, int4_avg_accum simply retrieves the state with
PG_GETARG_DATUM() instead of PG_GETARG_ARRAYTYPE_P(), relying on the fact
that it never returns toasted datums itself, and also doesn't bother to validate
the array size, for the same reason.

If the flag is not set, it uses PG_GETARG_ARRAYTYPE_COPY_P and does validate
the array size. In theory, it could further distinguish between a 1st call
in an aggregation context (where the copy is unnecessary), and a user-initiated
call (i.e. outside an aggregation). But that seems unnecessary - one additional
copy per aggregation group seems unlikely to be a problem.

With this in place, instruction-level profiling using Apple's Instruments.app
shows that int4_avg_accum takes about 1.5% of the total runtime of a simple
aggregation, while int4_sum takes about 1.2%.

A (very rough) patch is attached.

I haven't been able to repeat Tom's measurement which shows a 5% difference in
performance between the invertible and the non-invertible versions of SUM(int4),
so I cannot say if this removes that. But the profiling I've done would certainly
indicate it should.

best regards,
Florian Pflug

Attachments:

invtrans_sumint4_opt2.patchapplication/octet-stream; name=invtrans_sumint4_opt2.patchDownload
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 9a7ed93..7643ebb 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** typedef struct AggStatePerGroupData
*** 267,272 ****
--- 267,274 ----
  	bool		transValueIsNull;
  
  	bool		noTransValue;	/* true if transValue not set yet */
+ 	
+ 	char		fcflags;		/* flags to pass to transition function */
  
  	/*
  	 * Note: noTransValue initially has the same value as transValueIsNull,
*************** initialize_aggregates(AggState *aggstate
*** 399,404 ****
--- 401,408 ----
  		 * signals that we still need to do this.
  		 */
  		pergroupstate->noTransValue = peraggstate->initValueIsNull;
+ 		
+ 		pergroupstate->fcflags = FCFlagAggTransition1st;
  	}
  }
  
*************** advance_transition_function(AggState *ag
*** 479,488 ****
--- 483,495 ----
  	fcinfo->arg[0] = pergroupstate->transValue;
  	fcinfo->argnull[0] = pergroupstate->transValueIsNull;
  	fcinfo->isnull = false;		/* just in case transfn doesn't set it */
+ 	fcinfo->flags = pergroupstate->fcflags;
  
  	newVal = FunctionCallInvoke(fcinfo);
  
+ 	pergroupstate->fcflags = FCFlagAggTransitionNth;
  	aggstate->curperagg = NULL;
+ 	
  
  	/*
  	 * If pass-by-ref datatype, must copy the new value into aggcontext and
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2a29124..bfe81f0 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** int4_avg_accum(PG_FUNCTION_ARGS)
*** 3437,3458 ****
  	ArrayType  *transarray;
  	int32		newval = PG_GETARG_INT32(1);
  	Int8TransTypeData *transdata;
! 
! 	/*
! 	 * If we're invoked as an aggregate, we can cheat and modify our first
! 	 * parameter in-place to reduce palloc overhead. Otherwise we need to make
! 	 * a copy of it before scribbling on it.
! 	 */
! 	if (AggCheckCallContext(fcinfo, NULL))
! 		transarray = PG_GETARG_ARRAYTYPE_P(0);
! 	else
  		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
  
! 	if (ARR_HASNULL(transarray) ||
! 		ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
! 		elog(ERROR, "expected 2-element int8 array");
! 
! 	transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
  	transdata->count++;
  	transdata->sum += newval;
  
--- 3437,3455 ----
  	ArrayType  *transarray;
  	int32		newval = PG_GETARG_INT32(1);
  	Int8TransTypeData *transdata;
! 	
! 	if (fcinfo->flags & FCFlagAggTransitionNth)
! 		transarray = (ArrayType *) PG_GETARG_DATUM(0);
! 	else {
  		transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ 		
+ 		if (ARR_HASNULL(transarray) ||
+ 			ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
+ 			elog(ERROR, "expected 2-element int8 array");
+ 	}
  
! 	transdata = (Int8TransTypeData *) (((char *) transarray) +
! 									   ARR_OVERHEAD_NONULLS(1));
  	transdata->count++;
  	transdata->sum += newval;
  
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index c4d4864..72f63d8 100644
*** a/src/include/fmgr.h
--- b/src/include/fmgr.h
*************** typedef struct FunctionCallInfoData
*** 74,79 ****
--- 74,80 ----
  	fmNodePtr	resultinfo;		/* pass or return extra info about result */
  	Oid			fncollation;	/* collation for function to use */
  	bool		isnull;			/* function must set true if result is NULL */
+ 	char		flags;			/* various flags about the type of call */
  	short		nargs;			/* # arguments actually passed */
  	Datum		arg[FUNC_MAX_ARGS];		/* Arguments passed to function */
  	bool		argnull[FUNC_MAX_ARGS]; /* T if arg[i] is actually NULL */
*************** extern void fmgr_info_cxt(Oid functionId
*** 103,108 ****
--- 104,112 ----
  extern void fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo,
  			   MemoryContext destcxt);
  
+ #define FCFlagAggTransition1st 1
+ #define FCFlagAggTransitionNth 2
+ 
  /*
   * This macro initializes all the fields of a FunctionCallInfoData except
   * for the arg[] and argnull[] arrays.	Performance testing has shown that
*************** extern void fmgr_info_copy(FmgrInfo *dst
*** 117,122 ****
--- 121,127 ----
  		(Fcinfo).resultinfo = (Resultinfo); \
  		(Fcinfo).fncollation = (Collation); \
  		(Fcinfo).isnull = false; \
+ 		(Fcinfo).flags = 0; \
  		(Fcinfo).nargs = (Nargs); \
  	} while (0)
  
#199Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#193)
1 attachment(s)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

I was imagining that firsttrans would only be passed the first value
to be aggregated, not any previous state, and that it would be illegal
to specify both an initcond and a firsttrans function.

The forward transition function would only be called for values after
the first, by which point the state would be non-null, and so it could
be made strict in most cases. The same would apply to the invertible
transition functions, so they wouldn't have to do null counting, which
in turn would make their state types simpler.

I put together a very fast proof-of-concept patch for this (attached).
It has a valid execution path for an aggregate with initfunc, but I didn't
bother writing the CREATE AGGREGATE support yet. I made sum(int4) work
as you suggest, marking the transfn strict and ripping out int4_sum's
internal support for null inputs. The result seems to be about a 4% or so
improvement in the overall aggregation speed, for a simple "SELECT
sum(int4col) FROM table" query. So from a performance standpoint this
seems only marginally worth doing. The real problem is not that 4% isn't
worth the trouble, it's that AFAICS the only built-in aggregates that
can benefit are sum(int2) and sum(int4). So that looks like a rather
narrow use-case.

You had suggested upthread that we could use this idea to make the
transition functions strict for aggregates using "internal" transition
datatypes, but that does not work because the initfunc would violate
the safety rule that a function returning internal must take at least
one internal-type argument. That puts a pretty strong damper on the
usefulness of the approach, given how many internal-transtype aggregates
we have (and the moving-aggregate case is not going to be better is it?)

So at this point I'm feeling unexcited about the initfunc idea.
Unless it does something really good for the moving-aggregate case,
I think we should drop it.

regards, tom lane

Attachments:

aggregate-initfunc-prototype.patchtext/x-diff; charset=us-ascii; name=aggregate-initfunc-prototype.patchDownload
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index fe6dc8a..1ca5c8f 100644
*** a/src/backend/catalog/pg_aggregate.c
--- b/src/backend/catalog/pg_aggregate.c
*************** AggregateCreate(const char *aggName,
*** 390,395 ****
--- 390,396 ----
  	values[Anum_pg_aggregate_aggfnoid - 1] = ObjectIdGetDatum(procOid);
  	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
  	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
+ 	values[Anum_pg_aggregate_agginitfn - 1] = ObjectIdGetDatum(InvalidOid);
  	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
  	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
  	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7e4bca5..2671c49 100644
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** typedef struct AggStatePerAggData
*** 152,157 ****
--- 152,158 ----
  	int			numTransInputs;
  
  	/* Oids of transfer functions */
+ 	Oid			initfn_oid;		/* may be InvalidOid */
  	Oid			transfn_oid;
  	Oid			finalfn_oid;	/* may be InvalidOid */
  
*************** typedef struct AggStatePerAggData
*** 160,165 ****
--- 161,167 ----
  	 * corresponding oid is not InvalidOid.  Note in particular that fn_strict
  	 * flags are kept here.
  	 */
+ 	FmgrInfo	initfn;
  	FmgrInfo	transfn;
  	FmgrInfo	finalfn;
  
*************** typedef struct AggHashEntryData
*** 296,301 ****
--- 298,306 ----
  static void initialize_aggregates(AggState *aggstate,
  					  AggStatePerAgg peragg,
  					  AggStatePerGroup pergroup);
+ static void initialize_transition_value(AggState *aggstate,
+ 							AggStatePerAgg peraggstate,
+ 							AggStatePerGroup pergroupstate);
  static void advance_transition_function(AggState *aggstate,
  							AggStatePerAgg peraggstate,
  							AggStatePerGroup pergroupstate);
*************** initialize_aggregates(AggState *aggstate
*** 403,408 ****
--- 408,498 ----
  }
  
  /*
+  * Initialize the transition value upon reaching the first non-NULL input(s).
+  *
+  * We use this code when the initcond is NULL and the transfn is strict,
+  * which otherwise would mean the transition value can never become non-null.
+  * If an initfn has been provided, call it on the current input value(s);
+  * otherwise, take the current input value as the new transition value.
+  * (In the latter case, we already checked that this is okay datatype-wise.)
+  */
+ static void
+ initialize_transition_value(AggState *aggstate,
+ 							AggStatePerAgg peraggstate,
+ 							AggStatePerGroup pergroupstate)
+ {
+ 	FunctionCallInfo tfcinfo = &peraggstate->transfn_fcinfo;
+ 	MemoryContext oldContext;
+ 
+ 	if (OidIsValid(peraggstate->initfn_oid))
+ 	{
+ 		FunctionCallInfoData fcinfo;
+ 		int			numInitArgs;
+ 		Datum		newVal;
+ 
+ 		/* We run the transition functions in per-input-tuple memory context */
+ 		oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ 
+ 		/*
+ 		 * Set up an fcinfo for calling the initfn.  It takes one fewer
+ 		 * argument than the transfn: the old transValue is not passed, since
+ 		 * by definition it's NULL at this point.  We know that the other
+ 		 * transfn arguments are all non-null, so no need to check.
+ 		 */
+ 		numInitArgs = peraggstate->numTransInputs;
+ 		InitFunctionCallInfoData(fcinfo, &(peraggstate->initfn),
+ 								 numInitArgs,
+ 								 peraggstate->aggCollation,
+ 								 (void *) aggstate, NULL);
+ 		/* Skip tfcinfo->arg[0], which would be the transValue */
+ 		memcpy(fcinfo.arg, tfcinfo->arg + 1, numInitArgs * sizeof(Datum));
+ 		memset(fcinfo.argnull, 0, numInitArgs * sizeof(bool));
+ 
+ 		/* set up aggstate->curperagg for AggGetAggref() */
+ 		aggstate->curperagg = peraggstate;
+ 
+ 		newVal = FunctionCallInvoke(&fcinfo);
+ 
+ 		aggstate->curperagg = NULL;
+ 
+ 		/*
+ 		 * If pass-by-ref datatype, must copy the new value into aggcontext.
+ 		 * Needn't pfree the prior transValue, since it's NULL.
+ 		 */
+ 		if (!peraggstate->transtypeByVal && !fcinfo.isnull)
+ 		{
+ 			MemoryContextSwitchTo(aggstate->aggcontext);
+ 			newVal = datumCopy(newVal,
+ 							   peraggstate->transtypeByVal,
+ 							   peraggstate->transtypeLen);
+ 		}
+ 
+ 		pergroupstate->transValue = newVal;
+ 		pergroupstate->transValueIsNull = fcinfo.isnull;
+ 
+ 		MemoryContextSwitchTo(oldContext);
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * No initfn, so use the current input value as the new transValue.
+ 		 *
+ 		 * We must copy the datum into aggcontext if it is pass-by-ref. We do
+ 		 * not need to pfree the old transValue, since it's NULL.
+ 		 */
+ 		oldContext = MemoryContextSwitchTo(aggstate->aggcontext);
+ 		pergroupstate->transValue = datumCopy(tfcinfo->arg[1],
+ 											  peraggstate->transtypeByVal,
+ 											  peraggstate->transtypeLen);
+ 		pergroupstate->transValueIsNull = false;
+ 		MemoryContextSwitchTo(oldContext);
+ 	}
+ 
+ 	/* Reset state so we won't do this again in the current group */
+ 	pergroupstate->noTransValue = false;
+ }
+ 
+ /*
   * Given new input value(s), advance the transition function of an aggregate.
   *
   * The new values (and null flags) have been preloaded into argument positions
*************** advance_transition_function(AggState *ag
*** 438,458 ****
  		if (pergroupstate->noTransValue)
  		{
  			/*
! 			 * transValue has not been initialized. This is the first non-NULL
! 			 * input value. We use it as the initial value for transValue. (We
! 			 * already checked that the agg's input type is binary-compatible
! 			 * with its transtype, so straight copy here is OK.)
! 			 *
! 			 * We must copy the datum into aggcontext if it is pass-by-ref. We
! 			 * do not need to pfree the old transValue, since it's NULL.
  			 */
! 			oldContext = MemoryContextSwitchTo(aggstate->aggcontext);
! 			pergroupstate->transValue = datumCopy(fcinfo->arg[1],
! 												  peraggstate->transtypeByVal,
! 												  peraggstate->transtypeLen);
! 			pergroupstate->transValueIsNull = false;
! 			pergroupstate->noTransValue = false;
! 			MemoryContextSwitchTo(oldContext);
  			return;
  		}
  		if (pergroupstate->transValueIsNull)
--- 528,538 ----
  		if (pergroupstate->noTransValue)
  		{
  			/*
! 			 * We had a NULL initcond, and this is the first non-NULL input
! 			 * value(s).  Perform special initialization, using the initfn if
! 			 * any.
  			 */
! 			initialize_transition_value(aggstate, peraggstate, pergroupstate);
  			return;
  		}
  		if (pergroupstate->transValueIsNull)
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1676,1682 ****
  		Form_pg_aggregate aggform;
  		Oid			aggtranstype;
  		AclResult	aclresult;
! 		Oid			transfn_oid,
  					finalfn_oid;
  		Expr	   *transfnexpr,
  				   *finalfnexpr;
--- 1756,1763 ----
  		Form_pg_aggregate aggform;
  		Oid			aggtranstype;
  		AclResult	aclresult;
! 		Oid			initfn_oid,
! 					transfn_oid,
  					finalfn_oid;
  		Expr	   *transfnexpr,
  				   *finalfnexpr;
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1728,1733 ****
--- 1809,1815 ----
  						   get_func_name(aggref->aggfnoid));
  		InvokeFunctionExecuteHook(aggref->aggfnoid);
  
+ 		peraggstate->initfn_oid = initfn_oid = aggform->agginitfn;
  		peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
  		peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
  
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1802,1808 ****
  								&transfnexpr,
  								&finalfnexpr);
  
! 		/* set up infrastructure for calling the transfn and finalfn */
  		fmgr_info(transfn_oid, &peraggstate->transfn);
  		fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
--- 1884,1896 ----
  								&transfnexpr,
  								&finalfnexpr);
  
! 		/* set up infrastructure for calling the aggregate's functions */
! 		if (OidIsValid(initfn_oid))
! 		{
! 			fmgr_info(initfn_oid, &peraggstate->initfn);
! 			// fmgr_info_set_expr((Node *) initfnexpr, &peraggstate->initfn);
! 		}
! 
  		fmgr_info(transfn_oid, &peraggstate->transfn);
  		fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
  
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1849,1855 ****
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
  		{
  			if (numArguments <= numDirectArgs ||
  				!IsBinaryCoercible(inputTypes[numDirectArgs], aggtranstype))
--- 1937,1944 ----
  		 * transValue.	This should have been checked at agg definition time,
  		 * but just in case...
  		 */
! 		if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull &&
! 			!OidIsValid(initfn_oid))
  		{
  			if (numArguments <= numDirectArgs ||
  				!IsBinaryCoercible(inputTypes[numDirectArgs], aggtranstype))
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 64eb0f8..cc2a55d 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** int4_sum(PG_FUNCTION_ARGS)
*** 3021,3036 ****
  {
  	int64		newval;
  
- 	if (PG_ARGISNULL(0))
- 	{
- 		/* No non-null input seen so far... */
- 		if (PG_ARGISNULL(1))
- 			PG_RETURN_NULL();	/* still no non-null */
- 		/* This is the first non-null input. */
- 		newval = (int64) PG_GETARG_INT32(1);
- 		PG_RETURN_INT64(newval);
- 	}
- 
  	/*
  	 * If we're invoked as an aggregate, we can cheat and modify our first
  	 * parameter in-place to avoid palloc overhead. If not, we need to return
--- 3021,3026 ----
*************** int4_sum(PG_FUNCTION_ARGS)
*** 3043,3051 ****
  	{
  		int64	   *oldsum = (int64 *) PG_GETARG_POINTER(0);
  
! 		/* Leave the running sum unchanged in the new input is null */
! 		if (!PG_ARGISNULL(1))
! 			*oldsum = *oldsum + (int64) PG_GETARG_INT32(1);
  
  		PG_RETURN_POINTER(oldsum);
  	}
--- 3033,3039 ----
  	{
  		int64	   *oldsum = (int64 *) PG_GETARG_POINTER(0);
  
! 		*oldsum = *oldsum + (int64) PG_GETARG_INT32(1);
  
  		PG_RETURN_POINTER(oldsum);
  	}
*************** int4_sum(PG_FUNCTION_ARGS)
*** 3054,3064 ****
  	{
  		int64		oldsum = PG_GETARG_INT64(0);
  
- 		/* Leave sum unchanged if new input is null. */
- 		if (PG_ARGISNULL(1))
- 			PG_RETURN_INT64(oldsum);
- 
- 		/* OK to do the addition. */
  		newval = oldsum + (int64) PG_GETARG_INT32(1);
  
  		PG_RETURN_INT64(newval);
--- 3042,3047 ----
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index f189998..79d327c 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 30,35 ****
--- 30,36 ----
   *	aggfnoid			pg_proc OID of the aggregate itself
   *	aggkind				aggregate kind, see AGGKIND_ categories below
   *	aggnumdirectargs	number of arguments that are "direct" arguments
+  *	agginitfn			first-transition function (0 if none)
   *	aggtransfn			transition function
   *	aggfinalfn			final function (0 if none)
   *	aggsortop			associated sort operator (0 if none)
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 45,50 ****
--- 46,52 ----
  	regproc		aggfnoid;
  	char		aggkind;
  	int16		aggnumdirectargs;
+ 	regproc		agginitfn;
  	regproc		aggtransfn;
  	regproc		aggfinalfn;
  	Oid			aggsortop;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 68,83 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					9
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
! #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggsortop			6
! #define Anum_pg_aggregate_aggtranstype		7
! #define Anum_pg_aggregate_aggtransspace		8
! #define Anum_pg_aggregate_agginitval		9
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
--- 70,86 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					10
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
! #define Anum_pg_aggregate_agginitfn			4
! #define Anum_pg_aggregate_aggtransfn		5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggsortop			7
! #define Anum_pg_aggregate_aggtranstype		8
! #define Anum_pg_aggregate_aggtransspace		9
! #define Anum_pg_aggregate_agginitval		10
  
  /*
   * Symbolic values for aggkind column.	We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 101,277 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 int4_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 int2_sum		-				0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 float4pl		-				0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 float8pl		-				0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 cash_pl			-				0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 interval_pl		-				0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				0		20		0	"0" ));
! DATA(insert ( 2803	n 0 int8inc			-				0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8		-				0	20		0	"0" ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-			58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-			59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-			58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-					0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 int2or		-					0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 int4and		-					0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 int4or		-					0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 int8and		-					0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 int8or		-					0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 bitand		-					0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 bitor		-					0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-					0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn	0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn	json_object_agg_finalfn	0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
--- 104,280 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 -		int8_avg_accum	numeric_avg		0	2281	128 _null_ ));
! DATA(insert ( 2101	n 0 -		int4_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2102	n 0 -		int2_avg_accum	int8_avg		0	1016	0	"{0,0}" ));
! DATA(insert ( 2103	n 0 -		numeric_avg_accum	numeric_avg 0	2281	128 _null_ ));
! DATA(insert ( 2104	n 0 -		float4_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2105	n 0 -		float8_accum	float8_avg		0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2106	n 0 -		interval_accum	interval_avg	0	1187	0	"{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 -		int8_avg_accum	numeric_sum		0	2281	128 _null_ ));
! DATA(insert ( 2108	n 0 481		int4_sum		-			0	20		0	_null_ ));
! DATA(insert ( 2109	n 0 -		int2_sum		-			0	20		0	_null_ ));
! DATA(insert ( 2110	n 0 -		float4pl		-			0	700		0	_null_ ));
! DATA(insert ( 2111	n 0 -		float8pl		-			0	701		0	_null_ ));
! DATA(insert ( 2112	n 0 -		cash_pl			-			0	790		0	_null_ ));
! DATA(insert ( 2113	n 0 -		interval_pl		-			0	1186	0	_null_ ));
! DATA(insert ( 2114	n 0 -		numeric_avg_accum	numeric_sum 0	2281	128 _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 -		int8larger		-			413		20		0	_null_ ));
! DATA(insert ( 2116	n 0 -		int4larger		-			521		23		0	_null_ ));
! DATA(insert ( 2117	n 0 -		int2larger		-			520		21		0	_null_ ));
! DATA(insert ( 2118	n 0 -		oidlarger		-			610		26		0	_null_ ));
! DATA(insert ( 2119	n 0 -		float4larger	-			623		700		0	_null_ ));
! DATA(insert ( 2120	n 0 -		float8larger	-			674		701		0	_null_ ));
! DATA(insert ( 2121	n 0 -		int4larger		-			563		702		0	_null_ ));
! DATA(insert ( 2122	n 0 -		date_larger		-			1097	1082	0	_null_ ));
! DATA(insert ( 2123	n 0 -		time_larger		-			1112	1083	0	_null_ ));
! DATA(insert ( 2124	n 0 -		timetz_larger	-			1554	1266	0	_null_ ));
! DATA(insert ( 2125	n 0 -		cashlarger		-			903		790		0	_null_ ));
! DATA(insert ( 2126	n 0 -		timestamp_larger	-		2064	1114	0	_null_ ));
! DATA(insert ( 2127	n 0 -		timestamptz_larger	-		1324	1184	0	_null_ ));
! DATA(insert ( 2128	n 0 -		interval_larger -			1334	1186	0	_null_ ));
! DATA(insert ( 2129	n 0 -		text_larger		-			666		25		0	_null_ ));
! DATA(insert ( 2130	n 0 -		numeric_larger	-			1756	1700	0	_null_ ));
! DATA(insert ( 2050	n 0 -		array_larger	-			1073	2277	0	_null_ ));
! DATA(insert ( 2244	n 0 -		bpchar_larger	-			1060	1042	0	_null_ ));
! DATA(insert ( 2797	n 0 -		tidlarger		-			2800	27		0	_null_ ));
! DATA(insert ( 3526	n 0 -		enum_larger		-			3519	3500	0	_null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 -		int8smaller		-			412		20		0	_null_ ));
! DATA(insert ( 2132	n 0 -		int4smaller		-			97		23		0	_null_ ));
! DATA(insert ( 2133	n 0 -		int2smaller		-			95		21		0	_null_ ));
! DATA(insert ( 2134	n 0 -		oidsmaller		-			609		26		0	_null_ ));
! DATA(insert ( 2135	n 0 -		float4smaller	-			622		700		0	_null_ ));
! DATA(insert ( 2136	n 0 -		float8smaller	-			672		701		0	_null_ ));
! DATA(insert ( 2137	n 0 -		int4smaller		-			562		702		0	_null_ ));
! DATA(insert ( 2138	n 0 -		date_smaller	-			1095	1082	0	_null_ ));
! DATA(insert ( 2139	n 0 -		time_smaller	-			1110	1083	0	_null_ ));
! DATA(insert ( 2140	n 0 -		timetz_smaller	-			1552	1266	0	_null_ ));
! DATA(insert ( 2141	n 0 -		cashsmaller		-			902		790		0	_null_ ));
! DATA(insert ( 2142	n 0 -		timestamp_smaller	-		2062	1114	0	_null_ ));
! DATA(insert ( 2143	n 0 -		timestamptz_smaller -		1322	1184	0	_null_ ));
! DATA(insert ( 2144	n 0 -		interval_smaller	-		1332	1186	0	_null_ ));
! DATA(insert ( 2145	n 0 -		text_smaller	-			664		25		0	_null_ ));
! DATA(insert ( 2146	n 0 -		numeric_smaller -			1754	1700	0	_null_ ));
! DATA(insert ( 2051	n 0 -		array_smaller	-			1072	2277	0	_null_ ));
! DATA(insert ( 2245	n 0 -		bpchar_smaller	-			1058	1042	0	_null_ ));
! DATA(insert ( 2798	n 0 -		tidsmaller		-			2799	27		0	_null_ ));
! DATA(insert ( 3527	n 0 -		enum_smaller	-			3518	3500	0	_null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 -		int8inc_any		-			0		20		0	"0" ));
! DATA(insert ( 2803	n 0 -		int8inc			-			0		20		0	"0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 -		int8_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2719	n 0 -		int4_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2720	n 0 -		int2_accum	numeric_var_pop 0	2281	128 _null_ ));
! DATA(insert ( 2721	n 0 -		float4_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2722	n 0 -		float8_accum	float8_var_pop 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2723	n 0 -		numeric_accum	numeric_var_pop 0	2281	128 _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 -		int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2642	n 0 -		int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2643	n 0 -		int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2644	n 0 -		float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2645	n 0 -		float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2646	n 0 -		numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 -		int8_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2149	n 0 -		int4_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2150	n 0 -		int2_accum	numeric_var_samp	0	2281	128 _null_ ));
! DATA(insert ( 2151	n 0 -		float4_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2152	n 0 -		float8_accum	float8_var_samp 0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2153	n 0 -		numeric_accum	numeric_var_samp 0	2281	128 _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 -		int8_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2725	n 0 -		int4_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2726	n 0 -		int2_accum	numeric_stddev_pop		0	2281	128 _null_ ));
! DATA(insert ( 2727	n 0 -		float4_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2728	n 0 -		float8_accum	float8_stddev_pop	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2729	n 0 -		numeric_accum	numeric_stddev_pop	0	2281	128 _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 -		int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2713	n 0 -		int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2714	n 0 -		int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2715	n 0 -		float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2716	n 0 -		float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2717	n 0 -		numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 -		int8_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2155	n 0 -		int4_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2156	n 0 -		int2_accum	numeric_stddev_samp		0	2281	128 _null_ ));
! DATA(insert ( 2157	n 0 -		float4_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2158	n 0 -		float8_accum	float8_stddev_samp	0	1022	0	"{0,0,0}" ));
! DATA(insert ( 2159	n 0 -		numeric_accum	numeric_stddev_samp 0	2281	128 _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 -		int8inc_float8_float8		-			0	20		0	"0" ));
! DATA(insert ( 2819	n 0 -		float8_regr_accum	float8_regr_sxx			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2820	n 0 -		float8_regr_accum	float8_regr_syy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2821	n 0 -		float8_regr_accum	float8_regr_sxy			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2822	n 0 -		float8_regr_accum	float8_regr_avgx		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2823	n 0 -		float8_regr_accum	float8_regr_avgy		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2824	n 0 -		float8_regr_accum	float8_regr_r2			0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2825	n 0 -		float8_regr_accum	float8_regr_slope		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2826	n 0 -		float8_regr_accum	float8_regr_intercept	0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2827	n 0 -		float8_regr_accum	float8_covar_pop		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2828	n 0 -		float8_regr_accum	float8_covar_samp		0	1022	0	"{0,0,0,0,0,0}" ));
! DATA(insert ( 2829	n 0 -		float8_regr_accum	float8_corr				0	1022	0	"{0,0,0,0,0,0}" ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 -		booland_statefunc	-		58	16		0	_null_ ));
! DATA(insert ( 2518	n 0 -		boolor_statefunc	-		59	16		0	_null_ ));
! DATA(insert ( 2519	n 0 -		booland_statefunc	-		58	16		0	_null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 -		int2and		-				0	21		0	_null_ ));
! DATA(insert ( 2237	n 0 -		int2or		-				0	21		0	_null_ ));
! DATA(insert ( 2238	n 0 -		int4and		-				0	23		0	_null_ ));
! DATA(insert ( 2239	n 0 -		int4or		-				0	23		0	_null_ ));
! DATA(insert ( 2240	n 0 -		int8and		-				0	20		0	_null_ ));
! DATA(insert ( 2241	n 0 -		int8or		-				0	20		0	_null_ ));
! DATA(insert ( 2242	n 0 -		bitand		-				0	1560	0	_null_ ));
! DATA(insert ( 2243	n 0 -		bitor		-				0	1560	0	_null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 -		xmlconcat2	-				0	142		0	_null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 -		array_agg_transfn	array_agg_finalfn	0	2281	0	_null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 -		string_agg_transfn	string_agg_finalfn	0	2281	0	_null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 -		bytea_string_agg_transfn	bytea_string_agg_finalfn	0	2281	0	_null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 -		json_agg_transfn			json_agg_finalfn		0	2281	0	_null_ ));
! DATA(insert ( 3197	n 0 -		json_object_agg_transfn		json_object_agg_finalfn 0	2281	0	_null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 -		ordered_set_transition			percentile_disc_final					0	2281	0	_null_ ));
! DATA(insert ( 3974	o 1 -		ordered_set_transition			percentile_cont_float8_final			0	2281	0	_null_ ));
! DATA(insert ( 3976	o 1 -		ordered_set_transition			percentile_cont_interval_final			0	2281	0	_null_ ));
! DATA(insert ( 3978	o 1 -		ordered_set_transition			percentile_disc_multi_final				0	2281	0	_null_ ));
! DATA(insert ( 3980	o 1 -		ordered_set_transition			percentile_cont_float8_multi_final		0	2281	0	_null_ ));
! DATA(insert ( 3982	o 1 -		ordered_set_transition			percentile_cont_interval_multi_final	0	2281	0	_null_ ));
! DATA(insert ( 3984	o 0 -		ordered_set_transition			mode_final								0	2281	0	_null_ ));
! DATA(insert ( 3986	h 1 -		ordered_set_transition_multi	rank_final								0	2281	0	_null_ ));
! DATA(insert ( 3988	h 1 -		ordered_set_transition_multi	percent_rank_final						0	2281	0	_null_ ));
! DATA(insert ( 3990	h 1 -		ordered_set_transition_multi	cume_dist_final							0	2281	0	_null_ ));
! DATA(insert ( 3992	h 1 -		ordered_set_transition_multi	dense_rank_final						0	2281	0	_null_ ));
  
  
  /*
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 7b9c587..0bcb76d 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 1839 (  numeric_stddev
*** 2445,2451 ****
  DESCR("aggregate final function");
  DATA(insert OID = 1840 (  int2_sum		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 20 "20 21" _null_ _null_ _null_ _null_ int2_sum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
! DATA(insert OID = 1841 (  int4_sum		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 20 "20 23" _null_ _null_ _null_ _null_ int4_sum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1842 (  int8_sum		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1700 "1700 20" _null_ _null_ _null_ _null_ int8_sum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
--- 2445,2451 ----
  DESCR("aggregate final function");
  DATA(insert OID = 1840 (  int2_sum		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 20 "20 21" _null_ _null_ _null_ _null_ int2_sum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
! DATA(insert OID = 1841 (  int4_sum		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 20 "20 23" _null_ _null_ _null_ _null_ int4_sum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
  DATA(insert OID = 1842 (  int8_sum		   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1700 "1700 20" _null_ _null_ _null_ _null_ int8_sum _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
#200Florian Pflug
fgp@phlo.org
In reply to: Dean Rasheed (#197)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr10, 2014, at 21:34 , Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 10 April 2014 19:54, Tom Lane <tgl@sss.pgh.pa.us> wrote:

So if we go with that terminology, perhaps these names for the
new CREATE AGGREGATE parameters:

initfunc applies to plain aggregation, mutually exclusive with initcond
msfunc (or just mfunc?) forward transition for moving-agg mode
mifunc inverse transition for moving-agg mode
mstype state datatype for moving-agg mode
msspace space estimate for mstype
mfinalfunc final function for moving-agg mode
minitfunc "firsttrans" for moving-agg mode
minitcond mutually exclusive with minitfunc

I think I prefer "mfunc" to "msfunc", but perhaps that's just my
natural aversion to the "ms" prefix :-)

Also, perhaps "minvfunc" rather than "mifunc" because "i" by itself
could mean "initial".

I still think you're getting ahead of yourselves here. The number of
aggregates which benefit from this is tiny SUM(int2,int4) and maybe
BOOL_{AND,OR}. And in the SUM(int2,int4) case *only* on 64-bit archs -
for the others, the state type is already pass-by-ref.

I don't think we should be additional that much additional machinery
until it has been conclusively demonstrated that the performance
regression cannot be fixed any other way. Which, quite frankly, I
don't believe. Nothing in int4_avg_accum looks particularly expensive,
and the things that *do* seem to cost something certainly can be made
cheaper. c.f. the patch I just posted.

Another reason I'm so opposed to this is that inverse transition
functions might not be the last kind of transition functions we ever
add. For example, if we ever get ROLLUP/CUBE, we might want to have
a mergefunc which takes two aggregation states and combines them
into one. What do we do if we add those? Add yet a another set of
"mergable" transition functions? What about the various combinations
of invertible/non-invertible mergable/non-mergable that could result?
The opportunity cost seems pretty high here...

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#201Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#200)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

I still think you're getting ahead of yourselves here. The number of
aggregates which benefit from this is tiny SUM(int2,int4) and maybe
BOOL_{AND,OR}. And in the SUM(int2,int4) case *only* on 64-bit archs -
for the others, the state type is already pass-by-ref.

That argument is reasonable for the initfunc idea, but it doesn't apply
otherwise.

Another reason I'm so opposed to this is that inverse transition
functions might not be the last kind of transition functions we ever
add. For example, if we ever get ROLLUP/CUBE, we might want to have
a mergefunc which takes two aggregation states and combines them
into one. What do we do if we add those?

Make more pg_aggregate columns. What exactly do you think is either
easier or harder about such cases? Also, maybe I'm misremembering
the spec, but ROLLUP/CUBE wouldn't apply to window functions would
they? So if your argument is based on the assumption that these
features need to combine, I'm not sure it's true.

The bottom line for me is that it seems conceptually far cleaner to
make the moving-aggregate support be independent than to insist that
it share an stype and sfunc with the plain case.

Furthermore, I do not buy the argument that if we hack hard enough,
we can make the performance cost of forcing the sfunc to do double duty
negligible. In the first place, that argument is unsupported by much
evidence, and in the second place, it will certainly cost us complexity
to make the performance issue go away. Instead we can just design the
problem out, for nothing that I see as a serious drawback.

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

#202Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#201)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr11, 2014, at 00:07 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

I still think you're getting ahead of yourselves here. The number of
aggregates which benefit from this is tiny SUM(int2,int4) and maybe
BOOL_{AND,OR}. And in the SUM(int2,int4) case *only* on 64-bit archs -
for the others, the state type is already pass-by-ref.

That argument is reasonable for the initfunc idea, but it doesn't apply
otherwise.

Why not? AFAICS, the increase in cost comes from going from an
by-value to a by-reference state type. Once you're using a by-refence
type, you already pay the overhead of the additional dereferences, and
for calling AggCheckCallContext() or some equivalent.

Another reason I'm so opposed to this is that inverse transition
functions might not be the last kind of transition functions we ever
add. For example, if we ever get ROLLUP/CUBE, we might want to have
a mergefunc which takes two aggregation states and combines them
into one. What do we do if we add those?

Make more pg_aggregate columns. What exactly do you think is either
easier or harder about such cases? Also, maybe I'm misremembering
the spec, but ROLLUP/CUBE wouldn't apply to window functions would
they? So if your argument is based on the assumption that these
features need to combine, I'm not sure it's true.

Well, it was just an example - there might be other future extensions
which *do* need to combine. And we've been known to go beyond the spec
sometimes...

Furthermore, I do not buy the argument that if we hack hard enough,
we can make the performance cost of forcing the sfunc to do double duty
negligible. In the first place, that argument is unsupported by much
evidence, and in the second place, it will certainly cost us complexity
to make the performance issue go away. Instead we can just design the
problem out, for nothing that I see as a serious drawback.

My argument is that is costs us more complexity to duplicate everything
for the invertible case, *and* the result seems less flexible - not
from the POV of aggregate implementations, but from the POV of future
extensions.

As for evidence - have you looked at the patch I posted? I'd be very
interested to know if it removes the performance differences you saw.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#203Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#202)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

My argument is that is costs us more complexity to duplicate everything
for the invertible case, *and* the result seems less flexible - not
from the POV of aggregate implementations, but from the POV of future
extensions.

[ shrug... ] You can argue against any feature whatsoever by claiming
that it might possibly conflict with something we would wish to do
sometime in the future. I think you need to have a much more concrete
argument about specific issues this will cause in order to be convincing.
For all we know about ROLLUP/CUBE implementation issues right now, doing
this feature with separate implementations might make that easier not
harder. (I note that the crux of my complaint right now is that we're
asking sfuncs to serve two masters --- how's it going to be better when
they have to serve three or four?)

As for evidence - have you looked at the patch I posted? I'd be very
interested to know if it removes the performance differences you saw.

(1) You can't really prove the absence of a performance issue by showing
that one specific aggregate doesn't have an issue. (2) These results
(mine as well as yours) seem mighty platform-dependent, so the fact you
don't see an issue doesn't mean someone else won't. (3) A new
FunctionCallInfoData field just for this? Surely not. There's got to be
a distributed cost to that (although I notice you didn't bother
initializing the field most places, which is cheating).

Now point 3 could be addressed by doing the signaling in some other way
with the existing context field. But it's still the case that you're
trying to band-aid a bad design. There's no good reason to make the sfunc
do extra work to be invertible in contexts where we know, with certainty,
that that work is useless. Especially not when we know that even a few
instructions of extra work can be performance-significant.

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

#204Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#203)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr11, 2014, at 01:30 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

As for evidence - have you looked at the patch I posted? I'd be very
interested to know if it removes the performance differences you saw.

(1) You can't really prove the absence of a performance issue by showing
that one specific aggregate doesn't have an issue.

I'm claiming that SUM(int4) is about as simple as it gets, so if the
effect can be mitigated there, it can be mitigated everywhere. The more
complex a forward-only transition function is, the less will and added if
or two hurt.

(2) These results
(mine as well as yours) seem mighty platform-dependent, so the fact you
don't see an issue doesn't mean someone else won't.

Yeah, though *any* change - mine, the one your propose, and any other -
has the potential to hurt some platform due to weird interactions (say,
cache trashing).

(3) A new
FunctionCallInfoData field just for this? Surely not. There's got to be
a distributed cost to that (although I notice you didn't bother
initializing the field most places, which is cheating).

I think the field doesn't actually increase the size of the structure at
all - at least not if the bool before it has size 1 and the short following
it is 2-byte aligned. Or at least that was why I made it a char, and added
it at the place I did. But I might be missing something...

Also, InitFunctionCallInfoData *does* initialize the flags to zero. Though
maybe not everybody uses that - I didn't check, this was just a quick hack.

Now point 3 could be addressed by doing the signaling in some other way
with the existing context field. But it's still the case that you're
trying to band-aid a bad design. There's no good reason to make the sfunc
do extra work to be invertible in contexts where we know, with certainty,
that that work is useless.

This is the principal point where we disagree, I think. From my POV, the
problem isn't invertibility here at all. Heck, SUM(int4) wouldn't need
*any* extra state at all to be invertible, if it weren't for those pesky
issues surrounding NULL handling. In fact, an earlier version of the
invtrans patch *did* use int4_sum as SUM(int4)'s transfer functions! The
only reason this doesn't work nowadays is that Dean didn't like the
forward-nonstrict-but-inverse-strict special case that made this work.

The way I see it, the main problem is the drop in performance that comes
from using a pass-by-ref state type. Which IMHO happens because this
*already* is a heavily band-aided design - all the state validation and
"if (AggCheckCallContext(,NULL))" stuff really works around the fact that
transition functions *aren't* supposed to be user-called, yet they are,
and must deal.

Which is why I feel that having two separate sets of transition functions
and state types solves the wrong problem. If we find a way to prevent
transition functions from being called directly, we'll shave a few cycles
of quite a few existing aggregates, invertible or not. If we find a way
around the initfunc-for-internal-statetype problem you discovered, the
implementation would get much simpler, since we could then make nearly
all of them strict. And this would again shave off a few cycles - for lots
of NULL inputs, the effect could even be large.

Compared to that, the benefit of having a completely separate set of
transition functions and state types for the invertible case is much
less tangible, IMHO.

Especially not when we know that even a few instructions of extra work
can be performance-significant.

But where do we draw that line? nodeWindowAgg.c quite certainly wastes
about as many cycles as int4_avg_accum does on various checks that are
unnecessary unless in the non-sliding window case. Do we duplicate those
functions too?

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#205Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#199)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On 10 April 2014 22:52, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

I was imagining that firsttrans would only be passed the first value
to be aggregated, not any previous state, and that it would be illegal
to specify both an initcond and a firsttrans function.

The forward transition function would only be called for values after
the first, by which point the state would be non-null, and so it could
be made strict in most cases. The same would apply to the invertible
transition functions, so they wouldn't have to do null counting, which
in turn would make their state types simpler.

I put together a very fast proof-of-concept patch for this (attached).
It has a valid execution path for an aggregate with initfunc, but I didn't
bother writing the CREATE AGGREGATE support yet. I made sum(int4) work
as you suggest, marking the transfn strict and ripping out int4_sum's
internal support for null inputs. The result seems to be about a 4% or so
improvement in the overall aggregation speed, for a simple "SELECT
sum(int4col) FROM table" query. So from a performance standpoint this
seems only marginally worth doing. The real problem is not that 4% isn't
worth the trouble, it's that AFAICS the only built-in aggregates that
can benefit are sum(int2) and sum(int4). So that looks like a rather
narrow use-case.

You had suggested upthread that we could use this idea to make the
transition functions strict for aggregates using "internal" transition
datatypes, but that does not work because the initfunc would violate
the safety rule that a function returning internal must take at least
one internal-type argument. That puts a pretty strong damper on the
usefulness of the approach, given how many internal-transtype aggregates
we have (and the moving-aggregate case is not going to be better is it?)

Ah, that's disappointing. If it can't be made to work for aggregates
with internal state types, then I think it loses most of it's value,
and I don't think it will be of much more use in the moving aggregate
case either.

So at this point I'm feeling unexcited about the initfunc idea.
Unless it does something really good for the moving-aggregate case,
I think we should drop it.

Agreed. Thanks for prototyping it though.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#206Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#204)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

Which is why I feel that having two separate sets of transition functions
and state types solves the wrong problem. If we find a way to prevent
transition functions from being called directly, we'll shave a few cycles
of quite a few existing aggregates, invertible or not. If we find a way
around the initfunc-for-internal-statetype problem you discovered, the
implementation would get much simpler, since we could then make nearly
all of them strict. And this would again shave off a few cycles - for lots
of NULL inputs, the effect could even be large.

Since neither of those latter things seems likely to happen, I don't
find this argument convincing at all. Even if they did happen, they
would represent an incremental improvement that would be equally useful
with either one or two sfuncs.

Basically, this comes down to a design judgment call, and my judgment
is differing from yours. In the absence of opinions from others,
I'm going to do it my way.

However, quite independently of how many sfuncs there are, I'm interested
about this:

... SUM(int4) wouldn't need
*any* extra state at all to be invertible, if it weren't for those pesky
issues surrounding NULL handling. In fact, an earlier version of the
invtrans patch *did* use int4_sum as SUM(int4)'s transfer functions! The
only reason this doesn't work nowadays is that Dean didn't like the
forward-nonstrict-but-inverse-strict special case that made this work.

Tell me about the special case here again? Should we revisit the issue?

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

#207Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#206)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr11, 2014, at 17:09 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Basically, this comes down to a design judgment call, and my judgment
is differing from yours. In the absence of opinions from others,
I'm going to do it my way.

Ok. Are you going to do the necessary changes, or shall I? I'm happy to
leave it to you, but if you lack the time I can try to find some.

... SUM(int4) wouldn't need
*any* extra state at all to be invertible, if it weren't for those pesky
issues surrounding NULL handling. In fact, an earlier version of the
invtrans patch *did* use int4_sum as SUM(int4)'s transfer functions! The
only reason this doesn't work nowadays is that Dean didn't like the
forward-nonstrict-but-inverse-strict special case that made this work.

Tell me about the special case here again? Should we revisit the issue?

My original coding allows the combination of non-strict forward with strict
reverse transition functions. The combination behaved like a strict aggregate
regarding NULL handling - i.e., neither the forward nor the reverse transition
function received NULL inputs. But if the initial state was NULL, the forward
transition function *would* be called with that NULL state value upon seeing
the first non-NULL input. In the window case, the aggregation machinery would
also take care to reset the state type to it's initial value when it removed
the last non-NULL input from the aggregation state (just like it does for
strict aggregates today). This had two advantages

1) First, it allows strict aggregates to use state type internal. As it
stands now, aggregates with state type internal must implement their
own NULL handling, even if they behave exactly like most standard
aggregates do, namely ignore NULLS and return NULL only if there were
no non-NULL inputs.

2) It allows strict aggregates whose state type and input type aren't
binary coercible to return NULL if all inputs were NULL without any
special coding. As it stands today, this doesn't work without some
kind of counter in the state, because the final function otherwise
won't know if there were non-NULL inputs or not. Aggregates whose state
and input types are binary coercible get around that by setting the
initial value to NULL while *still* being strict, and relying on the
state replacement behaviour of the aggregate machinery.

It, however, also has a few disadvantages

3) It means that one needs to look at the inverse transition function's
strictness setting even if that function is never used.

4) It feels a bit hacky.

(2) is what affects SUM(int4). The only reason it track the number of inputs
is to be able to return NULL instead of 0 if no inputs remain.

One idea to fix (3) and (4) was *explicitly* flagging aggregates as
NULL-handling or NULL-ignoring, and also as what one might call
"weakly strict", i.e. as returning NULL exactly if there were no non-NULL
inputs. There are various variations of that theme possible - one flag for
each behaviour, or simply a single "common behaviour" flag. In the end, I
decided not to pursue that, mostly because the aggregates affected by that
issued turned out to be relatively easy to fix. For the ones with state type
internal, I simply added a counter, and I made SUM(int4) use AVG's transition
function.

I don't feel too strongly either way on this one - I initially implemented the
exception because I noticed that the NULL handling of some aggregates was
broken otherwise, and it seemed simpler to fix this in one place than going
over all the aggregates separately. OTOH, when I wrote the docs, I noticed
how hard it was to describe the behaviour accurately, which made me like it
less and less. And Dean wasn't happy with it at all, so that finally settled it.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#208Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#207)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

On Apr11, 2014, at 17:09 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Basically, this comes down to a design judgment call, and my judgment
is differing from yours. In the absence of opinions from others,
I'm going to do it my way.

Ok. Are you going to do the necessary changes, or shall I? I'm happy to
leave it to you, but if you lack the time I can try to find some.

Nah, I'm happy to do the work, since it's me insisting on changing it.

Tell me about the special case here again? Should we revisit the issue?

...
I don't feel too strongly either way on this one - I initially implemented the
exception because I noticed that the NULL handling of some aggregates was
broken otherwise, and it seemed simpler to fix this in one place than going
over all the aggregates separately. OTOH, when I wrote the docs, I noticed
how hard it was to describe the behaviour accurately, which made me like it
less and less. And Dean wasn't happy with it at all, so that finally settled it.

Yeah, if you can't document the design nicely, it's probably not a good
idea :-(. Thanks for the summary.

It strikes me that your second point

2) It allows strict aggregates whose state type and input type aren't
binary coercible to return NULL if all inputs were NULL without any
special coding. As it stands today, this doesn't work without some
kind of counter in the state, because the final function otherwise
won't know if there were non-NULL inputs or not. Aggregates whose state
and input types are binary coercible get around that by setting the
initial value to NULL while *still* being strict, and relying on the
state replacement behaviour of the aggregate machinery.

could be addressed by the initfunc idea, but I'm still not sufficiently
excited by that one. Given the problem with internal-type transition
values, I think this could only win if there are cases where it would let
us use a regular SQL type instead of internal for the transition value;
and I'm not sure that there are many/any such cases.

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

#209Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#208)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr11, 2014, at 19:04 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

It strikes me that your second point

2) It allows strict aggregates whose state type and input type aren't
binary coercible to return NULL if all inputs were NULL without any
special coding. As it stands today, this doesn't work without some
kind of counter in the state, because the final function otherwise
won't know if there were non-NULL inputs or not. Aggregates whose state
and input types are binary coercible get around that by setting the
initial value to NULL while *still* being strict, and relying on the
state replacement behaviour of the aggregate machinery.

could be addressed by the initfunc idea, but I'm still not sufficiently
excited by that one. Given the problem with internal-type transition
values, I think this could only win if there are cases where it would let
us use a regular SQL type instead of internal for the transition value;
and I'm not sure that there are many/any such cases.

Yes, the idea had come up at some point during the review discussion. I
agree that it's only worthwhile if it works for state type internal - though
I think there ought to be a way to allow that. We could for example simply
decree that the initfunc's first parameter must be of type internal if it's
return type is, and then just pass NULL for that parameter.

What I like about the initfunc idea is that it also naturally extends to
ordered-set aggregates, I think it'd be very useful for some possible
ordered-set aggregates to received their direct arguments beforehand and not
afterwards.

But that all seems largely orthogonal to the invtrans patch.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#210Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#197)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 10 April 2014 19:54, Tom Lane <tgl@sss.pgh.pa.us> wrote:

So if we go with that terminology, perhaps these names for the
new CREATE AGGREGATE parameters:

initfunc applies to plain aggregation, mutually exclusive with initcond
msfunc (or just mfunc?) forward transition for moving-agg mode
mifunc inverse transition for moving-agg mode
mstype state datatype for moving-agg mode
msspace space estimate for mstype
mfinalfunc final function for moving-agg mode
minitfunc "firsttrans" for moving-agg mode
minitcond mutually exclusive with minitfunc

Yeah, those work for me.

I think I prefer "mfunc" to "msfunc", but perhaps that's just my
natural aversion to the "ms" prefix :-)

Meh. We've got mstype, and I don't think leaving out the "s" there
feels right.

Also, perhaps "minvfunc" rather than "mifunc" because "i" by itself
could mean "initial".

Good point. So with initfuncs out of the picture, we have
new CREATE AGGREGATE parameter names

msfunc forward transition for moving-agg mode
minvfunc inverse transition for moving-agg mode
mfinalfunc final function for moving-agg mode
mstype state datatype for moving-agg mode
msspace space estimate for mstype
minitcond initial state value for moving-agg mode

and new pg_aggregate columns

aggmtransfn | regproc | not null
aggminvtransfn | regproc | not null
aggmfinalfn | regproc | not null
aggmtranstype | oid | not null
aggmtransspace | integer | not null
aggminitval | text |

It's a bit unfortunate that the catalog column names aren't quite on
the same page as CREATE AGGREGATE, but it doesn't seem like a good
idea to try to fix that now.

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

#211Tom Lane
tgl@sss.pgh.pa.us
In reply to: Florian Pflug (#209)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Florian Pflug <fgp@phlo.org> writes:

Yes, the idea had come up at some point during the review discussion. I
agree that it's only worthwhile if it works for state type internal - though
I think there ought to be a way to allow that. We could for example simply
decree that the initfunc's first parameter must be of type internal if it's
return type is, and then just pass NULL for that parameter.

I had thought about that, but it doesn't really work since it'd be
violating the strictness spec of the function.

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

#212Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#211)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr11, 2014, at 19:42 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Florian Pflug <fgp@phlo.org> writes:

Yes, the idea had come up at some point during the review discussion. I
agree that it's only worthwhile if it works for state type internal - though
I think there ought to be a way to allow that. We could for example simply
decree that the initfunc's first parameter must be of type internal if it's
return type is, and then just pass NULL for that parameter.

I had thought about that, but it doesn't really work since it'd be
violating the strictness spec of the function.

Only if we insist on passing SQL NULL, not if we passed an non-NULL value
that happens to be (char*)0.

Or we could simply require the initfunc to be non-strict in the case of
state type internal.

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#213Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#181)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

OK, I'm marking this ready for committer attention, on the
understanding that that doesn't include the invtrans_minmax patch.

I've finished reviewing/revising/committing this submission.
Some notes:

* I left out the EXPLAIN ANALYZE statistics, as I still feel that they're
of little if any use to regular users, and neither very well defined nor
well documented. We can revisit that later of course.

* I also left out the table documenting which aggregates have this
optimization. That's not the kind of thing we ordinarily document,
and it seems inevitable to me that such a table would be noteworthy
mostly for wrong/incomplete/obsolete information in the future.

* I rejected the invtrans_collecting sub-patch altogether. I didn't
like anything about the idea of adding a poorly-documented field to
ArrayBuildState and then teaching some random subset of the functions
using that struct what to do with it. I think it would've been simpler,
more reliable, and not that much less efficient to just do memmove's in
shiftArrayResult. The string-aggregate changes were at least more
localized, but they still seemed awfully messy. And there's a bigger
issue: these aggregates have to do O(N) work per row for a frame of length
N anyway, so it's not clear to me that there's any big-O gain to be had
from making them into moving aggregates. I doubt people are going to be
using these aggregates with very wide frames, just because they're going
to be horribly expensive no matter what we do.

Anyway, this is nice forward progress for 9.4, even if we get no further.

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

#214Florian Pflug
fgp@phlo.org
In reply to: Tom Lane (#213)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Apr13, 2014, at 20:23 , Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

OK, I'm marking this ready for committer attention, on the
understanding that that doesn't include the invtrans_minmax patch.

I've finished reviewing/revising/committing this submission.

Cool! Thanks!

Some notes:

* I left out the EXPLAIN ANALYZE statistics, as I still feel that they're
of little if any use to regular users, and neither very well defined nor
well documented. We can revisit that later of course.

* I also left out the table documenting which aggregates have this
optimization. That's not the kind of thing we ordinarily document,
and it seems inevitable to me that such a table would be noteworthy
mostly for wrong/incomplete/obsolete information in the future.

I can live with each leaving each of these out individually, but leaving
them both out means there's now no way of determining how a particular
sliding window aggregation will behave. That leaves our users a bit
out in the cold IMHO.

For example, with this patch you'd usually want to write
SUM(numeric_value::numeric(n,m))
instead of
SUM(numeric_value)::numeric(n,m)
in a sliding window aggregation, and there should be *some* way for
users to figure this out.

We have exhaustive tables of all the functions, operators and aggregates
we support, and maintenance of those seems to work well for the most
part. Why would a table of invertible aggregates be any different? If
it's the non-locality that bothers you - I guess the same information
could be added to the descriptions of the individual aggregates, instead
of having one big table.

* I rejected the invtrans_collecting sub-patch altogether. I didn't
like anything about the idea of adding a poorly-documented field to
ArrayBuildState and then teaching some random subset of the functions
using that struct what to do with it. I think it would've been simpler,
more reliable, and not that much less efficient to just do memmove's in
shiftArrayResult. The string-aggregate changes were at least more
localized, but they still seemed awfully messy. And there's a bigger
issue: these aggregates have to do O(N) work per row for a frame of length
N anyway, so it's not clear to me that there's any big-O gain to be had
from making them into moving aggregates.

Yeah, those probably aren't super-usefull. I initially added array_agg
because with the nonstrict-forward/strict-reverse rule still in place,
there wouldn't otherwise have been an in-core aggregate which exercises
the non-strict case, which seemed like a bad idea. For the string case -
I didn't expect that to turn out to be *quite* this messy when I started
implementing it.

Anyway, this is nice forward progress for 9.4, even if we get no further.

Yup! Thanks to everybody who made this happens!

best regards,
Florian Pflug

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#215David Rowley
dgrowley@gmail.com
In reply to: Tom Lane (#213)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

-----Original Message-----
From: Tom Lane [mailto:tgl@sss.pgh.pa.us]
Sent: 14 April 2014 06:23
To: Dean Rasheed
Cc: Florian Pflug; David Rowley; Kevin Grittner; Josh Berkus; Greg Stark;
PostgreSQL-development
Subject: Re: [HACKERS] [PATCH] Negative Transition Aggregate Functions
(WIP)

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

OK, I'm marking this ready for committer attention, on the
understanding that that doesn't include the invtrans_minmax patch.

I've finished reviewing/revising/committing this submission.

That's great news!
Tom, Thanks for taking the time to make the all modifications and commit
this.

Dean, Thank you for all your hard work reviewing the patch. The detail of
the review was quite impressive.

I'm really happy to see this make it in for 9.4.

Regards

David Rowley

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#216Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#213)
Re: [PATCH] Negative Transition Aggregate Functions (WIP)

On Sun, Apr 13, 2014 at 2:23 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

* I also left out the table documenting which aggregates have this
optimization. That's not the kind of thing we ordinarily document,
and it seems inevitable to me that such a table would be noteworthy
mostly for wrong/incomplete/obsolete information in the future.

I tend to think that not documenting such things is an error. Sure,
the documentation could become obsolete, but I don't see why it's
particularly more likely with this table than anywhere else, and if it
does happen, and people care, they'll submit patches to fix it. More
to the point, when we don't document things like this, it doesn't
cause the knowledge not to be important to end-users; it just means
that the knowledge lives on wiki pages, support fora, and the minds of
people who are "in the know" rather than being easily and generally
accessible. I'd rather have documentation on this topic that was,
say, 80% correct than have none at all.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers