some aspects of our qsort might not be ideal
While reviewing Thomas Munro's patchset to consider expanding the uses
of specialized qsort [1]/messages/by-id/CA+hUKGKztHEWm676csTFjYzortziWmOcf8HDss2Zr0muZ2xfEg@mail.gmail.com, I wondered about some aspects of the current
qsort implementation. For background, ours is based on Bentley &
McIlroy "Engineering a Sort Function" [2]https://cs.fit.edu/~pkc/classes/writing/samples/bentley93engineering.pdf, which is a classic paper
worth studying.
1) the check for pre-sorted input at the start each and every recursion
This has been discussed before [3]/messages/by-id/87F42982BF2B434F831FCEF4C45FC33E5BD369DF@EXCHANGE.corporate.connx.com. This is great if the input is
sorted, and mostly harmless if the current partition is badly sorted
enough that this check fails quickly, but it's not hard to imagine
that this is mostly a waste of cycles. There have been proposals to
base the pre-sorted check on input size [4]/messages/by-id/CA+TgmobKFcb6ajVEN8eSnBa78sB3FSwuEWTHXd2x9JC5DOh5OA@mail.gmail.com or to do it only once at
the beginning, but that strikes me as looking for the keys under the
lamppost because that's where the light is. The logical criterion for
proceeding to check if the input is sorted is whether we *think* the
input could be sorted. That may sound like a tautology, but consider
the case where the partitioning phase didn't perform any swaps. Then,
it makes sense to check, but we can go further. What if we do the
check, but towards the end that check fails. If just a couple elements
are out of place, does it really make sense to give up, partition, and
recurse? If just a few iterations of insertion sort are all that is
needed to finish, why not just do that? This way, we *dynamically*
decide to optimistically start an insertion sort, in the hopes that it
will occasionally prevent a recursion, and worst case the input is
slightly more sorted for the next recursion. All we need is a
lightweight way to detect that the partitioning phase didn't do any
swaps. More on this later.
2) cardinality issues can cancel abbreviated keys, but our qsort is
not optimized for that
Since in this case comparison is very expensive, it stands to reason
that qsort could profitably be optimized for a low number of unique
keys. The Bentley & McIlroy scheme does take great pains to prevent
quadratic behavior from lots of duplicates, but I've found something
that might have stronger guarantees: Pattern-defeating quicksort
(paper: [5]https://arxiv.org/pdf/2106.05123.pdf C++ implementation: [6]https://github.com/orlp/pdqsort) claims worst-case runtime of
O(nk) for inputs with k distinct elements. This is achieved via an
asymmetric partitioning scheme. It's not complex, but it is subtle, so
I won't try to describe it here. I recommend reading section 3 of the
paper for details. Rust and Boost use PDQuicksort in some form, so it
has some production use.
The partitioning scheme just mentioned also provides an easy way to
detect when no swaps have been done, thus solving #1 above.
There is one requirement that is a double-edged sword: Recursion to
the partitions must happen in order. This has an additional advantage
that in every case but one, insertion sort doesn't need to guard
against running off the beginning of the array. The downside for us is
that we currently recurse to a partition based on its size as a
stack-guarding measure, so that guard would have to be replaced. The
C++ implementation of PDQuicksort falls back to heap sort as a last
resort to bound runtime, but it would be just as effective at guarding
the stack. That's a bit of a snag for making it production-ready, but
not enough to prevent testing the actual qsort part.
Does anyone see a reason not to put in the necessary work to try it out?
[1]: /messages/by-id/CA+hUKGKztHEWm676csTFjYzortziWmOcf8HDss2Zr0muZ2xfEg@mail.gmail.com
[2]: https://cs.fit.edu/~pkc/classes/writing/samples/bentley93engineering.pdf
[3]: /messages/by-id/87F42982BF2B434F831FCEF4C45FC33E5BD369DF@EXCHANGE.corporate.connx.com
[4]: /messages/by-id/CA+TgmobKFcb6ajVEN8eSnBa78sB3FSwuEWTHXd2x9JC5DOh5OA@mail.gmail.com
[5]: https://arxiv.org/pdf/2106.05123.pdf
[6]: https://github.com/orlp/pdqsort
--
John Naylor
EDB: http://www.enterprisedb.com
[3] /messages/by-id/87F42982BF2B434F831FCEF4C45FC33E5BD369DF@EXCHANGE.corporate.connx.com
The top of that thread is where I meant to point to:
/messages/by-id/CAEYLb_Xn4-6f1ofsf2qduf24dDCVHbQidt7JPpdL_RiT1zBJ6A@mail.gmail.com
--
John Naylor
EDB: http://www.enterprisedb.com
This way, we *dynamically*
decide to optimistically start an insertion sort, in the hopes that it
will occasionally prevent a recursion, and worst case the input is
slightly more sorted for the next recursion.
I should mention one detail -- we put a limit on how many iterations
we attempt before we give up and partition/recurse. The author's
implementation chooses 8 for this limit. The paper mentions this
technique in section 5.2, but is not the origin of it.
--
John Naylor
EDB: http://www.enterprisedb.com
On Wed, Feb 16, 2022 at 2:29 AM John Naylor
<john.naylor@enterprisedb.com> wrote:
Does anyone see a reason not to put in the necessary work to try it out?
Seems reasonable to me. It's always a bit difficult, I feel, to know
what test cases to use - almost any idea is going to have some case
where it's worse than what we do today, and there can always be some
user who does that exact thing 100% of the time. Moreover, it's hard
to be certain that test cases we construct - say, ordered data,
reverse ordered data, randomly ordered data, almost ordered data with
a single element out of place, etc. - are actually covering all of the
interesting cases. At the same time, I don't think anyone would
seriously disagree with what you say in the subject line, and we won't
make any progress by NOT trying things that are recommended in the
academic literature.
--
Robert Haas
EDB: http://www.enterprisedb.com
Hi,
Attached is a draft series that implements some but not all features
of pattern-defeating quicksort, namely the ones I thought were
interesting for us. Recently this quicksort variant got committed for
the next release of the Go language 1.19 [1]https://github.com/golang/go/blob/72e77a7f41bbf45d466119444307fd3ae996e257/src/sort/zsortfunc.go (which in turn was based
on that of Rust [2]https://github.com/rust-lang/rust/blob/415c9f95884caebd0be9b837ef0885d1ffde1b9b/library/core/src/slice/sort.rs), and that implementation was a helpful additional
example. The bottom line is that replacing the partitioning scheme
this way is likely not worth doing because of our particular use
cases, but along the way I found some other things that might be worth
doing, so some good may come out of this.
0001 is a test module for running a microbenchmark, based on earlier
work by Thomas Munro.
0002 is just preliminary refactoring.
(From here on some regression tests change from patch to patch, since
tests that rely on a sort order might have duplicates in the sort
key.)
0003 starts a "partial insertion sort" if partitioning didn't do any
swaps, and bails if it doesn't finish quickly. A footnote to what I
said earlier:
All we need is a
lightweight way to detect that the partitioning phase didn't do any
swaps.
Actually, Postgres did detect this until a3f0b3d68 [3]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=a3f0b3d68f9a5357a3f72b40a45bcc714a9e0649;hp=570b726533fb561bc5a35ba50ea944f139a250d5, but wasn't
very bright about it -- it attempted a complete insertion sort.
Limiting the iterations is more sensible. In addition to the swap
count that I borrowed from ancient PG code, this patch checks the root
level as we do today (but now in a different code path), and there are
a couple additional heuristics to limit wasted work:
- Only try it if the pivot candidates are in order
- If the input is presorted we always return, but if it isn't we will
only proceed with swapping elements if the input was large enough that
we checked the order of nine pivot candidates.
- Only try partial insertion sort if the partitions are not highly
different in size, since bad pivots will lead to fewer swaps in
general, causing the swap count heuristic to be unhelpful. (Future
work: look into randomizing pivot candidates when encountering such
skewed partitions, as some other implementations do)
0004 builds on upon the previous and preemptively reverses the input
if there are nine pivot candidates and they are in descending order.
0005 implements the PDQuicksort partitioning scheme. In working on
this, I found I need to correct something I said earlier:
There is one requirement that is a double-edged sword: Recursion to
the partitions must happen in order. This has an additional advantage
that in every case but one, insertion sort doesn't need to guard
against running off the beginning of the array. The downside for us is
that we currently recurse to a partition based on its size as a
stack-guarding measure, so that guard would have to be replaced.
This is not true actually (I read too much into an implementation
detail), and with a tiny bit of bookkeepping we can retain our
practice of recursing to the smaller partition.
A very brief description of 0005: There are two partitioning
functions: left and right. Partition-right puts elements >= pivot in
the right partition. This is the default. When many duplicates end up
in the right partition, the pivot chosen for the next iteration there
could be the same as the previous pivot. In that case, we switch to
partition-left: put elements equal to the pivot in the left partition.
Since all those elements are the same, we are done and can iterate on
the right side. With this scheme, only 2-way comparators are needed.
Also, the previous counting of swaps during partitioning is replaced
by a simple pointer check once per iteration.
-- Tests:
I have two flavors of tests:
1) ORDER BY queries with 10 million records: Select a single value for
three types:
- bigint (so can use datum sort)
- 16-byte abbreviatable text
- 16-byte non-abbreviatable text
(median of 5 runs, script attached)
2) a microbenchmark sorting 1 million int32 datums with two ways to
vary comparison schemes: direct C function/fmgr function and
runtime/specialized, so four different combinations. The idea here is
to get an easy way to test comparators of different "heaviness", not
that these actual cases are all necessarily relevant.
(minimum of 10 runs)
The input distributions can be put into three categories:
- Varying amounts of randomness and presortedness (random, sort50/90/99, dither)
- Long runs of ascending and descending sequences (ascending,
descending, organ, merge)
- Low cardinality (dup8, dupsq, mod100/8)
To get the microbenchmark output in the same format as the query script I ran
psql -c 'select test_sort_cmp_weight(1 * 1024*1024)' 2> result.txt
perl -n -e 'print "$2\t$3\t$1\t$4\n" if /NOTICE: \[(.+)\] num=(\d+),
order=(\w+), time=(\d+\.\d+)/;' result.txt > result.csv
-- Summary of results:
Partial insertion sort:
- For small types sometimes possibly a bit faster, sometimes
significantly faster.
- With queries, significantly slower for some partially-presorted
distributions, otherwise mostly no change.
- Could be further optimized for types known at compile time by using
a "sift" algorithm rather than swapping at every iteration. We could
also try basing the iteration limit on type size.
Optimistically reversing input:
- 20-25x faster on descending input, which is nice. It's not clear if
it would benefit "mostly descending" input.
PDQuicksort:
- With light or moderate types/comparators, it seems significantly
faster than B&M.
- With heavier comparators (including all queries), it's slightly
faster at best, and regresses noticeably with low cardinality inputs.
This latter finding was somewhat surprising. The O(kn) behavior for
low cardinality input is academically interesting, but doesn't
translate to gains for some of our key use cases.
-- Conclusion: The partial insertion sort seems useful if the
regressions can be worked out. The reverse-input optimization is a
small addition on top of that and seems worth doing. The PDQuicksort
partitioning scheme is a win in some cases I tested but a loss in
others, so at this point I'm inclined not to pursue that part further.
I'd be happy to hear any thoughts anyone might have.
[1]: https://github.com/golang/go/blob/72e77a7f41bbf45d466119444307fd3ae996e257/src/sort/zsortfunc.go
[2]: https://github.com/rust-lang/rust/blob/415c9f95884caebd0be9b837ef0885d1ffde1b9b/library/core/src/slice/sort.rs
[3]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=a3f0b3d68f9a5357a3f72b40a45bcc714a9e0649;hp=570b726533fb561bc5a35ba50ea944f139a250d5
Attachments:
v1-0003-Optimistically-attempt-insertion-sort-on-pre-part.patchapplication/x-patch; name=v1-0003-Optimistically-attempt-insertion-sort-on-pre-part.patchDownload
From 4ec846adcc228c8bc63a50df314dfea0805b7b1a Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Mon, 30 May 2022 11:48:51 +0700
Subject: [PATCH v1 3/5] Optimistically attempt insertion sort on
pre-partitioned input
During the partitioning phase, when no swaps happened, and the partitions
were not highly unbalanced, inform the next recursion level. If all the
pivot candidates on the next level down are presorted, we will attempt
a "partial insertion sort". If that partition happens to be presorted,
it can return. Otherwise, if the sequence is large enough such that
we checked the ordering of nine pivot candidates, it will try a few
iterations of insertion sort in the hopes this will be enough to sort
the sequence.
For the root level, we only check the pivot candidates and assume
the other criteria are fine.
The changes to the regression tests are due to duplicates in sort
keys, since qsort is not a stable sort.
---
src/include/lib/sort_template.h | 194 ++++++++++++++++-----
src/test/regress/expected/create_index.out | 4 +-
src/test/regress/expected/tsrf.out | 42 ++---
src/test/regress/expected/tuplesort.out | 6 +-
src/test/regress/expected/window.out | 104 +++++------
5 files changed, 227 insertions(+), 123 deletions(-)
diff --git a/src/include/lib/sort_template.h b/src/include/lib/sort_template.h
index 54921de568..492ec2823b 100644
--- a/src/include/lib/sort_template.h
+++ b/src/include/lib/sort_template.h
@@ -57,8 +57,6 @@
* Modifications from vanilla NetBSD source:
* - Add do ... while() macro fix
* - Remove __inline, _DIAGASSERTs, __P
- * - Remove ill-considered "swap_cnt" switch to insertion sort, in favor
- * of a simple check for presorted input.
* - Take care to recurse on the smaller partition, to bound stack usage
* - Convert into a header that can generate specialized functions
*
@@ -104,11 +102,7 @@
* "Engineering a sort function",
* Software--Practice and Experience 23 (1993) 1249-1265.
*
- * We have modified their original by adding a check for already-sorted
- * input, which seems to be a win per discussions on pgsql-hackers around
- * 2006-03-21.
- *
- * Also, we recurse on the smaller partition and iterate on the larger one,
+ * We have modified their original by recursing on the smaller partition and iterating on the larger one,
* which ensures we cannot recurse more than log(N) levels (since the
* partition recursed to is surely no more than half of the input). Bentley
* and McIlroy explicitly rejected doing this on the grounds that it's "not
@@ -172,6 +166,9 @@
#define ST_SORT_INVOKE_ARG
#endif
+/* threshold for choosing pivot from nine candidates */
+#define ST_THRESHOLD_MED9 40
+
#ifdef ST_DECLARE
#ifdef ST_COMPARE_RUNTIME_POINTER
@@ -191,6 +188,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
/* sort private helper functions */
#define ST_MED3 ST_MAKE_NAME(ST_SORT, med3)
+#define ST_PARTIAL_INSERTION_SORT ST_MAKE_NAME(ST_SORT, insertion_sort_partial)
#define ST_SORT_INTERNAL ST_MAKE_NAME(ST_SORT, internal)
#define ST_SWAP ST_MAKE_NAME(ST_SORT, swap)
#define ST_SWAPN ST_MAKE_NAME(ST_SORT, swapn)
@@ -203,7 +201,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
#endif
/*
- * Create wrapper macros that know how to invoke compare, med3 and sort with
+ * Create wrapper macros that know how to invoke functions with
* the right arguments.
*/
#ifdef ST_COMPARE_RUNTIME_POINTER
@@ -213,12 +211,17 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
#else
#define DO_COMPARE(a_, b_) ST_COMPARE((a_), (b_))
#endif
-#define DO_MED3(a_, b_, c_) \
- ST_MED3((a_), (b_), (c_) \
+#define DO_MED3(a_, b_, c_, nr_) \
+ ST_MED3((a_), (b_), (c_), (nr_) \
+ ST_SORT_INVOKE_COMPARE \
+ ST_SORT_INVOKE_ARG)
+#define DO_PARTIAL_INSERTION_SORT(a_, n_) \
+ ST_PARTIAL_INSERTION_SORT((a_), (n_) \
+ ST_SORT_INVOKE_ELEMENT_SIZE \
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
-#define DO_SORT(a_, n_) \
- ST_SORT_INTERNAL((a_), (n_) \
+#define DO_SORT(a_, n_, bal_, ap_) \
+ ST_SORT_INTERNAL((a_), (n_), (bal_), (ap_) \
ST_SORT_INVOKE_ELEMENT_SIZE \
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
@@ -241,20 +244,55 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
#endif
/*
- * Find the median of three values. Currently, performance seems to be best
+ * Find the median of three values, and count the number of out-of-order
+ * elements. We don't care to keep an accurate count, only that we count
+ * report 0 only if the elements are presorted.
+ *
+ * Currently, performance seems to be best
* if the comparator is inlined here, but the med3 function is not inlined
* in the qsort function.
*/
static pg_noinline ST_ELEMENT_TYPE *
ST_MED3(ST_ELEMENT_TYPE * a,
ST_ELEMENT_TYPE * b,
- ST_ELEMENT_TYPE * c
+ ST_ELEMENT_TYPE * c,
+ int * num_reversed
ST_SORT_PROTO_COMPARE
ST_SORT_PROTO_ARG)
{
- return DO_COMPARE(a, b) < 0 ?
- (DO_COMPARE(b, c) < 0 ? b : (DO_COMPARE(a, c) < 0 ? c : a))
- : (DO_COMPARE(b, c) > 0 ? b : (DO_COMPARE(a, c) < 0 ? a : c));
+ if (DO_COMPARE(b, a) < 0)
+ {
+ (*num_reversed)++;
+ if (DO_COMPARE(c, a) < 0)
+ {
+ /* (*num_reversed)++; WIP */
+ if (DO_COMPARE(c, b) < 0)
+ {
+ /* (*num_reversed)++; WIP */
+ return b;
+ }
+ else
+ return c;
+ }
+ else
+ return a;
+ }
+ else
+ {
+ if (DO_COMPARE(c, b) < 0)
+ {
+ (*num_reversed)++;
+ if (DO_COMPARE(c, a) < 0)
+ {
+ /* out-of-order, but we already know the values are neither sorted nor reversed */
+ return a;
+ }
+ else
+ return c;
+ }
+ else
+ return b;
+ }
}
static inline void
@@ -273,11 +311,66 @@ ST_SWAPN(ST_POINTER_TYPE * a, ST_POINTER_TYPE * b, size_t n)
ST_SWAP(&a[i], &b[i]);
}
+static inline bool
+ST_PARTIAL_INSERTION_SORT(ST_POINTER_TYPE * begin, size_t n
+ ST_SORT_PROTO_ELEMENT_SIZE
+ ST_SORT_PROTO_COMPARE
+ ST_SORT_PROTO_ARG)
+{
+ ST_POINTER_TYPE *cur = begin + ST_POINTER_STEP;
+ ST_POINTER_TYPE *end = begin + n * ST_POINTER_STEP;
+ /* WIP: This number was adopted from the Rust stdlib and hasn't been tested within PG */
+ const int max_iters = 5;
+
+ for (int i = 0; i < max_iters; i++)
+ {
+ while (cur < end && !(DO_COMPARE(cur, cur - ST_POINTER_STEP) < 0))
+ cur += ST_POINTER_STEP;
+
+ /* are we done? */
+ if (cur == end)
+ return true;
+
+ /* only proceed with sorting if we checked the ordering of 9 pivot candidates */
+ if (n <= ST_THRESHOLD_MED9)
+ return false;
+
+ DO_SWAP(cur, cur - ST_POINTER_STEP);
+
+ // Shift the smaller one to the left.
+ if (cur - begin >= 2 * ST_POINTER_STEP)
+ for (ST_POINTER_TYPE * j = cur - ST_POINTER_STEP;
+ j > begin;
+ j -= ST_POINTER_STEP)
+ {
+ if (!(DO_COMPARE(j, j - ST_POINTER_STEP) < 0))
+ break;
+ else
+ DO_SWAP(j, j - ST_POINTER_STEP);
+ }
+
+ // Shift the larger one to the right.
+ if (end - cur >= 2 * ST_POINTER_STEP)
+ for (ST_POINTER_TYPE * j = cur + ST_POINTER_STEP;
+ j < end;
+ j += ST_POINTER_STEP)
+ {
+ if (!(DO_COMPARE(j, j - ST_POINTER_STEP) < 0))
+ break;
+ else
+ DO_SWAP(j, j - ST_POINTER_STEP);
+ }
+ }
+ return false;
+}
+
/*
* Workhorse for ST_SORT
*/
static void
-ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n
+ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n,
+ bool balanced,
+ bool already_partitioned
ST_SORT_PROTO_ELEMENT_SIZE
ST_SORT_PROTO_COMPARE
ST_SORT_PROTO_ARG)
@@ -291,9 +384,11 @@ ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n
*pm,
*pn;
size_t d1,
- d2;
+ d2,
+ n1,
+ n2;
int r,
- presorted;
+ num_reversed;
loop:
DO_CHECK_FOR_INTERRUPTS();
@@ -306,34 +401,33 @@ loop:
DO_SWAP(pl, pl - ST_POINTER_STEP);
return;
}
- presorted = 1;
- for (pm = a + ST_POINTER_STEP; pm < a + n * ST_POINTER_STEP;
- pm += ST_POINTER_STEP)
- {
- DO_CHECK_FOR_INTERRUPTS();
- if (DO_COMPARE(pm - ST_POINTER_STEP, pm) > 0)
- {
- presorted = 0;
- break;
- }
- }
- if (presorted)
- return;
+
+ num_reversed = 0; // we must reset this on every iteration
pm = a + (n / 2) * ST_POINTER_STEP;
if (n > 7)
{
pl = a;
pn = a + (n - 1) * ST_POINTER_STEP;
- if (n > 40)
+ if (n > ST_THRESHOLD_MED9)
{
size_t d = (n / 8) * ST_POINTER_STEP;
- pl = DO_MED3(pl, pl + d, pl + 2 * d);
- pm = DO_MED3(pm - d, pm, pm + d);
- pn = DO_MED3(pn - 2 * d, pn - d, pn);
+ pl = DO_MED3(pl, pl + d, pl + 2 * d, &num_reversed);
+ pm = DO_MED3(pm - d, pm, pm + d, &num_reversed);
+ pn = DO_MED3(pn - 2 * d, pn - d, pn, &num_reversed);
}
- pm = DO_MED3(pl, pm, pn);
+ pm = DO_MED3(pl, pm, pn, &num_reversed);
}
+
+ // If the sequence was decently balanced and already partitioned,
+ // and the pivot candidates were presorted, try to use insertion sort.
+ if (already_partitioned && balanced && num_reversed == 0)
+ {
+ if (DO_PARTIAL_INSERTION_SORT(a, n))
+ return;
+ }
+
+ /* put the pivot at the beginning */
DO_SWAP(a, pm);
pa = pb = a + ST_POINTER_STEP;
pc = pd = a + (n - 1) * ST_POINTER_STEP;
@@ -344,6 +438,7 @@ loop:
if (r == 0)
{
DO_SWAP(pa, pb);
+ already_partitioned = false;
pa += ST_POINTER_STEP;
}
pb += ST_POINTER_STEP;
@@ -354,6 +449,7 @@ loop:
if (r == 0)
{
DO_SWAP(pc, pd);
+ already_partitioned = false;
pd -= ST_POINTER_STEP;
}
pc -= ST_POINTER_STEP;
@@ -362,6 +458,7 @@ loop:
if (pb > pc)
break;
DO_SWAP(pb, pc);
+ already_partitioned = false;
pb += ST_POINTER_STEP;
pc -= ST_POINTER_STEP;
}
@@ -372,30 +469,34 @@ loop:
DO_SWAPN(pb, pn - d1, d1);
d1 = pb - pa;
d2 = pd - pc;
+ n1 = d1 / ST_POINTER_STEP;
+ n2 = d2 / ST_POINTER_STEP;
if (d1 <= d2)
{
/* Recurse on left partition, then iterate on right partition */
+ balanced = n1 >= n / 8;
if (d1 > ST_POINTER_STEP)
- DO_SORT(a, d1 / ST_POINTER_STEP);
+ DO_SORT(a, n1, balanced, already_partitioned);
if (d2 > ST_POINTER_STEP)
{
/* Iterate rather than recurse to save stack space */
- /* DO_SORT(pn - d2, d2 / ST_POINTER_STEP) */
+ /* DO_SORT(pn - d2, n2, ...) */
a = pn - d2;
- n = d2 / ST_POINTER_STEP;
+ n = n2;
goto loop;
}
}
else
{
/* Recurse on right partition, then iterate on left partition */
+ balanced = n2 >= n / 8;
if (d2 > ST_POINTER_STEP)
- DO_SORT(pn - d2, d2 / ST_POINTER_STEP);
+ DO_SORT(pn - d2, n2, balanced, already_partitioned);
if (d1 > ST_POINTER_STEP)
{
/* Iterate rather than recurse to save stack space */
- /* DO_SORT(a, d1 / ST_POINTER_STEP) */
- n = d1 / ST_POINTER_STEP;
+ /* DO_SORT(a, n1, ...) */
+ n = n1;
goto loop;
}
}
@@ -412,7 +513,7 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
{
ST_POINTER_TYPE *begin = (ST_POINTER_TYPE *) data;
- DO_SORT(begin, n);
+ DO_SORT(begin, n, true, true);
#ifdef USE_ASSERT_CHECKING
/* WIP: verify the sorting worked */
@@ -429,6 +530,7 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
#undef DO_CHECK_FOR_INTERRUPTS
#undef DO_COMPARE
#undef DO_MED3
+#undef DO_PARTIAL_INSERTION_SORT
#undef DO_SORT
#undef DO_SWAP
#undef DO_SWAPN
@@ -443,6 +545,7 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
#undef ST_MAKE_NAME_
#undef ST_MAKE_PREFIX
#undef ST_MED3
+#undef ST_PARTIAL_INSERTION_SORT
#undef ST_POINTER_STEP
#undef ST_POINTER_TYPE
#undef ST_SCOPE
@@ -456,3 +559,4 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
#undef ST_SORT_PROTO_ELEMENT_SIZE
#undef ST_SWAP
#undef ST_SWAPN
+#undef ST_THRESHOLD_MED9
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index d55aec3a1d..0a544e7167 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -165,8 +165,8 @@ SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
f1
-------------------
- (0,0)
(1e-300,-1e-300)
+ (0,0)
(-3,4)
(-10,0)
(10,10)
@@ -187,8 +187,8 @@ SELECT * FROM point_tbl WHERE f1 IS NULL;
SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
f1
-------------------
- (0,0)
(1e-300,-1e-300)
+ (0,0)
(-3,4)
(-10,0)
(10,10)
diff --git a/src/test/regress/expected/tsrf.out b/src/test/regress/expected/tsrf.out
index d47b5f6ec5..ef3bd4f539 100644
--- a/src/test/regress/expected/tsrf.out
+++ b/src/test/regress/expected/tsrf.out
@@ -348,19 +348,19 @@ SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(d
SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab) ORDER BY g;
dataa | b | g | count
-------+-----+---+-------
- a | bar | 1 | 1
+ b | | 1 | 1
a | foo | 1 | 1
a | | 1 | 2
b | bar | 1 | 1
- b | | 1 | 1
+ a | bar | 1 | 1
| | 1 | 3
| bar | 1 | 2
| foo | 1 | 1
- | foo | 2 | 1
+ | bar | 2 | 2
a | bar | 2 | 1
b | | 2 | 1
a | foo | 2 | 1
- | bar | 2 | 2
+ | foo | 2 | 1
a | | 2 | 2
| | 2 | 3
b | bar | 2 | 1
@@ -398,56 +398,56 @@ SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(d
SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY dataa;
dataa | b | g | count
-------+-----+---+-------
+ a | foo | 1 | 1
a | foo | | 2
a | | | 4
+ a | | 1 | 2
a | | 2 | 2
- a | bar | 1 | 1
a | bar | 2 | 1
+ a | bar | 1 | 1
a | bar | | 2
- a | foo | 1 | 1
a | foo | 2 | 1
- a | | 1 | 2
+ b | bar | | 2
+ b | | 1 | 1
b | bar | 1 | 1
b | | | 2
- b | | 1 | 1
- b | bar | 2 | 1
- b | bar | | 2
b | | 2 | 1
- | | 2 | 3
- | | | 6
+ b | bar | 2 | 1
| bar | 1 | 2
+ | | | 6
| bar | 2 | 2
| bar | | 4
| foo | 1 | 1
| foo | 2 | 1
| foo | | 2
| | 1 | 3
+ | | 2 | 3
(24 rows)
SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY g;
dataa | b | g | count
-------+-----+---+-------
- a | bar | 1 | 1
+ | bar | 1 | 2
a | foo | 1 | 1
b | bar | 1 | 1
- | bar | 1 | 2
+ a | bar | 1 | 1
| foo | 1 | 1
a | | 1 | 2
b | | 1 | 1
| | 1 | 3
- a | | 2 | 2
+ | foo | 2 | 1
b | | 2 | 1
| bar | 2 | 2
| | 2 | 3
- | foo | 2 | 1
+ b | bar | 2 | 1
a | bar | 2 | 1
a | foo | 2 | 1
- b | bar | 2 | 1
- a | | | 4
+ a | | 2 | 2
+ a | foo | | 2
b | bar | | 2
b | | | 2
| | | 6
- a | foo | | 2
+ a | | | 4
a | bar | | 2
| bar | | 4
| foo | | 2
@@ -597,9 +597,9 @@ SELECT DISTINCT ON (g) a, b, generate_series(1,3) g
FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b);
a | b | g
---+---+---
- 3 | 2 | 1
+ 1 | 4 | 1
5 | 1 | 2
- 3 | 1 | 3
+ 5 | 3 | 3
(3 rows)
-- LIMIT / OFFSET is evaluated after SRF evaluation
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 418f296a3f..a1a5889756 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -242,11 +242,11 @@ FROM abbrev_abort_uuids
ORDER BY ctid DESC LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
- 0 | | | |
- 20002 | | | |
20003 | | | |
- 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20002 | | | |
+ 0 | | | |
20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
(5 rows)
ROLLBACK;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 433a0bb025..c4fc72df93 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -23,8 +23,8 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
-----------+-------+--------+-------
develop | 7 | 4200 | 25100
develop | 9 | 4500 | 25100
- develop | 11 | 5200 | 25100
develop | 10 | 5200 | 25100
+ develop | 11 | 5200 | 25100
develop | 8 | 6000 | 25100
personnel | 5 | 3500 | 7400
personnel | 2 | 3900 | 7400
@@ -38,8 +38,8 @@ SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary
-----------+-------+--------+------
develop | 7 | 4200 | 1
develop | 9 | 4500 | 2
- develop | 11 | 5200 | 3
develop | 10 | 5200 | 3
+ develop | 11 | 5200 | 3
develop | 8 | 6000 | 5
personnel | 5 | 3500 | 1
personnel | 2 | 3900 | 2
@@ -78,11 +78,11 @@ GROUP BY four, ten ORDER BY four, ten;
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 | 10 | 5200 | 25100
develop | 9 | 4500 | 25100
develop | 8 | 6000 | 25100
- develop | 10 | 5200 | 25100
+ develop | 11 | 5200 | 25100
personnel | 5 | 3500 | 7400
personnel | 2 | 3900 | 7400
sales | 3 | 4800 | 14600
@@ -93,15 +93,15 @@ SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PA
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
+ develop | 7 | 4200 | 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 | 11 | 5200 | 3
develop | 8 | 6000 | 5
(10 rows)
@@ -803,9 +803,9 @@ SELECT sum(unique1) over (order by four range between current row and unbounded
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 45 | 0 | 0
45 | 8 | 0
45 | 4 | 0
+ 45 | 0 | 0
33 | 5 | 1
33 | 9 | 1
33 | 1 | 1
@@ -922,9 +922,9 @@ SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 f
FROM tenk1 WHERE unique1 < 10;
first_value | unique1 | four
-------------+---------+------
- 8 | 0 | 0
4 | 8 | 0
- 5 | 4 | 0
+ 0 | 4 | 0
+ 5 | 0 | 0
9 | 5 | 1
1 | 9 | 1
6 | 1 | 1
@@ -939,9 +939,9 @@ SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 f
FROM tenk1 WHERE unique1 < 10;
first_value | unique1 | four
-------------+---------+------
- | 0 | 0
- 5 | 8 | 0
+ | 8 | 0
5 | 4 | 0
+ 5 | 0 | 0
| 5 | 1
6 | 9 | 1
6 | 1 | 1
@@ -956,9 +956,9 @@ SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 f
FROM tenk1 WHERE unique1 < 10;
first_value | unique1 | four
-------------+---------+------
- 0 | 0 | 0
8 | 8 | 0
4 | 4 | 0
+ 0 | 0 | 0
5 | 5 | 1
9 | 9 | 1
1 | 1 | 1
@@ -973,9 +973,9 @@ SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 fo
FROM tenk1 WHERE unique1 < 10;
last_value | unique1 | four
------------+---------+------
- 4 | 0 | 0
- 5 | 8 | 0
- 9 | 4 | 0
+ 0 | 8 | 0
+ 5 | 4 | 0
+ 9 | 0 | 0
1 | 5 | 1
6 | 9 | 1
2 | 1 | 1
@@ -990,9 +990,9 @@ SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 fo
FROM tenk1 WHERE unique1 < 10;
last_value | unique1 | four
------------+---------+------
- | 0 | 0
- 5 | 8 | 0
- 9 | 4 | 0
+ | 8 | 0
+ 5 | 4 | 0
+ 9 | 0 | 0
| 5 | 1
6 | 9 | 1
2 | 1 | 1
@@ -1007,9 +1007,9 @@ SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 fo
FROM tenk1 WHERE unique1 < 10;
last_value | unique1 | four
------------+---------+------
- 0 | 0 | 0
- 5 | 8 | 0
- 9 | 4 | 0
+ 8 | 8 | 0
+ 5 | 4 | 0
+ 9 | 0 | 0
5 | 5 | 1
6 | 9 | 1
2 | 1 | 1
@@ -1075,9 +1075,9 @@ SELECT sum(unique1) over (w range between current row and unbounded following),
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
- 45 | 0 | 0
45 | 8 | 0
45 | 4 | 0
+ 45 | 0 | 0
33 | 5 | 1
33 | 9 | 1
33 | 1 | 1
@@ -1092,9 +1092,9 @@ SELECT sum(unique1) over (w range between unbounded preceding and current row ex
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
- 12 | 0 | 0
4 | 8 | 0
8 | 4 | 0
+ 12 | 0 | 0
22 | 5 | 1
18 | 9 | 1
26 | 1 | 1
@@ -1109,9 +1109,9 @@ SELECT sum(unique1) over (w range between unbounded preceding and current row ex
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
- | 0 | 0
| 8 | 0
| 4 | 0
+ | 0 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1126,9 +1126,9 @@ SELECT sum(unique1) over (w range between unbounded preceding and current row ex
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
- 0 | 0 | 0
8 | 8 | 0
4 | 4 | 0
+ 0 | 0 | 0
17 | 5 | 1
21 | 9 | 1
13 | 1 | 1
@@ -1145,9 +1145,9 @@ 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
+ 8 | 4 | 7 | 8 | 0
+ 8 | 4 | 7 | 4 | 0
+ 8 | 4 | 7 | 0 | 0
5 | 9 | 7 | 5 | 1
5 | 9 | 7 | 9 | 1
5 | 9 | 7 | 1 | 1
@@ -1349,9 +1349,9 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
| 8 | 0
| 4 | 0
+ | 0 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1373,9 +1373,9 @@ FROM tenk1 WHERE unique1 < 10;
18 | 9 | 1
18 | 5 | 1
18 | 1 | 1
- 23 | 0 | 0
23 | 8 | 0
23 | 4 | 0
+ 23 | 0 | 0
(10 rows)
SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude no others),
@@ -1383,9 +1383,9 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
| 8 | 0
| 4 | 0
+ | 0 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1400,9 +1400,9 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
| 8 | 0
| 4 | 0
+ | 0 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1417,9 +1417,9 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
| 8 | 0
| 4 | 0
+ | 0 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1434,9 +1434,9 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
| 8 | 0
| 4 | 0
+ | 0 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1451,9 +1451,9 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 33 | 0 | 0
41 | 8 | 0
37 | 4 | 0
+ 33 | 0 | 0
35 | 5 | 1
39 | 9 | 1
31 | 1 | 1
@@ -1468,9 +1468,9 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 33 | 0 | 0
33 | 8 | 0
33 | 4 | 0
+ 33 | 0 | 0
30 | 5 | 1
30 | 9 | 1
30 | 1 | 1
@@ -2588,9 +2588,9 @@ SELECT sum(unique1) over (order by four groups between unbounded preceding and c
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 12 | 0 | 0
12 | 8 | 0
12 | 4 | 0
+ 12 | 0 | 0
27 | 5 | 1
27 | 9 | 1
27 | 1 | 1
@@ -2605,9 +2605,9 @@ SELECT sum(unique1) over (order by four groups between unbounded preceding and u
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 45 | 0 | 0
45 | 8 | 0
45 | 4 | 0
+ 45 | 0 | 0
45 | 5 | 1
45 | 9 | 1
45 | 1 | 1
@@ -2622,9 +2622,9 @@ SELECT sum(unique1) over (order by four groups between current row and unbounded
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 45 | 0 | 0
45 | 8 | 0
45 | 4 | 0
+ 45 | 0 | 0
33 | 5 | 1
33 | 9 | 1
33 | 1 | 1
@@ -2639,9 +2639,9 @@ SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 45 | 0 | 0
45 | 8 | 0
45 | 4 | 0
+ 45 | 0 | 0
45 | 5 | 1
45 | 9 | 1
45 | 1 | 1
@@ -2656,9 +2656,9 @@ SELECT sum(unique1) over (order by four groups between 1 following and unbounded
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 33 | 0 | 0
33 | 8 | 0
33 | 4 | 0
+ 33 | 0 | 0
18 | 5 | 1
18 | 9 | 1
18 | 1 | 1
@@ -2673,9 +2673,9 @@ SELECT sum(unique1) over (order by four groups between unbounded preceding and 2
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 35 | 0 | 0
35 | 8 | 0
35 | 4 | 0
+ 35 | 0 | 0
45 | 5 | 1
45 | 9 | 1
45 | 1 | 1
@@ -2690,9 +2690,9 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 precedi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
| 8 | 0
| 4 | 0
+ | 0 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -2707,9 +2707,9 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 27 | 0 | 0
27 | 8 | 0
27 | 4 | 0
+ 27 | 0 | 0
35 | 5 | 1
35 | 9 | 1
35 | 1 | 1
@@ -2724,9 +2724,9 @@ SELECT sum(unique1) over (order by four groups between 0 preceding and 0 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 12 | 0 | 0
12 | 8 | 0
12 | 4 | 0
+ 12 | 0 | 0
15 | 5 | 1
15 | 9 | 1
15 | 1 | 1
@@ -2741,9 +2741,9 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 27 | 0 | 0
19 | 8 | 0
23 | 4 | 0
+ 27 | 0 | 0
30 | 5 | 1
26 | 9 | 1
34 | 1 | 1
@@ -2758,9 +2758,9 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 15 | 0 | 0
15 | 8 | 0
15 | 4 | 0
+ 15 | 0 | 0
20 | 5 | 1
20 | 9 | 1
20 | 1 | 1
@@ -2775,9 +2775,9 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 15 | 0 | 0
23 | 8 | 0
19 | 4 | 0
+ 15 | 0 | 0
25 | 5 | 1
29 | 9 | 1
21 | 1 | 1
@@ -3413,8 +3413,8 @@ WHERE r <= 3;
empno | salary | r
-------+--------+---
8 | 6000 | 1
- 10 | 5200 | 2
11 | 5200 | 2
+ 10 | 5200 | 2
(3 rows)
-- Ensure dr = 1 is converted to dr <= 1 to get all rows leading up to dr = 1
@@ -3473,8 +3473,8 @@ WHERE c <= 3;
empno | salary | c
-------+--------+---
8 | 6000 | 1
- 10 | 5200 | 3
11 | 5200 | 3
+ 10 | 5200 | 3
(3 rows)
EXPLAIN (COSTS OFF)
@@ -3502,8 +3502,8 @@ WHERE c <= 3;
empno | salary | c
-------+--------+---
8 | 6000 | 1
- 10 | 5200 | 3
11 | 5200 | 3
+ 10 | 5200 | 3
(3 rows)
EXPLAIN (COSTS OFF)
@@ -3637,8 +3637,8 @@ WHERE c <= 3;
empno | depname | salary | c
-------+-----------+--------+---
8 | develop | 6000 | 1
- 10 | develop | 5200 | 3
11 | develop | 5200 | 3
+ 10 | develop | 5200 | 3
2 | personnel | 3900 | 1
5 | personnel | 3500 | 2
1 | sales | 5000 | 1
--
2.36.1
v1-0002-Create-internal-workhorse-for-ST_SORT.patchapplication/x-patch; name=v1-0002-Create-internal-workhorse-for-ST_SORT.patchDownload
From 7491f7139f2ee9593717218f67dfbbba27af2a01 Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Mon, 30 May 2022 10:09:17 +0700
Subject: [PATCH v1 2/5] Create internal workhorse for ST_SORT
No functional changes.
---
src/include/lib/sort_template.h | 36 ++++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/src/include/lib/sort_template.h b/src/include/lib/sort_template.h
index 3122a93009..54921de568 100644
--- a/src/include/lib/sort_template.h
+++ b/src/include/lib/sort_template.h
@@ -191,6 +191,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
/* sort private helper functions */
#define ST_MED3 ST_MAKE_NAME(ST_SORT, med3)
+#define ST_SORT_INTERNAL ST_MAKE_NAME(ST_SORT, internal)
#define ST_SWAP ST_MAKE_NAME(ST_SORT, swap)
#define ST_SWAPN ST_MAKE_NAME(ST_SORT, swapn)
@@ -217,7 +218,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
#define DO_SORT(a_, n_) \
- ST_SORT((a_), (n_) \
+ ST_SORT_INTERNAL((a_), (n_) \
ST_SORT_INVOKE_ELEMENT_SIZE \
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
@@ -273,15 +274,15 @@ ST_SWAPN(ST_POINTER_TYPE * a, ST_POINTER_TYPE * b, size_t n)
}
/*
- * Sort an array.
+ * Workhorse for ST_SORT
*/
-ST_SCOPE void
-ST_SORT(ST_ELEMENT_TYPE * data, size_t n
+static void
+ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n
ST_SORT_PROTO_ELEMENT_SIZE
ST_SORT_PROTO_COMPARE
ST_SORT_PROTO_ARG)
{
- ST_POINTER_TYPE *a = (ST_POINTER_TYPE *) data,
+ ST_POINTER_TYPE *a = data,
*pa,
*pb,
*pc,
@@ -399,6 +400,30 @@ loop:
}
}
}
+
+/*
+ * Sort an array.
+ */
+ST_SCOPE void
+ST_SORT(ST_ELEMENT_TYPE * data, size_t n
+ ST_SORT_PROTO_ELEMENT_SIZE
+ ST_SORT_PROTO_COMPARE
+ ST_SORT_PROTO_ARG)
+{
+ ST_POINTER_TYPE *begin = (ST_POINTER_TYPE *) data;
+
+ DO_SORT(begin, n);
+
+#ifdef USE_ASSERT_CHECKING
+ /* WIP: verify the sorting worked */
+ for (ST_POINTER_TYPE *pm = begin + ST_POINTER_STEP; pm < begin + n * ST_POINTER_STEP;
+ pm += ST_POINTER_STEP)
+ {
+ if (DO_COMPARE(pm, pm - ST_POINTER_STEP) < 0)
+ Assert(false);
+ }
+#endif /* USE_ASSERT_CHECKING */
+}
#endif
#undef DO_CHECK_FOR_INTERRUPTS
@@ -422,6 +447,7 @@ loop:
#undef ST_POINTER_TYPE
#undef ST_SCOPE
#undef ST_SORT
+#undef ST_SORT_INTERNAL
#undef ST_SORT_INVOKE_ARG
#undef ST_SORT_INVOKE_COMPARE
#undef ST_SORT_INVOKE_ELEMENT_SIZE
--
2.36.1
v1-0001-Add-sort-test-module.patchapplication/x-patch; name=v1-0001-Add-sort-test-module.patchDownload
From f2cd82441e6d4193a7a1e35c76169733e0f2b271 Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Mon, 30 May 2022 09:55:46 +0700
Subject: [PATCH v1 1/5] Add sort test module
XXX not for commit
---
src/test/modules/test_sort_perf/Makefile | 20 ++
.../test_sort_cmp_weight_include.c | 237 ++++++++++++++++++
.../test_sort_perf/test_sort_perf--1.0.sql | 1 +
.../modules/test_sort_perf/test_sort_perf.c | 90 +++++++
.../test_sort_perf/test_sort_perf.control | 4 +
5 files changed, 352 insertions(+)
create mode 100644 src/test/modules/test_sort_perf/Makefile
create mode 100644 src/test/modules/test_sort_perf/test_sort_cmp_weight_include.c
create mode 100644 src/test/modules/test_sort_perf/test_sort_perf--1.0.sql
create mode 100644 src/test/modules/test_sort_perf/test_sort_perf.c
create mode 100644 src/test/modules/test_sort_perf/test_sort_perf.control
diff --git a/src/test/modules/test_sort_perf/Makefile b/src/test/modules/test_sort_perf/Makefile
new file mode 100644
index 0000000000..1e49fb43c0
--- /dev/null
+++ b/src/test/modules/test_sort_perf/Makefile
@@ -0,0 +1,20 @@
+MODULE_big = test_sort_perf
+OBJS = test_sort_perf.o
+PGFILEDESC = "test"
+EXTENSION = test_sort_perf
+DATA = test_sort_perf--1.0.sql
+
+first: all
+
+test_sort_perf.o: test_sort_cmp_weight_include.c
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_sort_perf
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_sort_perf/test_sort_cmp_weight_include.c b/src/test/modules/test_sort_perf/test_sort_cmp_weight_include.c
new file mode 100644
index 0000000000..06c9335361
--- /dev/null
+++ b/src/test/modules/test_sort_perf/test_sort_cmp_weight_include.c
@@ -0,0 +1,237 @@
+#include <math.h>
+
+static void run_tests(char* type, Datum *unsorted, Datum *sorted, BTSortArrayContext *cxt, int nobjects)
+{
+ const int numtests = 10;
+ double time,
+ min;
+
+ if (nobjects <= 100)
+ {
+ elog(NOTICE, "order: %s", type);
+ for (int j = 0; j < nobjects; ++j)
+ elog(NOTICE, "%d", DatumGetInt32(unsorted[j]));
+ return;
+ }
+
+ min = 1000000;
+ for (int i = 1; i <= numtests; ++i)
+ {
+ instr_time start_time, end_time;
+ memcpy(sorted, unsorted, sizeof(Datum) * nobjects);
+ INSTR_TIME_SET_CURRENT(start_time);
+ qsort_arg((void *) sorted, nobjects, sizeof(Datum), _bt_compare_array_elements, (void *) cxt);
+ INSTR_TIME_SET_CURRENT(end_time);
+ INSTR_TIME_SUBTRACT(end_time, start_time);
+ time = INSTR_TIME_GET_DOUBLE(end_time);
+ if (time < min)
+ min = time;
+ }
+ elog(NOTICE, "[runtime fmgr] num=%d, order=%s, time=%f", nobjects, type, min);
+
+ min = 1000000;
+ for (int i = 1; i <= numtests; ++i)
+ {
+ instr_time start_time, end_time;
+ memcpy(sorted, unsorted, sizeof(Datum) * nobjects);
+ INSTR_TIME_SET_CURRENT(start_time);
+ qsort_bt_array_elements(sorted, nobjects, cxt);
+ INSTR_TIME_SET_CURRENT(end_time);
+ INSTR_TIME_SUBTRACT(end_time, start_time);
+ time = INSTR_TIME_GET_DOUBLE(end_time);
+ if (time < min)
+ min = time;
+ }
+ elog(NOTICE, "[specialized fmgr] num=%d, order=%s, time=%f", nobjects, type, min);
+
+ min = 1000000;
+ for (int i = 1; i <= numtests; ++i)
+ {
+ instr_time start_time, end_time;
+ memcpy(sorted, unsorted, sizeof(Datum) * nobjects);
+ INSTR_TIME_SET_CURRENT(start_time);
+ qsort(sorted, nobjects, sizeof(Datum), btint4fastcmp);
+ INSTR_TIME_SET_CURRENT(end_time);
+ INSTR_TIME_SUBTRACT(end_time, start_time);
+ time = INSTR_TIME_GET_DOUBLE(end_time);
+ if (time < min)
+ min = time;
+ }
+ elog(NOTICE, "[runtime C] num=%d, order=%s, time=%f", nobjects, type, min);
+
+ min = 1000000;
+ for (int i = 0; i < numtests; ++i)
+ {
+ instr_time start_time, end_time;
+ memcpy(sorted, unsorted, sizeof(Datum) * nobjects);
+ INSTR_TIME_SET_CURRENT(start_time);
+ qsort_int32(sorted, nobjects);
+ INSTR_TIME_SET_CURRENT(end_time);
+ INSTR_TIME_SUBTRACT(end_time, start_time);
+ time = INSTR_TIME_GET_DOUBLE(end_time);
+ if (time < min)
+ min = time;
+ }
+ elog(NOTICE, "[specialized C] num=%d, order=%s, time=%f", nobjects, type, min);
+}
+
+typedef struct source_value
+{
+ int32 value;
+ float rand;
+} source_value;
+
+static int
+source_rand_cmp(const void * x, const void * y)
+{
+ source_value *a = (source_value *) x;
+ source_value *b = (source_value *) y;
+
+ if (a->rand > b->rand)
+ return 1;
+ else if (a->rand < b->rand)
+ return -1;
+ else
+ return 0;
+}
+
+static int
+source_dither_cmp(const void * x, const void * y, void * nobjects)
+{
+ source_value *a = (source_value *) x;
+ source_value *b = (source_value *) y;
+ float dither;
+
+ if (*(int *) nobjects <= 100)
+ dither = 5;
+ else
+ dither = 100;
+
+ if ((a->value + dither * a->rand) > (b->value + dither * b->rand))
+ return 1;
+ else if ((a->value + dither * a->rand) < (b->value + dither * b->rand))
+ return -1;
+ else
+ return 0;
+}
+
+static void
+do_sort_cmp_weight(int nobjects)
+{
+ source_value *source = malloc(sizeof(source_value) * nobjects);
+ source_value *tmp = malloc(sizeof(source_value) * nobjects);
+ Datum *unsorted = malloc(sizeof(Datum) * nobjects);
+ Datum *sorted = malloc(sizeof(Datum) * nobjects);
+
+ // for fmgr comparator tests
+
+ BTSortArrayContext cxt;
+ RegProcedure cmp_proc ;
+
+ // to keep from pulling in nbtree.h
+#define BTORDER_PROC 1
+
+ cmp_proc = get_opfamily_proc(INTEGER_BTREE_FAM_OID,
+ INT4OID,
+ INT4OID,
+ BTORDER_PROC);
+
+ fmgr_info(cmp_proc, &cxt.flinfo);
+ cxt.collation = DEFAULT_COLLATION_OID;
+ cxt.reverse = false;
+
+ // needed for some distributions: first populate source array with ascending value and random tag
+ for (int i = 0; i < nobjects; ++i)
+ {
+ source[i].value = i;
+ source[i].rand = (float) random() / (float) RAND_MAX; // between 0 and 1
+ }
+// if (nobjects <= 100)
+// for (int j = 0; j < nobjects; ++j)
+// elog(NOTICE, "%f", (source[j].rand));
+ qsort(source, nobjects, sizeof(source_value), source_rand_cmp);
+
+ // random
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(source[i].value);
+ run_tests("random", unsorted, sorted, &cxt, nobjects);
+
+ // for these, sort the first X % of the array
+
+ // sort50
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(source[i].value);
+ // sort first part of unsorted[]
+ qsort(unsorted, nobjects/2, sizeof(Datum), btint4fastcmp);
+ run_tests("sort50", unsorted, sorted, &cxt, nobjects);
+
+ // sort90
+ qsort(unsorted, nobjects/10 * 9, sizeof(Datum), btint4fastcmp);
+ run_tests("sort90", unsorted, sorted, &cxt, nobjects);
+
+ // sort99
+ qsort(unsorted, nobjects/100 * 99, sizeof(Datum), btint4fastcmp);
+ run_tests("sort99", unsorted, sorted, &cxt, nobjects);
+
+ // dither -- copy source to tmp array because it sorts differently
+ memcpy(tmp, source, sizeof(source_value) * nobjects);
+ qsort_arg(tmp, nobjects, sizeof(source_value), source_dither_cmp, (void *) &nobjects);
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(tmp[i].value);
+ run_tests("dither", unsorted, sorted, &cxt, nobjects);
+
+ // descending
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(nobjects - i);
+ run_tests("descending", unsorted, sorted, &cxt, nobjects);
+
+ // organ
+ for (int i = 0; i < nobjects/2; ++i)
+ unsorted[i] = Int32GetDatum(i);
+ for (int i = nobjects/2; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(nobjects - i);
+ run_tests("organ", unsorted, sorted, &cxt, nobjects);
+
+ // merge
+ for (int i = 0; i < nobjects/2; ++i)
+ unsorted[i] = Int32GetDatum(i);
+ for (int i = nobjects/2; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(i - nobjects/2);
+ run_tests("merge", unsorted, sorted, &cxt, nobjects);
+
+ // dup8
+ for (int i = 0; i < nobjects; ++i)
+ {
+ uint32 tmp = 1; // XXX this will only show duplicates for large nobjects
+ for (int j=0; j<8; ++j)
+ tmp *= (uint32) source[i].value;
+ tmp = (tmp + nobjects/2) % nobjects;
+ unsorted[i] = Int32GetDatum((int) tmp);
+ }
+ run_tests("dup8", unsorted, sorted, &cxt, nobjects);
+
+ // dupsq
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum((int) sqrt(source[i].value));
+ run_tests("dupsq", unsorted, sorted, &cxt, nobjects);
+
+ // mod100
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(source[i].value % 100);
+ run_tests("mod100", unsorted, sorted, &cxt, nobjects);
+
+ // mod8
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(source[i].value % 8);
+ run_tests("mod8", unsorted, sorted, &cxt, nobjects);
+
+ // ascending
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(i);
+ run_tests("ascending", unsorted, sorted, &cxt, nobjects);
+
+ free(tmp);
+ free(source);
+ free(sorted);
+ free(unsorted);
+}
diff --git a/src/test/modules/test_sort_perf/test_sort_perf--1.0.sql b/src/test/modules/test_sort_perf/test_sort_perf--1.0.sql
new file mode 100644
index 0000000000..22e0726a57
--- /dev/null
+++ b/src/test/modules/test_sort_perf/test_sort_perf--1.0.sql
@@ -0,0 +1 @@
+CREATE FUNCTION test_sort_cmp_weight(int4) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_sort_perf/test_sort_perf.c b/src/test/modules/test_sort_perf/test_sort_perf.c
new file mode 100644
index 0000000000..d162e42049
--- /dev/null
+++ b/src/test/modules/test_sort_perf/test_sort_perf.c
@@ -0,0 +1,90 @@
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "catalog/index.h"
+#include "catalog/pg_collation_d.h"
+#include "catalog/pg_type_d.h"
+#include "catalog/pg_opfamily_d.h"
+#include "miscadmin.h"
+#include "portability/instr_time.h"
+#include "storage/itemptr.h"
+#include "utils/lsyscache.h"
+
+#include <stdlib.h>
+
+// standard comparators
+
+// comparator for qsort_arg() copied from nbtutils.c
+static int
+btint4fastcmp(const void * x, const void * y)
+{
+ int32 *a = (int32 *) x;
+ int32 *b = (int32 *) y;
+
+ if (*a > *b)
+ return 1;
+ else if (*a == *b)
+ return 0;
+ else
+ return -1;
+}
+
+// specialized qsort with inlined comparator
+#define ST_SORT qsort_int32
+#define ST_ELEMENT_TYPE Datum
+#define ST_COMPARE(a, b) (btint4fastcmp(a, b))
+#define ST_SCOPE static
+#define ST_DEFINE
+#define ST_DECLARE
+#include "lib/sort_template.h"
+
+// SQL-callable comparators
+
+typedef struct BTSortArrayContext
+{
+ FmgrInfo flinfo;
+ Oid collation;
+ bool reverse;
+} BTSortArrayContext;
+
+// comparator for qsort arg
+static int
+_bt_compare_array_elements(const void *a, const void *b, void *arg)
+{
+ Datum da = *((const Datum *) a);
+ Datum db = *((const Datum *) b);
+ BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
+ int32 compare;
+
+ compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
+ cxt->collation,
+ da, db));
+ if (cxt->reverse)
+ INVERT_COMPARE_RESULT(compare);
+ return compare;
+}
+
+/* Define a specialized sort function for _bt_sort_array_elements. */
+#define ST_SORT qsort_bt_array_elements
+#define ST_ELEMENT_TYPE Datum
+#define ST_COMPARE(a, b, cxt) \
+ DatumGetInt32(FunctionCall2Coll(&cxt->flinfo, cxt->collation, *a, *b))
+#define ST_COMPARE_ARG_TYPE BTSortArrayContext
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
+
+PG_MODULE_MAGIC;
+
+/* include the test suites */
+#include "test_sort_cmp_weight_include.c"
+
+PG_FUNCTION_INFO_V1(test_sort_cmp_weight);
+Datum
+test_sort_cmp_weight(PG_FUNCTION_ARGS)
+{
+ const int32 nobjects = PG_GETARG_INT32(0);
+
+ do_sort_cmp_weight(nobjects);
+ PG_RETURN_NULL();
+}
diff --git a/src/test/modules/test_sort_perf/test_sort_perf.control b/src/test/modules/test_sort_perf/test_sort_perf.control
new file mode 100644
index 0000000000..336cb0c5ba
--- /dev/null
+++ b/src/test/modules/test_sort_perf/test_sort_perf.control
@@ -0,0 +1,4 @@
+comment = 'test'
+default_version = '1.0'
+module_pathname = '$libdir/test_sort_perf'
+relocatable = true
--
2.36.1
v1-0004-Optimize-sorts-on-likely-reversed-input.patchapplication/x-patch; name=v1-0004-Optimize-sorts-on-likely-reversed-input.patchDownload
From e4ad1dba29aae41f741020288008f9b0866a381d Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Mon, 30 May 2022 14:19:19 +0700
Subject: [PATCH v1 4/5] Optimize sorts on likely reversed input
If we have nine pivot candidates, and they are in reversed order,
preemptively reverse the whole sequence and treat it as likely sorted.
---
src/include/lib/sort_template.h | 29 ++++++++++++++++++++++---
src/test/regress/expected/tuplesort.out | 2 +-
2 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/src/include/lib/sort_template.h b/src/include/lib/sort_template.h
index 492ec2823b..ed21af297c 100644
--- a/src/include/lib/sort_template.h
+++ b/src/include/lib/sort_template.h
@@ -59,6 +59,7 @@
* - Remove __inline, _DIAGASSERTs, __P
* - Take care to recurse on the smaller partition, to bound stack usage
* - Convert into a header that can generate specialized functions
+ * - Add optimization for likely reversed input
*
* IDENTIFICATION
* src/include/lib/sort_template.h
@@ -246,7 +247,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
/*
* Find the median of three values, and count the number of out-of-order
* elements. We don't care to keep an accurate count, only that we count
- * report 0 only if the elements are presorted.
+ * report 0 if the elements are presorted and 3 if they are in reverse order.
*
* Currently, performance seems to be best
* if the comparator is inlined here, but the med3 function is not inlined
@@ -265,10 +266,10 @@ ST_MED3(ST_ELEMENT_TYPE * a,
(*num_reversed)++;
if (DO_COMPARE(c, a) < 0)
{
- /* (*num_reversed)++; WIP */
+ (*num_reversed)++;
if (DO_COMPARE(c, b) < 0)
{
- /* (*num_reversed)++; WIP */
+ (*num_reversed)++;
return b;
}
else
@@ -419,6 +420,28 @@ loop:
pm = DO_MED3(pl, pm, pn, &num_reversed);
}
+ /* if the pivot candidates were in reverse order, reverse the whole sequence */
+ if (num_reversed == 4 * 3) // 4 calls of MED3
+ {
+ ST_POINTER_TYPE * i = a;
+ ST_POINTER_TYPE * j = a + (n - 1) * ST_POINTER_STEP;
+
+ Assert(n > ST_THRESHOLD_MED9);
+ while (i < j)
+ {
+ DO_SWAP(i, j);
+ DO_CHECK_FOR_INTERRUPTS();
+ i += ST_POINTER_STEP;
+ j -= ST_POINTER_STEP;
+ }
+
+ /* reverse pivot position */
+ pm = (a + (n - 1) * ST_POINTER_STEP) - (pm - a);
+
+ /* hint that the sequence is now likely sorted */
+ num_reversed = 0;
+ }
+
// If the sequence was decently balanced and already partitioned,
// and the pivot candidates were presorted, try to use insertion sort.
if (already_partitioned && balanced && num_reversed == 0)
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index a1a5889756..4bdd466a09 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -260,8 +260,8 @@ FROM abbrev_abort_uuids
ORDER BY ctid LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
- 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001
19999 | 00000000-0000-0000-0000-000000019998 | 00000000-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002
20009 | 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003 | 00009988-0000-0000-0000-000000019997 | 00000003-0000-0000-0000-000000000003
--
2.36.1
v1-0005-Replace-qsort-s-partitioning-scheme.patchapplication/x-patch; name=v1-0005-Replace-qsort-s-partitioning-scheme.patchDownload
From 60b450c7e7eec0a0cf585acf9d41644f6a5d794c Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Mon, 30 May 2022 18:25:23 +0700
Subject: [PATCH v1 5/5] Replace qsort's partitioning scheme
Our previous qsort algorithm is described in the 1993 paper "Engineering
a Sort Function". It's solution to the "Dutch national flag problem" for
handling duplicates is to swap elements equal to the pivot to the edges
of the partition, and after partitioning swaps them to the middle. This
has the disadvantage that every element needs to be checked for equality
to the pivot before swapping, which is a wasted cost when there are
no duplicates.
Instead, use the asymetric partitioning scheme from "pattern-defeating
quicksort", described in the 2021 paper with the same name by Orson
Peters. There are two partition functions, "partition right" and
"partition left". Partition right puts elements equal or greater than
the pivot on the right side and is the default method. Partition left is
only used when the sequence follows a previous partition pivot (i.e. is
not the leftmost subsequence) and the current pivot is equal to that
previous pivot. In that case, elements equal to the pivot are swapped
left. Since all elements on the left side are in their proper place,
no further work is needed on the left partition.
---
src/include/lib/sort_template.h | 259 ++++++++++++------
.../modules/test_sort_perf/test_sort_perf.c | 8 +-
src/test/regress/expected/create_index.out | 2 +-
src/test/regress/expected/groupingsets.out | 34 +--
src/test/regress/expected/inet.out | 4 +-
src/test/regress/expected/merge.out | 12 +-
src/test/regress/expected/sqljson.out | 4 +-
src/test/regress/expected/tsrf.out | 104 +++----
src/test/regress/expected/tuplesort.out | 6 +-
src/test/regress/expected/window.out | 108 ++++----
10 files changed, 308 insertions(+), 233 deletions(-)
diff --git a/src/include/lib/sort_template.h b/src/include/lib/sort_template.h
index ed21af297c..cf9794a9fa 100644
--- a/src/include/lib/sort_template.h
+++ b/src/include/lib/sort_template.h
@@ -54,12 +54,10 @@
*
* HISTORY
*
- * Modifications from vanilla NetBSD source:
- * - Add do ... while() macro fix
- * - Remove __inline, _DIAGASSERTs, __P
- * - Take care to recurse on the smaller partition, to bound stack usage
- * - Convert into a header that can generate specialized functions
- * - Add optimization for likely reversed input
+ * - 2002: NetBSD qsort based on J. L. Bentley and M. D. McIlroy,
+ * "Engineering a sort function", SP&E November 1993
+ * - 2021: Convert into a header that can generate specialized functions
+ * - 2022: Pattern-defeating qsort
*
* IDENTIFICATION
* src/include/lib/sort_template.h
@@ -67,41 +65,31 @@
*-------------------------------------------------------------------------
*/
-/* $NetBSD: qsort.c,v 1.13 2003/08/07 16:43:42 agc Exp $ */
-
-/*-
- * Copyright (c) 1992, 1993
- * The Regents of the University of California. All rights reserved.
+/*
+ * Qsort routine based on Orson R. L. Peters, "Pattern-defeating Quicksort",
+ * arXiv:2106.05123 [cs.DS]
+ * https://arxiv.org/pdf/2106.05123.pdf
*
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
+ * C++ implementation by the paper author:
+ * https://github.com/orlp/pdqsort
+ * Rust:
+ * https://docs.rs/pdqsort/1.0.3/src/pdqsort/lib.rs.html
+ * Go:
+ * https://github.com/golang/go/blob/72e77a7f41bbf45d466119444307fd3ae996e257/src/sort/zsortfunc.go
*
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-/*
- * Qsort routine based on J. L. Bentley and M. D. McIlroy,
- * "Engineering a sort function",
- * Software--Practice and Experience 23 (1993) 1249-1265.
+ * Innovations adapted from Go and/or Rust:
+ * - The coding style for the partition functions and partial sort
+ * - Attempt partial insertion sort on each individual partition after
+ * recursion/iteration instead of before, and also speculatively at the
+ * root level
+ * - Optimization for likely reversed input
+ *
+ * Our implementation differs from the original C++ as follows:
+ * - We retain our previous pivot candidate selection
+ * - No swapping of pivot candidates while computing medians
+ * - No attempt to break up patterns on bad partitions
+ * - No BlockQuicksort optimizations
+ * - No fallback to heap sort
*
* We have modified their original by recursing on the smaller partition and iterating on the larger one,
* which ensures we cannot recurse more than log(N) levels (since the
@@ -190,6 +178,8 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
/* sort private helper functions */
#define ST_MED3 ST_MAKE_NAME(ST_SORT, med3)
#define ST_PARTIAL_INSERTION_SORT ST_MAKE_NAME(ST_SORT, insertion_sort_partial)
+#define ST_PARTITION_LEFT ST_MAKE_NAME(ST_SORT, partition_left)
+#define ST_PARTITION_RIGHT ST_MAKE_NAME(ST_SORT, partition_right)
#define ST_SORT_INTERNAL ST_MAKE_NAME(ST_SORT, internal)
#define ST_SWAP ST_MAKE_NAME(ST_SORT, swap)
#define ST_SWAPN ST_MAKE_NAME(ST_SORT, swapn)
@@ -221,8 +211,18 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
ST_SORT_INVOKE_ELEMENT_SIZE \
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
-#define DO_SORT(a_, n_, bal_, ap_) \
- ST_SORT_INTERNAL((a_), (n_), (bal_), (ap_) \
+#define DO_PARTITION_LEFT(begin_, end_) \
+ ST_PARTITION_LEFT((begin_), (end_) \
+ ST_SORT_INVOKE_ELEMENT_SIZE \
+ ST_SORT_INVOKE_COMPARE \
+ ST_SORT_INVOKE_ARG)
+#define DO_PARTITION_RIGHT(begin_, end_, ap_) \
+ ST_PARTITION_RIGHT((begin_), (end_), (ap_) \
+ ST_SORT_INVOKE_ELEMENT_SIZE \
+ ST_SORT_INVOKE_COMPARE \
+ ST_SORT_INVOKE_ARG)
+#define DO_SORT(a_, n_, l_, bal_, ap_) \
+ ST_SORT_INTERNAL((a_), (n_), (l_), (bal_), (ap_) \
ST_SORT_INVOKE_ELEMENT_SIZE \
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
@@ -365,11 +365,110 @@ ST_PARTIAL_INSERTION_SORT(ST_POINTER_TYPE * begin, size_t n
return false;
}
+// Partitions [begin, end) around pivot *begin. Elements equal
+// to the pivot are put in the right-hand partition. Returns the position of the pivot after
+// partitioning and whether the passed sequence was already correctly partitioned. Assumes the
+// pivot is a median of at least 3 elements and that [begin, end) is at least
+// insertion_sort_threshold long.
+static inline ST_POINTER_TYPE *
+ST_PARTITION_RIGHT(ST_POINTER_TYPE * begin,
+ ST_POINTER_TYPE * end,
+ bool * already_partitioned
+ ST_SORT_PROTO_ELEMENT_SIZE
+ ST_SORT_PROTO_COMPARE
+ ST_SORT_PROTO_ARG)
+{
+ // first and last are inclusive of the elements remaining to be partitioned
+ ST_POINTER_TYPE *pivot = begin;
+ ST_POINTER_TYPE *first = begin + ST_POINTER_STEP;
+ ST_POINTER_TYPE *last = end - ST_POINTER_STEP;
+
+ // Find the first element greater than or equal than the pivot
+ // WIP: if we guaranteed median of three during pivot selection, we can remove the guard
+ while (first <= last && DO_COMPARE(first, pivot) < 0)
+ first += ST_POINTER_STEP;
+
+ // Find the first element strictly smaller than the pivot.
+ while (first <= last && !(DO_COMPARE(last, pivot) < 0))
+ last -= ST_POINTER_STEP;
+
+ // If the pointers crossed, the sequence was already correctly partitioned.
+ if (first > last)
+ *already_partitioned = true;
+ else
+ {
+ *already_partitioned = false;
+
+ DO_SWAP(first, last);
+ first += ST_POINTER_STEP;
+ last -= ST_POINTER_STEP;
+
+ // Keep swapping pairs of elements that are on the wrong side of the pivot. Previously
+ // swapped pairs guard the searches, which is why the first iteration is special-cased
+ // above.
+ for (;;)
+ {
+ while (DO_COMPARE(first, pivot) < 0)
+ first += ST_POINTER_STEP;
+
+ while (!(DO_COMPARE(last, pivot) < 0))
+ last -= ST_POINTER_STEP;
+
+ if (first > last)
+ break;
+
+ DO_SWAP(first, last);
+ first += ST_POINTER_STEP;
+ last -= ST_POINTER_STEP;
+ DO_CHECK_FOR_INTERRUPTS();
+ }
+ }
+
+ // Put the pivot in the right place.
+ DO_SWAP(pivot, last);
+ return last;
+}
+
+// Similar to ST_PARTITION_RIGHT, except elements equal to the pivot are put to the left of
+// the pivot, and it doesn't check or return if the passed sequence was already partitioned.
+static inline ST_POINTER_TYPE *
+ST_PARTITION_LEFT(ST_POINTER_TYPE * begin,
+ ST_POINTER_TYPE * end
+ ST_SORT_PROTO_ELEMENT_SIZE
+ ST_SORT_PROTO_COMPARE
+ ST_SORT_PROTO_ARG)
+{
+ // first and last are inclusive of the elements remaining to be partitioned
+ ST_POINTER_TYPE *pivot = begin;
+ ST_POINTER_TYPE *first = begin + ST_POINTER_STEP;
+ ST_POINTER_TYPE *last = end - ST_POINTER_STEP;
+
+ for (;;)
+ {
+ while (first <= last && !(DO_COMPARE(pivot, first) < 0))
+ first += ST_POINTER_STEP;
+
+ while (first <= last && DO_COMPARE(pivot, last) < 0)
+ last -= ST_POINTER_STEP;
+
+ if (first > last)
+ break;
+
+ DO_SWAP(first, last);
+ first += ST_POINTER_STEP;
+ last -= ST_POINTER_STEP;
+ DO_CHECK_FOR_INTERRUPTS();
+ }
+
+ return first;
+}
+
/*
* Workhorse for ST_SORT
*/
static void
ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n,
+ bool leftmost,
bool balanced,
bool already_partitioned
ST_SORT_PROTO_ELEMENT_SIZE
@@ -377,10 +476,7 @@ ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n,
ST_SORT_PROTO_ARG)
{
ST_POINTER_TYPE *a = data,
- *pa,
- *pb,
- *pc,
- *pd,
+ *pivot_pos,
*pl,
*pm,
*pn;
@@ -388,8 +484,7 @@ ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n,
d2,
n1,
n2;
- int r,
- num_reversed;
+ int num_reversed;
loop:
DO_CHECK_FOR_INTERRUPTS();
@@ -397,7 +492,7 @@ loop:
{
for (pm = a + ST_POINTER_STEP; pm < a + n * ST_POINTER_STEP;
pm += ST_POINTER_STEP)
- for (pl = pm; pl > a && DO_COMPARE(pl - ST_POINTER_STEP, pl) > 0;
+ for (pl = pm; pl > a && DO_COMPARE(pl, pl - ST_POINTER_STEP) < 0;
pl -= ST_POINTER_STEP)
DO_SWAP(pl, pl - ST_POINTER_STEP);
return;
@@ -452,46 +547,23 @@ loop:
/* put the pivot at the beginning */
DO_SWAP(a, pm);
- pa = pb = a + ST_POINTER_STEP;
- pc = pd = a + (n - 1) * ST_POINTER_STEP;
- for (;;)
+
+ // If *(a - 1) is the pivot of a previous partition operation
+ // there is no element in [a, pn) that is smaller than *(a - 1). Then if our
+ // pivot compares equal to *(a - 1) we change strategy, putting equal elements in
+ // the left partition, greater elements in the right partition. We do not have to
+ // recurse on the left partition, since it's sorted (all equal).
+ pn = a + n * ST_POINTER_STEP;
+ if (!leftmost && !(DO_COMPARE(a - ST_POINTER_STEP, a) < 0))
{
- while (pb <= pc && (r = DO_COMPARE(pb, a)) <= 0)
- {
- if (r == 0)
- {
- DO_SWAP(pa, pb);
- already_partitioned = false;
- pa += ST_POINTER_STEP;
- }
- pb += ST_POINTER_STEP;
- DO_CHECK_FOR_INTERRUPTS();
- }
- while (pb <= pc && (r = DO_COMPARE(pc, a)) >= 0)
- {
- if (r == 0)
- {
- DO_SWAP(pc, pd);
- already_partitioned = false;
- pd -= ST_POINTER_STEP;
- }
- pc -= ST_POINTER_STEP;
- DO_CHECK_FOR_INTERRUPTS();
- }
- if (pb > pc)
- break;
- DO_SWAP(pb, pc);
- already_partitioned = false;
- pb += ST_POINTER_STEP;
- pc -= ST_POINTER_STEP;
+ a = DO_PARTITION_LEFT(a, pn);
+ n = (pn - a) / ST_POINTER_STEP;
+ goto loop;
}
- pn = a + n * ST_POINTER_STEP;
- d1 = Min(pa - a, pb - pa);
- DO_SWAPN(a, pb - d1, d1);
- d1 = Min(pd - pc, pn - pd - ST_POINTER_STEP);
- DO_SWAPN(pb, pn - d1, d1);
- d1 = pb - pa;
- d2 = pd - pc;
+
+ pivot_pos = DO_PARTITION_RIGHT(a, pn, &already_partitioned);
+ d1 = pivot_pos - a;
+ d2 = pn - (pivot_pos + ST_POINTER_STEP);
n1 = d1 / ST_POINTER_STEP;
n2 = d2 / ST_POINTER_STEP;
if (d1 <= d2)
@@ -499,13 +571,14 @@ loop:
/* Recurse on left partition, then iterate on right partition */
balanced = n1 >= n / 8;
if (d1 > ST_POINTER_STEP)
- DO_SORT(a, n1, balanced, already_partitioned);
+ DO_SORT(a, n1, leftmost, balanced, already_partitioned);
if (d2 > ST_POINTER_STEP)
{
/* Iterate rather than recurse to save stack space */
- /* DO_SORT(pn - d2, n2, ...) */
+ /* DO_SORT(pn - d2, n2, false, ...) */
a = pn - d2;
n = n2;
+ leftmost = false;
goto loop;
}
}
@@ -514,11 +587,11 @@ loop:
/* Recurse on right partition, then iterate on left partition */
balanced = n2 >= n / 8;
if (d2 > ST_POINTER_STEP)
- DO_SORT(pn - d2, n2, balanced, already_partitioned);
+ DO_SORT(pn - d2, n2, false, balanced, already_partitioned);
if (d1 > ST_POINTER_STEP)
{
/* Iterate rather than recurse to save stack space */
- /* DO_SORT(a, n1, ...) */
+ /* DO_SORT(a, n1, leftmost, ...) */
n = n1;
goto loop;
}
@@ -536,7 +609,7 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
{
ST_POINTER_TYPE *begin = (ST_POINTER_TYPE *) data;
- DO_SORT(begin, n, true, true);
+ DO_SORT(begin, n, true, true, true);
#ifdef USE_ASSERT_CHECKING
/* WIP: verify the sorting worked */
@@ -554,6 +627,8 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
#undef DO_COMPARE
#undef DO_MED3
#undef DO_PARTIAL_INSERTION_SORT
+#undef DO_PARTITION_LEFT
+#undef DO_PARTITION_RIGHT
#undef DO_SORT
#undef DO_SWAP
#undef DO_SWAPN
@@ -569,6 +644,8 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
#undef ST_MAKE_PREFIX
#undef ST_MED3
#undef ST_PARTIAL_INSERTION_SORT
+#undef ST_PARTITION_LEFT
+#undef ST_PARTITION_RIGHT
#undef ST_POINTER_STEP
#undef ST_POINTER_TYPE
#undef ST_SCOPE
diff --git a/src/test/modules/test_sort_perf/test_sort_perf.c b/src/test/modules/test_sort_perf/test_sort_perf.c
index d162e42049..1e56dfe8b0 100644
--- a/src/test/modules/test_sort_perf/test_sort_perf.c
+++ b/src/test/modules/test_sort_perf/test_sort_perf.c
@@ -21,12 +21,10 @@ btint4fastcmp(const void * x, const void * y)
int32 *a = (int32 *) x;
int32 *b = (int32 *) y;
- if (*a > *b)
- return 1;
- else if (*a == *b)
- return 0;
- else
+ if (*a < *b)
return -1;
+ else
+ return 1;
}
// specialized qsort with inlined comparator
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 0a544e7167..e14f269ff9 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -172,8 +172,8 @@ SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
(10,10)
(-5,-12)
(5.1,34.5)
- (Infinity,1e+300)
(1e+300,Infinity)
+ (Infinity,1e+300)
(NaN,NaN)
(11 rows)
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..5c2605c280 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -1938,21 +1938,21 @@ set jit_above_cost = 0;
explain (costs off)
select g100, g10, sum(g::numeric), count(*), max(g::text)
from gs_data_1 group by cube (g1000, g100,g10);
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+-----------------------------------
GroupAggregate
- Group Key: g1000, g100, g10
- Group Key: g1000, g100
- Group Key: g1000
+ Group Key: g10, g100
+ Group Key: g10
Group Key: ()
- Sort Key: g100, g10
- Group Key: g100, g10
+ Sort Key: g100, g1000, g10
+ Group Key: g100, g1000, g10
+ Group Key: g100, g1000
Group Key: g100
- Sort Key: g10, g1000
- Group Key: g10, g1000
- Group Key: g10
+ Sort Key: g1000, g10
+ Group Key: g1000, g10
+ Group Key: g1000
-> Sort
- Sort Key: g1000, g100, g10
+ Sort Key: g10, g100
-> Seq Scan on gs_data_1
(14 rows)
@@ -1968,13 +1968,13 @@ from gs_data_1 group by cube (g1000, g100,g10);
QUERY PLAN
------------------------------
MixedAggregate
- Hash Key: g1000, g100, g10
- Hash Key: g1000, g100
- Hash Key: g1000
- Hash Key: g100, g10
- Hash Key: g100
- Hash Key: g10, g1000
+ Hash Key: g10, g100
Hash Key: g10
+ Hash Key: g100, g1000, g10
+ Hash Key: g100, g1000
+ Hash Key: g100
+ Hash Key: g1000, g10
+ Hash Key: g1000
Group Key: ()
-> Seq Scan on gs_data_1
(10 rows)
diff --git a/src/test/regress/expected/inet.out b/src/test/regress/expected/inet.out
index d5bf9e2aaa..0885578584 100644
--- a/src/test/regress/expected/inet.out
+++ b/src/test/regress/expected/inet.out
@@ -390,9 +390,9 @@ SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY i;
c | i
-------------+-------------
10.0.0.0/8 | 9.1.2.3/8
- 10.0.0.0/32 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
10.1.0.0/16 | 10.1.2.3/16
10.1.2.0/24 | 10.1.2.3/24
10.1.2.3/32 | 10.1.2.3
@@ -538,9 +538,9 @@ SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY i;
c | i
-------------+-------------
10.0.0.0/8 | 9.1.2.3/8
- 10.0.0.0/32 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
10.1.0.0/16 | 10.1.2.3/16
10.1.2.0/24 | 10.1.2.3/24
10.1.2.3/32 | 10.1.2.3
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index af670e28e7..cda5e375c3 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -1554,14 +1554,14 @@ SELECT * FROM pa_target ORDER BY tid;
5 | 500 | initial
5 | 50 | inserted by merge
6 | 60 | inserted by merge
- 7 | 700 | initial
7 | 70 | inserted by merge
+ 7 | 700 | initial
8 | 80 | inserted by merge
9 | 90 | inserted by merge
9 | 900 | initial
10 | 100 | inserted by merge
- 11 | 1100 | initial
11 | 110 | inserted by merge
+ 11 | 1100 | initial
12 | 120 | inserted by merge
13 | 1300 | initial
13 | 130 | inserted by merge
@@ -1583,8 +1583,8 @@ SELECT * FROM pa_target ORDER BY tid;
-----+---------+--------------------------
2 | 110 | initial updated by merge
2 | 20 | inserted by merge
- 4 | 40 | inserted by merge
4 | 330 | initial updated by merge
+ 4 | 40 | inserted by merge
6 | 550 | initial updated by merge
6 | 60 | inserted by merge
8 | 80 | inserted by merge
@@ -1665,11 +1665,11 @@ SELECT * FROM pa_target ORDER BY tid;
7 | 700 | initial
7 | 70 | inserted by merge
8 | 80 | inserted by merge
- 9 | 900 | initial
9 | 90 | inserted by merge
+ 9 | 900 | initial
10 | 100 | inserted by merge
- 11 | 110 | inserted by merge
11 | 1100 | initial
+ 11 | 110 | inserted by merge
12 | 120 | inserted by merge
13 | 1300 | initial
13 | 130 | inserted by merge
@@ -1691,8 +1691,8 @@ SELECT * FROM pa_target ORDER BY tid;
-----+---------+--------------------------
2 | 110 | initial updated by merge
2 | 20 | inserted by merge
- 4 | 40 | inserted by merge
4 | 330 | initial updated by merge
+ 4 | 40 | inserted by merge
6 | 550 | initial updated by merge
6 | 60 | inserted by merge
8 | 80 | inserted by merge
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 0883261535..224fc30e8a 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -873,12 +873,12 @@ FROM
(VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar);
bar | json_arrayagg
-----+---------------
+ 2 | [4, 4]
4 | [4, 4]
4 | [4, 4]
- 2 | [4, 4]
5 | [5, 3, 5]
- 3 | [5, 3, 5]
1 | [5, 3, 5]
+ 3 | [5, 3, 5]
5 | [5, 3, 5]
|
|
diff --git a/src/test/regress/expected/tsrf.out b/src/test/regress/expected/tsrf.out
index ef3bd4f539..e76ddd11cd 100644
--- a/src/test/regress/expected/tsrf.out
+++ b/src/test/regress/expected/tsrf.out
@@ -349,108 +349,108 @@ SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(d
dataa | b | g | count
-------+-----+---+-------
b | | 1 | 1
- a | foo | 1 | 1
a | | 1 | 2
- b | bar | 1 | 1
+ a | foo | 1 | 1
+ | foo | 1 | 1
a | bar | 1 | 1
- | | 1 | 3
| bar | 1 | 2
- | foo | 1 | 1
- | bar | 2 | 2
+ b | bar | 1 | 1
+ | | 1 | 3
a | bar | 2 | 1
+ a | | 2 | 2
+ b | bar | 2 | 1
+ | | 2 | 3
b | | 2 | 1
+ | bar | 2 | 2
a | foo | 2 | 1
| foo | 2 | 1
- a | | 2 | 2
- | | 2 | 3
- b | bar | 2 | 1
(16 rows)
SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g);
dataa | b | g | count
-------+-----+---+-------
+ | bar | 1 | 2
+ | foo | 1 | 1
+ | | 1 | 3
+ | bar | 2 | 2
+ | foo | 2 | 1
+ | | 2 | 3
+ | | | 6
a | bar | 1 | 1
a | bar | 2 | 1
a | bar | | 2
- a | foo | 1 | 1
- a | foo | 2 | 1
- a | foo | | 2
- a | | | 4
b | bar | 1 | 1
b | bar | 2 | 1
b | bar | | 2
- b | | | 2
- | | | 6
- | bar | 1 | 2
- | bar | 2 | 2
| bar | | 4
- | foo | 1 | 1
- | foo | 2 | 1
+ a | foo | 1 | 1
+ a | foo | 2 | 1
+ a | foo | | 2
| foo | | 2
a | | 1 | 2
- b | | 1 | 1
- | | 1 | 3
a | | 2 | 2
+ a | | | 4
+ b | | 1 | 1
b | | 2 | 1
- | | 2 | 3
+ b | | | 2
(24 rows)
SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY dataa;
dataa | b | g | count
-------+-----+---+-------
- a | foo | 1 | 1
a | foo | | 2
- a | | | 4
- a | | 1 | 2
+ a | foo | 2 | 1
a | | 2 | 2
- a | bar | 2 | 1
- a | bar | 1 | 1
+ a | | 1 | 2
a | bar | | 2
- a | foo | 2 | 1
- b | bar | | 2
- b | | 1 | 1
- b | bar | 1 | 1
+ a | | | 4
+ a | foo | 1 | 1
+ a | bar | 1 | 1
+ a | bar | 2 | 1
b | | | 2
- b | | 2 | 1
+ b | bar | 1 | 1
b | bar | 2 | 1
- | bar | 1 | 2
- | | | 6
- | bar | 2 | 2
- | bar | | 4
- | foo | 1 | 1
- | foo | 2 | 1
+ b | bar | | 2
+ b | | 2 | 1
+ b | | 1 | 1
| foo | | 2
- | | 1 | 3
+ | foo | 1 | 1
| | 2 | 3
+ | bar | 2 | 2
+ | | 1 | 3
+ | foo | 2 | 1
+ | | | 6
+ | bar | | 4
+ | bar | 1 | 2
(24 rows)
SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY g;
dataa | b | g | count
-------+-----+---+-------
| bar | 1 | 2
+ a | | 1 | 2
+ | | 1 | 3
a | foo | 1 | 1
- b | bar | 1 | 1
- a | bar | 1 | 1
| foo | 1 | 1
- a | | 1 | 2
+ b | bar | 1 | 1
b | | 1 | 1
- | | 1 | 3
+ a | bar | 1 | 1
+ a | bar | 2 | 1
| foo | 2 | 1
- b | | 2 | 1
- | bar | 2 | 2
| | 2 | 3
b | bar | 2 | 1
- a | bar | 2 | 1
- a | foo | 2 | 1
+ b | | 2 | 1
a | | 2 | 2
- a | foo | | 2
+ | bar | 2 | 2
+ a | foo | 2 | 1
b | bar | | 2
- b | | | 2
- | | | 6
a | | | 4
- a | bar | | 2
+ a | foo | | 2
| bar | | 4
| foo | | 2
+ a | bar | | 2
+ | | | 6
+ b | | | 2
(24 rows)
reset enable_hashagg;
@@ -598,8 +598,8 @@ FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b);
a | b | g
---+---+---
1 | 4 | 1
- 5 | 1 | 2
- 5 | 3 | 3
+ 3 | 2 | 2
+ 1 | 4 | 3
(3 rows)
-- LIMIT / OFFSET is evaluated after SRF evaluation
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 4bdd466a09..8b9f64fa65 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -260,8 +260,8 @@ FROM abbrev_abort_uuids
ORDER BY ctid LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
- 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001
19999 | 00000000-0000-0000-0000-000000019998 | 00000000-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002
20009 | 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003 | 00009988-0000-0000-0000-000000019997 | 00000003-0000-0000-0000-000000000003
@@ -304,8 +304,8 @@ FROM abbrev_abort_uuids
ORDER BY ctid DESC LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
- 0 | | | |
20002 | | | |
+ 0 | | | |
20003 | | | |
10009 | 00000000-0000-0000-0000-000000010008 | 00000000-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992
10008 | 00000000-0000-0000-0000-000000010007 | 00000000-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993
@@ -335,8 +335,8 @@ FROM abbrev_abort_uuids
ORDER BY ctid DESC LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
- 0 | | | |
20003 | | | |
+ 0 | | | |
20002 | | | |
9993 | 00000000-0000-0000-0000-000000009992 | 00000000-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008
9994 | 00000000-0000-0000-0000-000000009993 | 00000000-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index c4fc72df93..81f1d250da 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -23,13 +23,13 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
-----------+-------+--------+-------
develop | 7 | 4200 | 25100
develop | 9 | 4500 | 25100
- develop | 10 | 5200 | 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 | 3 | 4800 | 14600
sales | 1 | 5000 | 14600
(10 rows)
@@ -43,8 +43,8 @@ SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary
develop | 8 | 6000 | 5
personnel | 5 | 3500 | 1
personnel | 2 | 3900 | 2
- sales | 3 | 4800 | 1
sales | 4 | 4800 | 1
+ sales | 3 | 4800 | 1
sales | 1 | 5000 | 3
(10 rows)
@@ -79,15 +79,15 @@ SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PA
depname | empno | salary | sum
-----------+-------+--------+-------
develop | 7 | 4200 | 25100
- develop | 10 | 5200 | 25100
- develop | 9 | 4500 | 25100
- develop | 8 | 6000 | 25100
develop | 11 | 5200 | 25100
- personnel | 5 | 3500 | 7400
+ develop | 8 | 6000 | 25100
+ develop | 9 | 4500 | 25100
+ develop | 10 | 5200 | 25100
personnel | 2 | 3900 | 7400
+ personnel | 5 | 3500 | 7400
+ sales | 4 | 4800 | 14600
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;
@@ -97,11 +97,11 @@ SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITI
develop | 7 | 4200 | 1
sales | 3 | 4800 | 1
sales | 4 | 4800 | 1
- personnel | 2 | 3900 | 2
develop | 9 | 4500 | 2
- sales | 1 | 5000 | 3
- develop | 10 | 5200 | 3
+ personnel | 2 | 3900 | 2
develop | 11 | 5200 | 3
+ develop | 10 | 5200 | 3
+ sales | 1 | 5000 | 3
develop | 8 | 6000 | 5
(10 rows)
@@ -804,8 +804,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
45 | 8 | 0
- 45 | 4 | 0
45 | 0 | 0
+ 45 | 4 | 0
33 | 5 | 1
33 | 9 | 1
33 | 1 | 1
@@ -922,9 +922,9 @@ SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 f
FROM tenk1 WHERE unique1 < 10;
first_value | unique1 | four
-------------+---------+------
- 4 | 8 | 0
- 0 | 4 | 0
- 5 | 0 | 0
+ 0 | 8 | 0
+ 4 | 0 | 0
+ 5 | 4 | 0
9 | 5 | 1
1 | 9 | 1
6 | 1 | 1
@@ -940,8 +940,8 @@ FROM tenk1 WHERE unique1 < 10;
first_value | unique1 | four
-------------+---------+------
| 8 | 0
- 5 | 4 | 0
5 | 0 | 0
+ 5 | 4 | 0
| 5 | 1
6 | 9 | 1
6 | 1 | 1
@@ -957,8 +957,8 @@ FROM tenk1 WHERE unique1 < 10;
first_value | unique1 | four
-------------+---------+------
8 | 8 | 0
- 4 | 4 | 0
0 | 0 | 0
+ 4 | 4 | 0
5 | 5 | 1
9 | 9 | 1
1 | 1 | 1
@@ -973,9 +973,9 @@ SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 fo
FROM tenk1 WHERE unique1 < 10;
last_value | unique1 | four
------------+---------+------
- 0 | 8 | 0
- 5 | 4 | 0
- 9 | 0 | 0
+ 4 | 8 | 0
+ 5 | 0 | 0
+ 9 | 4 | 0
1 | 5 | 1
6 | 9 | 1
2 | 1 | 1
@@ -991,8 +991,8 @@ FROM tenk1 WHERE unique1 < 10;
last_value | unique1 | four
------------+---------+------
| 8 | 0
- 5 | 4 | 0
- 9 | 0 | 0
+ 5 | 0 | 0
+ 9 | 4 | 0
| 5 | 1
6 | 9 | 1
2 | 1 | 1
@@ -1008,8 +1008,8 @@ FROM tenk1 WHERE unique1 < 10;
last_value | unique1 | four
------------+---------+------
8 | 8 | 0
- 5 | 4 | 0
- 9 | 0 | 0
+ 5 | 0 | 0
+ 9 | 4 | 0
5 | 5 | 1
6 | 9 | 1
2 | 1 | 1
@@ -1076,8 +1076,8 @@ FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
45 | 8 | 0
- 45 | 4 | 0
45 | 0 | 0
+ 45 | 4 | 0
33 | 5 | 1
33 | 9 | 1
33 | 1 | 1
@@ -1093,8 +1093,8 @@ FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
4 | 8 | 0
- 8 | 4 | 0
12 | 0 | 0
+ 8 | 4 | 0
22 | 5 | 1
18 | 9 | 1
26 | 1 | 1
@@ -1110,8 +1110,8 @@ FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
| 8 | 0
- | 4 | 0
| 0 | 0
+ | 4 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1127,8 +1127,8 @@ FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
8 | 8 | 0
- 4 | 4 | 0
0 | 0 | 0
+ 4 | 4 | 0
17 | 5 | 1
21 | 9 | 1
13 | 1 | 1
@@ -1145,9 +1145,9 @@ 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
-------------+-------+------------+---------+------
- 8 | 4 | 7 | 8 | 0
- 8 | 4 | 7 | 4 | 0
- 8 | 4 | 7 | 0 | 0
+ 8 | 0 | 7 | 8 | 0
+ 8 | 0 | 7 | 0 | 0
+ 8 | 0 | 7 | 4 | 0
5 | 9 | 7 | 5 | 1
5 | 9 | 7 | 9 | 1
5 | 9 | 7 | 1 | 1
@@ -1350,8 +1350,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
| 8 | 0
- | 4 | 0
| 0 | 0
+ | 4 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1370,9 +1370,9 @@ FROM tenk1 WHERE unique1 < 10;
| 7 | 3
10 | 6 | 2
10 | 2 | 2
+ 18 | 1 | 1
18 | 9 | 1
18 | 5 | 1
- 18 | 1 | 1
23 | 8 | 0
23 | 4 | 0
23 | 0 | 0
@@ -1384,8 +1384,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
| 8 | 0
- | 4 | 0
| 0 | 0
+ | 4 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1401,8 +1401,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
| 8 | 0
- | 4 | 0
| 0 | 0
+ | 4 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1418,8 +1418,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
| 8 | 0
- | 4 | 0
| 0 | 0
+ | 4 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1435,8 +1435,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
| 8 | 0
- | 4 | 0
| 0 | 0
+ | 4 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -1452,8 +1452,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
41 | 8 | 0
- 37 | 4 | 0
33 | 0 | 0
+ 37 | 4 | 0
35 | 5 | 1
39 | 9 | 1
31 | 1 | 1
@@ -1469,8 +1469,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
33 | 8 | 0
- 33 | 4 | 0
33 | 0 | 0
+ 33 | 4 | 0
30 | 5 | 1
30 | 9 | 1
30 | 1 | 1
@@ -2589,8 +2589,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
12 | 8 | 0
- 12 | 4 | 0
12 | 0 | 0
+ 12 | 4 | 0
27 | 5 | 1
27 | 9 | 1
27 | 1 | 1
@@ -2606,8 +2606,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
45 | 8 | 0
- 45 | 4 | 0
45 | 0 | 0
+ 45 | 4 | 0
45 | 5 | 1
45 | 9 | 1
45 | 1 | 1
@@ -2623,8 +2623,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
45 | 8 | 0
- 45 | 4 | 0
45 | 0 | 0
+ 45 | 4 | 0
33 | 5 | 1
33 | 9 | 1
33 | 1 | 1
@@ -2640,8 +2640,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
45 | 8 | 0
- 45 | 4 | 0
45 | 0 | 0
+ 45 | 4 | 0
45 | 5 | 1
45 | 9 | 1
45 | 1 | 1
@@ -2657,8 +2657,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
33 | 8 | 0
- 33 | 4 | 0
33 | 0 | 0
+ 33 | 4 | 0
18 | 5 | 1
18 | 9 | 1
18 | 1 | 1
@@ -2674,8 +2674,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
35 | 8 | 0
- 35 | 4 | 0
35 | 0 | 0
+ 35 | 4 | 0
45 | 5 | 1
45 | 9 | 1
45 | 1 | 1
@@ -2691,8 +2691,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
| 8 | 0
- | 4 | 0
| 0 | 0
+ | 4 | 0
12 | 5 | 1
12 | 9 | 1
12 | 1 | 1
@@ -2708,8 +2708,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
27 | 8 | 0
- 27 | 4 | 0
27 | 0 | 0
+ 27 | 4 | 0
35 | 5 | 1
35 | 9 | 1
35 | 1 | 1
@@ -2725,8 +2725,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
12 | 8 | 0
- 12 | 4 | 0
12 | 0 | 0
+ 12 | 4 | 0
15 | 5 | 1
15 | 9 | 1
15 | 1 | 1
@@ -2742,8 +2742,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
19 | 8 | 0
- 23 | 4 | 0
27 | 0 | 0
+ 23 | 4 | 0
30 | 5 | 1
26 | 9 | 1
34 | 1 | 1
@@ -2759,8 +2759,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
15 | 8 | 0
- 15 | 4 | 0
15 | 0 | 0
+ 15 | 4 | 0
20 | 5 | 1
20 | 9 | 1
20 | 1 | 1
@@ -2776,8 +2776,8 @@ FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
23 | 8 | 0
- 19 | 4 | 0
15 | 0 | 0
+ 19 | 4 | 0
25 | 5 | 1
29 | 9 | 1
21 | 1 | 1
@@ -3642,8 +3642,8 @@ WHERE c <= 3;
2 | personnel | 3900 | 1
5 | personnel | 3500 | 2
1 | sales | 5000 | 1
- 4 | sales | 4800 | 3
3 | sales | 4800 | 3
+ 4 | sales | 4800 | 3
(8 rows)
-- Some more complex cases with multiple window clauses
@@ -3685,8 +3685,8 @@ SELECT * FROM
) e WHERE rn <= 1 AND c1 <= 3;
depname | empno | salary | enroll_date | c1 | rn | c2 | c3
-----------+-------+--------+-------------+----+----+----+----
- personnel | 5 | 3500 | 12-10-2007 | 2 | 1 | 2 | 2
- sales | 3 | 4800 | 08-01-2007 | 3 | 1 | 3 | 3
+ personnel | 2 | 3900 | 12-23-2006 | 2 | 1 | 2 | 2
+ sales | 4 | 4800 | 08-08-2007 | 3 | 1 | 3 | 3
(2 rows)
-- Tests to ensure we don't push down the run condition when it's not valid to
--
2.36.1
On Thu, Jun 2, 2022 at 8:33 PM John Naylor <john.naylor@enterprisedb.com> wrote:
Attached is a draft series that implements some but not all features
of pattern-defeating quicksort, namely the ones I thought were
interesting for us. Recently this quicksort variant got committed for
the next release of the Go language 1.19 [1] (which in turn was based
on that of Rust [2]), and that implementation was a helpful additional
example. The bottom line is that replacing the partitioning scheme
this way is likely not worth doing because of our particular use
cases, but along the way I found some other things that might be worth
doing, so some good may come out of this.
What about dual-pivot quicksort, which is used in Java 7+? That is the
defacto successor to Bentley & McIlroy. In fact, Jon Bentley himself
collaborated with its author, and provided benchmarking input. The
underlying philosophy is essentially the same as the original -- it
is supposed to be an "industrial strength" quicksort, with various
adversarial cases considered directly.
See:
https://www.wild-inter.net/publications/wild-2018b.pdf
https://codeblab.com/wp-content/uploads/2009/09/DualPivotQuicksort.pdf
At one point quite a few years back I planned on investigating it
myself, but never followed through.
--
Peter Geoghegan
On Fri, Jun 3, 2022 at 10:44 AM Peter Geoghegan <pg@bowt.ie> wrote:
What about dual-pivot quicksort, which is used in Java 7+? That is the
defacto successor to Bentley & McIlroy. In fact, Jon Bentley himself
collaborated with its author, and provided benchmarking input. The
underlying philosophy is essentially the same as the original -- it
is supposed to be an "industrial strength" quicksort, with various
adversarial cases considered directly.
I had heard of it but not looked into it deeply. I did read that Java
7 uses dual pivot quicksort for primitives and timsort for objects. I
wasn't sure if dual pivot was not good for objects (which could have
possibly-complex comparators) or if timsort was just simply good for
Java's use cases. It seems accessible to try doing, so I'll look into
that.
--
John Naylor
EDB: http://www.enterprisedb.com
On Thu, Jun 2, 2022 at 9:24 PM John Naylor <john.naylor@enterprisedb.com> wrote:
I had heard of it but not looked into it deeply. I did read that Java
7 uses dual pivot quicksort for primitives and timsort for objects. I
wasn't sure if dual pivot was not good for objects (which could have
possibly-complex comparators) or if timsort was just simply good for
Java's use cases. It seems accessible to try doing, so I'll look into
that.
I think that Timsort is most useful with cases where individual
comparisons are inherently very expensive (perhaps even implemented in
Python), which might be common with objects due to the indirection
that Python (and even Java) impose on objects in general.
I would imagine that this is a lot less relevant in
Postgres/tuplesort, because we have optimizations like abbreviated
keys. And because we're mostly just sorting tuples on one or more
attributes of scalar types.
The abbreviated keys optimization is very much something that comes
from the world of databases, not the world of sorting. It's pretty much a
domain-specific technique. That seems relevant to me.
--
Peter Geoghegan
On Fri, Jun 3, 2022 at 10:44 AM Peter Geoghegan <pg@bowt.ie> wrote:
What about dual-pivot quicksort, which is used in Java 7+? That is the
defacto successor to Bentley & McIlroy. In fact, Jon Bentley himself
collaborated with its author, and provided benchmarking input. The
underlying philosophy is essentially the same as the original -- it
is supposed to be an "industrial strength" quicksort, with various
adversarial cases considered directly.
Here is a *rough* first pass at dual-pivot quicksort. I haven't
changed the regression tests to adjust for qsort being an unstable
sort, and there is some dead code. I looked to a couple JDKs for
examples of design decisions as a first approximation. It includes a
module with a few debugging printouts, run like so: select
debug_qsort_int(array[7,6,5,4,3,2,1]);
Pivot selection: A common way is to pick five candidates (here: mid,
+/- 1/7, +/- 2/7), sort them in place, then pick the 2nd and 4th of
them, or "tertile of five". If they are all evenly spaced, we can do
insertion sort.
Fall back to single-pivot: If any of the pivot candidates are equal,
JDK assumes there could be a large number of duplicates and falls back
to single-pivot, using the median of the five. I've done the same
here. Despite having two code paths in all of our template instances,
the VM binary is only about 5kb bigger, since MED3 is no longer built.
Performance testing: Not started yet. I'm thinking an apples-to-apples
comparison is not insightful enough, since the current insertion sort
threshold 7 is already a bit constrained for single-pivot, and would
be even more so for dual pivot, especially since 5 of the elements are
pre-sorted to choose the pivots. My plan is to retest the threshold on
HEAD using my latest tests, then use that as a starting point to test
thresholds with dual-pivot.
--
John Naylor
EDB: http://www.enterprisedb.com
Attachments:
v1-0001-Create-internal-workhorse-for-ST_SORT.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Create-internal-workhorse-for-ST_SORT.patchDownload
From 5e920d9d3e8d2a2a75e63ade8bc73c8322c1934b Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Mon, 30 May 2022 10:09:17 +0700
Subject: [PATCH v1 1/2] Create internal workhorse for ST_SORT
No functional changes.
---
src/include/lib/sort_template.h | 36 ++++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/src/include/lib/sort_template.h b/src/include/lib/sort_template.h
index 3122a93009..54921de568 100644
--- a/src/include/lib/sort_template.h
+++ b/src/include/lib/sort_template.h
@@ -191,6 +191,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
/* sort private helper functions */
#define ST_MED3 ST_MAKE_NAME(ST_SORT, med3)
+#define ST_SORT_INTERNAL ST_MAKE_NAME(ST_SORT, internal)
#define ST_SWAP ST_MAKE_NAME(ST_SORT, swap)
#define ST_SWAPN ST_MAKE_NAME(ST_SORT, swapn)
@@ -217,7 +218,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
#define DO_SORT(a_, n_) \
- ST_SORT((a_), (n_) \
+ ST_SORT_INTERNAL((a_), (n_) \
ST_SORT_INVOKE_ELEMENT_SIZE \
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
@@ -273,15 +274,15 @@ ST_SWAPN(ST_POINTER_TYPE * a, ST_POINTER_TYPE * b, size_t n)
}
/*
- * Sort an array.
+ * Workhorse for ST_SORT
*/
-ST_SCOPE void
-ST_SORT(ST_ELEMENT_TYPE * data, size_t n
+static void
+ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n
ST_SORT_PROTO_ELEMENT_SIZE
ST_SORT_PROTO_COMPARE
ST_SORT_PROTO_ARG)
{
- ST_POINTER_TYPE *a = (ST_POINTER_TYPE *) data,
+ ST_POINTER_TYPE *a = data,
*pa,
*pb,
*pc,
@@ -399,6 +400,30 @@ loop:
}
}
}
+
+/*
+ * Sort an array.
+ */
+ST_SCOPE void
+ST_SORT(ST_ELEMENT_TYPE * data, size_t n
+ ST_SORT_PROTO_ELEMENT_SIZE
+ ST_SORT_PROTO_COMPARE
+ ST_SORT_PROTO_ARG)
+{
+ ST_POINTER_TYPE *begin = (ST_POINTER_TYPE *) data;
+
+ DO_SORT(begin, n);
+
+#ifdef USE_ASSERT_CHECKING
+ /* WIP: verify the sorting worked */
+ for (ST_POINTER_TYPE *pm = begin + ST_POINTER_STEP; pm < begin + n * ST_POINTER_STEP;
+ pm += ST_POINTER_STEP)
+ {
+ if (DO_COMPARE(pm, pm - ST_POINTER_STEP) < 0)
+ Assert(false);
+ }
+#endif /* USE_ASSERT_CHECKING */
+}
#endif
#undef DO_CHECK_FOR_INTERRUPTS
@@ -422,6 +447,7 @@ loop:
#undef ST_POINTER_TYPE
#undef ST_SCOPE
#undef ST_SORT
+#undef ST_SORT_INTERNAL
#undef ST_SORT_INVOKE_ARG
#undef ST_SORT_INVOKE_COMPARE
#undef ST_SORT_INVOKE_ELEMENT_SIZE
--
2.36.1
v1-0002-Implement-dual-pivot-quicksort.patchtext/x-patch; charset=US-ASCII; name=v1-0002-Implement-dual-pivot-quicksort.patchDownload
From ac7f8aaa5b7257fb84f819b27525e00a5a6ced84 Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Thu, 23 Jun 2022 16:07:10 +0700
Subject: [PATCH v1 2/2] Implement dual-pivot quicksort
Choose pivots by running insertion sort on five candidates and choosing
the 2nd and 4th, ("tertile of five"). If any of the five are equal, we
assume the input has many duplicates and fall back to B&M since it's
optimized for that case.
---
src/include/lib/sort_template.h | 191 +++++++++++++++++-
src/test/modules/debug_qsort/Makefile | 18 ++
.../modules/debug_qsort/debug_qsort--1.0.sql | 1 +
src/test/modules/debug_qsort/debug_qsort.c | 70 +++++++
.../modules/debug_qsort/debug_qsort.control | 4 +
5 files changed, 282 insertions(+), 2 deletions(-)
create mode 100644 src/test/modules/debug_qsort/Makefile
create mode 100644 src/test/modules/debug_qsort/debug_qsort--1.0.sql
create mode 100644 src/test/modules/debug_qsort/debug_qsort.c
create mode 100644 src/test/modules/debug_qsort/debug_qsort.control
diff --git a/src/include/lib/sort_template.h b/src/include/lib/sort_template.h
index 54921de568..2e4ef2604d 100644
--- a/src/include/lib/sort_template.h
+++ b/src/include/lib/sort_template.h
@@ -240,6 +240,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
#define DO_SWAP(a_, b_) DO_SWAPN((a_), (b_), element_size)
#endif
+#if 0
/*
* Find the median of three values. Currently, performance seems to be best
* if the comparator is inlined here, but the med3 function is not inlined
@@ -256,6 +257,7 @@ ST_MED3(ST_ELEMENT_TYPE * a,
(DO_COMPARE(b, c) < 0 ? b : (DO_COMPARE(a, c) < 0 ? c : a))
: (DO_COMPARE(b, c) > 0 ? b : (DO_COMPARE(a, c) < 0 ? a : c));
}
+#endif
static inline void
ST_SWAP(ST_POINTER_TYPE * a, ST_POINTER_TYPE * b)
@@ -283,6 +285,18 @@ ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n
ST_SORT_PROTO_ARG)
{
ST_POINTER_TYPE *a = data,
+ *left,
+ *right,
+ *e1,
+ *e2,
+ *e3,
+ *e4,
+ *e5,
+ *pivot1,
+ *pivot2,
+ *less,
+ *great,
+ *k,
*pa,
*pb,
*pc,
@@ -291,9 +305,12 @@ ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n
*pm,
*pn;
size_t d1,
- d2;
+ d2,
+ d3,
+ seventh;
int r,
presorted;
+ bool unique_hint = true;
loop:
DO_CHECK_FOR_INTERRUPTS();
@@ -319,6 +336,7 @@ loop:
}
if (presorted)
return;
+#if 0
pm = a + (n / 2) * ST_POINTER_STEP;
if (n > 7)
{
@@ -334,7 +352,175 @@ loop:
}
pm = DO_MED3(pl, pm, pn);
}
- DO_SWAP(a, pm);
+#endif
+
+ left = a;
+ right = a + (n - 1) * ST_POINTER_STEP;
+
+#ifdef QSORT_DEBUG
+ elog(NOTICE, "start:");
+
+ for (ST_POINTER_TYPE *i = left; i <= right; i += ST_POINTER_STEP)
+ {
+ elog(NOTICE, "%d ", *i);
+ }
+#endif
+
+ // select five pivot candidates spaced equally around the midpoint
+ seventh = n / 7 * ST_POINTER_STEP;
+ e3 = a + (n / 2) * ST_POINTER_STEP;
+ e2 = e3 - seventh;
+ e1 = e2 - seventh;
+ e4 = e3 + seventh;
+ e5 = e4 + seventh;
+
+ // do insertion sort on pivot candidates
+ for (pm = e2; pm <= e5; pm += seventh)
+ for (pl = pm; pl > e1 && (r = DO_COMPARE(pl - seventh, pl)) >= 0;
+ pl -= seventh)
+ {
+ if (r == 0)
+ {
+ /* found two equal candidates */
+ unique_hint = false;
+ break;
+ }
+ else
+ DO_SWAP(pl, pl - seventh);
+ }
+
+#ifdef QSORT_DEBUG
+ elog(NOTICE, "after sorting pivot candidates:");
+ for (ST_POINTER_TYPE *i = left; i <= right; i += ST_POINTER_STEP)
+ {
+ if (i == e1)
+ elog(NOTICE, "%d e1", *i);
+ else if (i == e2)
+ elog(NOTICE, "%d e2", *i);
+ else if (i == e3)
+ elog(NOTICE, "%d e3", *i);
+ else if (i == e4)
+ elog(NOTICE, "%d e4", *i);
+ else if (i == e5)
+ elog(NOTICE, "%d e5", *i);
+ else
+ elog(NOTICE, "%d ", *i);
+ }
+#endif
+
+ /* if the pivot candidates were all unique, use dual-pivot quicksort, otherwise use B&M quicksort since it is faster on inputs with many duplicates */
+ if (unique_hint)
+ {
+ DO_SWAP(e2, left);
+ DO_SWAP(e4, right);
+
+ pivot1 = left;
+ pivot2 = right;
+ less = left + ST_POINTER_STEP;
+ great = right - ST_POINTER_STEP;
+
+#ifdef QSORT_DEBUG
+ elog(NOTICE, "before swap loop:");
+ for (ST_POINTER_TYPE *i = left; i <= right; i += ST_POINTER_STEP)
+ {
+ if (i == pivot1)
+ elog(NOTICE, "%d pivot 1", *i);
+ else if (i == pivot2)
+ elog(NOTICE, "%d pivot 2", *i);
+ else if (i == less && i == great)
+ elog(NOTICE, "%d less/great", *i);
+ else if (i == less)
+ elog(NOTICE, "%d less", *i);
+ else if (i == great)
+ elog(NOTICE, "%d great", *i);
+ else
+ elog(NOTICE, "%d ", *i);
+ }
+#endif
+
+ /*
+ * dual-pivot partitioning
+ *
+ * left part center part right part
+ * +--------------------------------------------------------------+
+ * | < pivot1 | pivot1 <= && <= pivot2 | ? | > pivot2 |
+ * +--------------------------------------------------------------+
+ * ^ ^ ^
+ * | | |
+ * less k great
+ *
+ * Invariants:
+ *
+ * all in (left, less) < pivot1
+ * pivot1 <= all in [less, k) <= pivot2
+ * all in (great, right) > pivot2
+ *
+ * k points to the first element in the "?" part
+ */
+ for (k = less; k <= great; k += ST_POINTER_STEP)
+ {
+ if (DO_COMPARE(k, pivot1) < 0)
+ {
+ if (k > less)
+ DO_SWAP(k, less);
+ less += ST_POINTER_STEP;
+ }
+ else if (DO_COMPARE(k, pivot2) > 0)
+ {
+ while (k < great && DO_COMPARE(great, pivot2) > 0)
+ {
+ great-= ST_POINTER_STEP;
+ DO_CHECK_FOR_INTERRUPTS();
+ }
+
+ DO_SWAP(k, great);
+ great-= ST_POINTER_STEP;
+
+ if (DO_COMPARE(k, pivot1) < 0)
+ {
+ if (k > less)
+ DO_SWAP(k, less);
+ less += ST_POINTER_STEP;
+ }
+ }
+ DO_CHECK_FOR_INTERRUPTS();
+ }
+
+#ifdef QSORT_DEBUG
+ elog(NOTICE, "after swap loop:");
+ for (ST_POINTER_TYPE *i = left; i <= right; i += ST_POINTER_STEP)
+ {
+ if (i == pivot1)
+ elog(NOTICE, "%d pivot 1", *i);
+ else if (i == pivot2)
+ elog(NOTICE, "%d pivot 2", *i);
+ else if (i == less && i == great)
+ elog(NOTICE, "%d less/great", *i);
+ else if (i == less)
+ elog(NOTICE, "%d less", *i);
+ else if (i == great)
+ elog(NOTICE, "%d great", *i);
+ else
+ elog(NOTICE, "%d ", *i);
+ }
+#endif
+
+ DO_SWAP(less - ST_POINTER_STEP, pivot1);
+ DO_SWAP(great + ST_POINTER_STEP, pivot2);
+
+ d1 = (less - ST_POINTER_STEP) - left;
+ d2 = (great + ST_POINTER_STEP) - less;
+ d3 = right - (great + ST_POINTER_STEP);
+
+ // WIP: don't worry about choosing a subarray for iteration for now
+ DO_SORT(left, d1 / ST_POINTER_STEP);
+ DO_SORT(less, d2 / ST_POINTER_STEP);
+ DO_SORT(great + 2 * ST_POINTER_STEP, d3 / ST_POINTER_STEP);
+ }
+ else // B&M
+ {
+ /* use median of five for the pivot */
+ DO_SWAP(a, e3);
pa = pb = a + ST_POINTER_STEP;
pc = pd = a + (n - 1) * ST_POINTER_STEP;
for (;;)
@@ -399,6 +585,7 @@ loop:
goto loop;
}
}
+ }
}
/*
diff --git a/src/test/modules/debug_qsort/Makefile b/src/test/modules/debug_qsort/Makefile
new file mode 100644
index 0000000000..6cb7ecba84
--- /dev/null
+++ b/src/test/modules/debug_qsort/Makefile
@@ -0,0 +1,18 @@
+MODULE_big = debug_qsort
+OBJS = debug_qsort.o
+PGFILEDESC = "test"
+EXTENSION = debug_qsort
+DATA = debug_qsort--1.0.sql
+
+first: all
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/debug_qsort
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/debug_qsort/debug_qsort--1.0.sql b/src/test/modules/debug_qsort/debug_qsort--1.0.sql
new file mode 100644
index 0000000000..9d4399a11c
--- /dev/null
+++ b/src/test/modules/debug_qsort/debug_qsort--1.0.sql
@@ -0,0 +1 @@
+CREATE FUNCTION debug_qsort_int(int[]) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/debug_qsort/debug_qsort.c b/src/test/modules/debug_qsort/debug_qsort.c
new file mode 100644
index 0000000000..0424df03cd
--- /dev/null
+++ b/src/test/modules/debug_qsort/debug_qsort.c
@@ -0,0 +1,70 @@
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "miscadmin.h"
+#include <stdlib.h>
+
+#include "utils/array.h"
+#include "utils/lsyscache.h"
+
+// comparator for qsort_arg()
+// XXX copied from the version in nbtutils.c
+static inline int
+btint4fastcmp(const void * x, const void * y)
+{
+ int32 *a = (int32 *) x;
+ int32 *b = (int32 *) y;
+
+ if (*a > *b)
+ return 1;
+ else if (*a == *b)
+ return 0;
+ else
+ return -1;
+}
+
+#define QSORT_DEBUG
+
+// qsort with inlined comparator
+// #define ST_SORT qsort_int32
+// #define ST_ELEMENT_TYPE int32
+// #define ST_COMPARE(a, b) (btint4fastcmp(a, b))
+// #define ST_SCOPE static
+// #define ST_DEFINE
+// #define ST_DECLARE
+// #include "lib/sort_template_dp.h"
+
+// like qsort.c
+// #define ST_THRESHOLD_INSERTION_SORT 7
+#define ST_SORT qsort_runtime
+#define ST_ELEMENT_TYPE_VOID
+#define ST_COMPARE_RUNTIME_POINTER
+#define ST_SCOPE
+#define ST_DECLARE
+#define ST_DEFINE
+#include "lib/sort_template.h"
+
+/* from contrib/intarray */
+#define ARRPTR(x) ( (int32 *) ARR_DATA_PTR(x) )
+#define ARRNELEMS(x) ArrayGetNItems(ARR_NDIM(x), ARR_DIMS(x))
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(debug_qsort_int);
+Datum
+debug_qsort_int(PG_FUNCTION_ARGS)
+{
+
+ ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
+ int count = ARRNELEMS(a);
+ int *arr = ARRPTR(a);
+
+ qsort_runtime(arr, count, sizeof(int32), btint4fastcmp);
+ elog(NOTICE, "sorted:");
+ for (int i = 0; i < count; i++)
+ {
+ elog(NOTICE, "%d", arr[i]);
+ }
+
+ PG_RETURN_NULL();
+}
diff --git a/src/test/modules/debug_qsort/debug_qsort.control b/src/test/modules/debug_qsort/debug_qsort.control
new file mode 100644
index 0000000000..860b803aa2
--- /dev/null
+++ b/src/test/modules/debug_qsort/debug_qsort.control
@@ -0,0 +1,4 @@
+comment = 'test'
+default_version = '1.0'
+module_pathname = '$libdir/debug_qsort'
+relocatable = true
--
2.36.1
On Thu, Jun 23, 2022 at 6:13 AM John Naylor
<john.naylor@enterprisedb.com> wrote:
Here is a *rough* first pass at dual-pivot quicksort. I haven't
changed the regression tests to adjust for qsort being an unstable
sort, ...
Hmm. I thought we had some reasons for preferring a stable sort algorithm.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, 23 Jun 2022 at 15:52, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Jun 23, 2022 at 6:13 AM John Naylor
<john.naylor@enterprisedb.com> wrote:Here is a *rough* first pass at dual-pivot quicksort. I haven't
changed the regression tests to adjust for qsort being an unstable
sort, ...Hmm. I thought we had some reasons for preferring a stable sort algorithm.
I think that mostly has to do with reliability / stability of ORDER BY
in combination with LIMIT and OFFSET, even though right now we cannot
fully guarantee such stability due to unstable results from underlying
plan nodes.
As an example, a table scan node under a sort node can start its scan
at an arbitrary point in the table (using synchronize_seqscans), and
because Sort nodes only sort MinimalTuple-s, each set of tuples that
have an equal sort value will be ordered by TID + y (mod tablesize),
with arbitrary values for y.
- Matthias
On Thu, Jun 23, 2022 at 7:51 AM Matthias van de Meent
<boekewurm+postgres@gmail.com> wrote:
I think that mostly has to do with reliability / stability of ORDER BY
in combination with LIMIT and OFFSET, even though right now we cannot
fully guarantee such stability due to unstable results from underlying
plan nodes.
The current qsort isn't stable. While quicksort is never stable, our
particular implementation has fairly significant optimizations that
strongly rely on not using a stable sort. In particular, the B&M
optimizations for duplicate elements.
These optimizations make things like datum tuplesorts for
'SELECT(DISTINCT mycol) ...' style queries on low cardinality columns
extremely fast. We're not really sorting so much as bucketing. This is
based on Dijkstra's Dutch national flag problem.
--
Peter Geoghegan
On Thu, 23 Jun 2022 at 17:04, Peter Geoghegan <pg@bowt.ie> wrote:
On Thu, Jun 23, 2022 at 7:51 AM Matthias van de Meent
<boekewurm+postgres@gmail.com> wrote:I think that mostly has to do with reliability / stability of ORDER BY
in combination with LIMIT and OFFSET, even though right now we cannot
fully guarantee such stability due to unstable results from underlying
plan nodes.The current qsort isn't stable.
Then I misunderstood Robert's comment, thanks for correcting me.
- Matthias
I've run the microbenchmarks only so far, since they complete much
faster than queries, and I wanted to tell quickly if this is a good
avenue to pursue. Later I'll repeat for queries. Methodology is
similar to what I did earlier in the thread, so I won't belabor that.
Takeaways:
- Contrary to some testing on single pivot I did some month ago with
fewer input distributions, in this test neither single nor dual pivot
benefit much from a large insertion sort threshold (i.e. 20 or so). I
picked 12 for both to do the head-to-head comparison, since this seems
to give a slight boost to specialized sorts on the slowest inputs.
They don't have to be the same, of course, but it seems about right
with these results. (Of course, it's easy to have specialized sorts
define their own threshold, as Thomas Munro has shown.)
- Generic qsort (type length and comparator passed at runtime) sees no
benefit from dual-pivot in this test, but specialized qsorts do get a
decent speedup.
- Descending inputs get a huge boost when specialized. This is
surprising to me, enough that I'm skeptical and want to double check
the test. If it's legitimate, I'm happy about it.
I've attached three spreadsheets with graphs, two for threshold tests
for single- and dual-pivot, and one comparing single and dual for
threshold=12.
0001 is the test module and rough script (not for commit). Note: The
server won't build, since the threshold is passed via CPPFLAGS.
0002 is trivial and mostly for assert builds
0003 is a mostly cleaned up patch for dual pivot, with passing
regression tests and default insertion sort threshold of 12
I'll add a CF entry for this.
TODO: add results for queries.
--
John Naylor
EDB: http://www.enterprisedb.com
Attachments:
v2-0001-Add-sort-test-module.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Add-sort-test-module.patchDownload
From 7a0286b3e1f403c622f8cf7dd65dc86103745ebb Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Mon, 30 May 2022 09:55:46 +0700
Subject: [PATCH v2 1/3] Add sort test module
XXX not for commit
---
src/include/lib/sort_template.h | 2 +-
src/test/modules/test_sort_perf/Makefile | 20 ++
.../run-microbenchmark-thresholds.sh | 37 +++
.../test_sort_cmp_weight_include.c | 237 ++++++++++++++++++
.../test_sort_perf/test_sort_perf--1.0.sql | 1 +
.../modules/test_sort_perf/test_sort_perf.c | 90 +++++++
.../test_sort_perf/test_sort_perf.control | 4 +
7 files changed, 390 insertions(+), 1 deletion(-)
create mode 100644 src/test/modules/test_sort_perf/Makefile
create mode 100755 src/test/modules/test_sort_perf/run-microbenchmark-thresholds.sh
create mode 100644 src/test/modules/test_sort_perf/test_sort_cmp_weight_include.c
create mode 100644 src/test/modules/test_sort_perf/test_sort_perf--1.0.sql
create mode 100644 src/test/modules/test_sort_perf/test_sort_perf.c
create mode 100644 src/test/modules/test_sort_perf/test_sort_perf.control
diff --git a/src/include/lib/sort_template.h b/src/include/lib/sort_template.h
index 3122a93009..5a2f1d3fbb 100644
--- a/src/include/lib/sort_template.h
+++ b/src/include/lib/sort_template.h
@@ -296,7 +296,7 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
loop:
DO_CHECK_FOR_INTERRUPTS();
- if (n < 7)
+ if (n < ST_INSERTION_SORT_THRESHOLD)
{
for (pm = a + ST_POINTER_STEP; pm < a + n * ST_POINTER_STEP;
pm += ST_POINTER_STEP)
diff --git a/src/test/modules/test_sort_perf/Makefile b/src/test/modules/test_sort_perf/Makefile
new file mode 100644
index 0000000000..1e49fb43c0
--- /dev/null
+++ b/src/test/modules/test_sort_perf/Makefile
@@ -0,0 +1,20 @@
+MODULE_big = test_sort_perf
+OBJS = test_sort_perf.o
+PGFILEDESC = "test"
+EXTENSION = test_sort_perf
+DATA = test_sort_perf--1.0.sql
+
+first: all
+
+test_sort_perf.o: test_sort_cmp_weight_include.c
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_sort_perf
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_sort_perf/run-microbenchmark-thresholds.sh b/src/test/modules/test_sort_perf/run-microbenchmark-thresholds.sh
new file mode 100755
index 0000000000..90df741f1e
--- /dev/null
+++ b/src/test/modules/test_sort_perf/run-microbenchmark-thresholds.sh
@@ -0,0 +1,37 @@
+# ../papers-sorting/sort-bench-pdqsort-sql-20220529.sh 1000 setup
+
+set -e
+
+#ROWS=10000000
+#../papers-sorting/sort-bench-pdqsort-sql-20220529.sh $ROWS setup
+
+
+# test actual funcs
+for threshold in 7 10 12 14 16 18 20 22 24 32; do
+#for threshold in 7 10; do
+
+# version 10
+echo "Threshold: $threshold"
+
+# TODO: run queries
+#../rebuild.sh
+#../papers-sorting/sort-bench-pdqsort-sql-20220529.sh $ROWS
+#mv results.csv queryresult-$(date +'%Y%m%d')-$threshold.csv
+
+# run microbenchmark
+
+# build perf module
+pushd src/test/modules/test_sort_perf/ >/dev/null
+touch test_sort_perf.c
+make -s CPPFLAGS=-DST_INSERTION_SORT_THRESHOLD=$threshold && make install -s
+popd >/dev/null
+
+padthreshold=$(printf "%02d" $threshold)
+./inst/bin/psql -c 'drop extension if exists test_sort_perf; create extension test_sort_perf;'
+./inst/bin/psql -c 'select test_sort_cmp_weight(1 * 1024*1024)' 2> microresult-$padthreshold.txt
+#./inst/bin/psql -c 'select test_sort_cmp_weight(1 * 1024)' 2> microresult-$padthreshold.txt
+
+perl -n -e 'print "$1\t$2\t$3\t$4\t$5\n" if /NOTICE: \[(.+)\] num=(\d+), threshold=(\d+), order=(\w+), time=(\d+\.\d+)/;' microresult-$padthreshold.txt > microresult-$(date +'%Y%m%d')-$padthreshold.csv
+
+done
+
diff --git a/src/test/modules/test_sort_perf/test_sort_cmp_weight_include.c b/src/test/modules/test_sort_perf/test_sort_cmp_weight_include.c
new file mode 100644
index 0000000000..eaf1191d09
--- /dev/null
+++ b/src/test/modules/test_sort_perf/test_sort_cmp_weight_include.c
@@ -0,0 +1,237 @@
+#include <math.h>
+
+static void run_tests(char* type, Datum *unsorted, Datum *sorted, BTSortArrayContext *cxt, int nobjects)
+{
+ const int numtests = 10;
+ double time,
+ min;
+
+ if (nobjects <= 100)
+ {
+ elog(NOTICE, "order: %s", type);
+ for (int j = 0; j < nobjects; ++j)
+ elog(NOTICE, "%d", DatumGetInt32(unsorted[j]));
+ return;
+ }
+
+ min = 1000000;
+ for (int i = 1; i <= numtests; ++i)
+ {
+ instr_time start_time, end_time;
+ memcpy(sorted, unsorted, sizeof(Datum) * nobjects);
+ INSTR_TIME_SET_CURRENT(start_time);
+ qsort_arg((void *) sorted, nobjects, sizeof(Datum), _bt_compare_array_elements, (void *) cxt);
+ INSTR_TIME_SET_CURRENT(end_time);
+ INSTR_TIME_SUBTRACT(end_time, start_time);
+ time = INSTR_TIME_GET_DOUBLE(end_time);
+ if (time < min)
+ min = time;
+ }
+ elog(NOTICE, "[fmgr runtime] num=%d, threshold=%d, order=%s, time=%f", nobjects, ST_INSERTION_SORT_THRESHOLD, type, min);
+
+ min = 1000000;
+ for (int i = 1; i <= numtests; ++i)
+ {
+ instr_time start_time, end_time;
+ memcpy(sorted, unsorted, sizeof(Datum) * nobjects);
+ INSTR_TIME_SET_CURRENT(start_time);
+ qsort_bt_array_elements(sorted, nobjects, cxt);
+ INSTR_TIME_SET_CURRENT(end_time);
+ INSTR_TIME_SUBTRACT(end_time, start_time);
+ time = INSTR_TIME_GET_DOUBLE(end_time);
+ if (time < min)
+ min = time;
+ }
+ elog(NOTICE, "[fmgr specialized] num=%d, threshold=%d, order=%s, time=%f", nobjects, ST_INSERTION_SORT_THRESHOLD, type, min);
+
+ min = 1000000;
+ for (int i = 1; i <= numtests; ++i)
+ {
+ instr_time start_time, end_time;
+ memcpy(sorted, unsorted, sizeof(Datum) * nobjects);
+ INSTR_TIME_SET_CURRENT(start_time);
+ qsort(sorted, nobjects, sizeof(Datum), btint4fastcmp);
+ INSTR_TIME_SET_CURRENT(end_time);
+ INSTR_TIME_SUBTRACT(end_time, start_time);
+ time = INSTR_TIME_GET_DOUBLE(end_time);
+ if (time < min)
+ min = time;
+ }
+ elog(NOTICE, "[C runtime] num=%d, threshold=%d, order=%s, time=%f", nobjects, ST_INSERTION_SORT_THRESHOLD, type, min);
+
+ min = 1000000;
+ for (int i = 0; i < numtests; ++i)
+ {
+ instr_time start_time, end_time;
+ memcpy(sorted, unsorted, sizeof(Datum) * nobjects);
+ INSTR_TIME_SET_CURRENT(start_time);
+ qsort_int32(sorted, nobjects);
+ INSTR_TIME_SET_CURRENT(end_time);
+ INSTR_TIME_SUBTRACT(end_time, start_time);
+ time = INSTR_TIME_GET_DOUBLE(end_time);
+ if (time < min)
+ min = time;
+ }
+ elog(NOTICE, "[C specialized] num=%d, threshold=%d, order=%s, time=%f", nobjects, ST_INSERTION_SORT_THRESHOLD, type, min);
+}
+
+typedef struct source_value
+{
+ int32 value;
+ float rand;
+} source_value;
+
+static int
+source_rand_cmp(const void * x, const void * y)
+{
+ source_value *a = (source_value *) x;
+ source_value *b = (source_value *) y;
+
+ if (a->rand > b->rand)
+ return 1;
+ else if (a->rand < b->rand)
+ return -1;
+ else
+ return 0;
+}
+
+static int
+source_dither_cmp(const void * x, const void * y, void * nobjects)
+{
+ source_value *a = (source_value *) x;
+ source_value *b = (source_value *) y;
+ float dither;
+
+ if (*(int *) nobjects <= 100)
+ dither = 5;
+ else
+ dither = 100;
+
+ if ((a->value + dither * a->rand) > (b->value + dither * b->rand))
+ return 1;
+ else if ((a->value + dither * a->rand) < (b->value + dither * b->rand))
+ return -1;
+ else
+ return 0;
+}
+
+static void
+do_sort_cmp_weight(int nobjects)
+{
+ source_value *source = malloc(sizeof(source_value) * nobjects);
+ source_value *tmp = malloc(sizeof(source_value) * nobjects);
+ Datum *unsorted = malloc(sizeof(Datum) * nobjects);
+ Datum *sorted = malloc(sizeof(Datum) * nobjects);
+
+ // for fmgr comparator tests
+
+ BTSortArrayContext cxt;
+ RegProcedure cmp_proc ;
+
+ // to keep from pulling in nbtree.h
+#define BTORDER_PROC 1
+
+ cmp_proc = get_opfamily_proc(INTEGER_BTREE_FAM_OID,
+ INT4OID,
+ INT4OID,
+ BTORDER_PROC);
+
+ fmgr_info(cmp_proc, &cxt.flinfo);
+ cxt.collation = DEFAULT_COLLATION_OID;
+ cxt.reverse = false;
+
+ // needed for some distributions: first populate source array with ascending value and random tag
+ for (int i = 0; i < nobjects; ++i)
+ {
+ source[i].value = i;
+ source[i].rand = (float) random() / (float) RAND_MAX; // between 0 and 1
+ }
+// if (nobjects <= 100)
+// for (int j = 0; j < nobjects; ++j)
+// elog(NOTICE, "%f", (source[j].rand));
+ qsort(source, nobjects, sizeof(source_value), source_rand_cmp);
+
+ // random
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(source[i].value);
+ run_tests("random", unsorted, sorted, &cxt, nobjects);
+
+ // for these, sort the first X % of the array
+
+ // sort50
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(source[i].value);
+ // sort first part of unsorted[]
+ qsort(unsorted, nobjects/2, sizeof(Datum), btint4fastcmp);
+ run_tests("sort50", unsorted, sorted, &cxt, nobjects);
+
+ // sort90
+ qsort(unsorted, nobjects/10 * 9, sizeof(Datum), btint4fastcmp);
+ run_tests("sort90", unsorted, sorted, &cxt, nobjects);
+
+ // sort99
+ qsort(unsorted, nobjects/100 * 99, sizeof(Datum), btint4fastcmp);
+ run_tests("sort99", unsorted, sorted, &cxt, nobjects);
+
+ // dither -- copy source to tmp array because it sorts differently
+ memcpy(tmp, source, sizeof(source_value) * nobjects);
+ qsort_arg(tmp, nobjects, sizeof(source_value), source_dither_cmp, (void *) &nobjects);
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(tmp[i].value);
+ run_tests("dither", unsorted, sorted, &cxt, nobjects);
+
+ // descending
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(nobjects - i);
+ run_tests("descending", unsorted, sorted, &cxt, nobjects);
+
+ // organ
+ for (int i = 0; i < nobjects/2; ++i)
+ unsorted[i] = Int32GetDatum(i);
+ for (int i = nobjects/2; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(nobjects - i);
+ run_tests("organ", unsorted, sorted, &cxt, nobjects);
+
+ // merge
+ for (int i = 0; i < nobjects/2; ++i)
+ unsorted[i] = Int32GetDatum(i);
+ for (int i = nobjects/2; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(i - nobjects/2);
+ run_tests("merge", unsorted, sorted, &cxt, nobjects);
+
+ // dup8
+ for (int i = 0; i < nobjects; ++i)
+ {
+ uint32 tmp = 1; // XXX this will only show duplicates for large nobjects
+ for (int j=0; j<8; ++j)
+ tmp *= (uint32) source[i].value;
+ tmp = (tmp + nobjects/2) % nobjects;
+ unsorted[i] = Int32GetDatum((int) tmp);
+ }
+ run_tests("dup8", unsorted, sorted, &cxt, nobjects);
+
+ // dupsq
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum((int) sqrt(source[i].value));
+ run_tests("dupsq", unsorted, sorted, &cxt, nobjects);
+
+ // mod100
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(source[i].value % 100);
+ run_tests("mod100", unsorted, sorted, &cxt, nobjects);
+
+ // mod8
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(source[i].value % 8);
+ run_tests("mod8", unsorted, sorted, &cxt, nobjects);
+
+ // ascending
+ for (int i = 0; i < nobjects; ++i)
+ unsorted[i] = Int32GetDatum(i);
+ run_tests("ascending", unsorted, sorted, &cxt, nobjects);
+
+ free(tmp);
+ free(source);
+ free(sorted);
+ free(unsorted);
+}
diff --git a/src/test/modules/test_sort_perf/test_sort_perf--1.0.sql b/src/test/modules/test_sort_perf/test_sort_perf--1.0.sql
new file mode 100644
index 0000000000..22e0726a57
--- /dev/null
+++ b/src/test/modules/test_sort_perf/test_sort_perf--1.0.sql
@@ -0,0 +1 @@
+CREATE FUNCTION test_sort_cmp_weight(int4) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_sort_perf/test_sort_perf.c b/src/test/modules/test_sort_perf/test_sort_perf.c
new file mode 100644
index 0000000000..d162e42049
--- /dev/null
+++ b/src/test/modules/test_sort_perf/test_sort_perf.c
@@ -0,0 +1,90 @@
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "catalog/index.h"
+#include "catalog/pg_collation_d.h"
+#include "catalog/pg_type_d.h"
+#include "catalog/pg_opfamily_d.h"
+#include "miscadmin.h"
+#include "portability/instr_time.h"
+#include "storage/itemptr.h"
+#include "utils/lsyscache.h"
+
+#include <stdlib.h>
+
+// standard comparators
+
+// comparator for qsort_arg() copied from nbtutils.c
+static int
+btint4fastcmp(const void * x, const void * y)
+{
+ int32 *a = (int32 *) x;
+ int32 *b = (int32 *) y;
+
+ if (*a > *b)
+ return 1;
+ else if (*a == *b)
+ return 0;
+ else
+ return -1;
+}
+
+// specialized qsort with inlined comparator
+#define ST_SORT qsort_int32
+#define ST_ELEMENT_TYPE Datum
+#define ST_COMPARE(a, b) (btint4fastcmp(a, b))
+#define ST_SCOPE static
+#define ST_DEFINE
+#define ST_DECLARE
+#include "lib/sort_template.h"
+
+// SQL-callable comparators
+
+typedef struct BTSortArrayContext
+{
+ FmgrInfo flinfo;
+ Oid collation;
+ bool reverse;
+} BTSortArrayContext;
+
+// comparator for qsort arg
+static int
+_bt_compare_array_elements(const void *a, const void *b, void *arg)
+{
+ Datum da = *((const Datum *) a);
+ Datum db = *((const Datum *) b);
+ BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
+ int32 compare;
+
+ compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
+ cxt->collation,
+ da, db));
+ if (cxt->reverse)
+ INVERT_COMPARE_RESULT(compare);
+ return compare;
+}
+
+/* Define a specialized sort function for _bt_sort_array_elements. */
+#define ST_SORT qsort_bt_array_elements
+#define ST_ELEMENT_TYPE Datum
+#define ST_COMPARE(a, b, cxt) \
+ DatumGetInt32(FunctionCall2Coll(&cxt->flinfo, cxt->collation, *a, *b))
+#define ST_COMPARE_ARG_TYPE BTSortArrayContext
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
+
+PG_MODULE_MAGIC;
+
+/* include the test suites */
+#include "test_sort_cmp_weight_include.c"
+
+PG_FUNCTION_INFO_V1(test_sort_cmp_weight);
+Datum
+test_sort_cmp_weight(PG_FUNCTION_ARGS)
+{
+ const int32 nobjects = PG_GETARG_INT32(0);
+
+ do_sort_cmp_weight(nobjects);
+ PG_RETURN_NULL();
+}
diff --git a/src/test/modules/test_sort_perf/test_sort_perf.control b/src/test/modules/test_sort_perf/test_sort_perf.control
new file mode 100644
index 0000000000..336cb0c5ba
--- /dev/null
+++ b/src/test/modules/test_sort_perf/test_sort_perf.control
@@ -0,0 +1,4 @@
+comment = 'test'
+default_version = '1.0'
+module_pathname = '$libdir/test_sort_perf'
+relocatable = true
--
2.36.1
v2-0002-Create-internal-workhorse-for-ST_SORT.patchtext/x-patch; charset=US-ASCII; name=v2-0002-Create-internal-workhorse-for-ST_SORT.patchDownload
From 669ae583a991b90e7d7847cc8468619bd2b535f5 Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Mon, 30 May 2022 10:09:17 +0700
Subject: [PATCH v2 2/3] Create internal workhorse for ST_SORT
Also use a macro intead of a hard-coded value for the insertion sort
threshold. This improves readability and enables specializing the
threshold if desired.
No functional changes.
---
src/include/lib/sort_template.h | 41 +++++++++++++++++++++++++++++----
1 file changed, 36 insertions(+), 5 deletions(-)
diff --git a/src/include/lib/sort_template.h b/src/include/lib/sort_template.h
index 5a2f1d3fbb..ea04011ce5 100644
--- a/src/include/lib/sort_template.h
+++ b/src/include/lib/sort_template.h
@@ -172,6 +172,11 @@
#define ST_SORT_INVOKE_ARG
#endif
+/* Default input size threshold to control algorithm choice. */
+#ifndef ST_INSERTION_SORT_THRESHOLD
+#define ST_INSERTION_SORT_THRESHOLD 7
+#endif
+
#ifdef ST_DECLARE
#ifdef ST_COMPARE_RUNTIME_POINTER
@@ -191,6 +196,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
/* sort private helper functions */
#define ST_MED3 ST_MAKE_NAME(ST_SORT, med3)
+#define ST_SORT_INTERNAL ST_MAKE_NAME(ST_SORT, internal)
#define ST_SWAP ST_MAKE_NAME(ST_SORT, swap)
#define ST_SWAPN ST_MAKE_NAME(ST_SORT, swapn)
@@ -217,7 +223,7 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
#define DO_SORT(a_, n_) \
- ST_SORT((a_), (n_) \
+ ST_SORT_INTERNAL((a_), (n_) \
ST_SORT_INVOKE_ELEMENT_SIZE \
ST_SORT_INVOKE_COMPARE \
ST_SORT_INVOKE_ARG)
@@ -273,15 +279,15 @@ ST_SWAPN(ST_POINTER_TYPE * a, ST_POINTER_TYPE * b, size_t n)
}
/*
- * Sort an array.
+ * Workhorse for ST_SORT
*/
-ST_SCOPE void
-ST_SORT(ST_ELEMENT_TYPE * data, size_t n
+static void
+ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n
ST_SORT_PROTO_ELEMENT_SIZE
ST_SORT_PROTO_COMPARE
ST_SORT_PROTO_ARG)
{
- ST_POINTER_TYPE *a = (ST_POINTER_TYPE *) data,
+ ST_POINTER_TYPE *a = data,
*pa,
*pb,
*pc,
@@ -399,6 +405,30 @@ loop:
}
}
}
+
+/*
+ * Sort an array.
+ */
+ST_SCOPE void
+ST_SORT(ST_ELEMENT_TYPE * data, size_t n
+ ST_SORT_PROTO_ELEMENT_SIZE
+ ST_SORT_PROTO_COMPARE
+ ST_SORT_PROTO_ARG)
+{
+ ST_POINTER_TYPE *begin = (ST_POINTER_TYPE *) data;
+
+ DO_SORT(begin, n);
+
+#ifdef USE_ASSERT_CHECKING
+ /* WIP: verify the sorting worked */
+ for (ST_POINTER_TYPE *pm = begin + ST_POINTER_STEP; pm < begin + n * ST_POINTER_STEP;
+ pm += ST_POINTER_STEP)
+ {
+ if (DO_COMPARE(pm, pm - ST_POINTER_STEP) < 0)
+ Assert(false);
+ }
+#endif /* USE_ASSERT_CHECKING */
+}
#endif
#undef DO_CHECK_FOR_INTERRUPTS
@@ -422,6 +452,7 @@ loop:
#undef ST_POINTER_TYPE
#undef ST_SCOPE
#undef ST_SORT
+#undef ST_SORT_INTERNAL
#undef ST_SORT_INVOKE_ARG
#undef ST_SORT_INVOKE_COMPARE
#undef ST_SORT_INVOKE_ELEMENT_SIZE
--
2.36.1
v2-0003-Implement-dual-pivot-quicksort.patchtext/x-patch; charset=US-ASCII; name=v2-0003-Implement-dual-pivot-quicksort.patchDownload
From bc2752902d1187ed8086641b4da1f36836940cb8 Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Thu, 30 Jun 2022 16:56:06 +0700
Subject: [PATCH v2 3/3] Implement dual-pivot quicksort
Choose pivots by running insertion sort on five candidates and choosing
the 2nd and 4th, ("tertile of five"). If any of the five are equal, we
assume the input has many duplicates and fall back to B&M since it's
optimized for that case.
Set default insertion sort threshold to 12, since it seems better
for dual pivot.
---
.../expected/pg_stat_statements.out | 4 +-
src/include/lib/sort_template.h | 219 +++++--
src/test/regress/expected/create_index.out | 2 +-
src/test/regress/expected/geometry.out | 4 +-
src/test/regress/expected/groupingsets.out | 4 +-
src/test/regress/expected/inet.out | 8 +-
src/test/regress/expected/join.out | 2 +-
src/test/regress/expected/merge.out | 22 +-
src/test/regress/expected/sqljson.out | 10 +-
src/test/regress/expected/tsrf.out | 60 +-
src/test/regress/expected/tuplesort.out | 16 +-
src/test/regress/expected/window.out | 544 +++++++++---------
12 files changed, 515 insertions(+), 380 deletions(-)
diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out
index 8f7f93172a..5a894a0f8b 100644
--- a/contrib/pg_stat_statements/expected/pg_stat_statements.out
+++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out
@@ -196,10 +196,10 @@ SELECT * FROM test ORDER BY a;
---+----------------------
1 | a
1 | 111
- 2 | b
2 | 222
- 3 | c
+ 2 | b
3 | 333
+ 3 | c
4 | 444
5 | 555
6 | 666
diff --git a/src/include/lib/sort_template.h b/src/include/lib/sort_template.h
index ea04011ce5..1b70331f05 100644
--- a/src/include/lib/sort_template.h
+++ b/src/include/lib/sort_template.h
@@ -135,7 +135,7 @@
/*
* If the user wants to be able to pass in compare functions at runtime,
- * we'll need to make that an argument of the sort and med3 functions.
+ * we'll need to make that an argument of the sort functions.
*/
#ifdef ST_COMPARE_RUNTIME_POINTER
/*
@@ -161,8 +161,8 @@
/*
* If the user wants to use a compare function or expression that takes an
- * extra argument, we'll need to make that an argument of the sort, compare and
- * med3 functions.
+ * extra argument, we'll need to make that an argument of the sort and compare
+ * functions.
*/
#ifdef ST_COMPARE_ARG_TYPE
#define ST_SORT_PROTO_ARG , ST_COMPARE_ARG_TYPE *arg
@@ -174,7 +174,7 @@
/* Default input size threshold to control algorithm choice. */
#ifndef ST_INSERTION_SORT_THRESHOLD
-#define ST_INSERTION_SORT_THRESHOLD 7
+#define ST_INSERTION_SORT_THRESHOLD 12
#endif
#ifdef ST_DECLARE
@@ -195,7 +195,6 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
#ifdef ST_DEFINE
/* sort private helper functions */
-#define ST_MED3 ST_MAKE_NAME(ST_SORT, med3)
#define ST_SORT_INTERNAL ST_MAKE_NAME(ST_SORT, internal)
#define ST_SWAP ST_MAKE_NAME(ST_SORT, swap)
#define ST_SWAPN ST_MAKE_NAME(ST_SORT, swapn)
@@ -208,8 +207,8 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
#endif
/*
- * Create wrapper macros that know how to invoke compare, med3 and sort with
- * the right arguments.
+ * Create wrapper macros that know how to invoke compare and sort with the
+ * right arguments.
*/
#ifdef ST_COMPARE_RUNTIME_POINTER
#define DO_COMPARE(a_, b_) ST_COMPARE((a_), (b_) ST_SORT_INVOKE_ARG)
@@ -218,10 +217,6 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
#else
#define DO_COMPARE(a_, b_) ST_COMPARE((a_), (b_))
#endif
-#define DO_MED3(a_, b_, c_) \
- ST_MED3((a_), (b_), (c_) \
- ST_SORT_INVOKE_COMPARE \
- ST_SORT_INVOKE_ARG)
#define DO_SORT(a_, n_) \
ST_SORT_INTERNAL((a_), (n_) \
ST_SORT_INVOKE_ELEMENT_SIZE \
@@ -245,23 +240,6 @@ ST_SCOPE void ST_SORT(ST_ELEMENT_TYPE * first, size_t n
#define DO_SWAP(a_, b_) DO_SWAPN((a_), (b_), element_size)
#endif
-/*
- * Find the median of three values. Currently, performance seems to be best
- * if the comparator is inlined here, but the med3 function is not inlined
- * in the qsort function.
- */
-static pg_noinline ST_ELEMENT_TYPE *
-ST_MED3(ST_ELEMENT_TYPE * a,
- ST_ELEMENT_TYPE * b,
- ST_ELEMENT_TYPE * c
- ST_SORT_PROTO_COMPARE
- ST_SORT_PROTO_ARG)
-{
- return DO_COMPARE(a, b) < 0 ?
- (DO_COMPARE(b, c) < 0 ? b : (DO_COMPARE(a, c) < 0 ? c : a))
- : (DO_COMPARE(b, c) > 0 ? b : (DO_COMPARE(a, c) < 0 ? a : c));
-}
-
static inline void
ST_SWAP(ST_POINTER_TYPE * a, ST_POINTER_TYPE * b)
{
@@ -288,6 +266,18 @@ ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n
ST_SORT_PROTO_ARG)
{
ST_POINTER_TYPE *a = data,
+ *left,
+ *right,
+ *e1,
+ *e2,
+ *e3,
+ *e4,
+ *e5,
+ *pivot1,
+ *pivot2,
+ *less,
+ *great,
+ *k,
*pa,
*pb,
*pc,
@@ -296,9 +286,12 @@ ST_SORT_INTERNAL(ST_POINTER_TYPE * data, size_t n
*pm,
*pn;
size_t d1,
- d2;
+ d2,
+ d3,
+ seventh;
int r,
presorted;
+ bool unique_hint = true;
loop:
DO_CHECK_FOR_INTERRUPTS();
@@ -324,22 +317,164 @@ loop:
}
if (presorted)
return;
- pm = a + (n / 2) * ST_POINTER_STEP;
- if (n > 7)
+
+ left = a;
+ right = a + (n - 1) * ST_POINTER_STEP;
+
+#ifdef QSORT_DEBUG
+ elog(NOTICE, "start:");
+
+ for (ST_POINTER_TYPE *i = left; i <= right; i += ST_POINTER_STEP)
{
- pl = a;
- pn = a + (n - 1) * ST_POINTER_STEP;
- if (n > 40)
+ elog(NOTICE, "%d ", *i);
+ }
+#endif
+
+ /* select five pivot candidates spaced equally around the midpoint */
+ seventh = n / 7 * ST_POINTER_STEP;
+ e3 = a + (n / 2) * ST_POINTER_STEP;
+ e2 = e3 - seventh;
+ e1 = e2 - seventh;
+ e4 = e3 + seventh;
+ e5 = e4 + seventh;
+
+ /* do insertion sort on pivot candidates */
+ for (pm = e2; pm <= e5; pm += seventh)
+ for (pl = pm; pl > e1 && (r = DO_COMPARE(pl - seventh, pl)) >= 0;
+ pl -= seventh)
+ {
+ if (r == 0)
+ {
+ /* found two equal candidates */
+ unique_hint = false;
+ break;
+ }
+ else
+ DO_SWAP(pl, pl - seventh);
+ }
+
+ /*
+ * If the pivot candidates were all unique, use dual-pivot quicksort,
+ * otherwise use B&M quicksort since it is faster on inputs with many
+ * duplicates.
+ */
+ if (unique_hint)
+ {
+ DO_SWAP(e2, left);
+ DO_SWAP(e4, right);
+
+ pivot1 = left;
+ pivot2 = right;
+ less = left + ST_POINTER_STEP;
+ great = right - ST_POINTER_STEP;
+
+ /*
+ * dual-pivot partitioning
+ *
+ * left part center part right part
+ * +--------------------------------------------------------------+
+ * | < pivot1 | pivot1 <= && <= pivot2 | ? | > pivot2 |
+ * +--------------------------------------------------------------+
+ * ^ ^ ^
+ * | | |
+ * less k great
+ *
+ * Invariants:
+ *
+ * all in (left, less) < pivot1
+ * pivot1 <= all in [less, k) <= pivot2
+ * all in (great, right) > pivot2
+ *
+ * k points to the first element in the "?" part
+ */
+ for (k = less; k <= great; k += ST_POINTER_STEP)
{
- size_t d = (n / 8) * ST_POINTER_STEP;
+ if (DO_COMPARE(k, pivot1) < 0)
+ {
+ if (k > less)
+ DO_SWAP(k, less);
+ less += ST_POINTER_STEP;
+ }
+ else if (DO_COMPARE(k, pivot2) > 0)
+ {
+ while (k < great && DO_COMPARE(great, pivot2) > 0)
+ {
+ great-= ST_POINTER_STEP;
+ DO_CHECK_FOR_INTERRUPTS();
+ }
+
+ DO_SWAP(k, great);
+ great-= ST_POINTER_STEP;
- pl = DO_MED3(pl, pl + d, pl + 2 * d);
- pm = DO_MED3(pm - d, pm, pm + d);
- pn = DO_MED3(pn - 2 * d, pn - d, pn);
+ if (DO_COMPARE(k, pivot1) < 0)
+ {
+ if (k > less)
+ DO_SWAP(k, less);
+ less += ST_POINTER_STEP;
+ }
+ }
+ DO_CHECK_FOR_INTERRUPTS();
+ }
+
+ DO_SWAP(less - ST_POINTER_STEP, pivot1);
+ DO_SWAP(great + ST_POINTER_STEP, pivot2);
+
+ d1 = (less - ST_POINTER_STEP) - left;
+ d2 = (great + ST_POINTER_STEP) - less;
+ d3 = right - (great + ST_POINTER_STEP);
+
+ /* recurse on shorter subarrays to save stack space */
+ if (d1 > d2)
+ {
+ /* recurse on d2 */
+ DO_SORT(less, d2 / ST_POINTER_STEP);
+ if (d1 > d3)
+ {
+ /* recurse on d3 */
+ DO_SORT(great + 2 * ST_POINTER_STEP, d3 / ST_POINTER_STEP);
+ /* iterate on d1 */
+ /* DO_SORT(left, d1 / ST_POINTER_STEP) */
+ n = d1 / ST_POINTER_STEP;
+ goto loop;
+ }
+ else
+ {
+ goto recurse_d1;
+ }
+ }
+ else
+ {
+recurse_d1:
+ /* recurse on d1 */
+ DO_SORT(left, d1 / ST_POINTER_STEP);
+ if (d2 > d3)
+ {
+ /* recurse on d3 */
+ DO_SORT(great + 2 * ST_POINTER_STEP, d3 / ST_POINTER_STEP);
+ /* iterate on d2 */
+ /* DO_SORT(less, d2 / ST_POINTER_STEP) */
+ a = less;
+ n = d2 / ST_POINTER_STEP;
+ goto loop;
+ }
+ else
+ {
+ /* recurse on d2 */
+ DO_SORT(less, d2 / ST_POINTER_STEP);
+ /* iterate on d3 */
+ /* DO_SORT(great + 2 * ST_POINTER_STEP, d3 / ST_POINTER_STEP) */
+ a = great + 2 * ST_POINTER_STEP;
+ n = d3 / ST_POINTER_STEP;
+ goto loop;
+ }
}
- pm = DO_MED3(pl, pm, pn);
}
- DO_SWAP(a, pm);
+ else
+ /* classic B&M quicksort with a single pivot */
+ // WIP: clean up variables to match dual pivot more closely
+ {
+ /* use median of five for the pivot */
+ DO_SWAP(a, e3);
pa = pb = a + ST_POINTER_STEP;
pc = pd = a + (n - 1) * ST_POINTER_STEP;
for (;;)
@@ -404,6 +539,7 @@ loop:
goto loop;
}
}
+ }
}
/*
@@ -433,7 +569,6 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
#undef DO_CHECK_FOR_INTERRUPTS
#undef DO_COMPARE
-#undef DO_MED3
#undef DO_SORT
#undef DO_SWAP
#undef DO_SWAPN
@@ -444,10 +579,10 @@ ST_SORT(ST_ELEMENT_TYPE * data, size_t n
#undef ST_COMPARE_RUNTIME_POINTER
#undef ST_ELEMENT_TYPE
#undef ST_ELEMENT_TYPE_VOID
+#undef ST_INSERTION_SORT_THRESHOLD
#undef ST_MAKE_NAME
#undef ST_MAKE_NAME_
#undef ST_MAKE_PREFIX
-#undef ST_MED3
#undef ST_POINTER_STEP
#undef ST_POINTER_TYPE
#undef ST_SCOPE
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index d55aec3a1d..9b548fe3e8 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -172,8 +172,8 @@ SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
(10,10)
(-5,-12)
(5.1,34.5)
- (Infinity,1e+300)
(1e+300,Infinity)
+ (Infinity,1e+300)
(NaN,NaN)
(11 rows)
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 3b364d1e3b..5eb0b29b16 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -4308,8 +4308,8 @@ SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
<(100,200),10> | (-5,-12) | 226.577682802
<(3,5),0> | (1e+300,Infinity) | Infinity
<(3,5),0> | (Infinity,1e+300) | Infinity
- <(1,2),3> | (1e+300,Infinity) | Infinity
<(5,1),3> | (1e+300,Infinity) | Infinity
+ <(1,2),3> | (1e+300,Infinity) | Infinity
<(5,1),3> | (Infinity,1e+300) | Infinity
<(1,2),3> | (Infinity,1e+300) | Infinity
<(1,3),5> | (1e+300,Infinity) | Infinity
@@ -4321,8 +4321,8 @@ SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
<(100,1),115> | (1e+300,Infinity) | Infinity
<(100,1),115> | (Infinity,1e+300) | Infinity
<(3,5),0> | (NaN,NaN) | NaN
- <(1,2),3> | (NaN,NaN) | NaN
<(5,1),3> | (NaN,NaN) | NaN
+ <(1,2),3> | (NaN,NaN) | NaN
<(1,3),5> | (NaN,NaN) | NaN
<(100,200),10> | (NaN,NaN) | NaN
<(1,2),100> | (NaN,NaN) | NaN
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..57dbb65a82 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -92,14 +92,14 @@ select a, b, grouping(a,b), sum(v), count(*), max(v)
---+---+----------+-----+-------+-----
| | 3 | 145 | 10 | 19
1 | | 1 | 60 | 5 | 14
- 1 | 1 | 0 | 21 | 2 | 11
2 | | 1 | 15 | 1 | 15
+ 1 | 1 | 0 | 21 | 2 | 11
3 | | 1 | 33 | 2 | 17
1 | 2 | 0 | 25 | 2 | 13
1 | 3 | 0 | 14 | 1 | 14
4 | | 1 | 37 | 2 | 19
- 4 | 1 | 0 | 37 | 2 | 19
2 | 3 | 0 | 15 | 1 | 15
+ 4 | 1 | 0 | 37 | 2 | 19
3 | 3 | 0 | 16 | 1 | 16
3 | 4 | 0 | 17 | 1 | 17
(12 rows)
diff --git a/src/test/regress/expected/inet.out b/src/test/regress/expected/inet.out
index d5bf9e2aaa..a02ac66298 100644
--- a/src/test/regress/expected/inet.out
+++ b/src/test/regress/expected/inet.out
@@ -390,8 +390,8 @@ SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY i;
c | i
-------------+-------------
10.0.0.0/8 | 9.1.2.3/8
- 10.0.0.0/32 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
10.1.0.0/16 | 10.1.2.3/16
10.1.2.0/24 | 10.1.2.3/24
@@ -451,8 +451,8 @@ SELECT * FROM inet_tbl WHERE i <> '192.168.1.0/24'::cidr ORDER BY i;
--------------------+------------------
10.0.0.0/8 | 9.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
- 10.0.0.0/32 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
10.1.0.0/16 | 10.1.2.3/16
10.1.2.0/24 | 10.1.2.3/24
10.1.2.3/32 | 10.1.2.3
@@ -538,8 +538,8 @@ SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY i;
c | i
-------------+-------------
10.0.0.0/8 | 9.1.2.3/8
- 10.0.0.0/32 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
10.1.0.0/16 | 10.1.2.3/16
10.1.2.0/24 | 10.1.2.3/24
@@ -599,8 +599,8 @@ SELECT * FROM inet_tbl WHERE i <> '192.168.1.0/24'::cidr ORDER BY i;
--------------------+------------------
10.0.0.0/8 | 9.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
- 10.0.0.0/32 | 10.1.2.3/8
10.0.0.0/8 | 10.1.2.3/8
+ 10.0.0.0/32 | 10.1.2.3/8
10.1.0.0/16 | 10.1.2.3/16
10.1.2.0/24 | 10.1.2.3/24
10.1.2.3/32 | 10.1.2.3
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 2538bd6a79..720d4ca4e8 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -2386,8 +2386,8 @@ select * from
---+---+-------+---+----
| | | | 0
| | | |
- | 0 | zero | |
| | null | |
+ | 0 | zero | |
8 | 8 | eight | |
7 | 7 | seven | |
6 | 6 | six | |
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index af670e28e7..a2a4aaaf85 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -1554,11 +1554,11 @@ SELECT * FROM pa_target ORDER BY tid;
5 | 500 | initial
5 | 50 | inserted by merge
6 | 60 | inserted by merge
- 7 | 700 | initial
7 | 70 | inserted by merge
+ 7 | 700 | initial
8 | 80 | inserted by merge
- 9 | 90 | inserted by merge
9 | 900 | initial
+ 9 | 90 | inserted by merge
10 | 100 | inserted by merge
11 | 1100 | initial
11 | 110 | inserted by merge
@@ -1583,12 +1583,12 @@ SELECT * FROM pa_target ORDER BY tid;
-----+---------+--------------------------
2 | 110 | initial updated by merge
2 | 20 | inserted by merge
- 4 | 40 | inserted by merge
4 | 330 | initial updated by merge
- 6 | 550 | initial updated by merge
+ 4 | 40 | inserted by merge
6 | 60 | inserted by merge
- 8 | 80 | inserted by merge
+ 6 | 550 | initial updated by merge
8 | 770 | initial updated by merge
+ 8 | 80 | inserted by merge
10 | 990 | initial updated by merge
10 | 100 | inserted by merge
12 | 1210 | initial updated by merge
@@ -1658,18 +1658,18 @@ SELECT * FROM pa_target ORDER BY tid;
-----+---------+--------------------------
1 | 110 | initial updated by merge
2 | 20 | inserted by merge
- 3 | 30 | inserted by merge
3 | 300 | initial
+ 3 | 30 | inserted by merge
4 | 40 | inserted by merge
6 | 60 | inserted by merge
- 7 | 700 | initial
7 | 70 | inserted by merge
+ 7 | 700 | initial
8 | 80 | inserted by merge
9 | 900 | initial
9 | 90 | inserted by merge
10 | 100 | inserted by merge
- 11 | 110 | inserted by merge
11 | 1100 | initial
+ 11 | 110 | inserted by merge
12 | 120 | inserted by merge
13 | 1300 | initial
13 | 130 | inserted by merge
@@ -1691,12 +1691,12 @@ SELECT * FROM pa_target ORDER BY tid;
-----+---------+--------------------------
2 | 110 | initial updated by merge
2 | 20 | inserted by merge
- 4 | 40 | inserted by merge
4 | 330 | initial updated by merge
- 6 | 550 | initial updated by merge
+ 4 | 40 | inserted by merge
6 | 60 | inserted by merge
- 8 | 80 | inserted by merge
+ 6 | 550 | initial updated by merge
8 | 770 | initial updated by merge
+ 8 | 80 | inserted by merge
10 | 990 | initial updated by merge
10 | 100 | inserted by merge
12 | 1210 | initial updated by merge
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 0883261535..db0f2c08ad 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -873,13 +873,13 @@ FROM
(VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar);
bar | json_arrayagg
-----+---------------
+ 2 | [4, 4]
4 | [4, 4]
4 | [4, 4]
- 2 | [4, 4]
- 5 | [5, 3, 5]
- 3 | [5, 3, 5]
- 1 | [5, 3, 5]
- 5 | [5, 3, 5]
+ 3 | [3, 5, 5]
+ 1 | [3, 5, 5]
+ 5 | [3, 5, 5]
+ 5 | [3, 5, 5]
|
|
|
diff --git a/src/test/regress/expected/tsrf.out b/src/test/regress/expected/tsrf.out
index d47b5f6ec5..f4c87d7a17 100644
--- a/src/test/regress/expected/tsrf.out
+++ b/src/test/regress/expected/tsrf.out
@@ -348,11 +348,11 @@ SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(d
SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab) ORDER BY g;
dataa | b | g | count
-------+-----+---+-------
- a | bar | 1 | 1
+ b | | 1 | 1
a | foo | 1 | 1
a | | 1 | 2
b | bar | 1 | 1
- b | | 1 | 1
+ a | bar | 1 | 1
| | 1 | 3
| bar | 1 | 2
| foo | 1 | 1
@@ -398,59 +398,59 @@ SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(d
SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY dataa;
dataa | b | g | count
-------+-----+---+-------
- a | foo | | 2
- a | | | 4
+ a | | 1 | 2
+ a | foo | 1 | 1
a | | 2 | 2
a | bar | 1 | 1
+ a | foo | 2 | 1
+ a | foo | | 2
+ a | | | 4
a | bar | 2 | 1
a | bar | | 2
- a | foo | 1 | 1
- a | foo | 2 | 1
- a | | 1 | 2
+ b | bar | | 2
b | bar | 1 | 1
+ b | bar | 2 | 1
b | | | 2
b | | 1 | 1
- b | bar | 2 | 1
- b | bar | | 2
b | | 2 | 1
- | | 2 | 3
- | | | 6
| bar | 1 | 2
- | bar | 2 | 2
- | bar | | 4
- | foo | 1 | 1
| foo | 2 | 1
| foo | | 2
+ | foo | 1 | 1
+ | | 2 | 3
| | 1 | 3
+ | | | 6
+ | bar | 2 | 2
+ | bar | | 4
(24 rows)
SELECT dataa, datab b, generate_series(1,2) g, count(*) FROM few GROUP BY CUBE(dataa, datab, g) ORDER BY g;
dataa | b | g | count
-------+-----+---+-------
- a | bar | 1 | 1
+ a | | 1 | 2
a | foo | 1 | 1
- b | bar | 1 | 1
| bar | 1 | 2
+ b | bar | 1 | 1
| foo | 1 | 1
- a | | 1 | 2
+ a | bar | 1 | 1
b | | 1 | 1
| | 1 | 3
+ a | foo | 2 | 1
+ | | 2 | 3
a | | 2 | 2
- b | | 2 | 1
| bar | 2 | 2
- | | 2 | 3
| foo | 2 | 1
- a | bar | 2 | 1
- a | foo | 2 | 1
b | bar | 2 | 1
- a | | | 4
+ a | bar | 2 | 1
+ b | | 2 | 1
+ a | foo | | 2
b | bar | | 2
b | | | 2
| | | 6
- a | foo | | 2
- a | bar | | 2
| bar | | 4
| foo | | 2
+ a | bar | | 2
+ a | | | 4
(24 rows)
reset enable_hashagg;
@@ -533,9 +533,9 @@ SELECT DISTINCT ON (a) a, b, generate_series(1,3) g
FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b);
a | b | g
---+---+---
- 1 | 1 | 1
- 3 | 2 | 1
- 5 | 3 | 1
+ 1 | 4 | 3
+ 3 | 2 | 3
+ 5 | 3 | 3
(3 rows)
-- unreferenced in DISTINCT ON or ORDER BY
@@ -597,9 +597,9 @@ SELECT DISTINCT ON (g) a, b, generate_series(1,3) g
FROM (VALUES (3, 2), (3,1), (1,1), (1,4), (5,3), (5,1)) AS t(a, b);
a | b | g
---+---+---
- 3 | 2 | 1
- 5 | 1 | 2
- 3 | 1 | 3
+ 1 | 1 | 1
+ 5 | 3 | 2
+ 1 | 1 | 3
(3 rows)
-- LIMIT / OFFSET is evaluated after SRF evaluation
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 418f296a3f..b3ad4cc5f3 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -242,9 +242,9 @@ FROM abbrev_abort_uuids
ORDER BY ctid DESC LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 20003 | | | |
0 | | | |
20002 | | | |
- 20003 | | | |
20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
(5 rows)
@@ -260,11 +260,11 @@ FROM abbrev_abort_uuids
ORDER BY ctid LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
- 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001
19999 | 00000000-0000-0000-0000-000000019998 | 00000000-0000-0000-0000-000000000002 | 00009989-0000-0000-0000-000000019998 | 00000002-0000-0000-0000-000000000002
- 20009 | 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003 | 00009988-0000-0000-0000-000000019997 | 00000003-0000-0000-0000-000000000003
+ 19998 | 00000000-0000-0000-0000-000000019997 | 00000000-0000-0000-0000-000000000003 | 00009988-0000-0000-0000-000000019997 | 00000003-0000-0000-0000-000000000003
(5 rows)
-- tail
@@ -273,9 +273,9 @@ FROM abbrev_abort_uuids
ORDER BY ctid DESC LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
- 0 | | | |
- 20002 | | | |
20003 | | | |
+ 20002 | | | |
+ 0 | | | |
1 | 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000
2 | 00000000-0000-0000-0000-000000000001 | 00000000-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999
(5 rows)
@@ -304,8 +304,8 @@ FROM abbrev_abort_uuids
ORDER BY ctid DESC LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
- 0 | | | |
20002 | | | |
+ 0 | | | |
20003 | | | |
10009 | 00000000-0000-0000-0000-000000010008 | 00000000-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992
10008 | 00000000-0000-0000-0000-000000010007 | 00000000-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993
@@ -322,8 +322,8 @@ FROM abbrev_abort_uuids
ORDER BY ctid LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
- 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
20001 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
+ 20010 | 00000000-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000 | 00009991-0000-0000-0000-000000020000 | 00000000-0000-0000-0000-000000000000
9992 | 00000000-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009 | 00009991-0000-0000-0000-000000009991 | 00000000-0000-0000-0000-000000010009
20000 | 00000000-0000-0000-0000-000000019999 | 00000000-0000-0000-0000-000000000001 | 00009990-0000-0000-0000-000000019999 | 00000001-0000-0000-0000-000000000001
9991 | 00000000-0000-0000-0000-000000009990 | 00000000-0000-0000-0000-000000010010 | 00009990-0000-0000-0000-000000009990 | 00000001-0000-0000-0000-000000010010
@@ -335,9 +335,9 @@ FROM abbrev_abort_uuids
ORDER BY ctid DESC LIMIT 5;
id | abort_increasing | abort_decreasing | noabort_increasing | noabort_decreasing
-------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------
+ 20002 | | | |
0 | | | |
20003 | | | |
- 20002 | | | |
9993 | 00000000-0000-0000-0000-000000009992 | 00000000-0000-0000-0000-000000010008 | 00009992-0000-0000-0000-000000009992 | 00010008-0000-0000-0000-000000010008
9994 | 00000000-0000-0000-0000-000000009993 | 00000000-0000-0000-0000-000000010007 | 00009993-0000-0000-0000-000000009993 | 00010007-0000-0000-0000-000000010007
(5 rows)
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 433a0bb025..aa173e5318 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -23,13 +23,13 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
-----------+-------+--------+-------
develop | 7 | 4200 | 25100
develop | 9 | 4500 | 25100
- develop | 11 | 5200 | 25100
develop | 10 | 5200 | 25100
+ develop | 11 | 5200 | 25100
develop | 8 | 6000 | 25100
personnel | 5 | 3500 | 7400
personnel | 2 | 3900 | 7400
- sales | 3 | 4800 | 14600
sales | 4 | 4800 | 14600
+ sales | 3 | 4800 | 14600
sales | 1 | 5000 | 14600
(10 rows)
@@ -38,13 +38,13 @@ SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary
-----------+-------+--------+------
develop | 7 | 4200 | 1
develop | 9 | 4500 | 2
- develop | 11 | 5200 | 3
develop | 10 | 5200 | 3
+ develop | 11 | 5200 | 3
develop | 8 | 6000 | 5
personnel | 5 | 3500 | 1
personnel | 2 | 3900 | 2
- sales | 3 | 4800 | 1
sales | 4 | 4800 | 1
+ sales | 3 | 4800 | 1
sales | 1 | 5000 | 3
(10 rows)
@@ -78,16 +78,16 @@ GROUP BY four, ten ORDER BY four, ten;
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 | 10 | 5200 | 25100
develop | 7 | 4200 | 25100
develop | 9 | 4500 | 25100
develop | 8 | 6000 | 25100
- develop | 10 | 5200 | 25100
+ develop | 11 | 5200 | 25100
personnel | 5 | 3500 | 7400
personnel | 2 | 3900 | 7400
- sales | 3 | 4800 | 14600
sales | 1 | 5000 | 14600
sales | 4 | 4800 | 14600
+ sales | 3 | 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;
@@ -95,13 +95,13 @@ SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITI
-----------+-------+--------+------
develop | 7 | 4200 | 1
personnel | 5 | 3500 | 1
- sales | 3 | 4800 | 1
sales | 4 | 4800 | 1
- personnel | 2 | 3900 | 2
+ sales | 3 | 4800 | 1
develop | 9 | 4500 | 2
- sales | 1 | 5000 | 3
- develop | 11 | 5200 | 3
+ personnel | 2 | 3900 | 2
develop | 10 | 5200 | 3
+ develop | 11 | 5200 | 3
+ sales | 1 | 5000 | 3
develop | 8 | 6000 | 5
(10 rows)
@@ -394,12 +394,12 @@ SELECT first_value(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM te
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
+ 2 | 0 | 0
+ 2 | 0 | 0
+ 2 | 0 | 2
1 | 1 | 3
1 | 1 | 1
+ 1 | 1 | 1
3 | 3 | 3
0 | 4 | 0
1 | 7 | 1
@@ -803,14 +803,14 @@ SELECT sum(unique1) over (order by four range between current row and unbounded
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 45 | 0 | 0
- 45 | 8 | 0
45 | 4 | 0
- 33 | 5 | 1
- 33 | 9 | 1
+ 45 | 8 | 0
+ 45 | 0 | 0
33 | 1 | 1
- 18 | 6 | 2
+ 33 | 9 | 1
+ 33 | 5 | 1
18 | 2 | 2
+ 18 | 6 | 2
10 | 3 | 3
10 | 7 | 3
(10 rows)
@@ -922,14 +922,14 @@ SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 f
FROM tenk1 WHERE unique1 < 10;
first_value | unique1 | four
-------------+---------+------
- 8 | 0 | 0
- 4 | 8 | 0
- 5 | 4 | 0
- 9 | 5 | 1
- 1 | 9 | 1
- 6 | 1 | 1
- 2 | 6 | 2
- 3 | 2 | 2
+ 8 | 4 | 0
+ 0 | 8 | 0
+ 1 | 0 | 0
+ 9 | 1 | 1
+ 5 | 9 | 1
+ 2 | 5 | 1
+ 6 | 2 | 2
+ 3 | 6 | 2
7 | 3 | 3
| 7 | 3
(10 rows)
@@ -939,14 +939,14 @@ SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 f
FROM tenk1 WHERE unique1 < 10;
first_value | unique1 | four
-------------+---------+------
- | 0 | 0
- 5 | 8 | 0
- 5 | 4 | 0
- | 5 | 1
- 6 | 9 | 1
- 6 | 1 | 1
- 3 | 6 | 2
+ | 4 | 0
+ 1 | 8 | 0
+ 1 | 0 | 0
+ | 1 | 1
+ 2 | 9 | 1
+ 2 | 5 | 1
3 | 2 | 2
+ 3 | 6 | 2
| 3 | 3
| 7 | 3
(10 rows)
@@ -956,14 +956,14 @@ SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 f
FROM tenk1 WHERE unique1 < 10;
first_value | unique1 | four
-------------+---------+------
- 0 | 0 | 0
- 8 | 8 | 0
4 | 4 | 0
- 5 | 5 | 1
- 9 | 9 | 1
+ 8 | 8 | 0
+ 0 | 0 | 0
1 | 1 | 1
- 6 | 6 | 2
+ 9 | 9 | 1
+ 5 | 5 | 1
2 | 2 | 2
+ 6 | 6 | 2
3 | 3 | 3
7 | 7 | 3
(10 rows)
@@ -973,14 +973,14 @@ SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 fo
FROM tenk1 WHERE unique1 < 10;
last_value | unique1 | four
------------+---------+------
- 4 | 0 | 0
- 5 | 8 | 0
- 9 | 4 | 0
- 1 | 5 | 1
- 6 | 9 | 1
- 2 | 1 | 1
- 3 | 6 | 2
- 7 | 2 | 2
+ 0 | 4 | 0
+ 1 | 8 | 0
+ 9 | 0 | 0
+ 5 | 1 | 1
+ 2 | 9 | 1
+ 6 | 5 | 1
+ 3 | 2 | 2
+ 7 | 6 | 2
7 | 3 | 3
| 7 | 3
(10 rows)
@@ -990,14 +990,14 @@ SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 fo
FROM tenk1 WHERE unique1 < 10;
last_value | unique1 | four
------------+---------+------
- | 0 | 0
- 5 | 8 | 0
- 9 | 4 | 0
- | 5 | 1
- 6 | 9 | 1
- 2 | 1 | 1
- 3 | 6 | 2
- 7 | 2 | 2
+ | 4 | 0
+ 1 | 8 | 0
+ 9 | 0 | 0
+ | 1 | 1
+ 2 | 9 | 1
+ 6 | 5 | 1
+ 3 | 2 | 2
+ 7 | 6 | 2
| 3 | 3
| 7 | 3
(10 rows)
@@ -1007,14 +1007,14 @@ SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 fo
FROM tenk1 WHERE unique1 < 10;
last_value | unique1 | four
------------+---------+------
- 0 | 0 | 0
- 5 | 8 | 0
- 9 | 4 | 0
- 5 | 5 | 1
- 6 | 9 | 1
- 2 | 1 | 1
- 3 | 6 | 2
- 7 | 2 | 2
+ 4 | 4 | 0
+ 1 | 8 | 0
+ 9 | 0 | 0
+ 1 | 1 | 1
+ 2 | 9 | 1
+ 6 | 5 | 1
+ 3 | 2 | 2
+ 7 | 6 | 2
3 | 3 | 3
7 | 7 | 3
(10 rows)
@@ -1075,14 +1075,14 @@ SELECT sum(unique1) over (w range between current row and unbounded following),
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
+ 45 | 8 | 0
+ 45 | 0 | 0
33 | 1 | 1
- 18 | 6 | 2
+ 33 | 9 | 1
+ 33 | 5 | 1
18 | 2 | 2
+ 18 | 6 | 2
10 | 3 | 3
10 | 7 | 3
(10 rows)
@@ -1092,14 +1092,14 @@ SELECT sum(unique1) over (w range between unbounded preceding and current row ex
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
- 12 | 0 | 0
- 4 | 8 | 0
8 | 4 | 0
- 22 | 5 | 1
- 18 | 9 | 1
+ 4 | 8 | 0
+ 12 | 0 | 0
26 | 1 | 1
- 29 | 6 | 2
+ 18 | 9 | 1
+ 22 | 5 | 1
33 | 2 | 2
+ 29 | 6 | 2
42 | 3 | 3
38 | 7 | 3
(10 rows)
@@ -1109,14 +1109,14 @@ SELECT sum(unique1) over (w range between unbounded preceding and current row ex
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
- | 0 | 0
- | 8 | 0
| 4 | 0
- 12 | 5 | 1
- 12 | 9 | 1
+ | 8 | 0
+ | 0 | 0
12 | 1 | 1
- 27 | 6 | 2
+ 12 | 9 | 1
+ 12 | 5 | 1
27 | 2 | 2
+ 27 | 6 | 2
35 | 3 | 3
35 | 7 | 3
(10 rows)
@@ -1126,14 +1126,14 @@ SELECT sum(unique1) over (w range between unbounded preceding and current row ex
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
- 0 | 0 | 0
- 8 | 8 | 0
4 | 4 | 0
- 17 | 5 | 1
- 21 | 9 | 1
+ 8 | 8 | 0
+ 0 | 0 | 0
13 | 1 | 1
- 33 | 6 | 2
+ 21 | 9 | 1
+ 17 | 5 | 1
29 | 2 | 2
+ 33 | 6 | 2
38 | 3 | 3
42 | 7 | 3
(10 rows)
@@ -1145,14 +1145,14 @@ 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
+ 4 | 8 | 7 | 4 | 0
+ 4 | 8 | 7 | 8 | 0
+ 4 | 8 | 7 | 0 | 0
+ 1 | 9 | 7 | 1 | 1
+ 1 | 9 | 7 | 9 | 1
+ 1 | 9 | 7 | 5 | 1
+ 2 | 6 | 7 | 2 | 2
+ 2 | 6 | 7 | 6 | 2
3 | 7 | 7 | 3 | 3
3 | 7 | 7 | 7 | 3
(10 rows)
@@ -1349,14 +1349,14 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
- | 8 | 0
| 4 | 0
- 12 | 5 | 1
- 12 | 9 | 1
+ | 8 | 0
+ | 0 | 0
12 | 1 | 1
- 27 | 6 | 2
+ 12 | 9 | 1
+ 12 | 5 | 1
27 | 2 | 2
+ 27 | 6 | 2
23 | 3 | 3
23 | 7 | 3
(10 rows)
@@ -1368,14 +1368,14 @@ FROM tenk1 WHERE unique1 < 10;
-----+---------+------
| 3 | 3
| 7 | 3
- 10 | 6 | 2
10 | 2 | 2
+ 10 | 6 | 2
+ 18 | 1 | 1
18 | 9 | 1
18 | 5 | 1
- 18 | 1 | 1
- 23 | 0 | 0
- 23 | 8 | 0
23 | 4 | 0
+ 23 | 8 | 0
+ 23 | 0 | 0
(10 rows)
SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude no others),
@@ -1383,14 +1383,14 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
- | 8 | 0
| 4 | 0
- 12 | 5 | 1
- 12 | 9 | 1
+ | 8 | 0
+ | 0 | 0
12 | 1 | 1
- 27 | 6 | 2
+ 12 | 9 | 1
+ 12 | 5 | 1
27 | 2 | 2
+ 27 | 6 | 2
23 | 3 | 3
23 | 7 | 3
(10 rows)
@@ -1400,14 +1400,14 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
- | 8 | 0
| 4 | 0
- 12 | 5 | 1
- 12 | 9 | 1
+ | 8 | 0
+ | 0 | 0
12 | 1 | 1
- 27 | 6 | 2
+ 12 | 9 | 1
+ 12 | 5 | 1
27 | 2 | 2
+ 27 | 6 | 2
23 | 3 | 3
23 | 7 | 3
(10 rows)
@@ -1417,14 +1417,14 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
- | 8 | 0
| 4 | 0
- 12 | 5 | 1
- 12 | 9 | 1
+ | 8 | 0
+ | 0 | 0
12 | 1 | 1
- 27 | 6 | 2
+ 12 | 9 | 1
+ 12 | 5 | 1
27 | 2 | 2
+ 27 | 6 | 2
23 | 3 | 3
23 | 7 | 3
(10 rows)
@@ -1434,14 +1434,14 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
- | 8 | 0
| 4 | 0
- 12 | 5 | 1
- 12 | 9 | 1
+ | 8 | 0
+ | 0 | 0
12 | 1 | 1
- 27 | 6 | 2
+ 12 | 9 | 1
+ 12 | 5 | 1
27 | 2 | 2
+ 27 | 6 | 2
23 | 3 | 3
23 | 7 | 3
(10 rows)
@@ -1451,14 +1451,14 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 33 | 0 | 0
- 41 | 8 | 0
37 | 4 | 0
- 35 | 5 | 1
- 39 | 9 | 1
+ 41 | 8 | 0
+ 33 | 0 | 0
31 | 1 | 1
- 43 | 6 | 2
+ 39 | 9 | 1
+ 35 | 5 | 1
39 | 2 | 2
+ 43 | 6 | 2
26 | 3 | 3
30 | 7 | 3
(10 rows)
@@ -1468,14 +1468,14 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::i
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 33 | 0 | 0
- 33 | 8 | 0
33 | 4 | 0
- 30 | 5 | 1
- 30 | 9 | 1
+ 33 | 8 | 0
+ 33 | 0 | 0
30 | 1 | 1
- 37 | 6 | 2
+ 30 | 9 | 1
+ 30 | 5 | 1
37 | 2 | 2
+ 37 | 6 | 2
23 | 3 | 3
23 | 7 | 3
(10 rows)
@@ -1521,13 +1521,13 @@ select sum(salary) over (order by enroll_date range between '1 year'::interval p
34900 | 5000 | 10-01-2006
34900 | 6000 | 10-01-2006
38400 | 3900 | 12-23-2006
- 47100 | 4800 | 08-01-2007
47100 | 5200 | 08-01-2007
+ 47100 | 4800 | 08-01-2007
47100 | 4800 | 08-08-2007
47100 | 5200 | 08-15-2007
36100 | 3500 | 12-10-2007
- 32200 | 4500 | 01-01-2008
32200 | 4200 | 01-01-2008
+ 32200 | 4500 | 01-01-2008
(10 rows)
select sum(salary) over (order by enroll_date desc range between '1 year'::interval preceding and '1 year'::interval following),
@@ -1539,8 +1539,8 @@ select sum(salary) over (order by enroll_date desc range between '1 year'::inter
36100 | 3500 | 12-10-2007
47100 | 5200 | 08-15-2007
47100 | 4800 | 08-08-2007
- 47100 | 4800 | 08-01-2007
47100 | 5200 | 08-01-2007
+ 47100 | 4800 | 08-01-2007
38400 | 3900 | 12-23-2006
34900 | 5000 | 10-01-2006
34900 | 6000 | 10-01-2006
@@ -1555,8 +1555,8 @@ select sum(salary) over (order by enroll_date desc range between '1 year'::inter
| 3500 | 12-10-2007
| 5200 | 08-15-2007
| 4800 | 08-08-2007
- | 4800 | 08-01-2007
| 5200 | 08-01-2007
+ | 4800 | 08-01-2007
| 3900 | 12-23-2006
| 5000 | 10-01-2006
| 6000 | 10-01-2006
@@ -1569,13 +1569,13 @@ select sum(salary) over (order by enroll_date range between '1 year'::interval p
29900 | 5000 | 10-01-2006
28900 | 6000 | 10-01-2006
34500 | 3900 | 12-23-2006
- 42300 | 4800 | 08-01-2007
41900 | 5200 | 08-01-2007
+ 42300 | 4800 | 08-01-2007
42300 | 4800 | 08-08-2007
41900 | 5200 | 08-15-2007
32600 | 3500 | 12-10-2007
- 27700 | 4500 | 01-01-2008
28000 | 4200 | 01-01-2008
+ 27700 | 4500 | 01-01-2008
(10 rows)
select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
@@ -1585,13 +1585,13 @@ select sum(salary) over (order by enroll_date range between '1 year'::interval p
23900 | 5000 | 10-01-2006
23900 | 6000 | 10-01-2006
34500 | 3900 | 12-23-2006
- 37100 | 4800 | 08-01-2007
37100 | 5200 | 08-01-2007
+ 37100 | 4800 | 08-01-2007
42300 | 4800 | 08-08-2007
41900 | 5200 | 08-15-2007
32600 | 3500 | 12-10-2007
- 23500 | 4500 | 01-01-2008
23500 | 4200 | 01-01-2008
+ 23500 | 4500 | 01-01-2008
(10 rows)
select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
@@ -1601,13 +1601,13 @@ select sum(salary) over (order by enroll_date range between '1 year'::interval p
28900 | 5000 | 10-01-2006
29900 | 6000 | 10-01-2006
38400 | 3900 | 12-23-2006
- 41900 | 4800 | 08-01-2007
42300 | 5200 | 08-01-2007
+ 41900 | 4800 | 08-01-2007
47100 | 4800 | 08-08-2007
47100 | 5200 | 08-15-2007
36100 | 3500 | 12-10-2007
- 28000 | 4500 | 01-01-2008
27700 | 4200 | 01-01-2008
+ 28000 | 4500 | 01-01-2008
(10 rows)
select first_value(salary) over(order by salary range between 1000 preceding and 1000 following),
@@ -1692,13 +1692,13 @@ select first_value(salary) over(order by enroll_date range between unbounded pre
5000 | 5200 | 5000 | 10-01-2006
6000 | 5200 | 6000 | 10-01-2006
5000 | 3500 | 3900 | 12-23-2006
- 5000 | 4200 | 4800 | 08-01-2007
- 5000 | 4200 | 5200 | 08-01-2007
- 5000 | 4200 | 4800 | 08-08-2007
- 5000 | 4200 | 5200 | 08-15-2007
- 5000 | 4200 | 3500 | 12-10-2007
- 5000 | 4200 | 4500 | 01-01-2008
- 5000 | 4200 | 4200 | 01-01-2008
+ 5000 | 4500 | 5200 | 08-01-2007
+ 5000 | 4500 | 4800 | 08-01-2007
+ 5000 | 4500 | 4800 | 08-08-2007
+ 5000 | 4500 | 5200 | 08-15-2007
+ 5000 | 4500 | 3500 | 12-10-2007
+ 5000 | 4500 | 4200 | 01-01-2008
+ 5000 | 4500 | 4500 | 01-01-2008
(10 rows)
select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
@@ -1711,13 +1711,13 @@ select first_value(salary) over(order by enroll_date range between unbounded pre
5000 | 5200 | 5000 | 10-01-2006
6000 | 5200 | 6000 | 10-01-2006
5000 | 3500 | 3900 | 12-23-2006
- 5000 | 4200 | 4800 | 08-01-2007
- 5000 | 4200 | 5200 | 08-01-2007
- 5000 | 4200 | 4800 | 08-08-2007
- 5000 | 4200 | 5200 | 08-15-2007
- 5000 | 4200 | 3500 | 12-10-2007
- 5000 | 4500 | 4500 | 01-01-2008
+ 5000 | 4500 | 5200 | 08-01-2007
+ 5000 | 4500 | 4800 | 08-01-2007
+ 5000 | 4500 | 4800 | 08-08-2007
+ 5000 | 4500 | 5200 | 08-15-2007
+ 5000 | 4500 | 3500 | 12-10-2007
5000 | 4200 | 4200 | 01-01-2008
+ 5000 | 4500 | 4500 | 01-01-2008
(10 rows)
select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
@@ -1730,13 +1730,13 @@ select first_value(salary) over(order by enroll_date range between unbounded pre
3900 | 5200 | 5000 | 10-01-2006
3900 | 5200 | 6000 | 10-01-2006
5000 | 3500 | 3900 | 12-23-2006
- 5000 | 4200 | 4800 | 08-01-2007
- 5000 | 4200 | 5200 | 08-01-2007
- 5000 | 4200 | 4800 | 08-08-2007
- 5000 | 4200 | 5200 | 08-15-2007
- 5000 | 4200 | 3500 | 12-10-2007
- 5000 | 3500 | 4500 | 01-01-2008
+ 5000 | 4500 | 5200 | 08-01-2007
+ 5000 | 4500 | 4800 | 08-01-2007
+ 5000 | 4500 | 4800 | 08-08-2007
+ 5000 | 4500 | 5200 | 08-15-2007
+ 5000 | 4500 | 3500 | 12-10-2007
5000 | 3500 | 4200 | 01-01-2008
+ 5000 | 3500 | 4500 | 01-01-2008
(10 rows)
select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
@@ -1749,13 +1749,13 @@ select first_value(salary) over(order by enroll_date range between unbounded pre
6000 | 5200 | 5000 | 10-01-2006
5000 | 5200 | 6000 | 10-01-2006
5000 | 3500 | 3900 | 12-23-2006
- 5000 | 4200 | 4800 | 08-01-2007
- 5000 | 4200 | 5200 | 08-01-2007
- 5000 | 4200 | 4800 | 08-08-2007
- 5000 | 4200 | 5200 | 08-15-2007
- 5000 | 4200 | 3500 | 12-10-2007
- 5000 | 4200 | 4500 | 01-01-2008
+ 5000 | 4500 | 5200 | 08-01-2007
+ 5000 | 4500 | 4800 | 08-01-2007
+ 5000 | 4500 | 4800 | 08-08-2007
+ 5000 | 4500 | 5200 | 08-15-2007
+ 5000 | 4500 | 3500 | 12-10-2007
5000 | 4500 | 4200 | 01-01-2008
+ 5000 | 4200 | 4500 | 01-01-2008
(10 rows)
-- RANGE offset PRECEDING/FOLLOWING with null values
@@ -1810,8 +1810,8 @@ window w as
(order by x desc nulls first range between 2 preceding and 2 following);
x | y | first_value | last_value
---+----+-------------+------------
- | 43 | 43 | 42
- | 42 | 43 | 42
+ | 42 | 42 | 43
+ | 43 | 42 | 43
5 | 5 | 5 | 3
4 | 4 | 5 | 2
3 | 3 | 5 | 1
@@ -2392,10 +2392,10 @@ window w as (order by f_time desc range between
10 | 20:00:00 | 10 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
- 7 | 17:00:00 | 8 | 5
- 6 | 15:00:00 | 6 | 3
- 5 | 15:00:00 | 6 | 3
- 4 | 14:00:00 | 6 | 2
+ 7 | 17:00:00 | 8 | 6
+ 5 | 15:00:00 | 5 | 3
+ 6 | 15:00:00 | 5 | 3
+ 4 | 14:00:00 | 5 | 2
3 | 13:00:00 | 4 | 1
2 | 12:00:00 | 3 | 1
1 | 11:00:00 | 2 | 1
@@ -2428,10 +2428,10 @@ window w as (order by f_timetz desc range between
10 | 20:00:00+01 | 10 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
- 7 | 17:00:00+01 | 8 | 5
- 6 | 15:00:00+01 | 6 | 3
- 5 | 15:00:00+01 | 6 | 3
- 4 | 14:00:00+01 | 6 | 2
+ 7 | 17:00:00+01 | 8 | 6
+ 5 | 15:00:00+01 | 5 | 3
+ 6 | 15:00:00+01 | 5 | 3
+ 4 | 14:00:00+01 | 5 | 2
3 | 13:00:00+01 | 4 | 1
2 | 12:00:00+01 | 3 | 1
1 | 11:00:00+01 | 2 | 1
@@ -2465,9 +2465,9 @@ window w as (order by f_interval desc range between
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
7 | @ 7 years | 8 | 7
- 6 | @ 5 years | 6 | 4
- 5 | @ 5 years | 6 | 4
- 4 | @ 4 years | 6 | 3
+ 5 | @ 5 years | 5 | 4
+ 6 | @ 5 years | 5 | 4
+ 4 | @ 4 years | 5 | 3
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
@@ -2503,10 +2503,10 @@ window w as (order by f_timestamptz desc range between
7 | Wed Oct 19 02:23:54 2005 PDT | 8 | 6
6 | Tue Oct 19 02:23:54 2004 PDT | 7 | 5
5 | Sun Oct 19 02:23:54 2003 PDT | 6 | 4
- 4 | Sat Oct 19 02:23:54 2002 PDT | 5 | 2
- 3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 5 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
- 1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 2 | 1
(10 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
@@ -2539,10 +2539,10 @@ window w as (order by f_timestamp desc range between
7 | Wed Oct 19 10:23:54 2005 | 8 | 6
6 | Tue Oct 19 10:23:54 2004 | 7 | 5
5 | Sun Oct 19 10:23:54 2003 | 6 | 4
- 4 | Sat Oct 19 10:23:54 2002 | 5 | 2
- 3 | Fri Oct 19 10:23:54 2001 | 4 | 1
+ 4 | Sat Oct 19 10:23:54 2002 | 5 | 3
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
- 1 | Thu Oct 19 10:23:54 2000 | 3 | 1
+ 3 | Fri Oct 19 10:23:54 2001 | 4 | 1
+ 1 | Thu Oct 19 10:23:54 2000 | 2 | 1
(10 rows)
-- RANGE offset PRECEDING/FOLLOWING error cases
@@ -2588,14 +2588,14 @@ SELECT sum(unique1) over (order by four groups between unbounded preceding and c
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 12 | 0 | 0
- 12 | 8 | 0
12 | 4 | 0
- 27 | 5 | 1
- 27 | 9 | 1
+ 12 | 8 | 0
+ 12 | 0 | 0
27 | 1 | 1
- 35 | 6 | 2
+ 27 | 9 | 1
+ 27 | 5 | 1
35 | 2 | 2
+ 35 | 6 | 2
45 | 3 | 3
45 | 7 | 3
(10 rows)
@@ -2605,14 +2605,14 @@ SELECT sum(unique1) over (order by four groups between unbounded preceding and u
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 45 | 0 | 0
- 45 | 8 | 0
45 | 4 | 0
- 45 | 5 | 1
- 45 | 9 | 1
+ 45 | 8 | 0
+ 45 | 0 | 0
45 | 1 | 1
- 45 | 6 | 2
+ 45 | 9 | 1
+ 45 | 5 | 1
45 | 2 | 2
+ 45 | 6 | 2
45 | 3 | 3
45 | 7 | 3
(10 rows)
@@ -2622,14 +2622,14 @@ SELECT sum(unique1) over (order by four groups between current row and unbounded
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 45 | 0 | 0
- 45 | 8 | 0
45 | 4 | 0
- 33 | 5 | 1
- 33 | 9 | 1
+ 45 | 8 | 0
+ 45 | 0 | 0
33 | 1 | 1
- 18 | 6 | 2
+ 33 | 9 | 1
+ 33 | 5 | 1
18 | 2 | 2
+ 18 | 6 | 2
10 | 3 | 3
10 | 7 | 3
(10 rows)
@@ -2639,14 +2639,14 @@ SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 45 | 0 | 0
- 45 | 8 | 0
45 | 4 | 0
- 45 | 5 | 1
- 45 | 9 | 1
+ 45 | 8 | 0
+ 45 | 0 | 0
45 | 1 | 1
- 33 | 6 | 2
+ 45 | 9 | 1
+ 45 | 5 | 1
33 | 2 | 2
+ 33 | 6 | 2
18 | 3 | 3
18 | 7 | 3
(10 rows)
@@ -2656,14 +2656,14 @@ SELECT sum(unique1) over (order by four groups between 1 following and unbounded
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 33 | 0 | 0
- 33 | 8 | 0
33 | 4 | 0
- 18 | 5 | 1
- 18 | 9 | 1
+ 33 | 8 | 0
+ 33 | 0 | 0
18 | 1 | 1
- 10 | 6 | 2
+ 18 | 9 | 1
+ 18 | 5 | 1
10 | 2 | 2
+ 10 | 6 | 2
| 3 | 3
| 7 | 3
(10 rows)
@@ -2673,14 +2673,14 @@ SELECT sum(unique1) over (order by four groups between unbounded preceding and 2
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 35 | 0 | 0
- 35 | 8 | 0
35 | 4 | 0
- 45 | 5 | 1
- 45 | 9 | 1
+ 35 | 8 | 0
+ 35 | 0 | 0
45 | 1 | 1
- 45 | 6 | 2
+ 45 | 9 | 1
+ 45 | 5 | 1
45 | 2 | 2
+ 45 | 6 | 2
45 | 3 | 3
45 | 7 | 3
(10 rows)
@@ -2690,14 +2690,14 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 precedi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- | 0 | 0
- | 8 | 0
| 4 | 0
- 12 | 5 | 1
- 12 | 9 | 1
+ | 8 | 0
+ | 0 | 0
12 | 1 | 1
- 27 | 6 | 2
+ 12 | 9 | 1
+ 12 | 5 | 1
27 | 2 | 2
+ 27 | 6 | 2
23 | 3 | 3
23 | 7 | 3
(10 rows)
@@ -2707,14 +2707,14 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 27 | 0 | 0
- 27 | 8 | 0
27 | 4 | 0
- 35 | 5 | 1
- 35 | 9 | 1
+ 27 | 8 | 0
+ 27 | 0 | 0
35 | 1 | 1
- 45 | 6 | 2
+ 35 | 9 | 1
+ 35 | 5 | 1
45 | 2 | 2
+ 45 | 6 | 2
33 | 3 | 3
33 | 7 | 3
(10 rows)
@@ -2724,14 +2724,14 @@ SELECT sum(unique1) over (order by four groups between 0 preceding and 0 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 12 | 0 | 0
- 12 | 8 | 0
12 | 4 | 0
- 15 | 5 | 1
- 15 | 9 | 1
+ 12 | 8 | 0
+ 12 | 0 | 0
15 | 1 | 1
- 8 | 6 | 2
+ 15 | 9 | 1
+ 15 | 5 | 1
8 | 2 | 2
+ 8 | 6 | 2
10 | 3 | 3
10 | 7 | 3
(10 rows)
@@ -2741,14 +2741,14 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 27 | 0 | 0
- 19 | 8 | 0
23 | 4 | 0
- 30 | 5 | 1
- 26 | 9 | 1
+ 19 | 8 | 0
+ 27 | 0 | 0
34 | 1 | 1
- 39 | 6 | 2
+ 26 | 9 | 1
+ 30 | 5 | 1
43 | 2 | 2
+ 39 | 6 | 2
30 | 3 | 3
26 | 7 | 3
(10 rows)
@@ -2758,14 +2758,14 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 15 | 0 | 0
- 15 | 8 | 0
15 | 4 | 0
- 20 | 5 | 1
- 20 | 9 | 1
+ 15 | 8 | 0
+ 15 | 0 | 0
20 | 1 | 1
- 37 | 6 | 2
+ 20 | 9 | 1
+ 20 | 5 | 1
37 | 2 | 2
+ 37 | 6 | 2
23 | 3 | 3
23 | 7 | 3
(10 rows)
@@ -2775,14 +2775,14 @@ SELECT sum(unique1) over (order by four groups between 2 preceding and 1 followi
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
- 15 | 0 | 0
- 23 | 8 | 0
19 | 4 | 0
- 25 | 5 | 1
- 29 | 9 | 1
+ 23 | 8 | 0
+ 15 | 0 | 0
21 | 1 | 1
- 43 | 6 | 2
+ 29 | 9 | 1
+ 25 | 5 | 1
39 | 2 | 2
+ 43 | 6 | 2
26 | 3 | 3
30 | 7 | 3
(10 rows)
@@ -2863,14 +2863,14 @@ select first_value(salary) over(order by enroll_date groups between 1 preceding
-------------+------+-----------+--------+-------------
5000 | 6000 | 5000 | 5000 | 10-01-2006
5000 | 3900 | 5000 | 6000 | 10-01-2006
- 5000 | 4800 | 5000 | 3900 | 12-23-2006
- 3900 | 5200 | 3900 | 4800 | 08-01-2007
+ 5000 | 5200 | 5000 | 3900 | 12-23-2006
3900 | 4800 | 3900 | 5200 | 08-01-2007
- 4800 | 5200 | 4800 | 4800 | 08-08-2007
+ 3900 | 4800 | 3900 | 4800 | 08-01-2007
+ 5200 | 5200 | 5200 | 4800 | 08-08-2007
4800 | 3500 | 4800 | 5200 | 08-15-2007
- 5200 | 4500 | 5200 | 3500 | 12-10-2007
- 3500 | 4200 | 3500 | 4500 | 01-01-2008
- 3500 | | 3500 | 4200 | 01-01-2008
+ 5200 | 4200 | 5200 | 3500 | 12-10-2007
+ 3500 | 4500 | 3500 | 4200 | 01-01-2008
+ 3500 | | 3500 | 4500 | 01-01-2008
(10 rows)
select last_value(salary) over(order by enroll_date groups between 1 preceding and 1 following),
@@ -2880,14 +2880,14 @@ select last_value(salary) over(order by enroll_date groups between 1 preceding a
------------+------+--------+-------------
3900 | | 5000 | 10-01-2006
3900 | 5000 | 6000 | 10-01-2006
- 5200 | 6000 | 3900 | 12-23-2006
- 4800 | 3900 | 4800 | 08-01-2007
- 4800 | 4800 | 5200 | 08-01-2007
- 5200 | 5200 | 4800 | 08-08-2007
+ 4800 | 6000 | 3900 | 12-23-2006
+ 4800 | 3900 | 5200 | 08-01-2007
+ 4800 | 5200 | 4800 | 08-01-2007
+ 5200 | 4800 | 4800 | 08-08-2007
3500 | 4800 | 5200 | 08-15-2007
- 4200 | 5200 | 3500 | 12-10-2007
- 4200 | 3500 | 4500 | 01-01-2008
- 4200 | 4500 | 4200 | 01-01-2008
+ 4500 | 5200 | 3500 | 12-10-2007
+ 4500 | 3500 | 4200 | 01-01-2008
+ 4500 | 4200 | 4500 | 01-01-2008
(10 rows)
select first_value(salary) over(order by enroll_date groups between 1 following and 3 following
@@ -2900,14 +2900,14 @@ select first_value(salary) over(order by enroll_date groups between 1 following
-------------+------+-----------+--------+-------------
3900 | 6000 | 3900 | 5000 | 10-01-2006
3900 | 3900 | 3900 | 6000 | 10-01-2006
- 4800 | 4800 | 4800 | 3900 | 12-23-2006
- 4800 | 5200 | 4800 | 4800 | 08-01-2007
+ 5200 | 5200 | 5200 | 3900 | 12-23-2006
4800 | 4800 | 4800 | 5200 | 08-01-2007
+ 4800 | 4800 | 4800 | 4800 | 08-01-2007
5200 | 5200 | 5200 | 4800 | 08-08-2007
3500 | 3500 | 3500 | 5200 | 08-15-2007
- 4500 | 4500 | 4500 | 3500 | 12-10-2007
- | 4200 | | 4500 | 01-01-2008
- | | | 4200 | 01-01-2008
+ 4200 | 4200 | 4200 | 3500 | 12-10-2007
+ | 4500 | | 4200 | 01-01-2008
+ | | | 4500 | 01-01-2008
(10 rows)
select last_value(salary) over(order by enroll_date groups between 1 following and 3 following
@@ -2919,13 +2919,13 @@ select last_value(salary) over(order by enroll_date groups between 1 following a
4800 | | 5000 | 10-01-2006
4800 | 5000 | 6000 | 10-01-2006
5200 | 6000 | 3900 | 12-23-2006
- 3500 | 3900 | 4800 | 08-01-2007
- 3500 | 4800 | 5200 | 08-01-2007
- 4200 | 5200 | 4800 | 08-08-2007
- 4200 | 4800 | 5200 | 08-15-2007
- 4200 | 5200 | 3500 | 12-10-2007
- | 3500 | 4500 | 01-01-2008
- | 4500 | 4200 | 01-01-2008
+ 3500 | 3900 | 5200 | 08-01-2007
+ 3500 | 5200 | 4800 | 08-01-2007
+ 4500 | 4800 | 4800 | 08-08-2007
+ 4500 | 4800 | 5200 | 08-15-2007
+ 4500 | 5200 | 3500 | 12-10-2007
+ | 3500 | 4200 | 01-01-2008
+ | 4200 | 4500 | 01-01-2008
(10 rows)
-- Show differences in offset interpretation between ROWS, RANGE, and GROUPS
@@ -3686,7 +3686,7 @@ SELECT * FROM
depname | empno | salary | enroll_date | c1 | rn | c2 | c3
-----------+-------+--------+-------------+----+----+----+----
personnel | 5 | 3500 | 12-10-2007 | 2 | 1 | 2 | 2
- sales | 3 | 4800 | 08-01-2007 | 3 | 1 | 3 | 3
+ sales | 1 | 5000 | 10-01-2006 | 3 | 1 | 3 | 3
(2 rows)
-- Tests to ensure we don't push down the run condition when it's not valid to
@@ -3824,7 +3824,7 @@ WHERE first_emp = 1 OR last_emp = 1;
depname | empno | salary | enroll_date | first_emp | last_emp
-----------+-------+--------+-------------+-----------+----------
develop | 8 | 6000 | 10-01-2006 | 1 | 5
- develop | 7 | 4200 | 01-01-2008 | 5 | 1
+ develop | 7 | 4200 | 01-01-2008 | 4 | 1
personnel | 2 | 3900 | 12-23-2006 | 1 | 2
personnel | 5 | 3500 | 12-10-2007 | 2 | 1
sales | 1 | 5000 | 10-01-2006 | 1 | 3
--
2.36.1
v2-dual-vs-single-12.odsapplication/vnd.oasis.opendocument.spreadsheet; name=v2-dual-vs-single-12.odsDownload
PK :�T�l9�. . mimetypeapplication/vnd.oasis.opendocument.spreadsheetPK :�T ObjectReplacements/Object 4�]klU>��V�-}ZJ��U(��K,�V�J+��`b��A���~��0JI0���G��&F1$
�������#���1����G=g�����;�����|;������9s�����������<Pb�@������S�x2��� �5�y�'a���A��e8� �Z�%p?���$� IK��Jy&���S���3O���'+J�#����N�����-}Xb���x�D4�F���H���q���+?w��y������5����75l�6��n]zb�F�O����
�)��~��n��Q�r�J"�s9�Zb�� �k��=������U<�x�����!7B������m��{s��V�������dX�������$���+=!tL.#Ib��)_���1��$�-Wu�� �crI[��L�B��2�$�\����:&��$���2K���1��$�-��O���1��$�+� 8��q��D����g��:&��D�����EO��L��r�\����:&��D�������:&��D�����'zB��\f%�K���+zB��\f%�K�*,=!tL.3���%r-�O���1��$J.���j�� �cr�I�\.���%�'���e&Qr�D���DO��L��r�\;�EO��L��r�\�}<��'�9"��D����{xG�2�$�\9iwa*��D��"���l&Qr�H.�'��I�\.���y�f%���r�gf%���r�Wf%���r�G.f%����9�� 3���%r�3G�2�L�\���!��
��Z�|pU�F��
������=����'����K���h��+���H�9��3�c�L��0Z&��)Jg����>����%Nd���h��
}F�;$�@���1�g4�,���)���P����|�g�fJg���B��aO��"����1<�/j�6G�K�AKg���!G���+q/�L���Ve���87�tF{K�E
��%~�CJg�C��h/�a�x7*%" ����o|���B�1p��v��7�Ed�������rEd������7g��h��#�����L�1p�;���)" �=�xG�%~3GD@F{�����{�����8�u���?KD@F{�����g�����8���%����h��#^u����"2�3� �:[�C[�[������nMcH�iy�c��Q��������;����zMm��P����EYn�G���u�\��w����L�����|��,P���W�� �%��p���=�J������G\Oa�
�������Ib��{j��lLd��:+�:+�:�k������na�1F�i=��>�m�ze�n��W?B5�O���^�q�9���d�
+�l�6�dG����l\�l�6���f��;�q��q\���]#%�����6�h;���a��X���59%���q�����W �0���j6���s;n{5�BPN*�������`eYX�6\�!.�P���$���Xg�c�5�:�o�������B��:����i�R���#|��\���{�) .�T�G�]�e�\�}�r���i@z!�� �`��N���nxHk����W��V���:tz[p]��B��3�G�U�g,�N��4
��� ��As^���of�����6+�\{�V:^
��A3��+K� �;icB����*�:n���Yp��:��G���k9V74g������<3��V&��I[bq���bn�|����k���(�����pX���RG�����)�FO���S����pO��/=�"��R�b����r��kL�P1e�{��L�)�1��JSf���=%��R�b���������cJ��)3�SjR��kL�R1%~<���z�i�������O� =�����
���
v�v?����r�q�����^s
�_|b��
�]� c�!\����T��4�����[2���������L]�M��J��J���m��a��i�5u'���8������x�e�0`zt�J�����|2���Z�``�FJ�rwuR���0�!vb�sPr���.<ix"wc�>��|��6���QW��s}
"�}�R�u��b;�[�> ��p��f��C{sy��^�:���3��_R�3��T}b�]�Z���������:�+Rx�y�^�G����uz�(X�Qq1���~�6�g��
�=���d1�k��JE���-�N��G8��J�p(��,�� �Z��jJ�<��PKU�^Y� {� PK :�T ObjectReplacements/Object 3�]]lE>�����K����������FQ(��'TZi���Z
E[h<�DE�&`�����F
F%�H�`|�1Q4&D�MD|�����l���p�C���������|3�9{v�;�li��hK�*A,>����� /� ��`~��X�x��`���A��wv K���%Bp��q+�"H�Ri�R��c0�����������R�0n,��
�;��K,�������`7���e�cv3$W~��YJ��q�)����-k�[6oZno?��*���������_�[������,����j����lj���o���&v����������5U]P�YS"�
o\�enC��[���j�Z�w
�Z=9n�������$���]"zB�M.'Ir���T�Bmr9I�[�uE�'���r�$�\�
DO���$In���DO���$In�9�'���r�$�\�2EO���$In�g��j��I��r���j��I��rQ���<�WI�\���\�Bmr�$F.��u�@��P�\*���#r]-=!�&�Jb���\����j�K%1ryD�KEO�����<"W��Bmr�$F.��UR.zB�M.������=!�&�Jb���\��DO�����<"����'���RI�\�k�b�Bmr�$F.���k�� �6�T#�G������>�E.������9�V�T����0�.LWI�\�K��z����!�������<$���6T#�������Jb���\�� RI�\�K�+T*���Cri�L%1ryH.�/��$F.���}F��������Z�RI&J.n��0D����M����U)q."�3Z����g�i�/K$^�V:�;����(W��8XV:�;����T�K<]V:�;������+�A���tFwo��Y��-1�������=��2��X3�3�c�!j88S��\���1<3]��W"�
k�tFwO��M��*�tFw�E
MS$~�����1�������Jgt��0[��,Gbc:X������*�g���#��*�������#���/O��G�I|�XD@Fw��Y ��B�1p��b����1p�;S!q8[D@Fw�N�$��-" �;�xGfI���G�������G��iS�Edt����"�CY"2�c��.�x:CD@Fw�js%nK�q,
��`*����Z��V�����feSX�iy�c��Q�C���������{wo���w@����0i ���(���j�H*�/����Q�'#��# ?����3��!�~$@?t���{��Bi�p�@��p=�unB�~?��)�"��A���M
�������J���;�;+3kC�69�p`E��s�����Wf�6@{��^��e�6>G��m��u��z.�r��66N`�
"m\wG6�36NpE�q�����8�m�����e�����6� ��vN}�������kr����� j�.j�_>��5�������4��Z��@9� �g��idk�����m���C\I��QkIBhH���;+#vV0f�7������,S�]vX#w���2u�p�KT?��7����
�X~��+�\��r��i@��*nV �+��:a����V�������������\���������U8�,gN��4
�/��:$�=x���J���7���-+8o�kL�D5���B�-����eU���%L�n�'�^����:��G�� Z9�nh���
M0��Ixf���LR9����L[k07}&�i�^K����(�����pX.@�"���y�S�*qzJm��Rk<e�{��i�S*��)�&�LzO�X���xcJ��)��SZ��T�S�LL����{��S��)U&�LzOy>GzJu�1����I�)�����xcJ��)��S����RwL�11e�{J}��S��)5&�$��|;����������.�������}�{���~@��?��?>�'�!�[�O�'��E�9���Wt�+A:*f���I�������2cge��?������Y���T-:u�RV�#�r�������'��B��XQ()�����;�%���[^X N��Y��x�`��G���<����X�e�dN'�
\?F�������5&�I������4��f��C�����}���7��#g�:6�3����1s6J-�����IPW\�)��=Tn��.��2OB0����2}��\� }�� ���b��l?��8�3�_X?�RG!8�[�t8��~Y�y���+(}�P�� PK�O��� ]� PK :�T ObjectReplacements/Object 2�][lU���-l��wh���M.](� P�Rn���J���\
��D� E�HH��^��}�J0�����F^0�(�������3;������3d��9�������9�93l�^��������}�~#�SK>
\��3-0���������+�v�,55�J�����_Y� K��B�|�`�I��[�����e�����;l��e�X ���&�f��V�)]�;f5C1q�����4��h�_����q}��hKC�����j�I~���y��������g�~�U��SH�\��V��b�b�x�.F7v�7�F`q/�������P'Ut��\������u���b[D��'��|9�d�\_��=!�&��$��W*{B�M.;If��Z${B�M.;If�u6_��P�\v�������'�����d�\Uy�'�����d�\}�eO��e'�l�.dO��e'�\����k�:I�\���Q�'���r��<"���Bmr9I�\���b�Bmr9I�\����Bmr9I�\���r�Bmr9I�\�+x�� �6��$F.��UV!{B�M.'���#rM�,{B�M.'���#r-�*{B�M.'���#r��!{B�M.'���#rm�%{B�M.'���#rm��=!�&�������y<���Cj��Ib���\�^�\N����0��|v��<$����N#����>��Ib���\�_Cp��<$���6�$F.���%'���Cri'�Ib���\�_�r��<$��7��$F.����#��K.'�����w4
��2[�) ���UE�B�tF����~����:[am�tFw_Y����pm)�tFw[�].NSx}���1�e��z�^��
A�3�cx��o��$�� ��1����8Q�&����1���5������+k�U��~����1<Z"k�\�0�i����au����H�7 ��1,-kX����w����a�YC�H�5A����0jE�/I1G�s��E@�O��#��9
�������#��3V������#����edt����)
�(���G�3a���22�c��w*����tFw��NP��\F@Fw���+��HF@Fw�:�(��TF@Fw�)V�z�����8�E�3RF@Fw���Rx=WF@�X ��A)47F�[����klX��>���<��1�������5]���n����'����+���|4���q�z�'Z���u�3V�|����~�r�QP_���j[���C��:������6�q7P
�m�5�?�z��C��~��)�%��A��������g��ge%��N��O�H��&y��Y80��x�����n�?�+�,���v���6>O��l\�u��z>�rcJ���}�6^t[6^dl��6.(Nd�E�e�E��im�nw]�(m�]h�������8��v��u�n]�sbK���;�yc��}M��q;��[p�#,��@9���g��������c��q-C���C����>P��<kX������I���o����'��B��:�)F��O)�e�~�^.]��;\�~*�-|'n��������1��4 }�F`�D�L�
��NX(ZA��(�W%����a:�������������y�����n����|���n�t�Aof�����6+� ��S:]
��A3���g�A���*�:��W%L�n�M����@YA����EN��3~uCLt|���~+�Q�$�T�ib��aW�l���Rz����T*�H�8��<em��S���)s��qO9Qj����=��x������� eOY`<e�{JE��S��)��qO��<%���6�H�)C�S���{J�#���yO�{��SR�F��v�{�����:���m�x����)OR� �x$��_�-����[�Za;n��^P>�X��5~��S�1�W~�y)KP�7q=�v(�3���I���QM��l�Mf��M���<kD�����[}����������j�
#�H�@!�s����og�^(����������/�T��}���B����k=D���X��.,�� �R>�(�3�����ad��-�\��]d�j�6����uEhJ��7M/L���4e��_�S&���F�3�O���=edg����r�d�Z����������4�+RxKy4]�G]*���=�:�"
N��8x4M�7x
�3H��%GH��rj�$?��;�3��ZX<�r�BpU��0NlY��a�6����>p(�PK�2��� �� PK :�T ObjectReplacements/Object 1�]klU>��-lK�v[
�b�P�}XbE�l�%��P��RiE[�l�bx�M 1��&j��D� ���#AM�&���U��;�wzwW�N����!����w�w�w���3{�����Dc��@l����= /���0�qPRp
�b�4,>�����^�VK�e�0�����4� �(��ZyF����x�3+/��F:��)|�7H��s�<0���b�aC:�+�����3R�lw��d��/^�He��Y��~}����u[#�
�'�7D��]�w��Z����&��:��A����e�D*�v&����3�h�OY��Dz���xx��,U7�j>Bn�T����}����r���l���a��i������$���}����1��$�-��<a �crYIR[������1��$�-�?KXB��\V���k@XB��\V�����_XB��\V����,XT��DB%�r�D����%�����h�\"��a �cr�$Z.���]����1�T-�K��-,!tL.�D�����+,!tL.�D���K�K%�r�D���B��RI�\.�k�"a �cr�$Z.��U�DXB��\*���%rmY&,!tL.�D������:&�J��r�\�J�%�����h�\"}O���5G�RI�\.����wT.�$u�
eN��T�D��"�_���h�\$��+�U-���r|��J��r�\���WI�\.���?CPI�\.�����PI�\.���z�K%�,�xx�#���Wc��m����ew"R9��4"a���,~^%1� �rF{_����\.1+�rF{_��_)�xc���1|h�y��A��B`�3�cx�\���B�O�D���1�3���Q$�{�rF{��D'
%��:T�h���9����������S�D��%��=S9�=��!�C}��;�`�3�c��)zX��M�����=�fIl��
�3�C��X�������K�i��\�8g����#2 �=�x���X�/2 �=�x_.�x4Wd@F{��>],qcHd@F{���%�
��h��3���D�x��>q�x��K<��g�cs%���h��3^�l�CA��1p���'��l��1p���H�+Cd@F{��*�%6Dd�@D8����������7ool���>"'�����1����1|� �:�i�n��8�nh���Z<�CC4���p�z�c�n��}�+�Y8cZ2L�Ot�o������@�~��Zp���w�P�!��������:d��Q���ib0�����&���*-~�7~�/~�?n���#��V�����g��'������4}�����^���_"��>��>�q����Q��������hW���U��I����X>����������8��\$|�
��������8�~^�}�j^��G��>NR���������0|�������ex� �d��<���E�&�,����� �f:���K
�@Mz��i�����
��rG����6_�jD���������L������5����aq��F�F�?������|���v�d:�~�^1@_��N���<l����,��-&�q��}��,F��Qh��u���^���v��5��I���ka1i��KW+������U/o�����C�Q������4S
�q����F|,S��_�l�S�������JL��@�>�
M���wC3��74�X���'s�oeR*��-�`����N+�4��Ka�����Ry��N����U��� "wEJ�k�T&)�:R�x�\���R�pN)�9e�GJ8�)���2�S�|���%#�<��R�s����[k�$�S�uN���R�.#�"��R�sJ�D�W�)�����7z�#�i7�A�B���?rY�OvZ�8y������;���i�2���1M�L�4�~W���%�V%��OW[�,�dk$R9��jW+���``, F*�v������Cl���P�@+��C��0��p���
�@�T�+n�S������$�����wq����1f��qje�� \�Q��*3nU�^�9�iI�/���A��`�^R�oy
��LO�o�����$k 7%�E!�V���������Y�g�,�&�P�Yq)�����=����������+}�*�L��^biu��K�-��?��S�PKZ!�N �{ PK :�T settings.xml�Zms�8�~���wB0i.0 B�K�\� I���F�h"k=��K��6d�k��X���?%������<Z����c�%I�_Y��S��A�����0��_X���\�lF���pU���"kz:���� ��$��'��r:��M������O����+k���i4V����u�b�h���F�v7����f�`�����~����v��b��_��euwv�-�{�]K��Nx�mj����+K��YRX=[�J��?��J:e�@&�[��j����+�{z��� ����/�U�4��m�����/RU�[������N�kp��`����9�c�&����sjJ%�X��!�i�&��m�)�?�
��t����s8b?�%U:�,�_���*���\6�U����@q���P�1���~g�2#����{��K��G��3��;N��w����v����r$n�Y|�z�J�W"�WDo�QJ����� ���wEm@���W������M�d��"2 ��*@����6&�X�{M/fW�g�%kF������
�U����r�A�e�4�UA�
BU����UE�_���0(y�pn�4D�*CU�����*T�!�u�u��Q?*���0�Ns�@��Ddw/�.*>��C���P@��,�oy�|������ �?�2m&�C�>2�����m�+�t�>����5<>��-�!����� �����o�L�!Y�Ky��&,$:9~�a��t�#��N�9�'8"RA����5��3�$�@j���9���vI�������`��%������|Q���{k*��Dr�-�Kn��=L A�?�����t��o��u5YR���O����}�m$�D���>aN���L���"������/@�>_:e��M��IJ�f��W7�y(6��p����}Dv������X�<r]ck1H��������XG�!�� �BopG$��T��7!zoU@����O��szv���� �;��:�
h�S�����D#7��� nz�[�d��D]S�����k����Q���y�FZ>&K�,o� 5�C�����=�3�&|�\��p��d��t?,��a���&�%�����1�G��{���)8}��);�w��=��q�T�����,P��r�u�#&�� �;����7���������PK"� � �. PK :�T��,�% �% Thumbnails/thumbnail.png�PNG
IHDR � � ���� fPLTE
""",,,333;;;CCCKKKTTT[[[ccckkksss{{{������������������������������������������������ ���2RSl %PIDATx��}�ZI����6R�������D�I�����m0��UV���������9{���3b�����1G{�WK;�����=��T�+e���E���_���O�5�Eh(����E��>69��W&�C!�f��F��������F����I�;������N����,����?�C,�mj�$�%���4��Bq��`��S&K�d�I���I��T��;
9!�S�"%�� K�&��67E�Y8�"�|��V��HI_"cfI*����Y*7���g�
^A��.��,�l3�����Ng�k;�~)�Y2tj+��hT�j�"��C�v.��5Q�������;��lp#����TKxu�#��ac���< ��S29���B���)���
��B����$���c6'������B:%Q�*$k�����6%�:]��{:IL��
����������d��L�z��r2y�39�x�\�:UM��(nU|���Jk%/���r��^
���L)�H�.��U
|��� � �7
!S�e`��L%��;�9�$3i"�'��8��Nt-YV���C�>�/� 7Q�?�l����er���K�����]�x�P����J�VH��26x�Yf�9?����z�MU*���B���������95��;.����:�������r�N���{�)J�����Ru*jK���Gx&e)��q�[�� r��2�N�E�ucV(gQ�,�����R���j����� XZ
�v��B�k�Pu,~�0f�R����c�� t�g-�Lc���!��B��������
��a2o�7r<���G��($�vQ�R��7���h��ml� ��� �!}P��a��})6��[9[QI4��i��K�������)��f��U��9k{u�b�
.r�n���A`����A�G���r��������8����������p�){� @�?���e�Bf-�+�[�u��[Q�{
I7X��b�1eW�9�R� �{��*����Md[�����@j@@S`����9�1;��Q�X�`���m��T�Q����� �5�{_0L4�?�7�C��9����+I8CTP���5'�= ���Ym\4�\�F�J�Q
�L�-���c�<f9?PH�k0u��J2���`���7��w�)�AT
��q�@.l�1G4�Eb����������>�����7�t3����+![��Q���e��e�/�����m:���inq����I�o�V�wRh����'�~0D���g+���K��(��r�{�R����D����t9�������,��Ga��;���c��r����� C\l�QH���7�������0�����s��b���)r+�}��mzG���)��B� ��%I���&Dw<n<D�3���I;���[3�[n'H�r'OzzG!Z���)���7���b���Q�����u�'�/�p�p����e#�
!��$�2�7��S$�k!
]I��SB��F��Upp6�OL*���S���r���?�h�*pD���P0�
!�7}+�����BC��`�d^��F��T�C �/���B[��\�]Z�ba�)���������)���zf�����yK���.�i����Y9��r���j^?%���>`u�m���mg����]�j��f�$iV��������F��b�D:����j/!x[����m�'�^����������e�n� ������p��RV�|G!��C��Y ���@o�z���5��4'��oDdpW����m�Kj��v�x' ���,�]�t��N&��c��������Zw��F�V��Z��k��uZ��A��f�u�$%Sz�!u�:���C�
�N
9i�<c���;��(���yU���H��Q��B�Co��SH��j���K�[�3�R��~�m Z�^����:�:���+����6[��h�e�3Xi���3�*�YC�*<�|Jg��t_��Tp��g�5`���D<}X�:�B�L�26oy�&H��+t�w�m���6�P��� �m���!�m<i�q�����ET~��F����uhL@�p���1��8�A0)��tq���d=��U��drP���*7���<�h|�Z<���3��%�=i�����o�]���/������Z��������Z��q�[��K�Q���O>���� �:8���ZAf=���pmKH����%�P����t5���k��6t��Gn$��:_������
���w��HGog*���R1�3**�IWrO����z�l�w�S�@Yg����e�b��4�+�pV
�iZ��Vz!4��O!/`��mwkI{v�%������eLls���r�x���7lj!
,w�ze1�m��/�����^��6�C`���`7���#�M��c��e�6�|m�S��U���Q�#�6��*�^+T��pD�D_����ms���VO�r��Z!r%%��6��=r�O%�d�x�e�ZE�#\��B�+zQ���
��_���M1t}�d}q
����`��?R�� Y?�?}�#Wv�����?~��������I��@2��\��\��8�W�P}��}{
��BCs��� ��+X#��N&u)��ms����%���<{�B�Z��)@�b4�a�����=0���v���I.g���]�B��v3!�&����-���}���Z��B\�zj[Rq��m_B������ �f�mS���A`{;�e1�J��� �o�a�_q��P�������m���!����&XQuX1a�5r �@'�N����~V����5��m��en��=I���N;�@5t��^�e���1�l��A0o$��y�O��B�
����>�C�O?s-j����j+��eX]�=`"�>��(��<<C*�8���O��;����Rm���P(��@Z? � 4!��j^-n;4}�9����VH��>������Rf���qO8�����m;�X~��M"�L/�F�jXLKk1%�����X�F2F���g���>�m����m� +�G���}
���FV�<k��;����k`}��% �.���,x�L��>.n�|4�=�����*�O�0B��Oj�w
ne^y�����(��>*j%�1�)���m�������N����o�Qw�FAs,�p��m��x���x��a�TT/<�O4������t����
���=� 5,+Hi������A���C�r��JO���>i���Z�>G��w���r�(���H��*T��o������iL�gn.q���b5�J��-r���/�v�J?R���9��e���eg�k���t6:��
�o��luB�c���6������L���:v�H���6��H���m;��Nn�]�m,��Q�m;����
�pe�d �.���6!�{���+`�gk�q
p���on�x�)���IM~����u>D�����r
��mf�����nL/n;p#���JM��!����6}��1�t�� #wZ��m�p����������Z/��:�[��?2��}�j���~���U�\�o���������v1cS��n]V>a/L�������8+�S��A+G�����(����IR�����0rG��(����T�l��7i�`��r�p�X�����z����$�J��,��%;�uq�@�"�Rx�bm ���Bc�>��rwOu���U9�l���q��^v�bh&�w
4H�z��IR���$��iJ[l4����:R��C�X����;�z�q�-K�)�g�U��U�;��oG���R���j�a�����J��6sf,4;��
��(���C�6BB���8�+�v��t`��J�(�6?���E�E�s�
�>D��U�-NA��}�>_;D��i?^%��U<�k
S�j����\Ko�C�������F{;yke�u0wU��A.�W�zY
����r�YT�}T��U,n[+u���<������p�IzI������0t����g������s��-�m~�F��mLD�X�����s�<��UN�Y�4����p�����3H��A��i�
�0�������}V��it41�����,R�����S�u���m����L,ltZ��
�l,��7��U^���3d+���
n9<�)6!CDq�P���#��m5MO��b�� .KNM���C�
�&
����d��_����/j.�^�@�T�-���,v?�I09��Lj.d��*s���L���
[?fN#6e�iJm�{�j�6���r�����8e>����`�
rG�\(�#�V��G�>o��KY8t=���J�.y�V]Bp�������_�ci�y���,�O����vy
�������mk��X�%���m��X����GOA�yw+#���x
��K����R'w<�.)6��n�|��O@n>aM�
y�h�����Vn��QYm���]��}�k�!�X����iXA�.�*G;/��w�
��$���~�c�7���
�?a�7P��o��k��~����H�V!�K�m~����������P���a��.O��\����N�}���K�������F�������8*����>��eIJz���C9{X������i|��6}�o�Z�XX����~��2+{Jj�:S�Q�P�����+4�7�on{w�mv����i\�����6�����d�-�1*�Tk��'�a���Z�9�.a���K��p�S����mWO�"����O7�#�5�������d:�C�`���{+w��
vH 2������v��o�����
9�>����?���r��9�<|�?�#����5a�����p��w=~�=�?@����l[�3eWu�;�W��dLu���>�6�[�7���/��kh8��mcn�X�!�
2zC���
��BG;;g2���Vu`Y����B�����FU���:\���X�����dY��ej�{1�E��?UaYa��6��CIZU��#����%�Cn[[����J��SG#1��9��
��}n3��f���E=���d���.�����Ei��6�S;X?�84��V3C�v��A����'�"�O��g�0i`�� �Ck�Z��.�5x��m;$��~��<[��<<���{��AC�*I'E����C�^��V������f��-'6^W����������h@�7d��4������H�����f��m8��6��fO�24����)�('����L.i;,�k����/LX�Bp�-����rgo}&D��{c7�����R��Z�
�")!HZ�g���re�����%��S ��#�#!�m$���U��DKm�9_�#�Z?�!.z��s�X�.
3���n'!?�-V$�vR��m�����d�B)�U�����1K����,Y��X���I*X��>qJ1o{��}UzY@������dp���^p�cF�N|���Yb^e�x�> ����q�q��
��8Xpu��g�
����v����!��ThVs+���.v����Qod���k$g�H��� 8�)a����_��5(�2U ���3�����F��P���_��N7,o���l���|����B�m���.;B�����.�x���x��m�������� �(���n�$O��vUv8kFY�qj��-����U;��nZ9 V1E{��W��l��m�_��������������_�Z����]c;��f'�t��x��WA���o��m�4�|�b@�
�_��(�@!��E0xS�x�L�]��CO� Va�Cn[K�� �=I���.���������W ����f���@��B^������X����JJ���l�v��l.����~� �o��aM�a����,3)w������M6 8*.b�A��6[���NL��L��/����D��m���"�-���d �m����4��ivo�6�+��7���
U�nq�������A������'D�<������A6�����VB�U����E
[��������]�%o
���i<y�c gx)��<����n�Jr- �z� ���x�Cx��E=��a�i0�F�f;��UjV�������
�M�A�A+7�
�`������� Qq�����`D@n[�,'�d����3���]n�����G�U����m���mcbSeE�G/*!������/�mp�Bn[�V����i�S(X�_K��)�����Nl N1��<�E�o&[�$K�.7qo�d�������f�J>�+{�����mkq��>�����P�p��9�=�i��<�x�
!��R3y�\y����m��M����^OL��� r_�����|z�p�;�$��a��AZ��j�&/�\q�Q�>���>����I���QLz�AJ�n�~��N{�p����N��1��9'hf�v%����B�!x��4�!��>\����7�3�,������4�C�!8����t�@D�Uz4^K��&�,n[��3'X�����X:}���t�M���v|)��L��B�G�,���o*�����d8x
;�Y�R��Trsi��N��!^�y�5��������c%q2���?���V�3V<��]�
|g��|��B���~S!l#���a�t(�KJ�cdCk�#�
bK����������� n[����,��mcd����u)���~��h�$�5|9��YX��B"�
��������7��2����T����f!�K�m��<�Y!|E�m ��eL`��nk��5#@�T�r1G]���#n;�(A���We:���m���9��mVp����]e��Q���mf��^��b�?�����';�&��T�k�������t+D�xh<y�OR������f�T��,I��������U������sy�<���V��j�����S�;#�;�u������:��w�c��FS�Ja��r��M����P=�������
���d�u
�I��;4�j�����_�PJ�X_�S�4�����Hc�m}���t�cK�v4]�f��
��5�4���o���C��NK�1�����z����)t�����^���=�'�t9������l
��s����i��%�����0�} �p��[�� `P����(�8�M��\]���f�xm
�gM��O%�m�(r�Q�Hf���r��mge�7�S�>���V�G^a���ezK"4���n�n����[�a,k���e�`����)��?����(�v���<��a���H��y�j��Oc����j+��������+�O��<������L�� �����8x�y/$��y6�����*I-y=+�|��#��,����I������
�����8���!�O�����a�"�*�����?���uW��7
��Y����y�Q7�K�3���p����XG�x(���~I���i���~�X�����b3�p�$ ��JW����~���B^�m�%cj��u<�3N�N���\�7�\[/d1�Y�)��O��n_�o{���e
�W*��N����N�:��u���� ��|E���3�����~�������m�����X6�* ~U1� Z�@��1X�7"Mx
~vN� )X;���+�������fI�n[-Z��XD��Al��;4.Db�mix>Z��b�5\�B��*���&�c��K�m���/����9���4D�����x����������k��^so�"�K#��J��������e�V��z������K6�������������a�<�����
i��\�m����� �lSW2�5KR�,����\+�c�R������? ���a�i��S�.9�+ ����J#�7sN���,�A.M�U!#����@
\�b��3�h�J����gn�����'M8^�l��"������iw��0o;B�Z��L�<���:Z�2K���o{k[b��Q���������d�S ���{
���X���HHs�A_3K�]���~�,���F�j|#7�?a������m
��A{��m��X��K!� ���q��9��hpz��������-����o2|v�����q5Yx��#�d}�k^-��Z�u��:8e
�&��R�X�2�q�G
������w��o`�������.n�������5��S���
_�^�w+�����G����0T��� ��o{��>@n'���M��.�������U&�l�X����gM����Z��m�i��m��Wn�M.�X%������)(0����%��ut��C�(�)j�0�&��� �C��@��x��E`�K!1�� ���
aq;l�Qy\�vJ����j1o�-���vQSRy���K����!��B�:��j�����)�(�M�j��(l9^�-��bp���^oo9�?����Z�N���<�ZW��� �m�ra�wzSS��{Y�1��W!��(4��Q#�=�����
+�������/��A�h���E_�����o����2�os��B����{��~�n���8u�B<����{! wg���J����)����oKA��A�N���J goZ�z������N�k�m��,I��A�]�H�(�����qA��Iw�5�UU����m[�����m�/���=��6A�� 7�P_�F���*� �����$���I����������.�_�m�~�����Z�9��J�C����fpeeE:Y���e��#� V!\��s�&�cOK��'��K�����Y� 7?a�m��o��s�O�Q-��V�p�G[:�]{� �����t ����S�%y���.��3TJ��~�x8����#��eZ{�C�/����7W�J}���{��&�C@��C��� @2y�t�z��3��x_!���.�#|����K�`[I����l7{��m�^6<4^=I �������dt��J�Ol\\��Po��m���)JO��<��]��m�z�4�H���mk�a����k�D'���A���S����D���BHf��mKF�m�%�q<)�q��8k�����n�T�H���T�g`��W��� _N��=�������x:�n0_^���Zo���c9aD�����.�)��6C0[����tx2����I,�OJ����a�8�[*i[��y��nyf��q�%w�r�z �e�r�\&�mm��#��Hw[�?&b������[�����\*���.�8�e
��kL��S���y���k��P"��������(�������0�W��F���6�%��I�h��B�� B��E:Z�����q�������!x�xEk�\g���!���;�.��/� ;���9�
|3����{
�����������+o;�q����.�v!��b5��������� ��M E3��6�.s���} uM>yq�r�,�~k��^6�!���6��m�/�a�b-R����(T��2��� ���9*�PQ*��
K8�W�5����m�L��}s��'Q9��
>1�v3 H�L/�?E���6Q�2�:�E��9���B����D�3�7`4~�yw��7����~�g������.��]��6�?vV�w�������R��s�� IEND�B`�PK :�T Object 4/meta.xml�RAn�0����H���CO�zi��"���`#c�/8��UT�`�����Y;��U���4R��DAH<T\�����<����R}8H�Th�V��_�e�@U
-�eF�������[���
�2�$��'c$g�5�j���*VaC-��F5K�o,u������5�����@D0c�R��[��$I�ug���]���1Vp�g4?2c��D!8�e�k�7��m{�_=����{���S��f��w�����C�Y��j���f�_��+��6��,/[Y
?N���~�����PK�|��# I PK :�T Object 4/styles.xml�U��� ��+\�YF�������|@j�,B2�� =��di�K��r��������el��gJs��$?f��I
%��5���3�����\��8e��5L�T�����%K�����&wcZ��0��8����/�����%3�N��� -���'��<C43��i��~~>���:C5U��^:%1�5��a��1����z���L��b�����)��D`[@���K��8`�r%4[�OY�#�HYo���`sz��%�
:Yc�id�(�jU���Y���%i���u����Ywf��&�.!;#Z���:^)��B�t�,�4�d��1�uq��_5Ye����m�����^���������W��y�j7 �:j�V�RhZ��'�Qp���mv_��������-��:���m�������-I���A>�����y!���C�������&��5V��W��vJ��J��`��Q~���^�f'��H��U����)*����L�
�]��Ct�PK���(� � PK :�T Object 4/content.xml�[����~�_a8(�>��d[��H}��!M��r%Zf��^{��gH������uU�����p�����p(�~8�t��� �z\��\�*c9����?���Y�������os�J\I'c����]�m�G\2��{)����Wf��x��~�{Ziig�V�x<��H�������O����s:��~�^�$r^>~gg�����i��Q��c�<=.�����V��b�O5�D!Ft�LpX�s������j���\�5�����d{\"XCHTe�����L��x���_�����S��g�X9+��
Z�4�
@$�h��L��[��(w���b�\P��(k$�3��Er��SWQ�}��*�NW�����bc/C4k��W�'�U�S�����@�X�K4�����%�����F��=��IPg�.l���l����OS
79��(�0Q���<}�rwvu(�1�l*��8�M��6Q!���������KP��������������zZ����s�c�)�
��-����~V^,���U��'���*8sJ1,�jv�~����6���_�������������,"���\�v�ZP\�zfx�h��4���v � `&���^<=��i��H&%�w��_��@i�<���{Q���D�o��{z�F��`(�K�r��Q��(�`f��:q}��;o]�����PI���v�N)8��@j
q��$X,T��R�}�*Vam�c�����9��5f�}��N��z�%?`�gR����BUW_yc����]k� r�G�?�D�l��b�U��D���u�a���6f�O#�)�����H�,���Pju[A��W�g2��5BG��������!����9��S
i�CT �+��2P�b�1�.k�jW��`�@�����K���N*��nNE��#n�����t:��*9PV^��1�C��@��K�
��XU_%���P�����;�<!_Z�9B%�P�
ZhgG����Uk�tp(���g������W3�r�� czu��S��{�-��z��/�|��WP������0on�0�n�T��G��������q��d�R#po�y���*��W��?c8�7G�n�>��H�/[t�(�?����;��My?�Q������x!l�Y��>h��L��O���������0��������5�v���q���Y���m����dEB4����[y7)��z �TD�A����>�����gl����s��+�5\����f����)\!�|�}�5������]�"7MB���o&�S�*sA�g���Jnj��5�~bT}�&L���S�,�9V��r�cL��dm���+��G�t@0�����EAP����>��M����Mb��Fcj�PNp����M7q�D���&|7H����M���'��m]��O�!���D����iO1c�4Y&fU�X(j(W8D�(n�g *WY��~$�~����a�^�:�u�j<k|7N�����(��6�s�H�c�����t)8�GW�R�D�f�z�����/��e^=`�\�������a���Lz�n���X���:}jF*�^
j���\��*�d.�)L���i
�O���
�Q:+8'��B�
;+��"Og��l����r�1��{Z��su�J���P�m����c�"6���,������ ������fi36����U@5�����I-��-j���9�u3Q@#"�"{I�*���g'|�W���>��{�O���BbtS�l����9+���;+>�rHF$���A��f�)�V2!6:���h�Y�q�u�H&L�z~}�|H�e�w�����+�EJ2����nz�c$W���l:��*��R-���&I���%wcv>�a�CH �����5g$��5'r��lH�t��=�Z2��z��U��0��7X����������e�_'�`��g$�`��U3�m�na���%�j`OI�Al�y�g$6M�� ���0����.��P�@I}�Z2��U�����%w�U�>�(��^g`$��[+�����<�gkf�M��{!k$��K���"Fr'fg+~�Y����H����Y��{p����|��-��^��`$7�;����}��|�f��<�/�����u<�PKi��HG 2 PK :�T
styles.xml�[ms�6�~�B�Lo�3��"��T[��K_n��t���c"A �� �e�����")��%�i��=N�]�>��� A���6���a��4�� �c��|s5�������z��K��$����e�s� yG��@�\,�4"�m�<_2$�X�(�b)�%+p��.�,�[�1�W������w��l7�1��?}�����V�N�{]�����Q��9.�N;�+���T�%��)V�����r�E\��V��B�l��U/fY3���n$�h7�������D���t�}c�����1�q5X����[�v��v�H��z�%�K��]�R����p�X�Z�TS6����^�z(��
��\W��j�c�����'�f�4���������2[c>�7��0���~Q����H���F���S_�5|[`N�Q�m��p0��-|�TO��a�+���IB�bL�}�K0G�
��W�eG�I�B#`X�!�A��7�.V9A0u�U���)c^&<�C��bY�]w�!��I����w?�S�gcCD�6�q�I1��F�����-S9�t:�R[L���[9S�f�b�%8�bui�j��k�j��l��N>��E��f��]M��
&�m�����aZ�{�=P�����(�������g�{&�v6y�oP�6��_���wB���Y���Gd&f����U
����m7�(7����vk�,[���x1�t������b��d�K�1#m��
/!B�\-)��5�������~gO����
�aPH�FQ�)��p�b(G��_>N�f=�<����WU.��qd������u�� Z��k�>S|��-��z'�J�%m��_�utp;�p�,�+��u�3�g!9���P������kt6���
.���M�H�T3�S+�f=g���]�%l�Ax
,�[6axB�t��jrLX��C�d�@*&I��QE��"7�E�������A�5�$���c[9?�+~�N6�^�0_ S
bI�`U����6�p���Q���v�#��u�j8�J�=������-�� �,�L��j���W�����(M�e(�L�\���KX�����z��x��L4x�������(����:�~4}�#:�rXhUN�V��S��N��?�}-�����l���Q���L[J{��\��6�/!"@��:lV6 Mb�M���(�sk'��
�C���v��r����*����j�1_a=�gz�|,��A���B4p��5�9m-�������G����@g�� ^byM�}U]�C����.���-������p���y��f�!kF���e,����:�5����mW���z%��_�h��r���_�������)�������G1�X?�����uLOc��8��>��%���C��*��
�}!��G�/zb���?%�`>O*��B�(�Tm�m{�x_�������F&�O��mX��z������L���)��'��2���Z8��d��"P?����0��O�=�c�a��a�m�)��{�d�8����%q3D"
7����3�t��{8����L��OH������s(ij6�h������Q��D-����>P���(�,�S�?0�e��:f��c�R�)�Ap~���x)�C����l>�v0������������Iiw��P:D��>�}Z_�yTj�����_5Sc�-���H<�:��{������u�=V��_� ���AR��������c����Ly��+�{�{.-���[�@���^�?��� ���&>����������^O��OOy4��GX��=�;q�q6��r�������I{Np�����������i�u��7d+e��"�J���4_�����}��b��]���1�~J3���6|�Y�x='���
�(N�����~�u��T/��`qqf^|��QY8_�dE'�&$���F�g�
E�����P�2(1_��n���u���
�O5E5
H�
>����1"��:i����[^� C�2Q��g��F>��X�gDG�3�W��7��Wl16�����K��h[��$��WL67;���v�����T�4V@V��j����B��v���j�3��F��sa�w����2Wo�[�
�C)4
��o��HZW5�_�]�c�I�hE�����H}?[�I��� Z����(��so���
�B)���8�0� X��
tWp6��!"����;��(W�E��i���wW*��k���PK����� �9 PK :�T Object 3/meta.xml�RAn�0����H���CO�zi��"���`#c�/8��UT�`�����Y;��U���4R��DAH<T\�����<����R}8H�Th�V��_�e�@U
-�eF�������[���
�2�$��'c$g�5�j���*VaC-��F5K�o,u������5�����@D0c�R��[��$I�ug���]���1Vp�g4?2c��D!8�e�k�7��m{�_=����{���S��f��w�����C�Y��j���f�_��+��6��,/[Y
?N���~�����PK�|��# I PK :�T Object 3/styles.xml�U��� ��+\�YF�������|@j�,B2�� =��di�K��r��������el��gJs��$?f��I
%��5���3�����\��8e��5L�T�����%K�����&wcZ��0��8����/�����%3�N��� -���'��<C43��i��~~>���:C5U��^:%1�5��a��1����z���L��b�����)��D`[@���K��8`�r%4[�OY�#�HYo���`sz��%�
:Yc�id�(�jU���Y���%i���u����Ywf��&�.!;#Z���:^)��B�t�,�4�d��1�uq��_5Ye����m�����^���������W��y�j7 �:j�V�RhZ��'�Qp���mv_��������-��:���m�������-I���A>�����y!���C�������&��5V��W��vJ��J��`��Q~���^�f'��H��U����)*����L�
�]��Ct�PK���(� � PK :�T Object 3/content.xml�[�����_a8(�>���-cw$�>����@_i����D��������,��W�����w9���GZ���SAg�����y���.S��2�����;��/yb�I�&c����tRVJ�{�K�I��K���|/e��<V��lq���|O+��!"���G�i��~���#�IzV;�YF��C������D�+������^�;�$I<�jUw����������Xl����(��n�
�v��U���\m[�+���>���t�g���]�����c�������F�:p�����+g��A��&��56�j�6���r�x���UH��B�l)n�qt{����koW�0v��m���Lo��RD��|�x4\��3u�i�� $�U/�Dcm)�N�QR~���j���c��uv�Jo�7`3���~k���N�D�����N���� j����Pl1m:�e���}aS����L�y�nw�P�%h�C}Im�tN�`�v�O+����f�t�>e_�t�� ����jUM��.�d8�4���S�~�V���;{�7E���J�����|��}������"RN����hw���l�g������P�_���f�::����)��n�tfJBy�<�����f�SAJ��e$'|����7p�CA]3����:E)3�y����w�h��t-��Z�C�oJ��!�%�����%��`1S%Gq�(Y�����[�����S����Up�� "u�%?`{gR����\uW_yc����>k� saF�?�D�l��b����D��^�u�a���3�����������6R�F{G(����Q�K�g4��-B������*BI��C��=�2\:)��f�� �72��d����c$\T���H1z�N�*��I��&C3�T)�h�E#���EC��i5��ddD@[ys(�b��DY�`"�$������Z����^��-�w��!_��B%�P�
FhgG�����h��p(����=����V���q=���9��3��V�����������_r��Z:���� �fH��M>�a���w^���-�����Z��F�����UE����gL����H�����)���]0J��G����W�x���.=����[��5���'�|����H2��,��0��j�����3�f���y���Y�U�m��lo��"!j��-�V�.
�f=�D*"� $��]6��>���?CG�p�����]��?������O^���F����f���5Os�J��$i9�I�g�}��*T���=�5����f���b�%a�7_��X�p������cE�N���7��~ZD��i�k8{$��[�L����(
Z�7\����?p��u?Vn�P�M�0�� �� ���d���X\��� ��&b7Vf��@��t�)>Y������v�=���13`�|Ueb���L�)�����(g\U����x��0���m0o�`���,�+�wq;�����k%�:��*����o��%�$<i�+����+�����{Lb�yE���s����:��Ns��B�V ��jk����)e�BP�+.�Th�2HF�
���>5�>���\��;"J�a��Qh�Za����A��J�?��2;(��s�w�����(�\����5#T���1(���/���s&� z���:g��Hzf!��/�6k=I�-X%T��`r�)�\���W�YE^�<�$rB�CdX�/%�]}����*; :��Nv����`z(%�2&Lc Beg�x^���H����wV�������Z0"3Z����2N���J2"]Z�����CR@0.c�u�~(EQ�X��6EF2�=? W]{Jr����&�1�B�A�%���?�FRK�l2� ^-:em$�E�_�n$���fD�1�
)�������H&��� p{Z�f��c��j2��$�;��H�c7��e�]%y�����hwVg1���A�(�#h-0������G+H&�����?���PM�2��{{�t��0�����A���������d:{Q]{J��>����l�
�8Zv..#����q���<���Z������i$��[�����$��M=m��:
:Ik$S����=����KDW�|���fE�{4���O�����PKo�R�C 2 PK :�T Object 2/meta.xml�RAn�0����H���CO�zi��"���`#c�/8��UT�`�����Y;��U���4R��DAH<T\�����<����R}8H�Th�V��_�e�@U
-�eF�������[���
�2�$��'c$g�5�j���*VaC-��F5K�o,u������5�����@D0c�R��[��$I�ug���]���1Vp�g4?2c��D!8�e�k�7��m{�_=����{���S��f��w�����C�Y��j���f�_��+��6��,/[Y
?N���~�����PK�|��# I PK :�T Object 2/styles.xml�U��� ��+\�YF�������|@j�,B2�� =��di�K��r��������el��gJs��$?f��I
%��5���3�����\��8e��5L�T�����%K�����&wcZ��0��8����/�����%3�N��� -���'��<C43��i��~~>���:C5U��^:%1�5��a��1����z���L��b�����)��D`[@���K��8`�r%4[�OY�#�HYo���`sz��%�
:Yc�id�(�jU���Y���%i���u����Ywf��&�.!;#Z���:^)��B�t�,�4�d��1�uq��_5Ye����m�����^���������W��y�j7 �:j�V�RhZ��'�Qp���mv_��������-��:���m�������-I���A>�����y!���C�������&��5V��W��vJ��J��`��Q~���^�f'��H��U����)*����L�
�]��Ct�PK���(� � PK :�T Object 2/content.xml�[����~�_a8(�>��e����r�>����@_i����D��������,��W�����w93�|3i��O��b.+�����g�LYF��y��_����?�����v$�������IY)���.�&�#.c�������X�K��e<�~�{Zinw�F�x<��P���O�9��g�s�etH{����{��y%����q��bpG�$��W������<?�rsv{S��
>U�1��`����C x������F��-�_��_~�g���3�De��.^������q���ei�\8�jY�a������AO_�ul2����nZ��h�k>v/�1�
I���}H��q�)J�����0v��m�Z�7�]z)�i�|�xt�J�g�x��� �U/�Dcm)��()�^��Z=�{������]I������s�����k�C�0O�0��m�'Q+�w��b��h��y.y�8�[4�
L<3��M���C ��E�%�m�9����K<��.��������}��u�['t����-T5a���\�p�i6��3����vw�w�~�n����:\{_`Q���s������"RN����hwS�+�-<�<������p�b'