An implementation of multi-key sort

Started by Wang Yaoover 1 year ago20 messages
#1Wang Yao
yaowangm@outlook.com
1 attachment(s)

Hi hackers,

I'd submit an implementation of multi-key sort for review. Please see the
code as attachment. Thanks for your reponse in advance.

Overview
--------

MKsort (multi-key sort) is an alternative of standard qsort algorithm,
which has better performance for particular sort scenarios, i.e. the data
set has multiple keys to be sorted.

The implementation is based on the paper:
Jon L. Bentley and Robert Sedgewick, "Fast Algorithms for Sorting and
Searching Strings", Jan 1997 [1]https://www.cs.tufts.edu/~nr/cs257/archive/bob-sedgewick/fast-strings.pdf

MKsort is applied only for tuple sort by the patch. Theoretically it can
be applied for general-purpose sort scenario when there are multiple sort
keys available, but it is relatively difficult in practice because kind of
unique interface is needed to manipulate the keys. So I limit the usage of
mksort to sort SortTuple.

Comparing to classic quick sort, it can get significant performance
improvement once multiple keys are available. A rough test shows it got
~129% improvement than qsort for ORDER BY on 6 keys, and ~52% for CREATE
INDEX on the same data set. (See more details in section "Performance
Test")

Author: Yao Wang <yaowangm@outlook.com>
Co-author: Hongxu Ma <interma@outlook.com>

Scope
-----

The interface of mksort is pretty simple: in tuplesort_sort_memtuples(),
mksort_tuple() is invoked instead of qsort_tuple() if mksort is applicable.
The major logic in mksort_tuple() is to apply mksort algorithm on
SortTuple, and kind of callback mechanism is used to handle
sort-variant-specific issue, e.g. comparing different datums, like
qsort_tuple() does. It also handles the complexity of "abbreviated keys".

A small difference from classic mksort algorithm is: for IndexTuple, when
all the columns are equal, an additional comparing based on ItemPointer
is performed to determine the order. It is to make the result consistent
to existing qsort.

I did consider about implementing mksort by the approach of kind of
template mechanism like qsort (see sort_template.h), but it seems
unnecessary because all concrete tuple types need to be handled are
derived from SortTuple. Use callback to isolate type specific features
is good enough.

Note that not all tuple types are supported by mksort. Please see the
comments inside tuplesort_sort_memtuples().

Test Cases
----------

The changes of test cases include:

* Generally, mksort should generate result exactly same to qsort. However
some test cases don't. The reason is that SQL doesn't specify order on
all possible columns, e.g. "select c1, c2 from t1 order by c1" will
generate different results between mksort/qsort when c1 values are equal,
and the solution is to order c2 as well ("select c1, c2 from t1 order by
c1, c2"). (e.g. geometry)
* Some cases need to be updated to display the new sort method "multi-key
sort" in explain result. (e.g. incremental_sort)
* regress/tuplesort was updated with new cases to cover some scenarios of
mksort.

Performance Test
----------------

The script I used to configure the build:

CFLAGS="-O3 -fargument-noalias-global -fno-omit-frame-pointer -g"
./configure --prefix=$PGHOME --with-pgport=5432 --with-perl --with-openssl
--with-python --with-pam --with-blocksize=16 --with-wal-blocksize=16
--with-perl --enable-tap-tests --with-gssapi --with-ldap

I used the script for a rough test for ORDER BY:

\timing on
create table t1 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 varchar(100));
insert into t1 values (generate_series(1,499999), 0, 0, 0, 0,
'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
update t1 set c2 = c1 % 100, c3 = c1 % 50, c4 = c1 % 10, c5 = c1 % 3;
update t1 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (c1 % 5)::text;

-- Use a large work mem to ensure the entire sort happens in memory
set work_mem='1GB';

-- switch between qsort/mksort
set enable_mk_sort=off;

explain analyze select c1 from t1 order by c6, c5, c4, c3, c2, c1;

Results:

mksort:
1341.283 ms (00:01.341)
1379.098 ms (00:01.379)
1369.868 ms (00:01.370)

qsort:
3137.277 ms (00:03.137)
3147.771 ms (00:03.148)
3131.887 ms (00:03.132)

The perf improvement is ~129%.

Another perf test for CREATE INDEX:

create index idx_t1_mk on t3 (c6, c5, c4, c3, c2, c1);

Results:

mksort:
1147.207 ms (00:01.147)
1200.501 ms (00:01.201)
1235.657 ms (00:01.236)

Qsort:
1852.957 ms (00:01.853)
1824.209 ms (00:01.824)
1808.781 ms (00:01.809)

The perf improvement is ~52%.

Another test is to use one of queries of TPC-H:

set work_mem='1GB';

-- query rewritten from TPCH-Q1, and there are 6001215 rows in lineitem
explain analyze select
 l_returnflag,l_linestatus,l_quantity,l_shipmode
from
 lineitem
where
 l_shipdate <= date'1998-12-01' - interval '65 days'
order by
 l_returnflag,l_linestatus,l_quantity,l_shipmode;

Result:

Qsort:
14582.626 ms
14524.188 ms
14524.111 ms

mksort:
11390.891 ms
11647.065 ms
11546.791 ms

The perf improvement is ~25.8%.

[1]: https://www.cs.tufts.edu/~nr/cs257/archive/bob-sedgewick/fast-strings.pdf
[2]: https://www.tpc.org/tpch/

Thanks,

Yao Wang

Attachments:

0001-Implement-multi-key-sort.patchapplication/octet-stream; name=0001-Implement-multi-key-sort.patchDownload
From 1d583dec54ecd47912a5d68038bc9a07c2339c45 Mon Sep 17 00:00:00 2001
From: Yao Wang <yaowangm@outlook.com>
Date: Tue, 7 May 2024 08:11:13 +0000
Subject: [PATCH] Implement multi-key sort

MKsort (multi-key sort) is an alternative of standard qsort algorithm,
which has better performance for particular sort scenarios, i.e. the data
set has multiple keys to be sorted. Comparing to classic quick sort, it
can get significant performance improvement once multiple keys are
available.

Author: Yao Wang <yaowangm@outlook.com>
Co-author: Hongxu Ma <interma@outlook.com>
---
 src/backend/utils/misc/guc_tables.c           |  11 +
 src/backend/utils/sort/mksort_tuple.c         | 358 +++++++++++++++++
 src/backend/utils/sort/tuplesort.c            |  44 ++
 src/backend/utils/sort/tuplesortvariants.c    | 300 +++++++++++++-
 src/include/c.h                               |   4 +
 src/include/utils/tuplesort.h                 |  34 +-
 src/test/regress/expected/geometry.out        |   4 +-
 .../regress/expected/incremental_sort.out     |  12 +-
 src/test/regress/expected/tuplesort.out       | 375 ++++++++++++++++++
 src/test/regress/expected/window.out          |  58 +--
 src/test/regress/sql/geometry.sql             |   2 +-
 src/test/regress/sql/tuplesort.sql            |  59 +++
 src/test/regress/sql/window.sql               |  22 +-
 13 files changed, 1215 insertions(+), 68 deletions(-)
 create mode 100644 src/backend/utils/sort/mksort_tuple.c

diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 3fd0b14dd8..b8fe447d68 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -103,6 +103,7 @@ extern char *default_tablespace;
 extern char *temp_tablespaces;
 extern bool ignore_checksum_failure;
 extern bool ignore_invalid_pages;
+extern bool enable_mk_sort;
 
 #ifdef TRACE_SYNCSCAN
 extern bool trace_syncscan;
@@ -839,6 +840,16 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_mk_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables multi-key"),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_mk_sort,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
diff --git a/src/backend/utils/sort/mksort_tuple.c b/src/backend/utils/sort/mksort_tuple.c
new file mode 100644
index 0000000000..949e9bbf8d
--- /dev/null
+++ b/src/backend/utils/sort/mksort_tuple.c
@@ -0,0 +1,358 @@
+/*
+ * MKsort (multiple-key sort) is an alternative of standard qsort algorithm,
+ * which has better performance for particular sort scenarios, i.e. the
+ * data set has multiple keys to be sorted.
+ *
+ * The sorting algorithm blends Quicksort and radix sort; Like regular
+ * Quicksort, it partitions its input into sets less than and greater than a
+ * given value; like radix sort, it moves on to the next field once the current
+ * input is known to be equal in the given field.
+ *
+ * The implementation is based on the paper:
+ *   Jon L. Bentley and Robert Sedgewick, "Fast Algorithms for Sorting and
+ *   Searching Strings", Jan 1997
+ *
+ * Some improvements which is related to additional handling for equal tuples
+ * have been adapted to keep consistency with the implementations of postgres
+ * qsort.
+ *
+ * For now, mksort_tuple() is called in tuplesort_sort_memtuples() as a
+ * replacement of qsort_tuple() when specific conditions are satisfied.
+ */
+
+/* Swap two tuples in sort tuple array */
+static inline void
+mksort_swap(int        a,
+			int        b,
+			SortTuple *x)
+{
+	SortTuple t;
+
+	if (a == b)
+		return;
+	t = x[a];
+	x[a] = x[b];
+	x[b] = t;
+}
+
+/* Swap tuples by batch in sort tuple array */
+static inline void
+mksort_vec_swap(int        a,
+				int        b,
+				int        size,
+				SortTuple *x)
+{
+	while (size-- > 0)
+	{
+		mksort_swap(a, b, x);
+		a++;
+		b++;
+	}
+}
+
+/*
+ * Check whether current datum (at specified tuple and depth) is null
+ * Note that the input x means a specified tuple provided by caller but not
+ * a tuple array, so tupleIndex is unnecessary
+ */
+static inline bool
+check_datum_null(SortTuple      *x,
+				 int             depth,
+				 Tuplesortstate *state)
+{
+	Datum datum;
+	bool isNull;
+
+	/* Since we have a specified tuple, the tupleIndex is always 0 */
+	state->base.mksortGetDatumFunc(x, 0, depth, state, &datum, &isNull, false);
+
+	/*
+	 * Note: for "abbreviated key", we don't need to handle more here because
+	 * if "abbreviated key" of a datum is null, the "full" datum must be null.
+	 */
+
+	return isNull;
+}
+
+/*
+ * Compare two tuples at specified depth
+ *
+ * If "abbreviated key" is disabled:
+ *   get specified datums and compare them by ApplySortComparator().
+ * If "abbreviated key" is enabled:
+ *   Only first datum may be abbr key according to the design (see the comments
+ *   of struct SortTuple), so different operations are needed for different
+ *   datum.
+ *   For first datum (depth == 0): get first datums ("abbr key" version) and
+ *   compare them by ApplySortComparator(). If they are equal, get "full"
+ *   version and compare again by ApplySortAbbrevFullComparator().
+ *   For other datums: get specified datums and compare them by
+ *   ApplySortComparator() as regular routine does.
+ *
+ * See comparetup_heap() for details.
+ */
+static inline int
+mksort_compare_datum(SortTuple      *tuple1,
+					 SortTuple      *tuple2,
+					 int			 depth,
+					 Tuplesortstate *state)
+{
+	Datum datum1, datum2;
+	bool isNull1, isNull2;
+	SortSupport sortKey;
+	int ret = 0;
+
+	Assert(state->mksortGetDatumFunc);
+
+	sortKey = state->base.sortKeys + depth;
+	state->base.mksortGetDatumFunc(tuple1, 0, depth, state,
+							  &datum1, &isNull1, false);
+	state->base.mksortGetDatumFunc(tuple2, 0, depth, state,
+							  &datum2, &isNull2, false);
+
+	ret = ApplySortComparator(datum1,
+							  isNull1,
+							  datum2,
+							  isNull2,
+							  sortKey);
+
+	/*
+	 * If "abbreviated key" is enabled, and we are in the first depth, it means
+	 * only "abbreviated keys" are compared. If the two datums are determined to
+	 * be equal by ApplySortComparator(), we need to perform an extra "full"
+	 * comparing by ApplySortAbbrevFullComparator().
+	 */
+	if (sortKey->abbrev_converter &&
+		depth == 0 &&
+		ret == 0)
+	{
+		/* Fetch "full" datum by setting useFullKey = true */
+		state->base.mksortGetDatumFunc(tuple1, 0, depth, state,
+								  &datum1, &isNull1, true);
+		state->base.mksortGetDatumFunc(tuple2, 0, depth, state,
+								  &datum2, &isNull2, true);
+
+		ret = ApplySortAbbrevFullComparator(datum1,
+											isNull1,
+											datum2,
+											isNull2,
+											sortKey);
+	}
+
+	return ret;
+}
+
+#ifdef USE_ASSERT_CHECKING
+/*
+ * Verify whether the SortTuple list is ordered or not at specified depth
+ */
+static void
+mksort_verify(SortTuple		  *x,
+			  int			   n,
+			  int			   depth,
+			  Tuplesortstate  *state)
+{
+	int ret;
+
+	for (int i = 0;i < n - 1;i++)
+	{
+		ret = mksort_compare_datum(x + i,
+								   x + i + 1,
+								   depth,
+								   state);
+		Assert(ret <= 0);
+	}
+}
+#endif
+
+/*
+ * Major of multi-key sort
+ *
+ * seenNull indicates whether we have seen NULL in any datum we checked
+ */
+static void
+mksort_tuple(SortTuple           *x,
+			 size_t               n,
+			 int                  depth,
+			 Tuplesortstate      *state,
+			 bool				  seenNull)
+{
+	/*
+	 * In the process, the tuple array consists of five parts:
+	 * left equal, less, not-processed, greater, right equal
+	 *
+	 * lessStart indicates the first position of less part
+	 * lessEnd indicates the next position after less part
+	 * greaterStart indicates the prior position before greater part
+	 * greaterEnd indicates the latest position of greater part
+	 * the range between lessEnd and greaterStart (inclusive) is not-processed
+	 */
+	int lessStart, lessEnd, greaterStart, greaterEnd, tupCount;
+	int32 dist;
+	SortTuple *pivot;
+	bool isDatumNull;
+
+	Assert(depth <= state->base.nKeys);
+	Assert(state->base.sortKeys);
+	Assert(state->base.mksortGetDatumFunc);
+
+	if (n <= 1)
+		return;
+
+	/* If we have exceeded the max depth, return immediately */
+	if (depth == state->base.nKeys)
+		return;
+
+	CHECK_FOR_INTERRUPTS();
+
+	/* Select pivot by random and move it to the first position */
+	lessStart = pg_prng_int64p(&pg_global_prng_state) % n;
+	mksort_swap(0, lessStart, x);
+	pivot = x;
+
+	lessStart = 1;
+	lessEnd = 1;
+	greaterStart = n - 1;
+	greaterEnd = n - 1;
+
+	/* Sort the array to three parts: lesser, equal, greater */
+	while (true)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		/* Compare the left end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare lessEnd and pivot at current depth */
+			dist = mksort_compare_datum(x + lessEnd,
+										pivot,
+										depth,
+										state);
+
+			if (dist > 0)
+				break;
+
+			/* If lessEnd is equal to pivot, move it to lessStart */
+			if (dist == 0)
+			{
+				mksort_swap(lessEnd, lessStart, x);
+				lessStart++;
+			}
+			lessEnd++;
+		}
+
+		/* Compare the right end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare greaterStart and pivot at current depth */
+			dist = mksort_compare_datum(x + greaterStart,
+										pivot,
+										depth,
+										state);
+
+			if (dist < 0)
+				break;
+
+			/* If greaterStart is equal to pivot, move it to greaterEnd */
+			if (dist == 0)
+			{
+				mksort_swap(greaterStart, greaterEnd, x);
+				greaterEnd--;
+			}
+			greaterStart--;
+		}
+
+		if (lessEnd > greaterStart)
+			break;
+		mksort_swap(lessEnd, greaterStart, x);
+		lessEnd++;
+		greaterStart--;
+	}
+
+	/*
+	 * Now the array has four parts:
+	 *   left equal, lesser, greater, right equal
+	 * Note greaterStart is less than lessEnd now
+	 */
+
+	/* Move the left equal part to middle */
+	dist = Min(lessStart, lessEnd - lessStart);
+	mksort_vec_swap(0, lessEnd - dist, dist, x);
+
+	/* Move the right equal part to middle */
+	dist = Min(greaterEnd - greaterStart, n - greaterEnd - 1);
+	mksort_vec_swap(lessEnd, n - dist, dist, x);
+
+	/*
+	 * Now the array has three parts:
+	 *   lesser, equal, greater
+	 * Note that one or two parts may have no element at all.
+	 */
+
+	/* Recursively sort the lesser part */
+
+	/* dist means the size of less part */
+	dist = lessEnd - lessStart;
+	mksort_tuple(x,
+				 dist,
+				 depth,
+				 state,
+				 seenNull);
+
+	/* Recursively sort the equal part */
+
+	/*
+	 * (x + dist) means the first tuple in the equal part
+	 * Since all tuples have equal datums at current depth, we just check any one
+	 * of them to determine whether we have seen null datum.
+	 */
+	isDatumNull = check_datum_null(x + dist, depth, state);
+
+	/* (lessStart + n - greaterEnd - 1) means the size of equal part */
+	tupCount = lessStart + n - greaterEnd - 1;
+
+	if (depth < state->base.nKeys - 1)
+	{
+		mksort_tuple(x + dist,
+					 tupCount,
+					 depth + 1,
+					 state,
+					 seenNull || isDatumNull);
+	} else {
+		/*
+		 * We have reach the max depth: Call mksortHandleDupFunc to handle duplicated
+		 * tuples if necessary, e.g. checking uniqueness or extra comparing
+		 */
+
+		/*
+		 * Call mksortHandleDupFunc if:
+		 *   1. mksortHandleDupFunc is filled
+		 *   2. the size of equal part > 1
+		 */
+		if (state->base.mksortHandleDupFunc &&
+			(tupCount > 1))
+		{
+			state->base.mksortHandleDupFunc(x + dist,
+									   tupCount,
+									   seenNull || isDatumNull,
+									   state);
+		}
+	}
+
+	/* Recursively sort the greater part */
+
+	/* dist means the size of greater part */
+	dist = greaterEnd - greaterStart;
+	mksort_tuple(x + n - dist,
+				 dist,
+				 depth,
+				 state,
+				 seenNull);
+
+#ifdef USE_ASSERT_CHECKING
+	mksort_verify(x,
+				  n,
+				  depth,
+				  state);
+#endif
+}
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 7c4d6dc106..c865772a7a 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -108,6 +108,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_rusage.h"
 #include "utils/tuplesort.h"
+#include "common/pg_prng.h"
 
 /*
  * Initial size of memtuples array.  We're trying to select this size so that
@@ -128,6 +129,7 @@ bool		trace_sort = false;
 bool		optimize_bounded_sort = true;
 #endif
 
+bool		enable_mk_sort = true;
 
 /*
  * During merge, we use a pre-allocated set of fixed-size slots to hold
@@ -337,6 +339,9 @@ struct Tuplesortstate
 #ifdef TRACE_SORT
 	PGRUsage	ru_start;
 #endif
+
+	/* Whether multi-key sort is used */
+	bool mksortUsed;
 };
 
 /*
@@ -622,6 +627,8 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
 #define ST_DEFINE
 #include "lib/sort_template.h"
 
+#include "mksort_tuple.c"
+
 /*
  *		tuplesort_begin_xxx
  *
@@ -690,6 +697,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate, int sortopt)
 	state->base.sortopt = sortopt;
 	state->base.tuples = true;
 	state->abbrevNext = 10;
+	state->mksortUsed = false;
 
 	/*
 	 * workMem is forced to be at least 64KB, the current minimum valid value
@@ -2559,6 +2567,8 @@ tuplesort_get_stats(Tuplesortstate *state,
 		case TSS_SORTEDINMEM:
 			if (state->boundUsed)
 				stats->sortMethod = SORT_TYPE_TOP_N_HEAPSORT;
+			else if (state->mksortUsed)
+				stats->sortMethod = SORT_TYPE_MKSORT;
 			else
 				stats->sortMethod = SORT_TYPE_QUICKSORT;
 			break;
@@ -2592,6 +2602,8 @@ tuplesort_method_name(TuplesortMethod m)
 			return "external sort";
 		case SORT_TYPE_EXTERNAL_MERGE:
 			return "external merge";
+		case SORT_TYPE_MKSORT:
+			return "multi-key sort";
 	}
 
 	return "unknown";
@@ -2717,6 +2729,38 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 
 	if (state->memtupcount > 1)
 	{
+		/*
+		 * Apply multi-key sort when:
+		 *   1. enable_mk_sort is set
+		 *   2. There are multiple keys available
+		 *   3. mksortGetDatumFunc is filled, which implies that current tuple
+		 *      type is supported by mksort. (By now only Heap tuple and Btree
+		 *      Index tuple are supported, and more types may be supported in
+		 *      future.)
+		 *
+		 * A summary of tuple types supported by mksort:
+		 *
+		 *   HeapTuple: supported
+		 *   IndexTuple(btree): supported
+		 *   IndexTuple(hash): not supported because there is only one key
+		 *   DatumTuple: not supported because there is only one key
+		 *   HeapTuple(for cluster): not supported yet
+		 *   IndexTuple(gist): not supported yet
+		 *   IndexTuple(brin): not supported yet
+		 */
+		if (enable_mk_sort &&
+			state->base.nKeys > 1 &&
+			state->base.mksortGetDatumFunc != NULL)
+		{
+			state->mksortUsed = true;
+			mksort_tuple(state->memtuples,
+						 state->memtupcount,
+						 0,
+						 state,
+						 false);
+			return;
+		}
+
 		/*
 		 * Do we have the leading column's value or abbreviation in datum1,
 		 * and is there a specialization for its comparator?
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 05a853caa3..1ae7947cc5 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -30,6 +30,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
+#include "miscadmin.h"
 
 
 /* sort-type codes for sort__start probes */
@@ -92,6 +93,41 @@ static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
 						  LogicalTape *tape, unsigned int len);
 static void freestate_cluster(Tuplesortstate *state);
 
+static Datum mksort_get_datum_heap(SortTuple      *x,
+								   const int       tupleIndex,
+								   const int       depth,
+								   Tuplesortstate *state,
+								   Datum          *datum,
+								   bool           *isNull,
+								   bool            useFullKey);
+
+static Datum mksort_get_datum_index_btree(SortTuple      *x,
+										  const int       tupleIndex,
+										  const int       depth,
+										  Tuplesortstate *state,
+										  Datum          *datum,
+										  bool           *isNull,
+										  bool            useFullKey);
+
+static void
+mksort_handle_dup_index_btree(SortTuple      *x,
+							  const int       tupleCount,
+							  const bool      seenNull,
+							  Tuplesortstate *state);
+
+static int
+mksort_compare_equal_index_btree(const SortTuple *a,
+								 const SortTuple *b,
+								 Tuplesortstate  *state);
+
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2);
+
+static void
+raise_error_of_dup_index(IndexTuple     x,
+						 Tuplesortstate *state);
+
 /*
  * Data structure pointed by "TuplesortPublic.arg" for the CLUSTER case.  Set by
  * the tuplesort_begin_cluster.
@@ -163,6 +199,14 @@ typedef struct BrinSortTuple
 /* Size of the BrinSortTuple, given length of the BrinTuple. */
 #define BRINSORTTUPLE_SIZE(len)		(offsetof(BrinSortTuple, tuple) + (len))
 
+#define ST_SORT qsort_tuple_by_itempointer
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) mksort_compare_equal_index_btree(a, b, state)
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
 
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
@@ -200,6 +244,7 @@ tuplesort_begin_heap(TupleDesc tupDesc,
 	base->removeabbrev = removeabbrev_heap;
 	base->comparetup = comparetup_heap;
 	base->comparetup_tiebreak = comparetup_heap_tiebreak;
+	base->mksortGetDatumFunc = mksort_get_datum_heap;
 	base->writetup = writetup_heap;
 	base->readtup = readtup_heap;
 	base->haveDatum1 = true;
@@ -388,6 +433,8 @@ tuplesort_begin_index_btree(Relation heapRel,
 	base->removeabbrev = removeabbrev_index;
 	base->comparetup = comparetup_index_btree;
 	base->comparetup_tiebreak = comparetup_index_btree_tiebreak;
+	base->mksortGetDatumFunc = mksort_get_datum_index_btree;
+	base->mksortHandleDupFunc = mksort_handle_dup_index_btree;
 	base->writetup = writetup_index;
 	base->readtup = readtup_index;
 	base->haveDatum1 = true;
@@ -1563,25 +1610,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 * attribute in order to ensure that all keys in the index are physically
 	 * unique.
 	 */
-	{
-		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
-		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
-
-		if (blk1 != blk2)
-			return (blk1 < blk2) ? -1 : 1;
-	}
-	{
-		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
-		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
-
-		if (pos1 != pos2)
-			return (pos1 < pos2) ? -1 : 1;
-	}
-
-	/* ItemPointer values should never be equal */
-	Assert(false);
-
-	return 0;
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
 }
 
 static int
@@ -1888,3 +1917,236 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
 	if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */
 		LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen));
 }
+
+/*
+ * Get specified datum from SortTuple (HeapTuple) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_heap() for details.
+ */
+static Datum
+mksort_get_datum_heap(SortTuple		 *x,
+					  int			  tupleIndex,
+					  int			  depth,
+					  Tuplesortstate *state,
+					  Datum			 *datum,
+					  bool			 *isNull,
+					  bool			  useFullKey)
+{
+	TupleDesc   tupDesc = NULL;
+	HeapTupleData heapTuple;
+	AttrNumber  attno;
+	SortTuple *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	SortSupport sortKey = base->sortKeys + depth;;
+
+	Assert(state);
+	Assert(depth < state->nKeys);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	tupDesc = (TupleDesc)base->arg;
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*datum = sortTuple->datum1;
+		*isNull = sortTuple->isnull1;
+		return *datum;
+	}
+
+	/* For any datums which depth > 0, extract it from sortTuple->tuple */
+	heapTuple.t_len = ((MinimalTuple) sortTuple->tuple)->t_len + MINIMAL_TUPLE_OFFSET;
+	heapTuple.t_data = (HeapTupleHeader) ((char *) sortTuple->tuple - MINIMAL_TUPLE_OFFSET);
+	attno = sortKey->ssup_attno;
+	*datum = heap_getattr(&heapTuple, attno, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Get specified datum from SortTuple (IndexTuple for btree index) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_index_btree() for details.
+ */
+static Datum
+mksort_get_datum_index_btree(SortTuple      *x,
+							 const int       tupleIndex,
+							 const int       depth,
+							 Tuplesortstate *state,
+							 Datum          *datum,
+							 bool           *isNull,
+							 bool			 useFullKey)
+{
+	TupleDesc   tupDesc;
+	IndexTuple  indexTuple;
+	SortTuple  *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	Assert(state);
+	Assert(depth < state->nKeys);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*isNull = sortTuple->isnull1;
+		*datum = sortTuple->datum1;
+		return *datum;
+	}
+
+	indexTuple = (IndexTuple) sortTuple->tuple;
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+
+	/*
+	 * Set parameter attnum = depth + 1 because attnum starts from 1 but depth
+	 * starts from 0
+	 */
+	*datum = index_getattr(indexTuple, depth + 1, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Handle duplicated SortTuples (IndexTuple for btree index during mksort)
+ *  x: the duplicated tuple list
+ *  tupleCount: count of the tuples
+ */
+static void
+mksort_handle_dup_index_btree(SortTuple      *x,
+							  const int       tupleCount,
+							  const bool      seenNull,
+							  Tuplesortstate *state)
+{
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	/* If enforceUnique is enabled and we never saw NULL, raise error */
+	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && seenNull))
+	{
+		Assert(state->comparetup == comparetup_index_btree);
+
+		/*
+		 * x means the first tuple of duplicated tuple list
+		 * Since they are duplicated, simply pick up the first one
+		 * to raise error
+		 */
+		raise_error_of_dup_index((IndexTuple)(x->tuple), state);
+	}
+
+	/*
+	 * If key values are equal, we sort on ItemPointer.  This is required for
+	 * btree indexes, since heap TID is treated as an implicit last key
+	 * attribute in order to ensure that all keys in the index are physically
+	 * unique.
+	 */
+	qsort_tuple_by_itempointer(x,
+							   tupleCount,
+							   state);
+}
+
+/*
+ * Compare two btree index tuples by ItemPointer
+ * It is a callback function for qsort_tuple() called by
+ * mksort_handle_dup_index_btree()
+ */
+static int
+mksort_compare_equal_index_btree(const SortTuple *a,
+								 const SortTuple *b,
+								 Tuplesortstate  *state)
+{
+	IndexTuple  tuple1;
+	IndexTuple  tuple2;
+
+	tuple1 = (IndexTuple) a->tuple;
+	tuple2 = (IndexTuple) b->tuple;
+
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
+}
+
+/* Compare two index tuples by ItemPointer */
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2)
+{
+	{
+		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
+		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
+
+		if (blk1 != blk2)
+			return (blk1 < blk2) ? -1 : 1;
+	}
+	{
+		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
+		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
+
+		if (pos1 != pos2)
+			return (pos1 < pos2) ? -1 : 1;
+	}
+
+	/* ItemPointer values should never be equal */
+	Assert(false);
+
+	return 0;
+}
+
+/* Raise error for duplicated tuple when creating unique index */
+static void
+raise_error_of_dup_index(IndexTuple      x,
+						 Tuplesortstate *state)
+{
+	Datum       values[INDEX_MAX_KEYS];
+	bool        isnull[INDEX_MAX_KEYS];
+	TupleDesc   tupDesc;
+	char       *key_desc;
+    TuplesortPublic *base = TuplesortstateGetPublic(state);
+    TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+	index_deform_tuple((IndexTuple)x, tupDesc, values, isnull);
+	key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNIQUE_VIOLATION),
+			errmsg("could not create unique index \"%s\"",
+				   RelationGetRelationName(arg->index.indexRel)),
+			key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+				errdetail("Duplicate keys exist."),
+				errtableconstraint(arg->index.heapRel,
+								   RelationGetRelationName(arg->index.indexRel))));
+}
diff --git a/src/include/c.h b/src/include/c.h
index dc1841346c..f7c368cd16 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -857,12 +857,14 @@ typedef NameData *Name;
 
 #define Assert(condition)	((void)true)
 #define AssertMacro(condition)	((void)true)
+#define AssertImply(condition1, condition2) ((void)true)
 
 #elif defined(FRONTEND)
 
 #include <assert.h>
 #define Assert(p) assert(p)
 #define AssertMacro(p)	((void) assert(p))
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
 
 #else							/* USE_ASSERT_CHECKING && !FRONTEND */
 
@@ -886,6 +888,8 @@ typedef NameData *Name;
 	((void) ((condition) || \
 			 (ExceptionalCondition(#condition, __FILE__, __LINE__), 0)))
 
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
+
 #endif							/* USE_ASSERT_CHECKING && !FRONTEND */
 
 /*
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index e7941a1f09..d3f27b49dc 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -29,7 +29,6 @@
 #include "utils/relcache.h"
 #include "utils/sortsupport.h"
 
-
 /*
  * Tuplesortstate and Sharedsort are opaque types whose details are not
  * known outside tuplesort.c.
@@ -79,9 +78,10 @@ typedef enum
 	SORT_TYPE_QUICKSORT = 1 << 1,
 	SORT_TYPE_EXTERNAL_SORT = 1 << 2,
 	SORT_TYPE_EXTERNAL_MERGE = 1 << 3,
+	SORT_TYPE_MKSORT = 1 << 4,
 } TuplesortMethod;
 
-#define NUM_TUPLESORTMETHODS 4
+#define NUM_TUPLESORTMETHODS 5
 
 typedef enum
 {
@@ -155,6 +155,21 @@ typedef struct
 typedef int (*SortTupleComparator) (const SortTuple *a, const SortTuple *b,
 									Tuplesortstate *state);
 
+typedef Datum
+(*MksortGetDatumFunc) (SortTuple      *x,
+					   const int       tupleIndex,
+					   const int       depth,
+					   Tuplesortstate *state,
+					   Datum          *datum,
+					   bool           *isNull,
+					   bool            useFullKey);
+
+typedef void
+(*MksortHandleDupFunc) (SortTuple      *x,
+						const int       tupleCount,
+						const bool      seenNull,
+						Tuplesortstate *state);
+
 /*
  * The public part of a Tuple sort operation state.  This data structure
  * contains the definition of sort-variant-specific interface methods and
@@ -249,6 +264,21 @@ typedef struct
 	bool		tuples;			/* Can SortTuple.tuple ever be set? */
 
 	void	   *arg;			/* Specific information for the sort variant */
+
+	/*
+	 * Function pointer, referencing a function to get specified datum from
+	 * SortTuple list with multi-key.
+	 * Used by mksort_tuple().
+	*/
+	MksortGetDatumFunc mksortGetDatumFunc;
+
+	/*
+	 * Function pointer, referencing a function to handle duplicated tuple
+	 * from SortTuple list with multi-key.
+	 * Used by mksort_tuple().
+	 * For now, the function pointer is filled for only btree index tuple.
+	*/
+	MksortHandleDupFunc mksortHandleDupFunc;
 } TuplesortPublic;
 
 /* Sort parallel code from state for sort__start probes */
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 8be694f46b..094d22861c 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -4273,7 +4273,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
      circle     |       point       |   distance    
 ----------------+-------------------+---------------
  <(1,2),3>      | (-3,4)            |   1.472135955
@@ -4310,8 +4310,8 @@ SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
  <(3,5),0>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (1e+300,Infinity) |      Infinity
  <(5,1),3>      | (1e+300,Infinity) |      Infinity
- <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (Infinity,1e+300) |      Infinity
+ <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,3),5>      | (1e+300,Infinity) |      Infinity
  <(1,3),5>      | (Infinity,1e+300) |      Infinity
  <(100,200),10> | (1e+300,Infinity) |      Infinity
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 5fd54a10b1..e8dba83389 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -520,13 +520,13 @@ select * from (select * from t order by a) s order by a, b limit 55;
 
 -- Test EXPLAIN ANALYZE with only a fullsort group.
 select explain_analyze_without_memory('select * from (select * from t order by a) s order by a, b limit 55');
-                                        explain_analyze_without_memory                                         
----------------------------------------------------------------------------------------------------------------
+                                           explain_analyze_without_memory                                           
+--------------------------------------------------------------------------------------------------------------------
  Limit (actual rows=55 loops=1)
    ->  Incremental Sort (actual rows=55 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 2  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 2  Sort Methods: top-N heapsort, multi-key sort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=101 loops=1)
                Sort Key: t.a
                Sort Method: quicksort  Memory: NNkB
@@ -554,7 +554,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
              "Group Count": 2,                  +
              "Sort Methods Used": [             +
                  "top-N heapsort",              +
-                 "quicksort"                    +
+                 "multi-key sort"               +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
@@ -728,7 +728,7 @@ select explain_analyze_without_memory('select * from (select * from t order by a
    ->  Incremental Sort (actual rows=70 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 1  Sort Method: quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 1  Sort Method: multi-key sort  Average Memory: NNkB  Peak Memory: NNkB
          Pre-sorted Groups: 5  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=1000 loops=1)
                Sort Key: t.a
@@ -756,7 +756,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
          "Full-sort Groups": {                  +
              "Group Count": 1,                  +
              "Sort Methods Used": [             +
-                 "quicksort"                    +
+                 "multi-key sort"               +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 6dd97e7427..cd08ce8b3c 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -703,3 +703,378 @@ EXPLAIN (COSTS OFF) :qry;
 (10 rows)
 
 COMMIT;
+-- Test cases for multi-key sort
+set work_mem='100MB';
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 50) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a | b  |  c   
+---+----+------
+ 0 |  0 | 3417
+ 0 |  5 | 98f1
+ 0 |  5 | c0c7
+ 0 | 10 | d3d9
+ 0 | 10 | d645
+ 1 |  1 | c16a
+ 1 |  1 | c4ca
+ 1 |  6 | 3c59
+ 1 | 11 | 3416
+ 1 | 11 | 6512
+ 2 |  2 | 6364
+ 2 |  2 | c81e
+ 2 |  7 | b6d7
+ 2 | 12 | a1d0
+ 2 | 12 | c20a
+ 3 |  3 | 182b
+ 3 |  3 | eccb
+ 3 |  8 | 3769
+ 3 | 13 | 17e6
+ 3 | 13 | c51c
+ 4 |  4 | a87f
+ 4 |  4 | e369
+ 4 |  9 | 1ff1
+ 4 | 14 | aab3
+ 4 | 14 | f717
+ 5 |  0 | 6c83
+ 5 |  0 | 9bf3
+ 5 |  5 | 1c38
+ 5 |  5 | e4da
+ 5 | 10 | 8e29
+ 6 |  1 | c74d
+ 6 |  1 | d9d4
+ 6 |  6 | 1679
+ 6 |  6 | 19ca
+ 6 | 11 | 4e73
+ 7 |  2 | 67c6
+ 7 |  2 | 70ef
+ 7 |  7 | 8f14
+ 7 |  7 | a5bf
+ 7 | 12 | 02e7
+ 8 |  3 | 642e
+ 8 |  3 | 6f49
+ 8 |  8 | a577
+ 8 |  8 | c9f0
+ 8 | 13 | 33e7
+ 9 |  4 | 1f0e
+ 9 |  4 | f457
+ 9 |  9 | 45c4
+ 9 |  9 | d67d
+ 9 | 14 | 6ea9
+(50 rows)
+
+drop table mksort_simple_tbl;
+-- test table with abbr keys
+create table abbr_tbl (a int, b varchar(100), c uuid);
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+--(see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+select c, b, a from abbr_tbl order by c, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+(50 rows)
+
+select c, b, a from abbr_tbl order by c desc, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+(50 rows)
+
+select c, b, a from abbr_tbl order by c, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+NOTICE:  index "idx_abbr_tbl" does not exist, skipping
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+                  c                   |                            b                            | a 
+--------------------------------------+---------------------------------------------------------+---
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 8
+(1 row)
+
+-- Uniqueness check of CREATE INDEX
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+ERROR:  could not create unique index "idx_abbr_tbl"
+DETAIL:  Key (c, b, a)=(00000000-0000-0000-0000-000000000001, aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1, 1) is duplicated.
+drop table abbr_tbl;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ae4e8851f8..2de20ca1d0 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -18,13 +18,13 @@ INSERT INTO empsalary VALUES
 ('sales', 3, 4800, '2007-08-01'),
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary |  sum  
 -----------+-------+--------+-------
  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
@@ -33,13 +33,13 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
  sales     |     1 |   5000 | 14600
 (10 rows)
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
  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
@@ -90,18 +90,18 @@ SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PA
  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;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
- develop   |     7 |   4200 |    1
- personnel |     5 |   3500 |    1
  sales     |     3 |   4800 |    1
  sales     |     4 |   4800 |    1
+ personnel |     5 |   3500 |    1
+ develop   |     7 |   4200 |    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)
 
@@ -3749,23 +3749,24 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
  empno |  depname  | rn | rnk | cnt 
 -------+-----------+----+-----+-----
-     8 | develop   |  1 |   1 |   1
-    10 | develop   |  2 |   2 |   1
-    11 | develop   |  3 |   3 |   1
-     9 | develop   |  4 |   4 |   2
-     7 | develop   |  5 |   4 |   2
-     2 | personnel |  1 |   1 |   1
-     5 | personnel |  2 |   2 |   1
      1 | sales     |  1 |   1 |   1
+     2 | personnel |  1 |   1 |   1
      3 | sales     |  2 |   2 |   1
      4 | sales     |  3 |   3 |   1
+     5 | personnel |  2 |   2 |   1
+     7 | develop   |  4 |   4 |   1
+     8 | develop   |  1 |   1 |   1
+     9 | develop   |  5 |   5 |   1
+    10 | develop   |  2 |   2 |   1
+    11 | develop   |  3 |   3 |   1
 (10 rows)
 
 -- Test pushdown of quals into a subquery containing window functions
@@ -4106,17 +4107,17 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
  empno |  depname  | salary | c 
 -------+-----------+--------+---
+     1 | sales     |   5000 | 1
+     2 | personnel |   3900 | 1
+     3 | sales     |   4800 | 3
+     4 | sales     |   4800 | 3
+     5 | personnel |   3500 | 2
      8 | develop   |   6000 | 1
     10 | develop   |   5200 | 3
     11 | develop   |   5200 | 3
-     2 | personnel |   3900 | 1
-     5 | personnel |   3500 | 2
-     1 | sales     |   5000 | 1
-     4 | sales     |   4800 | 3
-     3 | sales     |   4800 | 3
 (8 rows)
 
 -- Ensure we get the correct run condition when the window function is both
@@ -4468,14 +4469,15 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
   depname  | empno | salary | enroll_date | first_emp | last_emp 
 -----------+-------+--------+-------------+-----------+----------
+ develop   |     7 |   4200 | 01-01-2008  |         4 |        1
  develop   |     8 |   6000 | 10-01-2006  |         1 |        5
- develop   |     7 |   4200 | 01-01-2008  |         5 |        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
diff --git a/src/test/regress/sql/geometry.sql b/src/test/regress/sql/geometry.sql
index c3ea368da5..1f47f07f31 100644
--- a/src/test/regress/sql/geometry.sql
+++ b/src/test/regress/sql/geometry.sql
@@ -403,7 +403,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
 
 -- To polygon
 SELECT f1, f1::polygon FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
diff --git a/src/test/regress/sql/tuplesort.sql b/src/test/regress/sql/tuplesort.sql
index 8476e594e6..65ecbbd5c9 100644
--- a/src/test/regress/sql/tuplesort.sql
+++ b/src/test/regress/sql/tuplesort.sql
@@ -305,3 +305,62 @@ EXPLAIN (COSTS OFF) :qry;
 :qry;
 
 COMMIT;
+
+-- Test cases for multi-key sort
+
+set work_mem='100MB';
+
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 50) g;
+select * from mksort_simple_tbl order by a, b, c;
+
+drop table mksort_simple_tbl;
+
+-- test table with abbr keys
+
+create table abbr_tbl (a int, b varchar(100), c uuid);
+
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+--(see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+
+select c, b, a from abbr_tbl order by c, b, a;
+select c, b, a from abbr_tbl order by c desc, b, a;
+select c, b, a from abbr_tbl order by c, b desc, a;
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+
+-- Uniqueness check of CREATE INDEX
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop table abbr_tbl;
\ No newline at end of file
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6de5493b05..46359cb796 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -21,9 +21,9 @@ INSERT INTO empsalary VALUES
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
 
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
 
 -- with GROUP BY
 SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
@@ -31,7 +31,7 @@ GROUP BY four, ten ORDER BY four, ten;
 
 SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
 
-SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
 
 -- empty window specification
 SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
@@ -1146,11 +1146,12 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
 
 -- Test pushdown of quals into a subquery containing window functions
 
@@ -1332,7 +1333,7 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
 
 -- Ensure we get the correct run condition when the window function is both
 -- monotonically increasing and decreasing.
@@ -1510,10 +1511,11 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
 
 -- cleanup
 DROP TABLE empsalary;
-- 
2.25.1

#2Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Wang Yao (#1)
Re: An implementation of multi-key sort

On 22/05/2024 15:48, Wang Yao wrote:

Comparing to classic quick sort, it can get significant performance
improvement once multiple keys are available. A rough test shows it got
~129% improvement than qsort for ORDER BY on 6 keys, and ~52% for CREATE
INDEX on the same data set. (See more details in section "Performance
Test")

Impressive. Did you test the performance of the cases where MK-sort
doesn't help, to check if there is a performance regression?

--
Heikki Linnakangas
Neon (https://neon.tech)

#3Wang Yao
yaowangm@outlook.com
In reply to: Heikki Linnakangas (#2)
1 attachment(s)
回复: An implementation of multi-key sort

No obvious perf regression is expected because PG will follow original
qsort code path when mksort is disabled. For the case, the only extra
cost is the check in tuplesort_sort_memtuples() to enter mksort code path.

It's also proved by the experiment today:

Mksort disabled:
2949.287 ms
2955.258 ms
2947.262 ms

No mksort code:
2947.094 ms
2946.419 ms
2953.215 ms

Almost the same.

I also updated code with small enhancements. Please see the latest code
as attachment.

Thanks,

Yao Wang
________________________________
发件人: Heikki Linnakangas <hlinnaka@iki.fi>
发送时间: 2024年5月22日 23:29
收件人: Wang Yao <yaowangm@outlook.com>; PostgreSQL Hackers <pgsql-hackers@postgresql.org>
抄送: interma@outlook.com <interma@outlook.com>
主题: Re: An implementation of multi-key sort

On 22/05/2024 15:48, Wang Yao wrote:

Comparing to classic quick sort, it can get significant performance
improvement once multiple keys are available. A rough test shows it got
~129% improvement than qsort for ORDER BY on 6 keys, and ~52% for CREATE
INDEX on the same data set. (See more details in section "Performance
Test")

Impressive. Did you test the performance of the cases where MK-sort
doesn't help, to check if there is a performance regression?

--
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

v2-Implement-multi-key-sort.patchapplication/octet-stream; name=v2-Implement-multi-key-sort.patchDownload
From 3f1436ad4157fcff29c3b53f8cf3ecd96f2fbf6d Mon Sep 17 00:00:00 2001
From: Yao Wang <yaowangm@outlook.com>
Date: Tue, 7 May 2024 08:11:13 +0000
Subject: [PATCH] Implement multi-key sort

MKsort (multi-key sort) is an alternative of standard qsort algorithm,
which has better performance for particular sort scenarios, i.e. the data
set has multiple keys to be sorted. Comparing to classic quick sort, it
can get significant performance improvement once multiple keys are
available.

Author: Yao Wang <yaowangm@outlook.com>
Co-author: Hongxu Ma <interma@outlook.com>
---
 src/backend/utils/misc/guc_tables.c           |  11 +
 src/backend/utils/sort/mksort_tuple.c         | 358 +++++++++++++++++
 src/backend/utils/sort/tuplesort.c            |  44 ++
 src/backend/utils/sort/tuplesortvariants.c    | 313 +++++++++++++--
 src/include/c.h                               |   4 +
 src/include/utils/tuplesort.h                 |  34 +-
 src/test/regress/expected/geometry.out        |   4 +-
 .../regress/expected/incremental_sort.out     |  12 +-
 src/test/regress/expected/tuplesort.out       | 375 ++++++++++++++++++
 src/test/regress/expected/window.out          |  58 +--
 src/test/regress/sql/geometry.sql             |   2 +-
 src/test/regress/sql/tuplesort.sql            |  59 +++
 src/test/regress/sql/window.sql               |  22 +-
 13 files changed, 1216 insertions(+), 80 deletions(-)
 create mode 100644 src/backend/utils/sort/mksort_tuple.c

diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 3fd0b14dd8..b8fe447d68 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -103,6 +103,7 @@ extern char *default_tablespace;
 extern char *temp_tablespaces;
 extern bool ignore_checksum_failure;
 extern bool ignore_invalid_pages;
+extern bool enable_mk_sort;
 
 #ifdef TRACE_SYNCSCAN
 extern bool trace_syncscan;
@@ -839,6 +840,16 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_mk_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables multi-key"),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_mk_sort,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
diff --git a/src/backend/utils/sort/mksort_tuple.c b/src/backend/utils/sort/mksort_tuple.c
new file mode 100644
index 0000000000..949e9bbf8d
--- /dev/null
+++ b/src/backend/utils/sort/mksort_tuple.c
@@ -0,0 +1,358 @@
+/*
+ * MKsort (multiple-key sort) is an alternative of standard qsort algorithm,
+ * which has better performance for particular sort scenarios, i.e. the
+ * data set has multiple keys to be sorted.
+ *
+ * The sorting algorithm blends Quicksort and radix sort; Like regular
+ * Quicksort, it partitions its input into sets less than and greater than a
+ * given value; like radix sort, it moves on to the next field once the current
+ * input is known to be equal in the given field.
+ *
+ * The implementation is based on the paper:
+ *   Jon L. Bentley and Robert Sedgewick, "Fast Algorithms for Sorting and
+ *   Searching Strings", Jan 1997
+ *
+ * Some improvements which is related to additional handling for equal tuples
+ * have been adapted to keep consistency with the implementations of postgres
+ * qsort.
+ *
+ * For now, mksort_tuple() is called in tuplesort_sort_memtuples() as a
+ * replacement of qsort_tuple() when specific conditions are satisfied.
+ */
+
+/* Swap two tuples in sort tuple array */
+static inline void
+mksort_swap(int        a,
+			int        b,
+			SortTuple *x)
+{
+	SortTuple t;
+
+	if (a == b)
+		return;
+	t = x[a];
+	x[a] = x[b];
+	x[b] = t;
+}
+
+/* Swap tuples by batch in sort tuple array */
+static inline void
+mksort_vec_swap(int        a,
+				int        b,
+				int        size,
+				SortTuple *x)
+{
+	while (size-- > 0)
+	{
+		mksort_swap(a, b, x);
+		a++;
+		b++;
+	}
+}
+
+/*
+ * Check whether current datum (at specified tuple and depth) is null
+ * Note that the input x means a specified tuple provided by caller but not
+ * a tuple array, so tupleIndex is unnecessary
+ */
+static inline bool
+check_datum_null(SortTuple      *x,
+				 int             depth,
+				 Tuplesortstate *state)
+{
+	Datum datum;
+	bool isNull;
+
+	/* Since we have a specified tuple, the tupleIndex is always 0 */
+	state->base.mksortGetDatumFunc(x, 0, depth, state, &datum, &isNull, false);
+
+	/*
+	 * Note: for "abbreviated key", we don't need to handle more here because
+	 * if "abbreviated key" of a datum is null, the "full" datum must be null.
+	 */
+
+	return isNull;
+}
+
+/*
+ * Compare two tuples at specified depth
+ *
+ * If "abbreviated key" is disabled:
+ *   get specified datums and compare them by ApplySortComparator().
+ * If "abbreviated key" is enabled:
+ *   Only first datum may be abbr key according to the design (see the comments
+ *   of struct SortTuple), so different operations are needed for different
+ *   datum.
+ *   For first datum (depth == 0): get first datums ("abbr key" version) and
+ *   compare them by ApplySortComparator(). If they are equal, get "full"
+ *   version and compare again by ApplySortAbbrevFullComparator().
+ *   For other datums: get specified datums and compare them by
+ *   ApplySortComparator() as regular routine does.
+ *
+ * See comparetup_heap() for details.
+ */
+static inline int
+mksort_compare_datum(SortTuple      *tuple1,
+					 SortTuple      *tuple2,
+					 int			 depth,
+					 Tuplesortstate *state)
+{
+	Datum datum1, datum2;
+	bool isNull1, isNull2;
+	SortSupport sortKey;
+	int ret = 0;
+
+	Assert(state->mksortGetDatumFunc);
+
+	sortKey = state->base.sortKeys + depth;
+	state->base.mksortGetDatumFunc(tuple1, 0, depth, state,
+							  &datum1, &isNull1, false);
+	state->base.mksortGetDatumFunc(tuple2, 0, depth, state,
+							  &datum2, &isNull2, false);
+
+	ret = ApplySortComparator(datum1,
+							  isNull1,
+							  datum2,
+							  isNull2,
+							  sortKey);
+
+	/*
+	 * If "abbreviated key" is enabled, and we are in the first depth, it means
+	 * only "abbreviated keys" are compared. If the two datums are determined to
+	 * be equal by ApplySortComparator(), we need to perform an extra "full"
+	 * comparing by ApplySortAbbrevFullComparator().
+	 */
+	if (sortKey->abbrev_converter &&
+		depth == 0 &&
+		ret == 0)
+	{
+		/* Fetch "full" datum by setting useFullKey = true */
+		state->base.mksortGetDatumFunc(tuple1, 0, depth, state,
+								  &datum1, &isNull1, true);
+		state->base.mksortGetDatumFunc(tuple2, 0, depth, state,
+								  &datum2, &isNull2, true);
+
+		ret = ApplySortAbbrevFullComparator(datum1,
+											isNull1,
+											datum2,
+											isNull2,
+											sortKey);
+	}
+
+	return ret;
+}
+
+#ifdef USE_ASSERT_CHECKING
+/*
+ * Verify whether the SortTuple list is ordered or not at specified depth
+ */
+static void
+mksort_verify(SortTuple		  *x,
+			  int			   n,
+			  int			   depth,
+			  Tuplesortstate  *state)
+{
+	int ret;
+
+	for (int i = 0;i < n - 1;i++)
+	{
+		ret = mksort_compare_datum(x + i,
+								   x + i + 1,
+								   depth,
+								   state);
+		Assert(ret <= 0);
+	}
+}
+#endif
+
+/*
+ * Major of multi-key sort
+ *
+ * seenNull indicates whether we have seen NULL in any datum we checked
+ */
+static void
+mksort_tuple(SortTuple           *x,
+			 size_t               n,
+			 int                  depth,
+			 Tuplesortstate      *state,
+			 bool				  seenNull)
+{
+	/*
+	 * In the process, the tuple array consists of five parts:
+	 * left equal, less, not-processed, greater, right equal
+	 *
+	 * lessStart indicates the first position of less part
+	 * lessEnd indicates the next position after less part
+	 * greaterStart indicates the prior position before greater part
+	 * greaterEnd indicates the latest position of greater part
+	 * the range between lessEnd and greaterStart (inclusive) is not-processed
+	 */
+	int lessStart, lessEnd, greaterStart, greaterEnd, tupCount;
+	int32 dist;
+	SortTuple *pivot;
+	bool isDatumNull;
+
+	Assert(depth <= state->base.nKeys);
+	Assert(state->base.sortKeys);
+	Assert(state->base.mksortGetDatumFunc);
+
+	if (n <= 1)
+		return;
+
+	/* If we have exceeded the max depth, return immediately */
+	if (depth == state->base.nKeys)
+		return;
+
+	CHECK_FOR_INTERRUPTS();
+
+	/* Select pivot by random and move it to the first position */
+	lessStart = pg_prng_int64p(&pg_global_prng_state) % n;
+	mksort_swap(0, lessStart, x);
+	pivot = x;
+
+	lessStart = 1;
+	lessEnd = 1;
+	greaterStart = n - 1;
+	greaterEnd = n - 1;
+
+	/* Sort the array to three parts: lesser, equal, greater */
+	while (true)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		/* Compare the left end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare lessEnd and pivot at current depth */
+			dist = mksort_compare_datum(x + lessEnd,
+										pivot,
+										depth,
+										state);
+
+			if (dist > 0)
+				break;
+
+			/* If lessEnd is equal to pivot, move it to lessStart */
+			if (dist == 0)
+			{
+				mksort_swap(lessEnd, lessStart, x);
+				lessStart++;
+			}
+			lessEnd++;
+		}
+
+		/* Compare the right end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare greaterStart and pivot at current depth */
+			dist = mksort_compare_datum(x + greaterStart,
+										pivot,
+										depth,
+										state);
+
+			if (dist < 0)
+				break;
+
+			/* If greaterStart is equal to pivot, move it to greaterEnd */
+			if (dist == 0)
+			{
+				mksort_swap(greaterStart, greaterEnd, x);
+				greaterEnd--;
+			}
+			greaterStart--;
+		}
+
+		if (lessEnd > greaterStart)
+			break;
+		mksort_swap(lessEnd, greaterStart, x);
+		lessEnd++;
+		greaterStart--;
+	}
+
+	/*
+	 * Now the array has four parts:
+	 *   left equal, lesser, greater, right equal
+	 * Note greaterStart is less than lessEnd now
+	 */
+
+	/* Move the left equal part to middle */
+	dist = Min(lessStart, lessEnd - lessStart);
+	mksort_vec_swap(0, lessEnd - dist, dist, x);
+
+	/* Move the right equal part to middle */
+	dist = Min(greaterEnd - greaterStart, n - greaterEnd - 1);
+	mksort_vec_swap(lessEnd, n - dist, dist, x);
+
+	/*
+	 * Now the array has three parts:
+	 *   lesser, equal, greater
+	 * Note that one or two parts may have no element at all.
+	 */
+
+	/* Recursively sort the lesser part */
+
+	/* dist means the size of less part */
+	dist = lessEnd - lessStart;
+	mksort_tuple(x,
+				 dist,
+				 depth,
+				 state,
+				 seenNull);
+
+	/* Recursively sort the equal part */
+
+	/*
+	 * (x + dist) means the first tuple in the equal part
+	 * Since all tuples have equal datums at current depth, we just check any one
+	 * of them to determine whether we have seen null datum.
+	 */
+	isDatumNull = check_datum_null(x + dist, depth, state);
+
+	/* (lessStart + n - greaterEnd - 1) means the size of equal part */
+	tupCount = lessStart + n - greaterEnd - 1;
+
+	if (depth < state->base.nKeys - 1)
+	{
+		mksort_tuple(x + dist,
+					 tupCount,
+					 depth + 1,
+					 state,
+					 seenNull || isDatumNull);
+	} else {
+		/*
+		 * We have reach the max depth: Call mksortHandleDupFunc to handle duplicated
+		 * tuples if necessary, e.g. checking uniqueness or extra comparing
+		 */
+
+		/*
+		 * Call mksortHandleDupFunc if:
+		 *   1. mksortHandleDupFunc is filled
+		 *   2. the size of equal part > 1
+		 */
+		if (state->base.mksortHandleDupFunc &&
+			(tupCount > 1))
+		{
+			state->base.mksortHandleDupFunc(x + dist,
+									   tupCount,
+									   seenNull || isDatumNull,
+									   state);
+		}
+	}
+
+	/* Recursively sort the greater part */
+
+	/* dist means the size of greater part */
+	dist = greaterEnd - greaterStart;
+	mksort_tuple(x + n - dist,
+				 dist,
+				 depth,
+				 state,
+				 seenNull);
+
+#ifdef USE_ASSERT_CHECKING
+	mksort_verify(x,
+				  n,
+				  depth,
+				  state);
+#endif
+}
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 7c4d6dc106..c865772a7a 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -108,6 +108,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_rusage.h"
 #include "utils/tuplesort.h"
+#include "common/pg_prng.h"
 
 /*
  * Initial size of memtuples array.  We're trying to select this size so that
@@ -128,6 +129,7 @@ bool		trace_sort = false;
 bool		optimize_bounded_sort = true;
 #endif
 
+bool		enable_mk_sort = true;
 
 /*
  * During merge, we use a pre-allocated set of fixed-size slots to hold
@@ -337,6 +339,9 @@ struct Tuplesortstate
 #ifdef TRACE_SORT
 	PGRUsage	ru_start;
 #endif
+
+	/* Whether multi-key sort is used */
+	bool mksortUsed;
 };
 
 /*
@@ -622,6 +627,8 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
 #define ST_DEFINE
 #include "lib/sort_template.h"
 
+#include "mksort_tuple.c"
+
 /*
  *		tuplesort_begin_xxx
  *
@@ -690,6 +697,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate, int sortopt)
 	state->base.sortopt = sortopt;
 	state->base.tuples = true;
 	state->abbrevNext = 10;
+	state->mksortUsed = false;
 
 	/*
 	 * workMem is forced to be at least 64KB, the current minimum valid value
@@ -2559,6 +2567,8 @@ tuplesort_get_stats(Tuplesortstate *state,
 		case TSS_SORTEDINMEM:
 			if (state->boundUsed)
 				stats->sortMethod = SORT_TYPE_TOP_N_HEAPSORT;
+			else if (state->mksortUsed)
+				stats->sortMethod = SORT_TYPE_MKSORT;
 			else
 				stats->sortMethod = SORT_TYPE_QUICKSORT;
 			break;
@@ -2592,6 +2602,8 @@ tuplesort_method_name(TuplesortMethod m)
 			return "external sort";
 		case SORT_TYPE_EXTERNAL_MERGE:
 			return "external merge";
+		case SORT_TYPE_MKSORT:
+			return "multi-key sort";
 	}
 
 	return "unknown";
@@ -2717,6 +2729,38 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 
 	if (state->memtupcount > 1)
 	{
+		/*
+		 * Apply multi-key sort when:
+		 *   1. enable_mk_sort is set
+		 *   2. There are multiple keys available
+		 *   3. mksortGetDatumFunc is filled, which implies that current tuple
+		 *      type is supported by mksort. (By now only Heap tuple and Btree
+		 *      Index tuple are supported, and more types may be supported in
+		 *      future.)
+		 *
+		 * A summary of tuple types supported by mksort:
+		 *
+		 *   HeapTuple: supported
+		 *   IndexTuple(btree): supported
+		 *   IndexTuple(hash): not supported because there is only one key
+		 *   DatumTuple: not supported because there is only one key
+		 *   HeapTuple(for cluster): not supported yet
+		 *   IndexTuple(gist): not supported yet
+		 *   IndexTuple(brin): not supported yet
+		 */
+		if (enable_mk_sort &&
+			state->base.nKeys > 1 &&
+			state->base.mksortGetDatumFunc != NULL)
+		{
+			state->mksortUsed = true;
+			mksort_tuple(state->memtuples,
+						 state->memtupcount,
+						 0,
+						 state,
+						 false);
+			return;
+		}
+
 		/*
 		 * Do we have the leading column's value or abbreviation in datum1,
 		 * and is there a specialization for its comparator?
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 05a853caa3..c105eb1e35 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -30,6 +30,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
+#include "miscadmin.h"
 
 
 /* sort-type codes for sort__start probes */
@@ -92,6 +93,41 @@ static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
 						  LogicalTape *tape, unsigned int len);
 static void freestate_cluster(Tuplesortstate *state);
 
+static Datum mksort_get_datum_heap(SortTuple      *x,
+								   const int       tupleIndex,
+								   const int       depth,
+								   Tuplesortstate *state,
+								   Datum          *datum,
+								   bool           *isNull,
+								   bool            useFullKey);
+
+static Datum mksort_get_datum_index_btree(SortTuple      *x,
+										  const int       tupleIndex,
+										  const int       depth,
+										  Tuplesortstate *state,
+										  Datum          *datum,
+										  bool           *isNull,
+										  bool            useFullKey);
+
+static void
+mksort_handle_dup_index_btree(SortTuple      *x,
+							  const int       tupleCount,
+							  const bool      seenNull,
+							  Tuplesortstate *state);
+
+static int
+mksort_compare_equal_index_btree(const SortTuple *a,
+								 const SortTuple *b,
+								 Tuplesortstate  *state);
+
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2);
+
+static inline void
+raise_error_of_dup_index(IndexTuple     x,
+						 Tuplesortstate *state);
+
 /*
  * Data structure pointed by "TuplesortPublic.arg" for the CLUSTER case.  Set by
  * the tuplesort_begin_cluster.
@@ -163,6 +199,14 @@ typedef struct BrinSortTuple
 /* Size of the BrinSortTuple, given length of the BrinTuple. */
 #define BRINSORTTUPLE_SIZE(len)		(offsetof(BrinSortTuple, tuple) + (len))
 
+#define ST_SORT qsort_tuple_by_itempointer
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) mksort_compare_equal_index_btree(a, b, state)
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
 
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
@@ -200,6 +244,7 @@ tuplesort_begin_heap(TupleDesc tupDesc,
 	base->removeabbrev = removeabbrev_heap;
 	base->comparetup = comparetup_heap;
 	base->comparetup_tiebreak = comparetup_heap_tiebreak;
+	base->mksortGetDatumFunc = mksort_get_datum_heap;
 	base->writetup = writetup_heap;
 	base->readtup = readtup_heap;
 	base->haveDatum1 = true;
@@ -388,6 +433,8 @@ tuplesort_begin_index_btree(Relation heapRel,
 	base->removeabbrev = removeabbrev_index;
 	base->comparetup = comparetup_index_btree;
 	base->comparetup_tiebreak = comparetup_index_btree_tiebreak;
+	base->mksortGetDatumFunc = mksort_get_datum_index_btree;
+	base->mksortHandleDupFunc = mksort_handle_dup_index_btree;
 	base->writetup = writetup_index;
 	base->readtup = readtup_index;
 	base->haveDatum1 = true;
@@ -1543,18 +1590,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 		 */
 		Assert(tuple1 != tuple2);
 
-		index_deform_tuple(tuple1, tupDes, values, isnull);
-
-		key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
-
-		ereport(ERROR,
-				(errcode(ERRCODE_UNIQUE_VIOLATION),
-				 errmsg("could not create unique index \"%s\"",
-						RelationGetRelationName(arg->index.indexRel)),
-				 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
-				 errdetail("Duplicate keys exist."),
-				 errtableconstraint(arg->index.heapRel,
-									RelationGetRelationName(arg->index.indexRel))));
+		raise_error_of_dup_index(tuple1, state);
 	}
 
 	/*
@@ -1563,25 +1599,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 * attribute in order to ensure that all keys in the index are physically
 	 * unique.
 	 */
-	{
-		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
-		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
-
-		if (blk1 != blk2)
-			return (blk1 < blk2) ? -1 : 1;
-	}
-	{
-		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
-		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
-
-		if (pos1 != pos2)
-			return (pos1 < pos2) ? -1 : 1;
-	}
-
-	/* ItemPointer values should never be equal */
-	Assert(false);
-
-	return 0;
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
 }
 
 static int
@@ -1888,3 +1906,236 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
 	if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */
 		LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen));
 }
+
+/*
+ * Get specified datum from SortTuple (HeapTuple) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_heap() for details.
+ */
+static Datum
+mksort_get_datum_heap(SortTuple		 *x,
+					  int			  tupleIndex,
+					  int			  depth,
+					  Tuplesortstate *state,
+					  Datum			 *datum,
+					  bool			 *isNull,
+					  bool			  useFullKey)
+{
+	TupleDesc   tupDesc = NULL;
+	HeapTupleData heapTuple;
+	AttrNumber  attno;
+	SortTuple *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	SortSupport sortKey = base->sortKeys + depth;;
+
+	Assert(state);
+	Assert(depth < state->nKeys);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	tupDesc = (TupleDesc)base->arg;
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*datum = sortTuple->datum1;
+		*isNull = sortTuple->isnull1;
+		return *datum;
+	}
+
+	/* For any datums which depth > 0, extract it from sortTuple->tuple */
+	heapTuple.t_len = ((MinimalTuple) sortTuple->tuple)->t_len + MINIMAL_TUPLE_OFFSET;
+	heapTuple.t_data = (HeapTupleHeader) ((char *) sortTuple->tuple - MINIMAL_TUPLE_OFFSET);
+	attno = sortKey->ssup_attno;
+	*datum = heap_getattr(&heapTuple, attno, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Get specified datum from SortTuple (IndexTuple for btree index) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_index_btree() for details.
+ */
+static Datum
+mksort_get_datum_index_btree(SortTuple      *x,
+							 const int       tupleIndex,
+							 const int       depth,
+							 Tuplesortstate *state,
+							 Datum          *datum,
+							 bool           *isNull,
+							 bool			 useFullKey)
+{
+	TupleDesc   tupDesc;
+	IndexTuple  indexTuple;
+	SortTuple  *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	Assert(state);
+	Assert(depth < state->nKeys);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*isNull = sortTuple->isnull1;
+		*datum = sortTuple->datum1;
+		return *datum;
+	}
+
+	indexTuple = (IndexTuple) sortTuple->tuple;
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+
+	/*
+	 * Set parameter attnum = depth + 1 because attnum starts from 1 but depth
+	 * starts from 0
+	 */
+	*datum = index_getattr(indexTuple, depth + 1, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Handle duplicated SortTuples (IndexTuple for btree index during mksort)
+ *  x: the duplicated tuple list
+ *  tupleCount: count of the tuples
+ */
+static void
+mksort_handle_dup_index_btree(SortTuple      *x,
+							  const int       tupleCount,
+							  const bool      seenNull,
+							  Tuplesortstate *state)
+{
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	/* If enforceUnique is enabled and we never saw NULL, raise error */
+	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && seenNull))
+	{
+		Assert(state->comparetup == comparetup_index_btree);
+
+		/*
+		 * x means the first tuple of duplicated tuple list
+		 * Since they are duplicated, simply pick up the first one
+		 * to raise error
+		 */
+		raise_error_of_dup_index((IndexTuple)(x->tuple), state);
+	}
+
+	/*
+	 * If key values are equal, we sort on ItemPointer.  This is required for
+	 * btree indexes, since heap TID is treated as an implicit last key
+	 * attribute in order to ensure that all keys in the index are physically
+	 * unique.
+	 */
+	qsort_tuple_by_itempointer(x,
+							   tupleCount,
+							   state);
+}
+
+/*
+ * Compare two btree index tuples by ItemPointer
+ * It is a callback function for qsort_tuple() called by
+ * mksort_handle_dup_index_btree()
+ */
+static int
+mksort_compare_equal_index_btree(const SortTuple *a,
+								 const SortTuple *b,
+								 Tuplesortstate  *state)
+{
+	IndexTuple  tuple1;
+	IndexTuple  tuple2;
+
+	tuple1 = (IndexTuple) a->tuple;
+	tuple2 = (IndexTuple) b->tuple;
+
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
+}
+
+/* Compare two index tuples by ItemPointer */
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2)
+{
+	{
+		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
+		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
+
+		if (blk1 != blk2)
+			return (blk1 < blk2) ? -1 : 1;
+	}
+	{
+		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
+		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
+
+		if (pos1 != pos2)
+			return (pos1 < pos2) ? -1 : 1;
+	}
+
+	/* ItemPointer values should never be equal */
+	Assert(false);
+
+	return 0;
+}
+
+/* Raise error for duplicated tuple when creating unique index */
+static inline void
+raise_error_of_dup_index(IndexTuple      x,
+						 Tuplesortstate *state)
+{
+	Datum       values[INDEX_MAX_KEYS];
+	bool        isnull[INDEX_MAX_KEYS];
+	TupleDesc   tupDesc;
+	char       *key_desc;
+    TuplesortPublic *base = TuplesortstateGetPublic(state);
+    TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+	index_deform_tuple((IndexTuple)x, tupDesc, values, isnull);
+	key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNIQUE_VIOLATION),
+			errmsg("could not create unique index \"%s\"",
+				   RelationGetRelationName(arg->index.indexRel)),
+			key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+				errdetail("Duplicate keys exist."),
+				errtableconstraint(arg->index.heapRel,
+								   RelationGetRelationName(arg->index.indexRel))));
+}
diff --git a/src/include/c.h b/src/include/c.h
index dc1841346c..f7c368cd16 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -857,12 +857,14 @@ typedef NameData *Name;
 
 #define Assert(condition)	((void)true)
 #define AssertMacro(condition)	((void)true)
+#define AssertImply(condition1, condition2) ((void)true)
 
 #elif defined(FRONTEND)
 
 #include <assert.h>
 #define Assert(p) assert(p)
 #define AssertMacro(p)	((void) assert(p))
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
 
 #else							/* USE_ASSERT_CHECKING && !FRONTEND */
 
@@ -886,6 +888,8 @@ typedef NameData *Name;
 	((void) ((condition) || \
 			 (ExceptionalCondition(#condition, __FILE__, __LINE__), 0)))
 
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
+
 #endif							/* USE_ASSERT_CHECKING && !FRONTEND */
 
 /*
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index e7941a1f09..d3f27b49dc 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -29,7 +29,6 @@
 #include "utils/relcache.h"
 #include "utils/sortsupport.h"
 
-
 /*
  * Tuplesortstate and Sharedsort are opaque types whose details are not
  * known outside tuplesort.c.
@@ -79,9 +78,10 @@ typedef enum
 	SORT_TYPE_QUICKSORT = 1 << 1,
 	SORT_TYPE_EXTERNAL_SORT = 1 << 2,
 	SORT_TYPE_EXTERNAL_MERGE = 1 << 3,
+	SORT_TYPE_MKSORT = 1 << 4,
 } TuplesortMethod;
 
-#define NUM_TUPLESORTMETHODS 4
+#define NUM_TUPLESORTMETHODS 5
 
 typedef enum
 {
@@ -155,6 +155,21 @@ typedef struct
 typedef int (*SortTupleComparator) (const SortTuple *a, const SortTuple *b,
 									Tuplesortstate *state);
 
+typedef Datum
+(*MksortGetDatumFunc) (SortTuple      *x,
+					   const int       tupleIndex,
+					   const int       depth,
+					   Tuplesortstate *state,
+					   Datum          *datum,
+					   bool           *isNull,
+					   bool            useFullKey);
+
+typedef void
+(*MksortHandleDupFunc) (SortTuple      *x,
+						const int       tupleCount,
+						const bool      seenNull,
+						Tuplesortstate *state);
+
 /*
  * The public part of a Tuple sort operation state.  This data structure
  * contains the definition of sort-variant-specific interface methods and
@@ -249,6 +264,21 @@ typedef struct
 	bool		tuples;			/* Can SortTuple.tuple ever be set? */
 
 	void	   *arg;			/* Specific information for the sort variant */
+
+	/*
+	 * Function pointer, referencing a function to get specified datum from
+	 * SortTuple list with multi-key.
+	 * Used by mksort_tuple().
+	*/
+	MksortGetDatumFunc mksortGetDatumFunc;
+
+	/*
+	 * Function pointer, referencing a function to handle duplicated tuple
+	 * from SortTuple list with multi-key.
+	 * Used by mksort_tuple().
+	 * For now, the function pointer is filled for only btree index tuple.
+	*/
+	MksortHandleDupFunc mksortHandleDupFunc;
 } TuplesortPublic;
 
 /* Sort parallel code from state for sort__start probes */
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 8be694f46b..094d22861c 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -4273,7 +4273,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
      circle     |       point       |   distance    
 ----------------+-------------------+---------------
  <(1,2),3>      | (-3,4)            |   1.472135955
@@ -4310,8 +4310,8 @@ SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
  <(3,5),0>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (1e+300,Infinity) |      Infinity
  <(5,1),3>      | (1e+300,Infinity) |      Infinity
- <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (Infinity,1e+300) |      Infinity
+ <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,3),5>      | (1e+300,Infinity) |      Infinity
  <(1,3),5>      | (Infinity,1e+300) |      Infinity
  <(100,200),10> | (1e+300,Infinity) |      Infinity
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 5fd54a10b1..e8dba83389 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -520,13 +520,13 @@ select * from (select * from t order by a) s order by a, b limit 55;
 
 -- Test EXPLAIN ANALYZE with only a fullsort group.
 select explain_analyze_without_memory('select * from (select * from t order by a) s order by a, b limit 55');
-                                        explain_analyze_without_memory                                         
----------------------------------------------------------------------------------------------------------------
+                                           explain_analyze_without_memory                                           
+--------------------------------------------------------------------------------------------------------------------
  Limit (actual rows=55 loops=1)
    ->  Incremental Sort (actual rows=55 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 2  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 2  Sort Methods: top-N heapsort, multi-key sort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=101 loops=1)
                Sort Key: t.a
                Sort Method: quicksort  Memory: NNkB
@@ -554,7 +554,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
              "Group Count": 2,                  +
              "Sort Methods Used": [             +
                  "top-N heapsort",              +
-                 "quicksort"                    +
+                 "multi-key sort"               +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
@@ -728,7 +728,7 @@ select explain_analyze_without_memory('select * from (select * from t order by a
    ->  Incremental Sort (actual rows=70 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 1  Sort Method: quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 1  Sort Method: multi-key sort  Average Memory: NNkB  Peak Memory: NNkB
          Pre-sorted Groups: 5  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=1000 loops=1)
                Sort Key: t.a
@@ -756,7 +756,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
          "Full-sort Groups": {                  +
              "Group Count": 1,                  +
              "Sort Methods Used": [             +
-                 "quicksort"                    +
+                 "multi-key sort"               +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 6dd97e7427..cd08ce8b3c 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -703,3 +703,378 @@ EXPLAIN (COSTS OFF) :qry;
 (10 rows)
 
 COMMIT;
+-- Test cases for multi-key sort
+set work_mem='100MB';
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 50) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a | b  |  c   
+---+----+------
+ 0 |  0 | 3417
+ 0 |  5 | 98f1
+ 0 |  5 | c0c7
+ 0 | 10 | d3d9
+ 0 | 10 | d645
+ 1 |  1 | c16a
+ 1 |  1 | c4ca
+ 1 |  6 | 3c59
+ 1 | 11 | 3416
+ 1 | 11 | 6512
+ 2 |  2 | 6364
+ 2 |  2 | c81e
+ 2 |  7 | b6d7
+ 2 | 12 | a1d0
+ 2 | 12 | c20a
+ 3 |  3 | 182b
+ 3 |  3 | eccb
+ 3 |  8 | 3769
+ 3 | 13 | 17e6
+ 3 | 13 | c51c
+ 4 |  4 | a87f
+ 4 |  4 | e369
+ 4 |  9 | 1ff1
+ 4 | 14 | aab3
+ 4 | 14 | f717
+ 5 |  0 | 6c83
+ 5 |  0 | 9bf3
+ 5 |  5 | 1c38
+ 5 |  5 | e4da
+ 5 | 10 | 8e29
+ 6 |  1 | c74d
+ 6 |  1 | d9d4
+ 6 |  6 | 1679
+ 6 |  6 | 19ca
+ 6 | 11 | 4e73
+ 7 |  2 | 67c6
+ 7 |  2 | 70ef
+ 7 |  7 | 8f14
+ 7 |  7 | a5bf
+ 7 | 12 | 02e7
+ 8 |  3 | 642e
+ 8 |  3 | 6f49
+ 8 |  8 | a577
+ 8 |  8 | c9f0
+ 8 | 13 | 33e7
+ 9 |  4 | 1f0e
+ 9 |  4 | f457
+ 9 |  9 | 45c4
+ 9 |  9 | d67d
+ 9 | 14 | 6ea9
+(50 rows)
+
+drop table mksort_simple_tbl;
+-- test table with abbr keys
+create table abbr_tbl (a int, b varchar(100), c uuid);
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+--(see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+select c, b, a from abbr_tbl order by c, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+(50 rows)
+
+select c, b, a from abbr_tbl order by c desc, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+(50 rows)
+
+select c, b, a from abbr_tbl order by c, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+NOTICE:  index "idx_abbr_tbl" does not exist, skipping
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+                  c                   |                            b                            | a 
+--------------------------------------+---------------------------------------------------------+---
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 8
+(1 row)
+
+-- Uniqueness check of CREATE INDEX
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+ERROR:  could not create unique index "idx_abbr_tbl"
+DETAIL:  Key (c, b, a)=(00000000-0000-0000-0000-000000000001, aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1, 1) is duplicated.
+drop table abbr_tbl;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ae4e8851f8..2de20ca1d0 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -18,13 +18,13 @@ INSERT INTO empsalary VALUES
 ('sales', 3, 4800, '2007-08-01'),
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary |  sum  
 -----------+-------+--------+-------
  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
@@ -33,13 +33,13 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
  sales     |     1 |   5000 | 14600
 (10 rows)
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
  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
@@ -90,18 +90,18 @@ SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PA
  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;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
- develop   |     7 |   4200 |    1
- personnel |     5 |   3500 |    1
  sales     |     3 |   4800 |    1
  sales     |     4 |   4800 |    1
+ personnel |     5 |   3500 |    1
+ develop   |     7 |   4200 |    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)
 
@@ -3749,23 +3749,24 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
  empno |  depname  | rn | rnk | cnt 
 -------+-----------+----+-----+-----
-     8 | develop   |  1 |   1 |   1
-    10 | develop   |  2 |   2 |   1
-    11 | develop   |  3 |   3 |   1
-     9 | develop   |  4 |   4 |   2
-     7 | develop   |  5 |   4 |   2
-     2 | personnel |  1 |   1 |   1
-     5 | personnel |  2 |   2 |   1
      1 | sales     |  1 |   1 |   1
+     2 | personnel |  1 |   1 |   1
      3 | sales     |  2 |   2 |   1
      4 | sales     |  3 |   3 |   1
+     5 | personnel |  2 |   2 |   1
+     7 | develop   |  4 |   4 |   1
+     8 | develop   |  1 |   1 |   1
+     9 | develop   |  5 |   5 |   1
+    10 | develop   |  2 |   2 |   1
+    11 | develop   |  3 |   3 |   1
 (10 rows)
 
 -- Test pushdown of quals into a subquery containing window functions
@@ -4106,17 +4107,17 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
  empno |  depname  | salary | c 
 -------+-----------+--------+---
+     1 | sales     |   5000 | 1
+     2 | personnel |   3900 | 1
+     3 | sales     |   4800 | 3
+     4 | sales     |   4800 | 3
+     5 | personnel |   3500 | 2
      8 | develop   |   6000 | 1
     10 | develop   |   5200 | 3
     11 | develop   |   5200 | 3
-     2 | personnel |   3900 | 1
-     5 | personnel |   3500 | 2
-     1 | sales     |   5000 | 1
-     4 | sales     |   4800 | 3
-     3 | sales     |   4800 | 3
 (8 rows)
 
 -- Ensure we get the correct run condition when the window function is both
@@ -4468,14 +4469,15 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
   depname  | empno | salary | enroll_date | first_emp | last_emp 
 -----------+-------+--------+-------------+-----------+----------
+ develop   |     7 |   4200 | 01-01-2008  |         4 |        1
  develop   |     8 |   6000 | 10-01-2006  |         1 |        5
- develop   |     7 |   4200 | 01-01-2008  |         5 |        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
diff --git a/src/test/regress/sql/geometry.sql b/src/test/regress/sql/geometry.sql
index c3ea368da5..1f47f07f31 100644
--- a/src/test/regress/sql/geometry.sql
+++ b/src/test/regress/sql/geometry.sql
@@ -403,7 +403,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
 
 -- To polygon
 SELECT f1, f1::polygon FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
diff --git a/src/test/regress/sql/tuplesort.sql b/src/test/regress/sql/tuplesort.sql
index 8476e594e6..65ecbbd5c9 100644
--- a/src/test/regress/sql/tuplesort.sql
+++ b/src/test/regress/sql/tuplesort.sql
@@ -305,3 +305,62 @@ EXPLAIN (COSTS OFF) :qry;
 :qry;
 
 COMMIT;
+
+-- Test cases for multi-key sort
+
+set work_mem='100MB';
+
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 50) g;
+select * from mksort_simple_tbl order by a, b, c;
+
+drop table mksort_simple_tbl;
+
+-- test table with abbr keys
+
+create table abbr_tbl (a int, b varchar(100), c uuid);
+
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+--(see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+
+select c, b, a from abbr_tbl order by c, b, a;
+select c, b, a from abbr_tbl order by c desc, b, a;
+select c, b, a from abbr_tbl order by c, b desc, a;
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+
+-- Uniqueness check of CREATE INDEX
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop table abbr_tbl;
\ No newline at end of file
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6de5493b05..46359cb796 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -21,9 +21,9 @@ INSERT INTO empsalary VALUES
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
 
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
 
 -- with GROUP BY
 SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
@@ -31,7 +31,7 @@ GROUP BY four, ten ORDER BY four, ten;
 
 SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
 
-SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
 
 -- empty window specification
 SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
@@ -1146,11 +1146,12 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
 
 -- Test pushdown of quals into a subquery containing window functions
 
@@ -1332,7 +1333,7 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
 
 -- Ensure we get the correct run condition when the window function is both
 -- monotonically increasing and decreasing.
@@ -1510,10 +1511,11 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
 
 -- cleanup
 DROP TABLE empsalary;
-- 
2.25.1

#4Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Wang Yao (#3)
Re: 回复: An implementation of multi-key sort

On 23/05/2024 15:39, Wang Yao wrote:

No obvious perf regression is expected because PG will follow original
qsort code path when mksort is disabled. For the case, the only extra
cost is the check in tuplesort_sort_memtuples() to enter mksort code path.

And what about the case the mksort is enabled, but it's not effective
because all leading keys are different?

--
Heikki Linnakangas
Neon (https://neon.tech)

#5Yao Wang
yao-yw.wang@broadcom.com
In reply to: Wang Yao (#1)
Re: 回复: An implementation of multi-key sort

When all leading keys are different, mksort will finish the entire sort at the
first sort key and never touch other keys. For the case, mksort falls back to
kind of qsort actually.

I created another data set with distinct values in all sort keys:

create table t2 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 varchar(100));
insert into t2 values (generate_series(1,499999), 0, 0, 0, 0, '');
update t2 set c2 = 999990 - c1, c3 = 999991 - c1, c4 = 999992 - c1, c5
= 999993 - c1;
update t2 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (999994 - c1)::text;
explain analyze select c1 from t2 order by c6, c5, c4, c3, c2, c1;

Results:

MKsort:
12374.427 ms
12528.068 ms
12554.718 ms

qsort:
12251.422 ms
12279.938 ms
12280.254 ms

MKsort is a bit slower than qsort, which can be explained by extra
checks of MKsort.

Yao Wang

On Fri, May 24, 2024 at 8:36 PM Wang Yao <yaowangm@outlook.com> wrote:

获取Outlook for Android
________________________________
From: Heikki Linnakangas <hlinnaka@iki.fi>
Sent: Thursday, May 23, 2024 8:47:29 PM
To: Wang Yao <yaowangm@outlook.com>; PostgreSQL Hackers <pgsql-hackers@postgresql.org>
Cc: interma@outlook.com <interma@outlook.com>
Subject: Re: 回复: An implementation of multi-key sort

On 23/05/2024 15:39, Wang Yao wrote:

No obvious perf regression is expected because PG will follow original
qsort code path when mksort is disabled. For the case, the only extra
cost is the check in tuplesort_sort_memtuples() to enter mksort code path.

And what about the case the mksort is enabled, but it's not effective
because all leading keys are different?

--
Heikki Linnakangas
Neon (https://neon.tech)

--
This electronic communication and the information and any files transmitted
with it, or attached to it, are confidential and are intended solely for
the use of the individual or entity to whom it is addressed and may contain
information that is confidential, legally privileged, protected by privacy
laws, or otherwise restricted from disclosure to anyone else. If you are
not the intended recipient or the person responsible for delivering the
e-mail to the intended recipient, you are hereby notified that any use,
copying, distributing, dissemination, forwarding, printing, or copying of
this e-mail is strictly prohibited. If you received this e-mail in error,
please return the e-mail to the sender, delete it from your computer, and
destroy any printed copy of it.

#6Yao Wang
yao-yw.wang@broadcom.com
In reply to: Yao Wang (#5)
1 attachment(s)
Re: 回复: An implementation of multi-key sort

I added two optimizations to mksort which exist on qsort_tuple():

1. When selecting pivot, always pick the item in the middle of array but
not by random. Theoretically it has the same effect to old approach, but
it can eliminate some unstable perf test results, plus a bit perf benefit by
removing random value generator.
2. Always check whether the array is ordered already, and return
immediately if it is. The pre-ordered check requires extra cost and
impacts perf numbers on some data sets, but can improve perf
significantly on other data sets.

By now, mksort has perf results equal or better than qsort on all data
sets I ever used.

I also updated test case. Please see v3 code as attachment.

Perf test results:

Data set 1 (with mass duplicate values):
-----------------------------------------

create table t1 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 varchar(100));
insert into t1 values (generate_series(1,499999), 0, 0, 0, 0,
'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
update t1 set c2 = c1 % 100, c3 = c1 % 50, c4 = c1 % 10, c5 = c1 % 3;
update t1 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (c1 % 5)::text;

Query 1:

explain analyze select c1 from t1 order by c6, c5, c4, c3, c2, c1;

Disable Mksort

3021.636 ms
3014.669 ms
3033.588 ms

Enable Mksort

1688.590 ms
1686.956 ms
1688.567 ms

The improvement is 78.9%, which is reduced from the previous version
(129%). The most cost should be the pre-ordered check.

Query 2:

create index idx_t1_mk on t1 (c6, c5, c4, c3, c2, c1);

Disable Mksort

1674.648 ms
1680.608 ms
1681.373 ms

Enable Mksort

1143.341 ms
1143.462 ms
1143.894 ms

The improvement is ~47%, which is also reduced a bit (52%).

Data set 2 (with distinct values):
----------------------------------

create table t2 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 varchar(100));
insert into t2 values (generate_series(1,499999), 0, 0, 0, 0, '');
update t2 set c2 = 999990 - c1, c3 = 999991 - c1, c4 = 999992 - c1, c5
= 999993 - c1;
update t2 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (999994 - c1)::text;

Query 1:

explain analyze select c1 from t2 order by c6, c5, c4, c3, c2, c1;

Disable Mksort

12199.963 ms
12197.068 ms
12191.657 ms

Enable Mksort

9538.219 ms
9571.681 ms
9536.335 ms

The improvement is 27.9%, which is much better than the old approach (-6.2%).

Query 2 (the data is pre-ordered):

explain analyze select c1 from t2 order by c6 desc, c5, c4, c3, c2, c1;

Enable Mksort

768.191 ms
768.079 ms
767.026 ms

Disable Mksort

768.757 ms
766.166 ms
766.149 ms

They are almost the same since no actual sort was performed, and much
better than the old approach (-1198.1%).

Thanks,

Yao Wang

On Fri, May 24, 2024 at 8:50 PM Yao Wang <yao-yw.wang@broadcom.com> wrote:

When all leading keys are different, mksort will finish the entire sort at the
first sort key and never touch other keys. For the case, mksort falls back to
kind of qsort actually.

I created another data set with distinct values in all sort keys:

create table t2 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 varchar(100));
insert into t2 values (generate_series(1,499999), 0, 0, 0, 0, '');
update t2 set c2 = 999990 - c1, c3 = 999991 - c1, c4 = 999992 - c1, c5
= 999993 - c1;
update t2 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (999994 - c1)::text;
explain analyze select c1 from t2 order by c6, c5, c4, c3, c2, c1;

Results:

MKsort:
12374.427 ms
12528.068 ms
12554.718 ms

qsort:
12251.422 ms
12279.938 ms
12280.254 ms

MKsort is a bit slower than qsort, which can be explained by extra
checks of MKsort.

Yao Wang

On Fri, May 24, 2024 at 8:36 PM Wang Yao <yaowangm@outlook.com> wrote:

获取Outlook for Android
________________________________
From: Heikki Linnakangas <hlinnaka@iki.fi>
Sent: Thursday, May 23, 2024 8:47:29 PM
To: Wang Yao <yaowangm@outlook.com>; PostgreSQL Hackers <pgsql-hackers@postgresql.org>
Cc: interma@outlook.com <interma@outlook.com>
Subject: Re: 回复: An implementation of multi-key sort

On 23/05/2024 15:39, Wang Yao wrote:

No obvious perf regression is expected because PG will follow original
qsort code path when mksort is disabled. For the case, the only extra
cost is the check in tuplesort_sort_memtuples() to enter mksort code path.

And what about the case the mksort is enabled, but it's not effective
because all leading keys are different?

--
Heikki Linnakangas
Neon (https://neon.tech)

--
This electronic communication and the information and any files transmitted
with it, or attached to it, are confidential and are intended solely for
the use of the individual or entity to whom it is addressed and may contain
information that is confidential, legally privileged, protected by privacy
laws, or otherwise restricted from disclosure to anyone else. If you are
not the intended recipient or the person responsible for delivering the
e-mail to the intended recipient, you are hereby notified that any use,
copying, distributing, dissemination, forwarding, printing, or copying of
this e-mail is strictly prohibited. If you received this e-mail in error,
please return the e-mail to the sender, delete it from your computer, and
destroy any printed copy of it.

Attachments:

v3-Implement-multi-key-sort.patchapplication/octet-stream; name=v3-Implement-multi-key-sort.patchDownload
From dfeba76ea15f4a56535b1534aa8a06880cdc32cb Mon Sep 17 00:00:00 2001
From: Yao Wang <yaowangm@outlook.com>
Date: Tue, 7 May 2024 08:11:13 +0000
Subject: [PATCH] Implement multi-key sort

MKsort (multi-key sort) is an alternative of standard qsort algorithm,
which has better performance for particular sort scenarios, i.e. the data
set has multiple keys to be sorted. Comparing to classic quick sort, it
can get significant performance improvement once multiple keys are
available.

Author: Yao Wang <yao-yw.wang@broadcom.com>
Co-author: Hongxu Ma <hongxu.ma@broadcom.com>
---
 src/backend/utils/misc/guc_tables.c           |  11 +
 src/backend/utils/sort/mksort_tuple.c         | 384 ++++++++++++++++++
 src/backend/utils/sort/tuplesort.c            |  44 ++
 src/backend/utils/sort/tuplesortvariants.c    | 313 ++++++++++++--
 src/include/c.h                               |   4 +
 src/include/utils/tuplesort.h                 |  34 +-
 src/test/regress/expected/geometry.out        |   4 +-
 .../regress/expected/incremental_sort.out     |  12 +-
 src/test/regress/expected/tuplesort.out       | 376 +++++++++++++++++
 src/test/regress/expected/window.out          |  58 +--
 src/test/regress/sql/geometry.sql             |   2 +-
 src/test/regress/sql/tuplesort.sql            |  66 +++
 src/test/regress/sql/window.sql               |  22 +-
 13 files changed, 1250 insertions(+), 80 deletions(-)
 create mode 100644 src/backend/utils/sort/mksort_tuple.c

diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 3fd0b14dd8..b8fe447d68 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -103,6 +103,7 @@ extern char *default_tablespace;
 extern char *temp_tablespaces;
 extern bool ignore_checksum_failure;
 extern bool ignore_invalid_pages;
+extern bool enable_mk_sort;
 
 #ifdef TRACE_SYNCSCAN
 extern bool trace_syncscan;
@@ -839,6 +840,16 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_mk_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables multi-key"),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_mk_sort,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
diff --git a/src/backend/utils/sort/mksort_tuple.c b/src/backend/utils/sort/mksort_tuple.c
new file mode 100644
index 0000000000..85e69aa783
--- /dev/null
+++ b/src/backend/utils/sort/mksort_tuple.c
@@ -0,0 +1,384 @@
+/*
+ * MKsort (multiple-key sort) is an alternative of standard qsort algorithm,
+ * which has better performance for particular sort scenarios, i.e. the
+ * data set has multiple keys to be sorted.
+ *
+ * The sorting algorithm blends Quicksort and radix sort; Like regular
+ * Quicksort, it partitions its input into sets less than and greater than a
+ * given value; like radix sort, it moves on to the next field once the current
+ * input is known to be equal in the given field.
+ *
+ * The implementation is based on the paper:
+ *   Jon L. Bentley and Robert Sedgewick, "Fast Algorithms for Sorting and
+ *   Searching Strings", Jan 1997
+ *
+ * Some improvements which is related to additional handling for equal tuples
+ * have been adapted to keep consistency with the implementations of postgres
+ * qsort.
+ *
+ * For now, mksort_tuple() is called in tuplesort_sort_memtuples() as a
+ * replacement of qsort_tuple() when specific conditions are satisfied.
+ */
+
+/* Swap two tuples in sort tuple array */
+static inline void
+mksort_swap(int        a,
+			int        b,
+			SortTuple *x)
+{
+	SortTuple t;
+
+	if (a == b)
+		return;
+	t = x[a];
+	x[a] = x[b];
+	x[b] = t;
+}
+
+/* Swap tuples by batch in sort tuple array */
+static inline void
+mksort_vec_swap(int        a,
+				int        b,
+				int        size,
+				SortTuple *x)
+{
+	while (size-- > 0)
+	{
+		mksort_swap(a, b, x);
+		a++;
+		b++;
+	}
+}
+
+/*
+ * Check whether current datum (at specified tuple and depth) is null
+ * Note that the input x means a specified tuple provided by caller but not
+ * a tuple array, so tupleIndex is unnecessary
+ */
+static inline bool
+check_datum_null(SortTuple      *x,
+				 int             depth,
+				 Tuplesortstate *state)
+{
+	Datum datum;
+	bool isNull;
+
+	/* Since we have a specified tuple, the tupleIndex is always 0 */
+	state->base.mksortGetDatumFunc(x, 0, depth, state, &datum, &isNull, false);
+
+	/*
+	 * Note: for "abbreviated key", we don't need to handle more here because
+	 * if "abbreviated key" of a datum is null, the "full" datum must be null.
+	 */
+
+	return isNull;
+}
+
+/*
+ * Compare two tuples at specified depth
+ *
+ * If "abbreviated key" is disabled:
+ *   get specified datums and compare them by ApplySortComparator().
+ * If "abbreviated key" is enabled:
+ *   Only first datum may be abbr key according to the design (see the comments
+ *   of struct SortTuple), so different operations are needed for different
+ *   datum.
+ *   For first datum (depth == 0): get first datums ("abbr key" version) and
+ *   compare them by ApplySortComparator(). If they are equal, get "full"
+ *   version and compare again by ApplySortAbbrevFullComparator().
+ *   For other datums: get specified datums and compare them by
+ *   ApplySortComparator() as regular routine does.
+ *
+ * See comparetup_heap() for details.
+ */
+static inline int
+mksort_compare_datum(SortTuple      *tuple1,
+					 SortTuple      *tuple2,
+					 int			 depth,
+					 Tuplesortstate *state)
+{
+	Datum datum1, datum2;
+	bool isNull1, isNull2;
+	SortSupport sortKey;
+	int ret = 0;
+
+	Assert(state->mksortGetDatumFunc);
+
+	sortKey = state->base.sortKeys + depth;
+	state->base.mksortGetDatumFunc(tuple1, 0, depth, state,
+							  &datum1, &isNull1, false);
+	state->base.mksortGetDatumFunc(tuple2, 0, depth, state,
+							  &datum2, &isNull2, false);
+
+	ret = ApplySortComparator(datum1,
+							  isNull1,
+							  datum2,
+							  isNull2,
+							  sortKey);
+
+	/*
+	 * If "abbreviated key" is enabled, and we are in the first depth, it means
+	 * only "abbreviated keys" are compared. If the two datums are determined to
+	 * be equal by ApplySortComparator(), we need to perform an extra "full"
+	 * comparing by ApplySortAbbrevFullComparator().
+	 */
+	if (sortKey->abbrev_converter &&
+		depth == 0 &&
+		ret == 0)
+	{
+		/* Fetch "full" datum by setting useFullKey = true */
+		state->base.mksortGetDatumFunc(tuple1, 0, depth, state,
+								  &datum1, &isNull1, true);
+		state->base.mksortGetDatumFunc(tuple2, 0, depth, state,
+								  &datum2, &isNull2, true);
+
+		ret = ApplySortAbbrevFullComparator(datum1,
+											isNull1,
+											datum2,
+											isNull2,
+											sortKey);
+	}
+
+	return ret;
+}
+
+#ifdef USE_ASSERT_CHECKING
+/*
+ * Verify whether the SortTuple list is ordered or not at specified depth
+ */
+static void
+mksort_verify(SortTuple		  *x,
+			  int			   n,
+			  int			   depth,
+			  Tuplesortstate  *state)
+{
+	int ret;
+
+	for (int i = 0;i < n - 1;i++)
+	{
+		ret = mksort_compare_datum(x + i,
+								   x + i + 1,
+								   depth,
+								   state);
+		Assert(ret <= 0);
+	}
+}
+#endif
+
+/*
+ * Major of multi-key sort
+ *
+ * seenNull indicates whether we have seen NULL in any datum we checked
+ */
+static void
+mksort_tuple(SortTuple           *x,
+			 size_t               n,
+			 int                  depth,
+			 Tuplesortstate      *state,
+			 bool				  seenNull)
+{
+	/*
+	 * In the process, the tuple array consists of five parts:
+	 * left equal, less, not-processed, greater, right equal
+	 *
+	 * lessStart indicates the first position of less part
+	 * lessEnd indicates the next position after less part
+	 * greaterStart indicates the prior position before greater part
+	 * greaterEnd indicates the latest position of greater part
+	 * the range between lessEnd and greaterStart (inclusive) is not-processed
+	 */
+	int lessStart, lessEnd, greaterStart, greaterEnd, tupCount;
+	int32 dist;
+	SortTuple *pivot;
+	bool isDatumNull;
+	bool strictOrdered = true;
+
+	Assert(depth <= state->base.nKeys);
+	Assert(state->base.sortKeys);
+	Assert(state->base.mksortGetDatumFunc);
+
+	if (n <= 1)
+		return;
+
+	/* If we have exceeded the max depth, return immediately */
+	if (depth == state->base.nKeys)
+		return;
+
+	CHECK_FOR_INTERRUPTS();
+
+	/*
+	 * Check if the array is ordered already. If yes, return immediately.
+	 * Different from qsort_tuple(), the array must be strict ordered (no
+	 * equal datums). If there are equal datums, we must continue the mksort
+	 * process to check datums on lower depth.
+	 */
+	for (int i = 0;i < n - 1;i++)
+	{
+		int ret;
+
+		CHECK_FOR_INTERRUPTS();
+		ret = mksort_compare_datum(x + i,
+								   x + i + 1,
+								   depth,
+								   state);
+		if (ret >= 0)
+		{
+			strictOrdered = false;
+			break;
+		}
+	}
+
+	if (strictOrdered)
+		return;
+
+	/* Select pivot by random and move it to the first position */
+	lessStart = n / 2;
+	mksort_swap(0, lessStart, x);
+	pivot = x;
+
+	lessStart = 1;
+	lessEnd = 1;
+	greaterStart = n - 1;
+	greaterEnd = n - 1;
+
+	/* Sort the array to three parts: lesser, equal, greater */
+	while (true)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		/* Compare the left end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare lessEnd and pivot at current depth */
+			dist = mksort_compare_datum(x + lessEnd,
+										pivot,
+										depth,
+										state);
+
+			if (dist > 0)
+				break;
+
+			/* If lessEnd is equal to pivot, move it to lessStart */
+			if (dist == 0)
+			{
+				mksort_swap(lessEnd, lessStart, x);
+				lessStart++;
+			}
+			lessEnd++;
+		}
+
+		/* Compare the right end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare greaterStart and pivot at current depth */
+			dist = mksort_compare_datum(x + greaterStart,
+										pivot,
+										depth,
+										state);
+
+			if (dist < 0)
+				break;
+
+			/* If greaterStart is equal to pivot, move it to greaterEnd */
+			if (dist == 0)
+			{
+				mksort_swap(greaterStart, greaterEnd, x);
+				greaterEnd--;
+			}
+			greaterStart--;
+		}
+
+		if (lessEnd > greaterStart)
+			break;
+		mksort_swap(lessEnd, greaterStart, x);
+		lessEnd++;
+		greaterStart--;
+	}
+
+	/*
+	 * Now the array has four parts:
+	 *   left equal, lesser, greater, right equal
+	 * Note greaterStart is less than lessEnd now
+	 */
+
+	/* Move the left equal part to middle */
+	dist = Min(lessStart, lessEnd - lessStart);
+	mksort_vec_swap(0, lessEnd - dist, dist, x);
+
+	/* Move the right equal part to middle */
+	dist = Min(greaterEnd - greaterStart, n - greaterEnd - 1);
+	mksort_vec_swap(lessEnd, n - dist, dist, x);
+
+	/*
+	 * Now the array has three parts:
+	 *   lesser, equal, greater
+	 * Note that one or two parts may have no element at all.
+	 */
+
+	/* Recursively sort the lesser part */
+
+	/* dist means the size of less part */
+	dist = lessEnd - lessStart;
+	mksort_tuple(x,
+				 dist,
+				 depth,
+				 state,
+				 seenNull);
+
+	/* Recursively sort the equal part */
+
+	/*
+	 * (x + dist) means the first tuple in the equal part
+	 * Since all tuples have equal datums at current depth, we just check any one
+	 * of them to determine whether we have seen null datum.
+	 */
+	isDatumNull = check_datum_null(x + dist, depth, state);
+
+	/* (lessStart + n - greaterEnd - 1) means the size of equal part */
+	tupCount = lessStart + n - greaterEnd - 1;
+
+	if (depth < state->base.nKeys - 1)
+	{
+		mksort_tuple(x + dist,
+					 tupCount,
+					 depth + 1,
+					 state,
+					 seenNull || isDatumNull);
+	} else {
+		/*
+		 * We have reach the max depth: Call mksortHandleDupFunc to handle duplicated
+		 * tuples if necessary, e.g. checking uniqueness or extra comparing
+		 */
+
+		/*
+		 * Call mksortHandleDupFunc if:
+		 *   1. mksortHandleDupFunc is filled
+		 *   2. the size of equal part > 1
+		 */
+		if (state->base.mksortHandleDupFunc &&
+			(tupCount > 1))
+		{
+			state->base.mksortHandleDupFunc(x + dist,
+									   tupCount,
+									   seenNull || isDatumNull,
+									   state);
+		}
+	}
+
+	/* Recursively sort the greater part */
+
+	/* dist means the size of greater part */
+	dist = greaterEnd - greaterStart;
+	mksort_tuple(x + n - dist,
+				 dist,
+				 depth,
+				 state,
+				 seenNull);
+
+#ifdef USE_ASSERT_CHECKING
+	mksort_verify(x,
+				  n,
+				  depth,
+				  state);
+#endif
+}
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 7c4d6dc106..c865772a7a 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -108,6 +108,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_rusage.h"
 #include "utils/tuplesort.h"
+#include "common/pg_prng.h"
 
 /*
  * Initial size of memtuples array.  We're trying to select this size so that
@@ -128,6 +129,7 @@ bool		trace_sort = false;
 bool		optimize_bounded_sort = true;
 #endif
 
+bool		enable_mk_sort = true;
 
 /*
  * During merge, we use a pre-allocated set of fixed-size slots to hold
@@ -337,6 +339,9 @@ struct Tuplesortstate
 #ifdef TRACE_SORT
 	PGRUsage	ru_start;
 #endif
+
+	/* Whether multi-key sort is used */
+	bool mksortUsed;
 };
 
 /*
@@ -622,6 +627,8 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
 #define ST_DEFINE
 #include "lib/sort_template.h"
 
+#include "mksort_tuple.c"
+
 /*
  *		tuplesort_begin_xxx
  *
@@ -690,6 +697,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate, int sortopt)
 	state->base.sortopt = sortopt;
 	state->base.tuples = true;
 	state->abbrevNext = 10;
+	state->mksortUsed = false;
 
 	/*
 	 * workMem is forced to be at least 64KB, the current minimum valid value
@@ -2559,6 +2567,8 @@ tuplesort_get_stats(Tuplesortstate *state,
 		case TSS_SORTEDINMEM:
 			if (state->boundUsed)
 				stats->sortMethod = SORT_TYPE_TOP_N_HEAPSORT;
+			else if (state->mksortUsed)
+				stats->sortMethod = SORT_TYPE_MKSORT;
 			else
 				stats->sortMethod = SORT_TYPE_QUICKSORT;
 			break;
@@ -2592,6 +2602,8 @@ tuplesort_method_name(TuplesortMethod m)
 			return "external sort";
 		case SORT_TYPE_EXTERNAL_MERGE:
 			return "external merge";
+		case SORT_TYPE_MKSORT:
+			return "multi-key sort";
 	}
 
 	return "unknown";
@@ -2717,6 +2729,38 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 
 	if (state->memtupcount > 1)
 	{
+		/*
+		 * Apply multi-key sort when:
+		 *   1. enable_mk_sort is set
+		 *   2. There are multiple keys available
+		 *   3. mksortGetDatumFunc is filled, which implies that current tuple
+		 *      type is supported by mksort. (By now only Heap tuple and Btree
+		 *      Index tuple are supported, and more types may be supported in
+		 *      future.)
+		 *
+		 * A summary of tuple types supported by mksort:
+		 *
+		 *   HeapTuple: supported
+		 *   IndexTuple(btree): supported
+		 *   IndexTuple(hash): not supported because there is only one key
+		 *   DatumTuple: not supported because there is only one key
+		 *   HeapTuple(for cluster): not supported yet
+		 *   IndexTuple(gist): not supported yet
+		 *   IndexTuple(brin): not supported yet
+		 */
+		if (enable_mk_sort &&
+			state->base.nKeys > 1 &&
+			state->base.mksortGetDatumFunc != NULL)
+		{
+			state->mksortUsed = true;
+			mksort_tuple(state->memtuples,
+						 state->memtupcount,
+						 0,
+						 state,
+						 false);
+			return;
+		}
+
 		/*
 		 * Do we have the leading column's value or abbreviation in datum1,
 		 * and is there a specialization for its comparator?
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 05a853caa3..c105eb1e35 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -30,6 +30,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
+#include "miscadmin.h"
 
 
 /* sort-type codes for sort__start probes */
@@ -92,6 +93,41 @@ static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
 						  LogicalTape *tape, unsigned int len);
 static void freestate_cluster(Tuplesortstate *state);
 
+static Datum mksort_get_datum_heap(SortTuple      *x,
+								   const int       tupleIndex,
+								   const int       depth,
+								   Tuplesortstate *state,
+								   Datum          *datum,
+								   bool           *isNull,
+								   bool            useFullKey);
+
+static Datum mksort_get_datum_index_btree(SortTuple      *x,
+										  const int       tupleIndex,
+										  const int       depth,
+										  Tuplesortstate *state,
+										  Datum          *datum,
+										  bool           *isNull,
+										  bool            useFullKey);
+
+static void
+mksort_handle_dup_index_btree(SortTuple      *x,
+							  const int       tupleCount,
+							  const bool      seenNull,
+							  Tuplesortstate *state);
+
+static int
+mksort_compare_equal_index_btree(const SortTuple *a,
+								 const SortTuple *b,
+								 Tuplesortstate  *state);
+
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2);
+
+static inline void
+raise_error_of_dup_index(IndexTuple     x,
+						 Tuplesortstate *state);
+
 /*
  * Data structure pointed by "TuplesortPublic.arg" for the CLUSTER case.  Set by
  * the tuplesort_begin_cluster.
@@ -163,6 +199,14 @@ typedef struct BrinSortTuple
 /* Size of the BrinSortTuple, given length of the BrinTuple. */
 #define BRINSORTTUPLE_SIZE(len)		(offsetof(BrinSortTuple, tuple) + (len))
 
+#define ST_SORT qsort_tuple_by_itempointer
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) mksort_compare_equal_index_btree(a, b, state)
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
 
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
@@ -200,6 +244,7 @@ tuplesort_begin_heap(TupleDesc tupDesc,
 	base->removeabbrev = removeabbrev_heap;
 	base->comparetup = comparetup_heap;
 	base->comparetup_tiebreak = comparetup_heap_tiebreak;
+	base->mksortGetDatumFunc = mksort_get_datum_heap;
 	base->writetup = writetup_heap;
 	base->readtup = readtup_heap;
 	base->haveDatum1 = true;
@@ -388,6 +433,8 @@ tuplesort_begin_index_btree(Relation heapRel,
 	base->removeabbrev = removeabbrev_index;
 	base->comparetup = comparetup_index_btree;
 	base->comparetup_tiebreak = comparetup_index_btree_tiebreak;
+	base->mksortGetDatumFunc = mksort_get_datum_index_btree;
+	base->mksortHandleDupFunc = mksort_handle_dup_index_btree;
 	base->writetup = writetup_index;
 	base->readtup = readtup_index;
 	base->haveDatum1 = true;
@@ -1543,18 +1590,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 		 */
 		Assert(tuple1 != tuple2);
 
-		index_deform_tuple(tuple1, tupDes, values, isnull);
-
-		key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
-
-		ereport(ERROR,
-				(errcode(ERRCODE_UNIQUE_VIOLATION),
-				 errmsg("could not create unique index \"%s\"",
-						RelationGetRelationName(arg->index.indexRel)),
-				 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
-				 errdetail("Duplicate keys exist."),
-				 errtableconstraint(arg->index.heapRel,
-									RelationGetRelationName(arg->index.indexRel))));
+		raise_error_of_dup_index(tuple1, state);
 	}
 
 	/*
@@ -1563,25 +1599,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 * attribute in order to ensure that all keys in the index are physically
 	 * unique.
 	 */
-	{
-		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
-		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
-
-		if (blk1 != blk2)
-			return (blk1 < blk2) ? -1 : 1;
-	}
-	{
-		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
-		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
-
-		if (pos1 != pos2)
-			return (pos1 < pos2) ? -1 : 1;
-	}
-
-	/* ItemPointer values should never be equal */
-	Assert(false);
-
-	return 0;
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
 }
 
 static int
@@ -1888,3 +1906,236 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
 	if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */
 		LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen));
 }
+
+/*
+ * Get specified datum from SortTuple (HeapTuple) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_heap() for details.
+ */
+static Datum
+mksort_get_datum_heap(SortTuple		 *x,
+					  int			  tupleIndex,
+					  int			  depth,
+					  Tuplesortstate *state,
+					  Datum			 *datum,
+					  bool			 *isNull,
+					  bool			  useFullKey)
+{
+	TupleDesc   tupDesc = NULL;
+	HeapTupleData heapTuple;
+	AttrNumber  attno;
+	SortTuple *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	SortSupport sortKey = base->sortKeys + depth;;
+
+	Assert(state);
+	Assert(depth < state->nKeys);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	tupDesc = (TupleDesc)base->arg;
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*datum = sortTuple->datum1;
+		*isNull = sortTuple->isnull1;
+		return *datum;
+	}
+
+	/* For any datums which depth > 0, extract it from sortTuple->tuple */
+	heapTuple.t_len = ((MinimalTuple) sortTuple->tuple)->t_len + MINIMAL_TUPLE_OFFSET;
+	heapTuple.t_data = (HeapTupleHeader) ((char *) sortTuple->tuple - MINIMAL_TUPLE_OFFSET);
+	attno = sortKey->ssup_attno;
+	*datum = heap_getattr(&heapTuple, attno, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Get specified datum from SortTuple (IndexTuple for btree index) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_index_btree() for details.
+ */
+static Datum
+mksort_get_datum_index_btree(SortTuple      *x,
+							 const int       tupleIndex,
+							 const int       depth,
+							 Tuplesortstate *state,
+							 Datum          *datum,
+							 bool           *isNull,
+							 bool			 useFullKey)
+{
+	TupleDesc   tupDesc;
+	IndexTuple  indexTuple;
+	SortTuple  *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	Assert(state);
+	Assert(depth < state->nKeys);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*isNull = sortTuple->isnull1;
+		*datum = sortTuple->datum1;
+		return *datum;
+	}
+
+	indexTuple = (IndexTuple) sortTuple->tuple;
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+
+	/*
+	 * Set parameter attnum = depth + 1 because attnum starts from 1 but depth
+	 * starts from 0
+	 */
+	*datum = index_getattr(indexTuple, depth + 1, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Handle duplicated SortTuples (IndexTuple for btree index during mksort)
+ *  x: the duplicated tuple list
+ *  tupleCount: count of the tuples
+ */
+static void
+mksort_handle_dup_index_btree(SortTuple      *x,
+							  const int       tupleCount,
+							  const bool      seenNull,
+							  Tuplesortstate *state)
+{
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	/* If enforceUnique is enabled and we never saw NULL, raise error */
+	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && seenNull))
+	{
+		Assert(state->comparetup == comparetup_index_btree);
+
+		/*
+		 * x means the first tuple of duplicated tuple list
+		 * Since they are duplicated, simply pick up the first one
+		 * to raise error
+		 */
+		raise_error_of_dup_index((IndexTuple)(x->tuple), state);
+	}
+
+	/*
+	 * If key values are equal, we sort on ItemPointer.  This is required for
+	 * btree indexes, since heap TID is treated as an implicit last key
+	 * attribute in order to ensure that all keys in the index are physically
+	 * unique.
+	 */
+	qsort_tuple_by_itempointer(x,
+							   tupleCount,
+							   state);
+}
+
+/*
+ * Compare two btree index tuples by ItemPointer
+ * It is a callback function for qsort_tuple() called by
+ * mksort_handle_dup_index_btree()
+ */
+static int
+mksort_compare_equal_index_btree(const SortTuple *a,
+								 const SortTuple *b,
+								 Tuplesortstate  *state)
+{
+	IndexTuple  tuple1;
+	IndexTuple  tuple2;
+
+	tuple1 = (IndexTuple) a->tuple;
+	tuple2 = (IndexTuple) b->tuple;
+
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
+}
+
+/* Compare two index tuples by ItemPointer */
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2)
+{
+	{
+		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
+		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
+
+		if (blk1 != blk2)
+			return (blk1 < blk2) ? -1 : 1;
+	}
+	{
+		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
+		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
+
+		if (pos1 != pos2)
+			return (pos1 < pos2) ? -1 : 1;
+	}
+
+	/* ItemPointer values should never be equal */
+	Assert(false);
+
+	return 0;
+}
+
+/* Raise error for duplicated tuple when creating unique index */
+static inline void
+raise_error_of_dup_index(IndexTuple      x,
+						 Tuplesortstate *state)
+{
+	Datum       values[INDEX_MAX_KEYS];
+	bool        isnull[INDEX_MAX_KEYS];
+	TupleDesc   tupDesc;
+	char       *key_desc;
+    TuplesortPublic *base = TuplesortstateGetPublic(state);
+    TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+	index_deform_tuple((IndexTuple)x, tupDesc, values, isnull);
+	key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNIQUE_VIOLATION),
+			errmsg("could not create unique index \"%s\"",
+				   RelationGetRelationName(arg->index.indexRel)),
+			key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+				errdetail("Duplicate keys exist."),
+				errtableconstraint(arg->index.heapRel,
+								   RelationGetRelationName(arg->index.indexRel))));
+}
diff --git a/src/include/c.h b/src/include/c.h
index dc1841346c..f7c368cd16 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -857,12 +857,14 @@ typedef NameData *Name;
 
 #define Assert(condition)	((void)true)
 #define AssertMacro(condition)	((void)true)
+#define AssertImply(condition1, condition2) ((void)true)
 
 #elif defined(FRONTEND)
 
 #include <assert.h>
 #define Assert(p) assert(p)
 #define AssertMacro(p)	((void) assert(p))
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
 
 #else							/* USE_ASSERT_CHECKING && !FRONTEND */
 
@@ -886,6 +888,8 @@ typedef NameData *Name;
 	((void) ((condition) || \
 			 (ExceptionalCondition(#condition, __FILE__, __LINE__), 0)))
 
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
+
 #endif							/* USE_ASSERT_CHECKING && !FRONTEND */
 
 /*
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index e7941a1f09..d3f27b49dc 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -29,7 +29,6 @@
 #include "utils/relcache.h"
 #include "utils/sortsupport.h"
 
-
 /*
  * Tuplesortstate and Sharedsort are opaque types whose details are not
  * known outside tuplesort.c.
@@ -79,9 +78,10 @@ typedef enum
 	SORT_TYPE_QUICKSORT = 1 << 1,
 	SORT_TYPE_EXTERNAL_SORT = 1 << 2,
 	SORT_TYPE_EXTERNAL_MERGE = 1 << 3,
+	SORT_TYPE_MKSORT = 1 << 4,
 } TuplesortMethod;
 
-#define NUM_TUPLESORTMETHODS 4
+#define NUM_TUPLESORTMETHODS 5
 
 typedef enum
 {
@@ -155,6 +155,21 @@ typedef struct
 typedef int (*SortTupleComparator) (const SortTuple *a, const SortTuple *b,
 									Tuplesortstate *state);
 
+typedef Datum
+(*MksortGetDatumFunc) (SortTuple      *x,
+					   const int       tupleIndex,
+					   const int       depth,
+					   Tuplesortstate *state,
+					   Datum          *datum,
+					   bool           *isNull,
+					   bool            useFullKey);
+
+typedef void
+(*MksortHandleDupFunc) (SortTuple      *x,
+						const int       tupleCount,
+						const bool      seenNull,
+						Tuplesortstate *state);
+
 /*
  * The public part of a Tuple sort operation state.  This data structure
  * contains the definition of sort-variant-specific interface methods and
@@ -249,6 +264,21 @@ typedef struct
 	bool		tuples;			/* Can SortTuple.tuple ever be set? */
 
 	void	   *arg;			/* Specific information for the sort variant */
+
+	/*
+	 * Function pointer, referencing a function to get specified datum from
+	 * SortTuple list with multi-key.
+	 * Used by mksort_tuple().
+	*/
+	MksortGetDatumFunc mksortGetDatumFunc;
+
+	/*
+	 * Function pointer, referencing a function to handle duplicated tuple
+	 * from SortTuple list with multi-key.
+	 * Used by mksort_tuple().
+	 * For now, the function pointer is filled for only btree index tuple.
+	*/
+	MksortHandleDupFunc mksortHandleDupFunc;
 } TuplesortPublic;
 
 /* Sort parallel code from state for sort__start probes */
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 8be694f46b..094d22861c 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -4273,7 +4273,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
      circle     |       point       |   distance    
 ----------------+-------------------+---------------
  <(1,2),3>      | (-3,4)            |   1.472135955
@@ -4310,8 +4310,8 @@ SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
  <(3,5),0>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (1e+300,Infinity) |      Infinity
  <(5,1),3>      | (1e+300,Infinity) |      Infinity
- <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (Infinity,1e+300) |      Infinity
+ <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,3),5>      | (1e+300,Infinity) |      Infinity
  <(1,3),5>      | (Infinity,1e+300) |      Infinity
  <(100,200),10> | (1e+300,Infinity) |      Infinity
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 5fd54a10b1..e8dba83389 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -520,13 +520,13 @@ select * from (select * from t order by a) s order by a, b limit 55;
 
 -- Test EXPLAIN ANALYZE with only a fullsort group.
 select explain_analyze_without_memory('select * from (select * from t order by a) s order by a, b limit 55');
-                                        explain_analyze_without_memory                                         
----------------------------------------------------------------------------------------------------------------
+                                           explain_analyze_without_memory                                           
+--------------------------------------------------------------------------------------------------------------------
  Limit (actual rows=55 loops=1)
    ->  Incremental Sort (actual rows=55 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 2  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 2  Sort Methods: top-N heapsort, multi-key sort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=101 loops=1)
                Sort Key: t.a
                Sort Method: quicksort  Memory: NNkB
@@ -554,7 +554,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
              "Group Count": 2,                  +
              "Sort Methods Used": [             +
                  "top-N heapsort",              +
-                 "quicksort"                    +
+                 "multi-key sort"               +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
@@ -728,7 +728,7 @@ select explain_analyze_without_memory('select * from (select * from t order by a
    ->  Incremental Sort (actual rows=70 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 1  Sort Method: quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 1  Sort Method: multi-key sort  Average Memory: NNkB  Peak Memory: NNkB
          Pre-sorted Groups: 5  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=1000 loops=1)
                Sort Key: t.a
@@ -756,7 +756,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
          "Full-sort Groups": {                  +
              "Group Count": 1,                  +
              "Sort Methods Used": [             +
-                 "quicksort"                    +
+                 "multi-key sort"               +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 6dd97e7427..4ec7b10884 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -703,3 +703,379 @@ EXPLAIN (COSTS OFF) :qry;
 (10 rows)
 
 COMMIT;
+-- Test cases for multi-key sort
+set work_mem='100MB';
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a | b  |  c   
+---+----+------
+ 0 |  5 | 98f1
+ 0 | 10 | d3d9
+ 1 |  1 | c4ca
+ 1 | 11 | 6512
+ 2 |  2 | c81e
+ 2 | 12 | c20a
+ 3 |  3 | eccb
+ 3 | 13 | c51c
+ 4 |  4 | a87f
+ 4 | 14 | aab3
+ 5 |  0 | 9bf3
+ 5 |  5 | e4da
+ 6 |  1 | c74d
+ 6 |  6 | 1679
+ 7 |  2 | 70ef
+ 7 |  7 | 8f14
+ 8 |  3 | 6f49
+ 8 |  8 | c9f0
+ 9 |  4 | 1f0e
+ 9 |  9 | 45c4
+(20 rows)
+
+-- test sorting on distinct values, in which mksort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a  | b  | c  
+----+----+----
+  0 | 20 | 20
+  1 | 19 | 19
+  2 | 18 | 18
+  3 | 17 | 17
+  4 | 16 | 16
+  5 | 15 | 15
+  6 | 14 | 14
+  7 | 13 | 13
+  8 | 12 | 12
+  9 | 11 | 11
+ 10 | 10 | 10
+ 11 |  9 | 9
+ 12 |  8 | 8
+ 13 |  7 | 7
+ 14 |  6 | 6
+ 15 |  5 | 5
+ 16 |  4 | 4
+ 17 |  3 | 3
+ 18 |  2 | 2
+ 19 |  1 | 1
+(20 rows)
+
+drop table mksort_simple_tbl;
+-- test table with abbr keys
+create table abbr_tbl (a int, b varchar(100), c uuid);
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+--(see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+select c, b, a from abbr_tbl order by c, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+(50 rows)
+
+select c, b, a from abbr_tbl order by c desc, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+(50 rows)
+
+select c, b, a from abbr_tbl order by c, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+NOTICE:  index "idx_abbr_tbl" does not exist, skipping
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+                  c                   |                            b                            | a 
+--------------------------------------+---------------------------------------------------------+---
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 8
+(1 row)
+
+-- Uniqueness check of CREATE INDEX
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+ERROR:  could not create unique index "idx_abbr_tbl"
+DETAIL:  Key (c, b, a)=(00000000-0000-0000-0000-000000000001, aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1, 1) is duplicated.
+drop table abbr_tbl;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ae4e8851f8..2de20ca1d0 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -18,13 +18,13 @@ INSERT INTO empsalary VALUES
 ('sales', 3, 4800, '2007-08-01'),
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary |  sum  
 -----------+-------+--------+-------
  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
@@ -33,13 +33,13 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
  sales     |     1 |   5000 | 14600
 (10 rows)
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
  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
@@ -90,18 +90,18 @@ SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PA
  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;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
- develop   |     7 |   4200 |    1
- personnel |     5 |   3500 |    1
  sales     |     3 |   4800 |    1
  sales     |     4 |   4800 |    1
+ personnel |     5 |   3500 |    1
+ develop   |     7 |   4200 |    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)
 
@@ -3749,23 +3749,24 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
  empno |  depname  | rn | rnk | cnt 
 -------+-----------+----+-----+-----
-     8 | develop   |  1 |   1 |   1
-    10 | develop   |  2 |   2 |   1
-    11 | develop   |  3 |   3 |   1
-     9 | develop   |  4 |   4 |   2
-     7 | develop   |  5 |   4 |   2
-     2 | personnel |  1 |   1 |   1
-     5 | personnel |  2 |   2 |   1
      1 | sales     |  1 |   1 |   1
+     2 | personnel |  1 |   1 |   1
      3 | sales     |  2 |   2 |   1
      4 | sales     |  3 |   3 |   1
+     5 | personnel |  2 |   2 |   1
+     7 | develop   |  4 |   4 |   1
+     8 | develop   |  1 |   1 |   1
+     9 | develop   |  5 |   5 |   1
+    10 | develop   |  2 |   2 |   1
+    11 | develop   |  3 |   3 |   1
 (10 rows)
 
 -- Test pushdown of quals into a subquery containing window functions
@@ -4106,17 +4107,17 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
  empno |  depname  | salary | c 
 -------+-----------+--------+---
+     1 | sales     |   5000 | 1
+     2 | personnel |   3900 | 1
+     3 | sales     |   4800 | 3
+     4 | sales     |   4800 | 3
+     5 | personnel |   3500 | 2
      8 | develop   |   6000 | 1
     10 | develop   |   5200 | 3
     11 | develop   |   5200 | 3
-     2 | personnel |   3900 | 1
-     5 | personnel |   3500 | 2
-     1 | sales     |   5000 | 1
-     4 | sales     |   4800 | 3
-     3 | sales     |   4800 | 3
 (8 rows)
 
 -- Ensure we get the correct run condition when the window function is both
@@ -4468,14 +4469,15 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
   depname  | empno | salary | enroll_date | first_emp | last_emp 
 -----------+-------+--------+-------------+-----------+----------
+ develop   |     7 |   4200 | 01-01-2008  |         4 |        1
  develop   |     8 |   6000 | 10-01-2006  |         1 |        5
- develop   |     7 |   4200 | 01-01-2008  |         5 |        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
diff --git a/src/test/regress/sql/geometry.sql b/src/test/regress/sql/geometry.sql
index c3ea368da5..1f47f07f31 100644
--- a/src/test/regress/sql/geometry.sql
+++ b/src/test/regress/sql/geometry.sql
@@ -403,7 +403,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
 
 -- To polygon
 SELECT f1, f1::polygon FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
diff --git a/src/test/regress/sql/tuplesort.sql b/src/test/regress/sql/tuplesort.sql
index 8476e594e6..7812a3e2bb 100644
--- a/src/test/regress/sql/tuplesort.sql
+++ b/src/test/regress/sql/tuplesort.sql
@@ -305,3 +305,69 @@ EXPLAIN (COSTS OFF) :qry;
 :qry;
 
 COMMIT;
+
+-- Test cases for multi-key sort
+
+set work_mem='100MB';
+
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+
+-- test sorting on distinct values, in which mksort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+drop table mksort_simple_tbl;
+
+-- test table with abbr keys
+
+create table abbr_tbl (a int, b varchar(100), c uuid);
+
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+--(see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+
+select c, b, a from abbr_tbl order by c, b, a;
+select c, b, a from abbr_tbl order by c desc, b, a;
+select c, b, a from abbr_tbl order by c, b desc, a;
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+
+-- Uniqueness check of CREATE INDEX
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop table abbr_tbl;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6de5493b05..46359cb796 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -21,9 +21,9 @@ INSERT INTO empsalary VALUES
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
 
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
 
 -- with GROUP BY
 SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
@@ -31,7 +31,7 @@ GROUP BY four, ten ORDER BY four, ten;
 
 SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
 
-SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
 
 -- empty window specification
 SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
@@ -1146,11 +1146,12 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
 
 -- Test pushdown of quals into a subquery containing window functions
 
@@ -1332,7 +1333,7 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
 
 -- Ensure we get the correct run condition when the window function is both
 -- monotonically increasing and decreasing.
@@ -1510,10 +1511,11 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
 
 -- cleanup
 DROP TABLE empsalary;
-- 
2.25.1

#7Yao Wang
yao-yw.wang@broadcom.com
In reply to: Yao Wang (#6)
1 attachment(s)
Re: 回复: An implementation of multi-key sort

To be accurate, "multi-key sort" includes both "multi-key quick sort"
and "multi-key heap sort". This patch includes code change related to
only "multi-key quick sort" which is used to replace standard quick
sort for tuplesort. The "multi-key heap sort" is about an implementation
of multi-key heap and should be treated as a separated task. We need
to clarify the naming to avoid confusion.

I updated code which is related to only function/var renaming and
relevant comments, plus some minor assertions changes. Please see the
attachment.

Thanks,

Yao Wang

On Fri, May 31, 2024 at 8:09 PM Yao Wang <yao-yw.wang@broadcom.com> wrote:

I added two optimizations to mksort which exist on qsort_tuple():

1. When selecting pivot, always pick the item in the middle of array but
not by random. Theoretically it has the same effect to old approach, but
it can eliminate some unstable perf test results, plus a bit perf benefit by
removing random value generator.
2. Always check whether the array is ordered already, and return
immediately if it is. The pre-ordered check requires extra cost and
impacts perf numbers on some data sets, but can improve perf
significantly on other data sets.

By now, mksort has perf results equal or better than qsort on all data
sets I ever used.

I also updated test case. Please see v3 code as attachment.

Perf test results:

Data set 1 (with mass duplicate values):
-----------------------------------------

create table t1 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 varchar(100));
insert into t1 values (generate_series(1,499999), 0, 0, 0, 0,
'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
update t1 set c2 = c1 % 100, c3 = c1 % 50, c4 = c1 % 10, c5 = c1 % 3;
update t1 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (c1 % 5)::text;

Query 1:

explain analyze select c1 from t1 order by c6, c5, c4, c3, c2, c1;

Disable Mksort

3021.636 ms
3014.669 ms
3033.588 ms

Enable Mksort

1688.590 ms
1686.956 ms
1688.567 ms

The improvement is 78.9%, which is reduced from the previous version
(129%). The most cost should be the pre-ordered check.

Query 2:

create index idx_t1_mk on t1 (c6, c5, c4, c3, c2, c1);

Disable Mksort

1674.648 ms
1680.608 ms
1681.373 ms

Enable Mksort

1143.341 ms
1143.462 ms
1143.894 ms

The improvement is ~47%, which is also reduced a bit (52%).

Data set 2 (with distinct values):
----------------------------------

create table t2 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 varchar(100));
insert into t2 values (generate_series(1,499999), 0, 0, 0, 0, '');
update t2 set c2 = 999990 - c1, c3 = 999991 - c1, c4 = 999992 - c1, c5
= 999993 - c1;
update t2 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (999994 - c1)::text;

Query 1:

explain analyze select c1 from t2 order by c6, c5, c4, c3, c2, c1;

Disable Mksort

12199.963 ms
12197.068 ms
12191.657 ms

Enable Mksort

9538.219 ms
9571.681 ms
9536.335 ms

The improvement is 27.9%, which is much better than the old approach (-6.2%).

Query 2 (the data is pre-ordered):

explain analyze select c1 from t2 order by c6 desc, c5, c4, c3, c2, c1;

Enable Mksort

768.191 ms
768.079 ms
767.026 ms

Disable Mksort

768.757 ms
766.166 ms
766.149 ms

They are almost the same since no actual sort was performed, and much
better than the old approach (-1198.1%).

Thanks,

Yao Wang

On Fri, May 24, 2024 at 8:50 PM Yao Wang <yao-yw.wang@broadcom.com> wrote:

When all leading keys are different, mksort will finish the entire sort at the
first sort key and never touch other keys. For the case, mksort falls back to
kind of qsort actually.

I created another data set with distinct values in all sort keys:

create table t2 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 varchar(100));
insert into t2 values (generate_series(1,499999), 0, 0, 0, 0, '');
update t2 set c2 = 999990 - c1, c3 = 999991 - c1, c4 = 999992 - c1, c5
= 999993 - c1;
update t2 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (999994 - c1)::text;
explain analyze select c1 from t2 order by c6, c5, c4, c3, c2, c1;

Results:

MKsort:
12374.427 ms
12528.068 ms
12554.718 ms

qsort:
12251.422 ms
12279.938 ms
12280.254 ms

MKsort is a bit slower than qsort, which can be explained by extra
checks of MKsort.

Yao Wang

On Fri, May 24, 2024 at 8:36 PM Wang Yao <yaowangm@outlook.com> wrote:

获取Outlook for Android
________________________________
From: Heikki Linnakangas <hlinnaka@iki.fi>
Sent: Thursday, May 23, 2024 8:47:29 PM
To: Wang Yao <yaowangm@outlook.com>; PostgreSQL Hackers <pgsql-hackers@postgresql.org>
Cc: interma@outlook.com <interma@outlook.com>
Subject: Re: 回复: An implementation of multi-key sort

On 23/05/2024 15:39, Wang Yao wrote:

No obvious perf regression is expected because PG will follow original
qsort code path when mksort is disabled. For the case, the only extra
cost is the check in tuplesort_sort_memtuples() to enter mksort code path.

And what about the case the mksort is enabled, but it's not effective
because all leading keys are different?

--
Heikki Linnakangas
Neon (https://neon.tech)

--
This electronic communication and the information and any files transmitted
with it, or attached to it, are confidential and are intended solely for
the use of the individual or entity to whom it is addressed and may contain
information that is confidential, legally privileged, protected by privacy
laws, or otherwise restricted from disclosure to anyone else. If you are
not the intended recipient or the person responsible for delivering the
e-mail to the intended recipient, you are hereby notified that any use,
copying, distributing, dissemination, forwarding, printing, or copying of
this e-mail is strictly prohibited. If you received this e-mail in error,
please return the e-mail to the sender, delete it from your computer, and
destroy any printed copy of it.

Attachments:

v4-Implement-multi-key-quick-sort.patchapplication/octet-stream; name=v4-Implement-multi-key-quick-sort.patchDownload
From fa8825580522c03984a5542a25953bb1fe7ecca3 Mon Sep 17 00:00:00 2001
From: Yao Wang <yaowangm@outlook.com>
Date: Tue, 7 May 2024 08:11:13 +0000
Subject: [PATCH] Implement multi-key quick sort

MK qsort (multi-key quick sort) is an alternative of standard qsort algorithm,
which has better performance for particular sort scenarios, i.e. the data set
has multiple keys to be sorted. Comparing to classic quick sort, it can get
significant performance improvement once multiple keys are available.

Author: Yao Wang <yao-yw.wang@broadcom.com>
Co-author: Hongxu Ma <hongxu.ma@broadcom.com>
---
 src/backend/utils/misc/guc_tables.c           |  11 +
 src/backend/utils/sort/mk_qsort_tuple.c       | 388 ++++++++++++++++++
 src/backend/utils/sort/tuplesort.c            |  44 ++
 src/backend/utils/sort/tuplesortvariants.c    | 313 ++++++++++++--
 src/include/c.h                               |   4 +
 src/include/utils/tuplesort.h                 |  36 +-
 src/test/regress/expected/geometry.out        |   4 +-
 .../regress/expected/incremental_sort.out     |  12 +-
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/expected/tuplesort.out       | 376 +++++++++++++++++
 src/test/regress/expected/window.out          |  58 +--
 src/test/regress/sql/geometry.sql             |   2 +-
 src/test/regress/sql/tuplesort.sql            |  66 +++
 src/test/regress/sql/window.sql               |  22 +-
 14 files changed, 1254 insertions(+), 85 deletions(-)
 create mode 100644 src/backend/utils/sort/mk_qsort_tuple.c

diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 3fd0b14dd8..5aee20f422 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -103,6 +103,7 @@ extern char *default_tablespace;
 extern char *temp_tablespaces;
 extern bool ignore_checksum_failure;
 extern bool ignore_invalid_pages;
+extern bool enable_mk_sort;
 
 #ifdef TRACE_SYNCSCAN
 extern bool trace_syncscan;
@@ -839,6 +840,16 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_mk_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables multi-key sort"),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_mk_sort,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
diff --git a/src/backend/utils/sort/mk_qsort_tuple.c b/src/backend/utils/sort/mk_qsort_tuple.c
new file mode 100644
index 0000000000..9c5715380a
--- /dev/null
+++ b/src/backend/utils/sort/mk_qsort_tuple.c
@@ -0,0 +1,388 @@
+/*
+ * MK qsort (multi-key quick sort) is an alternative of standard qsort
+ * algorithm, which has better performance for particular sort scenarios, i.e.
+ * the data set has multiple keys to be sorted.
+ *
+ * The sorting algorithm blends Quicksort and radix sort; Like regular
+ * Quicksort, it partitions its input into sets less than and greater than a
+ * given value; like radix sort, it moves on to the next field once the current
+ * input is known to be equal in the given field.
+ *
+ * The implementation is based on the paper:
+ *   Jon L. Bentley and Robert Sedgewick, "Fast Algorithms for Sorting and
+ *   Searching Strings", Jan 1997
+ *
+ * Some improvements which is related to additional handling for equal tuples
+ * have been adapted to keep consistency with the implementations of postgres
+ * qsort.
+ *
+ * For now, mk_qsort_tuple() is called in tuplesort_sort_memtuples() as a
+ * replacement of qsort_tuple() when specific conditions are satisfied.
+ */
+
+/* Swap two tuples in sort tuple array */
+static inline void
+mkqs_swap(int        a,
+		  int        b,
+		  SortTuple *x)
+{
+	SortTuple t;
+
+	if (a == b)
+		return;
+	t = x[a];
+	x[a] = x[b];
+	x[b] = t;
+}
+
+/* Swap tuples by batch in sort tuple array */
+static inline void
+mkqs_vec_swap(int        a,
+			  int        b,
+			  int        size,
+			  SortTuple *x)
+{
+	while (size-- > 0)
+	{
+		mkqs_swap(a, b, x);
+		a++;
+		b++;
+	}
+}
+
+/*
+ * Check whether current datum (at specified tuple and depth) is null
+ * Note that the input x means a specified tuple provided by caller but not
+ * a tuple array, so tupleIndex is unnecessary
+ */
+static inline bool
+check_datum_null(SortTuple      *x,
+				 int             depth,
+				 Tuplesortstate *state)
+{
+	Datum datum;
+	bool isNull;
+
+	Assert(depth < state->base.nKeys);
+
+	/* Since we have a specified tuple, the tupleIndex is always 0 */
+	state->base.mkqsGetDatumFunc(x, 0, depth, state, &datum, &isNull, false);
+
+	/*
+	 * Note: for "abbreviated key", we don't need to handle more here because
+	 * if "abbreviated key" of a datum is null, the "full" datum must be null.
+	 */
+
+	return isNull;
+}
+
+/*
+ * Compare two tuples at specified depth
+ *
+ * If "abbreviated key" is disabled:
+ *   get specified datums and compare them by ApplySortComparator().
+ * If "abbreviated key" is enabled:
+ *   Only first datum may be abbr key according to the design (see the comments
+ *   of struct SortTuple), so different operations are needed for different
+ *   datum.
+ *   For first datum (depth == 0): get first datums ("abbr key" version) and
+ *   compare them by ApplySortComparator(). If they are equal, get "full"
+ *   version and compare again by ApplySortAbbrevFullComparator().
+ *   For other datums: get specified datums and compare them by
+ *   ApplySortComparator() as regular routine does.
+ *
+ * See comparetup_heap() for details.
+ */
+static inline int
+mkqs_compare_datum(SortTuple      *tuple1,
+				   SortTuple      *tuple2,
+				   int			 depth,
+				   Tuplesortstate *state)
+{
+	Datum datum1, datum2;
+	bool isNull1, isNull2;
+	SortSupport sortKey;
+	int ret = 0;
+
+	Assert(state->base.mkqsGetDatumFunc);
+	Assert(depth < state->base.nKeys);
+
+	sortKey = state->base.sortKeys + depth;
+	state->base.mkqsGetDatumFunc(tuple1, 0, depth, state,
+								 &datum1, &isNull1, false);
+	state->base.mkqsGetDatumFunc(tuple2, 0, depth, state,
+								 &datum2, &isNull2, false);
+
+	ret = ApplySortComparator(datum1,
+							  isNull1,
+							  datum2,
+							  isNull2,
+							  sortKey);
+
+	/*
+	 * If "abbreviated key" is enabled, and we are in the first depth, it means
+	 * only "abbreviated keys" are compared. If the two datums are determined to
+	 * be equal by ApplySortComparator(), we need to perform an extra "full"
+	 * comparing by ApplySortAbbrevFullComparator().
+	 */
+	if (sortKey->abbrev_converter &&
+		depth == 0 &&
+		ret == 0)
+	{
+		/* Fetch "full" datum by setting useFullKey = true */
+		state->base.mkqsGetDatumFunc(tuple1, 0, depth, state,
+									 &datum1, &isNull1, true);
+		state->base.mkqsGetDatumFunc(tuple2, 0, depth, state,
+									 &datum2, &isNull2, true);
+
+		ret = ApplySortAbbrevFullComparator(datum1,
+											isNull1,
+											datum2,
+											isNull2,
+											sortKey);
+	}
+
+	return ret;
+}
+
+#ifdef USE_ASSERT_CHECKING
+/*
+ * Verify whether the SortTuple list is ordered or not at specified depth
+ */
+static void
+mkqs_verify(SortTuple      *x,
+			int				n,
+			int				depth,
+			Tuplesortstate *state)
+{
+	int ret;
+
+	for (int i = 0;i < n - 1;i++)
+	{
+		ret = mkqs_compare_datum(x + i,
+								 x + i + 1,
+								 depth,
+								 state);
+		Assert(ret <= 0);
+	}
+}
+#endif
+
+/*
+ * Major of multi-key quick sort
+ *
+ * seenNull indicates whether we have seen NULL in any datum we checked
+ */
+static void
+mk_qsort_tuple(SortTuple           *x,
+			   size_t               n,
+			   int                  depth,
+			   Tuplesortstate      *state,
+			   bool                 seenNull)
+{
+	/*
+	 * In the process, the tuple array consists of five parts:
+	 * left equal, less, not-processed, greater, right equal
+	 *
+	 * lessStart indicates the first position of less part
+	 * lessEnd indicates the next position after less part
+	 * greaterStart indicates the prior position before greater part
+	 * greaterEnd indicates the latest position of greater part
+	 * the range between lessEnd and greaterStart (inclusive) is not-processed
+	 */
+	int lessStart, lessEnd, greaterStart, greaterEnd, tupCount;
+	int32 dist;
+	SortTuple *pivot;
+	bool isDatumNull;
+	bool strictOrdered = true;
+
+	Assert(depth <= state->base.nKeys);
+	Assert(state->base.sortKeys);
+	Assert(state->base.mkqsGetDatumFunc);
+
+	if (n <= 1)
+		return;
+
+	/* If we have exceeded the max depth, return immediately */
+	if (depth == state->base.nKeys)
+		return;
+
+	CHECK_FOR_INTERRUPTS();
+
+	/*
+	 * Check if the array is ordered already. If yes, return immediately.
+	 * Different from qsort_tuple(), the array must be strict ordered (no
+	 * equal datums). If there are equal datums, we must continue the mk
+	 * qsort process to check datums on lower depth.
+	 */
+	for (int i = 0;i < n - 1;i++)
+	{
+		int ret;
+
+		CHECK_FOR_INTERRUPTS();
+		ret = mkqs_compare_datum(x + i,
+								 x + i + 1,
+								 depth,
+								 state);
+		if (ret >= 0)
+		{
+			strictOrdered = false;
+			break;
+		}
+	}
+
+	if (strictOrdered)
+		return;
+
+	/* Select pivot by random and move it to the first position */
+	lessStart = n / 2;
+	mkqs_swap(0, lessStart, x);
+	pivot = x;
+
+	lessStart = 1;
+	lessEnd = 1;
+	greaterStart = n - 1;
+	greaterEnd = n - 1;
+
+	/* Sort the array to three parts: lesser, equal, greater */
+	while (true)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		/* Compare the left end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare lessEnd and pivot at current depth */
+			dist = mkqs_compare_datum(x + lessEnd,
+									  pivot,
+									  depth,
+									  state);
+
+			if (dist > 0)
+				break;
+
+			/* If lessEnd is equal to pivot, move it to lessStart */
+			if (dist == 0)
+			{
+				mkqs_swap(lessEnd, lessStart, x);
+				lessStart++;
+			}
+			lessEnd++;
+		}
+
+		/* Compare the right end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare greaterStart and pivot at current depth */
+			dist = mkqs_compare_datum(x + greaterStart,
+									  pivot,
+									  depth,
+									  state);
+
+			if (dist < 0)
+				break;
+
+			/* If greaterStart is equal to pivot, move it to greaterEnd */
+			if (dist == 0)
+			{
+				mkqs_swap(greaterStart, greaterEnd, x);
+				greaterEnd--;
+			}
+			greaterStart--;
+		}
+
+		if (lessEnd > greaterStart)
+			break;
+		mkqs_swap(lessEnd, greaterStart, x);
+		lessEnd++;
+		greaterStart--;
+	}
+
+	/*
+	 * Now the array has four parts:
+	 *   left equal, lesser, greater, right equal
+	 * Note greaterStart is less than lessEnd now
+	 */
+
+	/* Move the left equal part to middle */
+	dist = Min(lessStart, lessEnd - lessStart);
+	mkqs_vec_swap(0, lessEnd - dist, dist, x);
+
+	/* Move the right equal part to middle */
+	dist = Min(greaterEnd - greaterStart, n - greaterEnd - 1);
+	mkqs_vec_swap(lessEnd, n - dist, dist, x);
+
+	/*
+	 * Now the array has three parts:
+	 *   lesser, equal, greater
+	 * Note that one or two parts may have no element at all.
+	 */
+
+	/* Recursively sort the lesser part */
+
+	/* dist means the size of less part */
+	dist = lessEnd - lessStart;
+	mk_qsort_tuple(x,
+				   dist,
+				   depth,
+				   state,
+				   seenNull);
+
+	/* Recursively sort the equal part */
+
+	/*
+	 * (x + dist) means the first tuple in the equal part
+	 * Since all tuples have equal datums at current depth, we just check any one
+	 * of them to determine whether we have seen null datum.
+	 */
+	isDatumNull = check_datum_null(x + dist, depth, state);
+
+	/* (lessStart + n - greaterEnd - 1) means the size of equal part */
+	tupCount = lessStart + n - greaterEnd - 1;
+
+	if (depth < state->base.nKeys - 1)
+	{
+		mk_qsort_tuple(x + dist,
+					   tupCount,
+					   depth + 1,
+					   state,
+					   seenNull || isDatumNull);
+	} else {
+		/*
+		 * We have reach the max depth: Call mkqsHandleDupFunc to handle
+		 * duplicated tuples if necessary, e.g. checking uniqueness or extra
+		 * comparing
+		 */
+
+		/*
+		 * Call mkqsHandleDupFunc if:
+		 *   1. mkqsHandleDupFunc is filled
+		 *   2. the size of equal part > 1
+		 */
+		if (state->base.mkqsHandleDupFunc &&
+			(tupCount > 1))
+		{
+			state->base.mkqsHandleDupFunc(x + dist,
+										  tupCount,
+										  seenNull || isDatumNull,
+										  state);
+		}
+	}
+
+	/* Recursively sort the greater part */
+
+	/* dist means the size of greater part */
+	dist = greaterEnd - greaterStart;
+	mk_qsort_tuple(x + n - dist,
+				   dist,
+				   depth,
+				   state,
+				   seenNull);
+
+#ifdef USE_ASSERT_CHECKING
+	mkqs_verify(x,
+				n,
+				depth,
+				state);
+#endif
+}
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 7c4d6dc106..5718911eb9 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -128,6 +128,7 @@ bool		trace_sort = false;
 bool		optimize_bounded_sort = true;
 #endif
 
+bool		enable_mk_sort = true;
 
 /*
  * During merge, we use a pre-allocated set of fixed-size slots to hold
@@ -337,6 +338,9 @@ struct Tuplesortstate
 #ifdef TRACE_SORT
 	PGRUsage	ru_start;
 #endif
+
+	/* Whether multi-key quick sort is used */
+	bool mkqsUsed;
 };
 
 /*
@@ -622,6 +626,8 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
 #define ST_DEFINE
 #include "lib/sort_template.h"
 
+#include "mk_qsort_tuple.c"
+
 /*
  *		tuplesort_begin_xxx
  *
@@ -690,6 +696,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate, int sortopt)
 	state->base.sortopt = sortopt;
 	state->base.tuples = true;
 	state->abbrevNext = 10;
+	state->mkqsUsed = false;
 
 	/*
 	 * workMem is forced to be at least 64KB, the current minimum valid value
@@ -2559,6 +2566,8 @@ tuplesort_get_stats(Tuplesortstate *state,
 		case TSS_SORTEDINMEM:
 			if (state->boundUsed)
 				stats->sortMethod = SORT_TYPE_TOP_N_HEAPSORT;
+			else if (state->mkqsUsed)
+				stats->sortMethod = SORT_TYPE_MK_QSORT;
 			else
 				stats->sortMethod = SORT_TYPE_QUICKSORT;
 			break;
@@ -2592,6 +2601,8 @@ tuplesort_method_name(TuplesortMethod m)
 			return "external sort";
 		case SORT_TYPE_EXTERNAL_MERGE:
 			return "external merge";
+		case SORT_TYPE_MK_QSORT:
+			return "multi-key quick sort";
 	}
 
 	return "unknown";
@@ -2717,6 +2728,39 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 
 	if (state->memtupcount > 1)
 	{
+		/*
+		 * Apply multi-key quick sort when:
+		 *   1. enable_mk_sort is set
+		 *   2. There are multiple keys available
+		 *   3. mkqsGetDatumFunc is filled, which implies that current tuple
+		 *      type is supported by mk qsort. (By now only Heap tuple and Btree
+		 *      Index tuple are supported, and more types may be supported in
+		 *      future.)
+		 *
+		 * A summary of tuple types supported by mk qsort:
+		 *
+		 *   HeapTuple: supported
+		 *   IndexTuple(btree): supported
+		 *   IndexTuple(hash): not supported because there is only one key
+		 *   DatumTuple: not supported because there is only one key
+		 *   HeapTuple(for cluster): not supported yet
+		 *   IndexTuple(gist): not supported yet
+		 *   IndexTuple(brin): not supported yet
+		 */
+		if (enable_mk_sort &&
+			state->base.nKeys > 1 &&
+			state->base.mkqsGetDatumFunc != NULL)
+		{
+			state->mkqsUsed = true;
+			mk_qsort_tuple(state->memtuples,
+						   state->memtupcount,
+						   0,
+						   state,
+						   false);
+
+			return;
+		}
+
 		/*
 		 * Do we have the leading column's value or abbreviation in datum1,
 		 * and is there a specialization for its comparator?
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 05a853caa3..ddcffa5094 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -30,6 +30,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
+#include "miscadmin.h"
 
 
 /* sort-type codes for sort__start probes */
@@ -92,6 +93,41 @@ static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
 						  LogicalTape *tape, unsigned int len);
 static void freestate_cluster(Tuplesortstate *state);
 
+static Datum mkqs_get_datum_heap(SortTuple      *x,
+								 const int       tupleIndex,
+								 const int       depth,
+								 Tuplesortstate *state,
+								 Datum          *datum,
+								 bool           *isNull,
+								 bool            useFullKey);
+
+static Datum mkqs_get_datum_index_btree(SortTuple      *x,
+										const int       tupleIndex,
+										const int       depth,
+										Tuplesortstate *state,
+										Datum          *datum,
+										bool           *isNull,
+										bool            useFullKey);
+
+static void
+mkqs_handle_dup_index_btree(SortTuple      *x,
+							const int       tupleCount,
+							const bool      seenNull,
+							Tuplesortstate *state);
+
+static int
+mkqs_compare_equal_index_btree(const SortTuple *a,
+							   const SortTuple *b,
+							   Tuplesortstate  *state);
+
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2);
+
+static inline void
+raise_error_of_dup_index(IndexTuple     x,
+						 Tuplesortstate *state);
+
 /*
  * Data structure pointed by "TuplesortPublic.arg" for the CLUSTER case.  Set by
  * the tuplesort_begin_cluster.
@@ -163,6 +199,14 @@ typedef struct BrinSortTuple
 /* Size of the BrinSortTuple, given length of the BrinTuple. */
 #define BRINSORTTUPLE_SIZE(len)		(offsetof(BrinSortTuple, tuple) + (len))
 
+#define ST_SORT qsort_tuple_by_itempointer
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) mkqs_compare_equal_index_btree(a, b, state)
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
 
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
@@ -200,6 +244,7 @@ tuplesort_begin_heap(TupleDesc tupDesc,
 	base->removeabbrev = removeabbrev_heap;
 	base->comparetup = comparetup_heap;
 	base->comparetup_tiebreak = comparetup_heap_tiebreak;
+	base->mkqsGetDatumFunc = mkqs_get_datum_heap;
 	base->writetup = writetup_heap;
 	base->readtup = readtup_heap;
 	base->haveDatum1 = true;
@@ -388,6 +433,8 @@ tuplesort_begin_index_btree(Relation heapRel,
 	base->removeabbrev = removeabbrev_index;
 	base->comparetup = comparetup_index_btree;
 	base->comparetup_tiebreak = comparetup_index_btree_tiebreak;
+	base->mkqsGetDatumFunc = mkqs_get_datum_index_btree;
+	base->mkqsHandleDupFunc = mkqs_handle_dup_index_btree;
 	base->writetup = writetup_index;
 	base->readtup = readtup_index;
 	base->haveDatum1 = true;
@@ -1531,10 +1578,6 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 */
 	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && equal_hasnull))
 	{
-		Datum		values[INDEX_MAX_KEYS];
-		bool		isnull[INDEX_MAX_KEYS];
-		char	   *key_desc;
-
 		/*
 		 * Some rather brain-dead implementations of qsort (such as the one in
 		 * QNX 4) will sometimes call the comparison routine to compare a
@@ -1543,18 +1586,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 		 */
 		Assert(tuple1 != tuple2);
 
-		index_deform_tuple(tuple1, tupDes, values, isnull);
-
-		key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
-
-		ereport(ERROR,
-				(errcode(ERRCODE_UNIQUE_VIOLATION),
-				 errmsg("could not create unique index \"%s\"",
-						RelationGetRelationName(arg->index.indexRel)),
-				 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
-				 errdetail("Duplicate keys exist."),
-				 errtableconstraint(arg->index.heapRel,
-									RelationGetRelationName(arg->index.indexRel))));
+		raise_error_of_dup_index(tuple1, state);
 	}
 
 	/*
@@ -1563,25 +1595,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 * attribute in order to ensure that all keys in the index are physically
 	 * unique.
 	 */
-	{
-		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
-		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
-
-		if (blk1 != blk2)
-			return (blk1 < blk2) ? -1 : 1;
-	}
-	{
-		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
-		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
-
-		if (pos1 != pos2)
-			return (pos1 < pos2) ? -1 : 1;
-	}
-
-	/* ItemPointer values should never be equal */
-	Assert(false);
-
-	return 0;
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
 }
 
 static int
@@ -1888,3 +1902,232 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
 	if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */
 		LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen));
 }
+
+/*
+ * Get specified datum from SortTuple (HeapTuple) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_heap() for details.
+ */
+static Datum
+mkqs_get_datum_heap(SortTuple      *x,
+					int             tupleIndex,
+					int             depth,
+					Tuplesortstate *state,
+					Datum          *datum,
+					bool           *isNull,
+					bool            useFullKey)
+{
+	TupleDesc   tupDesc = NULL;
+	HeapTupleData heapTuple;
+	AttrNumber  attno;
+	SortTuple *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	SortSupport sortKey = base->sortKeys + depth;;
+
+	Assert(state);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	tupDesc = (TupleDesc)base->arg;
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*datum = sortTuple->datum1;
+		*isNull = sortTuple->isnull1;
+		return *datum;
+	}
+
+	/* For any datums which depth > 0, extract it from sortTuple->tuple */
+	heapTuple.t_len = ((MinimalTuple) sortTuple->tuple)->t_len + MINIMAL_TUPLE_OFFSET;
+	heapTuple.t_data = (HeapTupleHeader) ((char *) sortTuple->tuple - MINIMAL_TUPLE_OFFSET);
+	attno = sortKey->ssup_attno;
+	*datum = heap_getattr(&heapTuple, attno, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Get specified datum from SortTuple (IndexTuple for btree index) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_index_btree() for details.
+ */
+static Datum
+mkqs_get_datum_index_btree(SortTuple      *x,
+						   const int       tupleIndex,
+						   const int       depth,
+						   Tuplesortstate *state,
+						   Datum          *datum,
+						   bool           *isNull,
+						   bool            useFullKey)
+{
+	TupleDesc   tupDesc;
+	IndexTuple  indexTuple;
+	SortTuple  *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	Assert(state);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*isNull = sortTuple->isnull1;
+		*datum = sortTuple->datum1;
+		return *datum;
+	}
+
+	indexTuple = (IndexTuple) sortTuple->tuple;
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+
+	/*
+	 * Set parameter attnum = depth + 1 because attnum starts from 1 but depth
+	 * starts from 0
+	 */
+	*datum = index_getattr(indexTuple, depth + 1, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Handle duplicated SortTuples (IndexTuple for btree index during mk qsort)
+ *  x: the duplicated tuple list
+ *  tupleCount: count of the tuples
+ */
+static void
+mkqs_handle_dup_index_btree(SortTuple      *x,
+							const int       tupleCount,
+							const bool      seenNull,
+							Tuplesortstate *state)
+{
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	/* If enforceUnique is enabled and we never saw NULL, raise error */
+	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && seenNull))
+	{
+		/*
+		 * x means the first tuple of duplicated tuple list
+		 * Since they are duplicated, simply pick up the first one
+		 * to raise error
+		 */
+		raise_error_of_dup_index((IndexTuple)(x->tuple), state);
+	}
+
+	/*
+	 * If key values are equal, we sort on ItemPointer.  This is required for
+	 * btree indexes, since heap TID is treated as an implicit last key
+	 * attribute in order to ensure that all keys in the index are physically
+	 * unique.
+	 */
+	qsort_tuple_by_itempointer(x,
+							   tupleCount,
+							   state);
+}
+
+/*
+ * Compare two btree index tuples by ItemPointer
+ * It is a callback function for qsort_tuple() called by
+ * mkqs_handle_dup_index_btree()
+ */
+static int
+mkqs_compare_equal_index_btree(const SortTuple *a,
+							   const SortTuple *b,
+							   Tuplesortstate  *state)
+{
+	IndexTuple  tuple1;
+	IndexTuple  tuple2;
+
+	tuple1 = (IndexTuple) a->tuple;
+	tuple2 = (IndexTuple) b->tuple;
+
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
+}
+
+/* Compare two index tuples by ItemPointer */
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2)
+{
+	{
+		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
+		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
+
+		if (blk1 != blk2)
+			return (blk1 < blk2) ? -1 : 1;
+	}
+	{
+		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
+		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
+
+		if (pos1 != pos2)
+			return (pos1 < pos2) ? -1 : 1;
+	}
+
+	/* ItemPointer values should never be equal */
+	Assert(false);
+
+	return 0;
+}
+
+/* Raise error for duplicated tuple when creating unique index */
+static inline void
+raise_error_of_dup_index(IndexTuple      x,
+						 Tuplesortstate *state)
+{
+	Datum       values[INDEX_MAX_KEYS];
+	bool        isnull[INDEX_MAX_KEYS];
+	TupleDesc   tupDesc;
+	char       *key_desc;
+    TuplesortPublic *base = TuplesortstateGetPublic(state);
+    TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+	index_deform_tuple((IndexTuple)x, tupDesc, values, isnull);
+	key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNIQUE_VIOLATION),
+			errmsg("could not create unique index \"%s\"",
+				   RelationGetRelationName(arg->index.indexRel)),
+			key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+				errdetail("Duplicate keys exist."),
+				errtableconstraint(arg->index.heapRel,
+								   RelationGetRelationName(arg->index.indexRel))));
+}
diff --git a/src/include/c.h b/src/include/c.h
index dc1841346c..f7c368cd16 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -857,12 +857,14 @@ typedef NameData *Name;
 
 #define Assert(condition)	((void)true)
 #define AssertMacro(condition)	((void)true)
+#define AssertImply(condition1, condition2) ((void)true)
 
 #elif defined(FRONTEND)
 
 #include <assert.h>
 #define Assert(p) assert(p)
 #define AssertMacro(p)	((void) assert(p))
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
 
 #else							/* USE_ASSERT_CHECKING && !FRONTEND */
 
@@ -886,6 +888,8 @@ typedef NameData *Name;
 	((void) ((condition) || \
 			 (ExceptionalCondition(#condition, __FILE__, __LINE__), 0)))
 
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
+
 #endif							/* USE_ASSERT_CHECKING && !FRONTEND */
 
 /*
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index e7941a1f09..74a6a5ae5c 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -29,7 +29,6 @@
 #include "utils/relcache.h"
 #include "utils/sortsupport.h"
 
-
 /*
  * Tuplesortstate and Sharedsort are opaque types whose details are not
  * known outside tuplesort.c.
@@ -79,9 +78,10 @@ typedef enum
 	SORT_TYPE_QUICKSORT = 1 << 1,
 	SORT_TYPE_EXTERNAL_SORT = 1 << 2,
 	SORT_TYPE_EXTERNAL_MERGE = 1 << 3,
+	SORT_TYPE_MK_QSORT = 1 << 4,
 } TuplesortMethod;
 
-#define NUM_TUPLESORTMETHODS 4
+#define NUM_TUPLESORTMETHODS 5
 
 typedef enum
 {
@@ -155,6 +155,23 @@ typedef struct
 typedef int (*SortTupleComparator) (const SortTuple *a, const SortTuple *b,
 									Tuplesortstate *state);
 
+/* Multi-key quick sort */
+
+typedef Datum
+(*MkqsGetDatumFunc) (SortTuple      *x,
+					 const int       tupleIndex,
+					 const int       depth,
+					 Tuplesortstate *state,
+					 Datum          *datum,
+					 bool           *isNull,
+					 bool            useFullKey);
+
+typedef void
+(*MkqsHandleDupFunc) (SortTuple      *x,
+					  const int       tupleCount,
+					  const bool      seenNull,
+					  Tuplesortstate *state);
+
 /*
  * The public part of a Tuple sort operation state.  This data structure
  * contains the definition of sort-variant-specific interface methods and
@@ -249,6 +266,21 @@ typedef struct
 	bool		tuples;			/* Can SortTuple.tuple ever be set? */
 
 	void	   *arg;			/* Specific information for the sort variant */
+
+	/*
+	 * Function pointer, referencing a function to get specified datum from
+	 * SortTuple list with multi-key.
+	 * Used by mk_qsort_tuple().
+	*/
+	MkqsGetDatumFunc mkqsGetDatumFunc;
+
+	/*
+	 * Function pointer, referencing a function to handle duplicated tuple
+	 * from SortTuple list with multi-key.
+	 * Used by mk_qsort_tuple().
+	 * For now, the function pointer is filled for only btree index tuple.
+	*/
+	MkqsHandleDupFunc mkqsHandleDupFunc;
 } TuplesortPublic;
 
 /* Sort parallel code from state for sort__start probes */
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 8be694f46b..094d22861c 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -4273,7 +4273,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
      circle     |       point       |   distance    
 ----------------+-------------------+---------------
  <(1,2),3>      | (-3,4)            |   1.472135955
@@ -4310,8 +4310,8 @@ SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
  <(3,5),0>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (1e+300,Infinity) |      Infinity
  <(5,1),3>      | (1e+300,Infinity) |      Infinity
- <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (Infinity,1e+300) |      Infinity
+ <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,3),5>      | (1e+300,Infinity) |      Infinity
  <(1,3),5>      | (Infinity,1e+300) |      Infinity
  <(100,200),10> | (1e+300,Infinity) |      Infinity
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 5fd54a10b1..a26f8f100a 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -520,13 +520,13 @@ select * from (select * from t order by a) s order by a, b limit 55;
 
 -- Test EXPLAIN ANALYZE with only a fullsort group.
 select explain_analyze_without_memory('select * from (select * from t order by a) s order by a, b limit 55');
-                                        explain_analyze_without_memory                                         
----------------------------------------------------------------------------------------------------------------
+                                              explain_analyze_without_memory                                              
+--------------------------------------------------------------------------------------------------------------------------
  Limit (actual rows=55 loops=1)
    ->  Incremental Sort (actual rows=55 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 2  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 2  Sort Methods: top-N heapsort, multi-key quick sort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=101 loops=1)
                Sort Key: t.a
                Sort Method: quicksort  Memory: NNkB
@@ -554,7 +554,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
              "Group Count": 2,                  +
              "Sort Methods Used": [             +
                  "top-N heapsort",              +
-                 "quicksort"                    +
+                 "multi-key quick sort"         +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
@@ -728,7 +728,7 @@ select explain_analyze_without_memory('select * from (select * from t order by a
    ->  Incremental Sort (actual rows=70 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 1  Sort Method: quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 1  Sort Method: multi-key quick sort  Average Memory: NNkB  Peak Memory: NNkB
          Pre-sorted Groups: 5  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=1000 loops=1)
                Sort Key: t.a
@@ -756,7 +756,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
          "Full-sort Groups": {                  +
              "Group Count": 1,                  +
              "Sort Methods Used": [             +
-                 "quicksort"                    +
+                 "multi-key quick sort"         +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 2f3eb4e7f1..44840e7e5c 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -146,6 +146,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_material                | on
  enable_memoize                 | on
  enable_mergejoin               | on
+ enable_mk_sort                 | on
  enable_nestloop                | on
  enable_parallel_append         | on
  enable_parallel_hash           | on
@@ -157,7 +158,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(23 rows)
+(24 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 6dd97e7427..ad9e56c254 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -703,3 +703,379 @@ EXPLAIN (COSTS OFF) :qry;
 (10 rows)
 
 COMMIT;
+-- Test cases for multi-key quick sort
+set work_mem='100MB';
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a | b  |  c   
+---+----+------
+ 0 |  5 | 98f1
+ 0 | 10 | d3d9
+ 1 |  1 | c4ca
+ 1 | 11 | 6512
+ 2 |  2 | c81e
+ 2 | 12 | c20a
+ 3 |  3 | eccb
+ 3 | 13 | c51c
+ 4 |  4 | a87f
+ 4 | 14 | aab3
+ 5 |  0 | 9bf3
+ 5 |  5 | e4da
+ 6 |  1 | c74d
+ 6 |  6 | 1679
+ 7 |  2 | 70ef
+ 7 |  7 | 8f14
+ 8 |  3 | 6f49
+ 8 |  8 | c9f0
+ 9 |  4 | 1f0e
+ 9 |  9 | 45c4
+(20 rows)
+
+-- test sorting on distinct values, in which mk qsort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a  | b  | c  
+----+----+----
+  0 | 20 | 20
+  1 | 19 | 19
+  2 | 18 | 18
+  3 | 17 | 17
+  4 | 16 | 16
+  5 | 15 | 15
+  6 | 14 | 14
+  7 | 13 | 13
+  8 | 12 | 12
+  9 | 11 | 11
+ 10 | 10 | 10
+ 11 |  9 | 9
+ 12 |  8 | 8
+ 13 |  7 | 7
+ 14 |  6 | 6
+ 15 |  5 | 5
+ 16 |  4 | 4
+ 17 |  3 | 3
+ 18 |  2 | 2
+ 19 |  1 | 1
+(20 rows)
+
+drop table mksort_simple_tbl;
+-- test table with abbr keys
+create table abbr_tbl (a int, b varchar(100), c uuid);
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+-- (see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+select c, b, a from abbr_tbl order by c, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+(50 rows)
+
+select c, b, a from abbr_tbl order by c desc, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+(50 rows)
+
+select c, b, a from abbr_tbl order by c, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+NOTICE:  index "idx_abbr_tbl" does not exist, skipping
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+                  c                   |                            b                            | a 
+--------------------------------------+---------------------------------------------------------+---
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 8
+(1 row)
+
+-- Uniqueness check of CREATE INDEX
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+ERROR:  could not create unique index "idx_abbr_tbl"
+DETAIL:  Key (c, b, a)=(00000000-0000-0000-0000-000000000001, aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1, 1) is duplicated.
+drop table abbr_tbl;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ae4e8851f8..2de20ca1d0 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -18,13 +18,13 @@ INSERT INTO empsalary VALUES
 ('sales', 3, 4800, '2007-08-01'),
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary |  sum  
 -----------+-------+--------+-------
  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
@@ -33,13 +33,13 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
  sales     |     1 |   5000 | 14600
 (10 rows)
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
  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
@@ -90,18 +90,18 @@ SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PA
  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;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
- develop   |     7 |   4200 |    1
- personnel |     5 |   3500 |    1
  sales     |     3 |   4800 |    1
  sales     |     4 |   4800 |    1
+ personnel |     5 |   3500 |    1
+ develop   |     7 |   4200 |    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)
 
@@ -3749,23 +3749,24 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
  empno |  depname  | rn | rnk | cnt 
 -------+-----------+----+-----+-----
-     8 | develop   |  1 |   1 |   1
-    10 | develop   |  2 |   2 |   1
-    11 | develop   |  3 |   3 |   1
-     9 | develop   |  4 |   4 |   2
-     7 | develop   |  5 |   4 |   2
-     2 | personnel |  1 |   1 |   1
-     5 | personnel |  2 |   2 |   1
      1 | sales     |  1 |   1 |   1
+     2 | personnel |  1 |   1 |   1
      3 | sales     |  2 |   2 |   1
      4 | sales     |  3 |   3 |   1
+     5 | personnel |  2 |   2 |   1
+     7 | develop   |  4 |   4 |   1
+     8 | develop   |  1 |   1 |   1
+     9 | develop   |  5 |   5 |   1
+    10 | develop   |  2 |   2 |   1
+    11 | develop   |  3 |   3 |   1
 (10 rows)
 
 -- Test pushdown of quals into a subquery containing window functions
@@ -4106,17 +4107,17 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
  empno |  depname  | salary | c 
 -------+-----------+--------+---
+     1 | sales     |   5000 | 1
+     2 | personnel |   3900 | 1
+     3 | sales     |   4800 | 3
+     4 | sales     |   4800 | 3
+     5 | personnel |   3500 | 2
      8 | develop   |   6000 | 1
     10 | develop   |   5200 | 3
     11 | develop   |   5200 | 3
-     2 | personnel |   3900 | 1
-     5 | personnel |   3500 | 2
-     1 | sales     |   5000 | 1
-     4 | sales     |   4800 | 3
-     3 | sales     |   4800 | 3
 (8 rows)
 
 -- Ensure we get the correct run condition when the window function is both
@@ -4468,14 +4469,15 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
   depname  | empno | salary | enroll_date | first_emp | last_emp 
 -----------+-------+--------+-------------+-----------+----------
+ develop   |     7 |   4200 | 01-01-2008  |         4 |        1
  develop   |     8 |   6000 | 10-01-2006  |         1 |        5
- develop   |     7 |   4200 | 01-01-2008  |         5 |        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
diff --git a/src/test/regress/sql/geometry.sql b/src/test/regress/sql/geometry.sql
index c3ea368da5..1f47f07f31 100644
--- a/src/test/regress/sql/geometry.sql
+++ b/src/test/regress/sql/geometry.sql
@@ -403,7 +403,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
 
 -- To polygon
 SELECT f1, f1::polygon FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
diff --git a/src/test/regress/sql/tuplesort.sql b/src/test/regress/sql/tuplesort.sql
index 8476e594e6..a7d11a146f 100644
--- a/src/test/regress/sql/tuplesort.sql
+++ b/src/test/regress/sql/tuplesort.sql
@@ -305,3 +305,69 @@ EXPLAIN (COSTS OFF) :qry;
 :qry;
 
 COMMIT;
+
+-- Test cases for multi-key quick sort
+
+set work_mem='100MB';
+
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+
+-- test sorting on distinct values, in which mk qsort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+drop table mksort_simple_tbl;
+
+-- test table with abbr keys
+
+create table abbr_tbl (a int, b varchar(100), c uuid);
+
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+-- (see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+
+select c, b, a from abbr_tbl order by c, b, a;
+select c, b, a from abbr_tbl order by c desc, b, a;
+select c, b, a from abbr_tbl order by c, b desc, a;
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+
+-- Uniqueness check of CREATE INDEX
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop table abbr_tbl;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6de5493b05..46359cb796 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -21,9 +21,9 @@ INSERT INTO empsalary VALUES
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
 
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
 
 -- with GROUP BY
 SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
@@ -31,7 +31,7 @@ GROUP BY four, ten ORDER BY four, ten;
 
 SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
 
-SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
 
 -- empty window specification
 SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
@@ -1146,11 +1146,12 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
 
 -- Test pushdown of quals into a subquery containing window functions
 
@@ -1332,7 +1333,7 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
 
 -- Ensure we get the correct run condition when the window function is both
 -- monotonically increasing and decreasing.
@@ -1510,10 +1511,11 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
 
 -- cleanup
 DROP TABLE empsalary;
-- 
2.25.1

#8Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Yao Wang (#7)
7 attachment(s)
Re: 回复: An implementation of multi-key sort

Hello Yao,

I was interested in the patch, considering the promise of significant
speedups of sorting, so I took a quick look and did some basic perf
testing today. Unfortunately, my benchmarks don't really confirm any
peformance benefits, so I haven't looked at the code very much and only
have some very basic feedback:

1) The new GUC is missing from the .sample config, triggering a failure
of "make check-world". Fixed by 0002.

2) There's a place mixing tabs/spaces in indentation. Fixed by 0003.

3) I tried running pgindent, mostly to see how that would affect the
comments, and for most it's probably fine, but a couple are mangled
(usually those with a numbered list of items). Might needs some changes
to use formatting that's not reformatted like this. The changes from
pgindent are in 0004, but this is not a fix - it just shows the changes
after running pgindent.

Now, regarding the performance tests - I decided to do the usual black
box testing, i.e. generate tables with varying numbers of columns, data
types, different data distribution (random, correlated, ...) and so on.
And then run simple ORDER BY queries on that, measuring timing with and
without mk-sort, and checking the effect.

So I wrote a simple bash script (attached) that does exactly that - it
generates a table with 1k - 10M rows, fills with with data (with some
basic simple data distributions), and then runs the queries.

The raw results are too large to attach, I'm only attaching a PDF
showing the summary with a "speedup heatmap" - it's a pivot with the
parameters on the left, and then the GUC and number on columns on top.
So the first group of columns is with enable_mk_sort=off, the second
group with enable_mk_sort=on, and finally the heatmap with relative
timing (enable_mk_sort=on / enable_mk_sort=off).

So values <100% mean it got faster (green color - good), and values

100% mean it got slower (red - bad). And the thing is - pretty much

everything is red, often in the 200%-300% range, meaning it got 2x-3x
slower. There's only very few combinations where it got faster. That
does not seem very promising ... but maybe I did something wrong?

After seeing this, I took a look at your example again, which showed
some nice speedups. But it seems very dependent on the order of keys in
the ORDER BY clause. For example consider this:

set enable_mk_sort = on;
explain (analyze, timing off)
select * from t1 order by c6, c5, c4, c3, c2, c1;

QUERY PLAN
-------------------------------------------------------------------
Sort (cost=72328.81..73578.81 rows=499999 width=76)
(actual rows=499999 loops=1)
Sort Key: c6, c5, c4, c3, c2, c1
Sort Method: quicksort Memory: 59163kB
-> Seq Scan on t1 (cost=0.00..24999.99 rows=499999 width=76)
(actual rows=499999 loops=1)
Planning Time: 0.054 ms
Execution Time: 1095.183 ms
(6 rows)

set enable_mk_sort = on;
explain (analyze, timing off)
select * from t1 order by c6, c5, c4, c3, c2, c1;

QUERY PLAN
-------------------------------------------------------------------
Sort (cost=72328.81..73578.81 rows=499999 width=76)
(actual rows=499999 loops=1)
Sort Key: c6, c5, c4, c3, c2, c1
Sort Method: multi-key quick sort Memory: 59163kB
-> Seq Scan on t1 (cost=0.00..24999.99 rows=499999 width=76)
(actual rows=499999 loops=1)
Planning Time: 0.130 ms
Execution Time: 633.635 ms
(6 rows)

Which seems great, but let's reverse the sort keys:

set enable_mk_sort = off;
explain (analyze, timing off)
select * from t1 order by c1, c2, c3, c4, c5, c6;

QUERY PLAN
-------------------------------------------------------------------

Sort (cost=72328.81..73578.81 rows=499999 width=76)
(actual rows=499999 loops=1)
Sort Key: c1, c2, c3, c4, c5, c6
Sort Method: quicksort Memory: 59163kB
-> Seq Scan on t1 (cost=0.00..24999.99 rows=499999 width=76)
(actual rows=499999 loops=1)
Planning Time: 0.146 ms
Execution Time: 170.085 ms
(6 rows)

set enable_mk_sort = off;
explain (analyze, timing off)
select * from t1 order by c1, c2, c3, c4, c5, c6;

QUERY PLAN
-------------------------------------------------------------------
Sort (cost=72328.81..73578.81 rows=499999 width=76)
(actual rows=499999 loops=1)
Sort Key: c1, c2, c3, c4, c5, c6
Sort Method: multi-key quick sort Memory: 59163kB
-> Seq Scan on t1 (cost=0.00..24999.99 rows=499999 width=76)
(actual rows=499999 loops=1)
Planning Time: 0.127 ms
Execution Time: 367.263 ms
(6 rows)

I believe this is the case Heikki was asking about. I see the response
was that it's OK and the overhead is very low, but without too much
detail so I don't know what case you measured.

Anyway, I think it seems to be very sensitive to the exact data set.
Which is not entirely surprising, I guess - most optimizations have a
mix of improved/regressed cases, yielding a heatmap with a mix of green
and red areas, and we have to either optimize the code (or heuristics to
enable the feature), or convince ourselves the "red" cases are less
important / unlikely etc.

But here the results are almost universally "red", so it's going to be
very hard to convince ourselves this is a good trade off. Of course, you
may argue the cases I've tested are wrong and not representative. I
don't think that's the case, though.

It's also interesting (and perhaps a little bit bizarre) that almost all
the cases that got better are for a single-column sort. Which is exactly
the case the patch should not affect. But it seems pretty consistent, so
maybe this is something worth investigating.

FWIW I'm not familiar with the various quicksort variants, but I noticed
that the Bentley & Sedgewick paper mentioned as the basis for the patch
is from 1997, and apparently implements stuff originally proposed by
Hoare in 1961. So maybe this is just an example of an algorithm that was
good for a hardware at that time, but the changes (e.g. the growing
important of on-CPU caches) made it less relevant?

Another thing I noticed while skimming [1]https://en.wikipedia.org/wiki/Multi-key_quicksort is this:

The algorithm is designed to exploit the property that in many
problems, strings tend to have shared prefixes.

If that's the case, isn't it wrong to apply this to all sorts, including
sorts with non-string keys? It might explain why your example works OK,
as it involves key c6 which is string with all values sharing the same
(fairly long) prefix. But then maybe we should be careful and restrict
this to only such those cases?

regards

[1]: https://en.wikipedia.org/wiki/Multi-key_quicksort

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

v20240609-0001-patch-2024-06-07.patchtext/x-patch; charset=UTF-8; name=v20240609-0001-patch-2024-06-07.patchDownload
From 592500255df863baaf2afade60c6801411ab8eca Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tv@fuzzy.cz>
Date: Sun, 9 Jun 2024 13:26:15 +0200
Subject: [PATCH v20240609 1/4] patch 2024/06/07

---
 src/backend/utils/misc/guc_tables.c           |  11 +
 src/backend/utils/sort/mk_qsort_tuple.c       | 388 ++++++++++++++++++
 src/backend/utils/sort/tuplesort.c            |  44 ++
 src/backend/utils/sort/tuplesortvariants.c    | 313 ++++++++++++--
 src/include/c.h                               |   4 +
 src/include/utils/tuplesort.h                 |  36 +-
 src/test/regress/expected/geometry.out        |   4 +-
 .../regress/expected/incremental_sort.out     |  12 +-
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/expected/tuplesort.out       | 376 +++++++++++++++++
 src/test/regress/expected/window.out          |  58 +--
 src/test/regress/sql/geometry.sql             |   2 +-
 src/test/regress/sql/tuplesort.sql            |  66 +++
 src/test/regress/sql/window.sql               |  22 +-
 14 files changed, 1254 insertions(+), 85 deletions(-)
 create mode 100644 src/backend/utils/sort/mk_qsort_tuple.c

diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 46c258be282..a5f8b3798cc 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -103,6 +103,7 @@ extern char *default_tablespace;
 extern char *temp_tablespaces;
 extern bool ignore_checksum_failure;
 extern bool ignore_invalid_pages;
+extern bool enable_mk_sort;
 
 #ifdef TRACE_SYNCSCAN
 extern bool trace_syncscan;
@@ -839,6 +840,16 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_mk_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables multi-key sort"),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_mk_sort,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
diff --git a/src/backend/utils/sort/mk_qsort_tuple.c b/src/backend/utils/sort/mk_qsort_tuple.c
new file mode 100644
index 00000000000..9c5715380aa
--- /dev/null
+++ b/src/backend/utils/sort/mk_qsort_tuple.c
@@ -0,0 +1,388 @@
+/*
+ * MK qsort (multi-key quick sort) is an alternative of standard qsort
+ * algorithm, which has better performance for particular sort scenarios, i.e.
+ * the data set has multiple keys to be sorted.
+ *
+ * The sorting algorithm blends Quicksort and radix sort; Like regular
+ * Quicksort, it partitions its input into sets less than and greater than a
+ * given value; like radix sort, it moves on to the next field once the current
+ * input is known to be equal in the given field.
+ *
+ * The implementation is based on the paper:
+ *   Jon L. Bentley and Robert Sedgewick, "Fast Algorithms for Sorting and
+ *   Searching Strings", Jan 1997
+ *
+ * Some improvements which is related to additional handling for equal tuples
+ * have been adapted to keep consistency with the implementations of postgres
+ * qsort.
+ *
+ * For now, mk_qsort_tuple() is called in tuplesort_sort_memtuples() as a
+ * replacement of qsort_tuple() when specific conditions are satisfied.
+ */
+
+/* Swap two tuples in sort tuple array */
+static inline void
+mkqs_swap(int        a,
+		  int        b,
+		  SortTuple *x)
+{
+	SortTuple t;
+
+	if (a == b)
+		return;
+	t = x[a];
+	x[a] = x[b];
+	x[b] = t;
+}
+
+/* Swap tuples by batch in sort tuple array */
+static inline void
+mkqs_vec_swap(int        a,
+			  int        b,
+			  int        size,
+			  SortTuple *x)
+{
+	while (size-- > 0)
+	{
+		mkqs_swap(a, b, x);
+		a++;
+		b++;
+	}
+}
+
+/*
+ * Check whether current datum (at specified tuple and depth) is null
+ * Note that the input x means a specified tuple provided by caller but not
+ * a tuple array, so tupleIndex is unnecessary
+ */
+static inline bool
+check_datum_null(SortTuple      *x,
+				 int             depth,
+				 Tuplesortstate *state)
+{
+	Datum datum;
+	bool isNull;
+
+	Assert(depth < state->base.nKeys);
+
+	/* Since we have a specified tuple, the tupleIndex is always 0 */
+	state->base.mkqsGetDatumFunc(x, 0, depth, state, &datum, &isNull, false);
+
+	/*
+	 * Note: for "abbreviated key", we don't need to handle more here because
+	 * if "abbreviated key" of a datum is null, the "full" datum must be null.
+	 */
+
+	return isNull;
+}
+
+/*
+ * Compare two tuples at specified depth
+ *
+ * If "abbreviated key" is disabled:
+ *   get specified datums and compare them by ApplySortComparator().
+ * If "abbreviated key" is enabled:
+ *   Only first datum may be abbr key according to the design (see the comments
+ *   of struct SortTuple), so different operations are needed for different
+ *   datum.
+ *   For first datum (depth == 0): get first datums ("abbr key" version) and
+ *   compare them by ApplySortComparator(). If they are equal, get "full"
+ *   version and compare again by ApplySortAbbrevFullComparator().
+ *   For other datums: get specified datums and compare them by
+ *   ApplySortComparator() as regular routine does.
+ *
+ * See comparetup_heap() for details.
+ */
+static inline int
+mkqs_compare_datum(SortTuple      *tuple1,
+				   SortTuple      *tuple2,
+				   int			 depth,
+				   Tuplesortstate *state)
+{
+	Datum datum1, datum2;
+	bool isNull1, isNull2;
+	SortSupport sortKey;
+	int ret = 0;
+
+	Assert(state->base.mkqsGetDatumFunc);
+	Assert(depth < state->base.nKeys);
+
+	sortKey = state->base.sortKeys + depth;
+	state->base.mkqsGetDatumFunc(tuple1, 0, depth, state,
+								 &datum1, &isNull1, false);
+	state->base.mkqsGetDatumFunc(tuple2, 0, depth, state,
+								 &datum2, &isNull2, false);
+
+	ret = ApplySortComparator(datum1,
+							  isNull1,
+							  datum2,
+							  isNull2,
+							  sortKey);
+
+	/*
+	 * If "abbreviated key" is enabled, and we are in the first depth, it means
+	 * only "abbreviated keys" are compared. If the two datums are determined to
+	 * be equal by ApplySortComparator(), we need to perform an extra "full"
+	 * comparing by ApplySortAbbrevFullComparator().
+	 */
+	if (sortKey->abbrev_converter &&
+		depth == 0 &&
+		ret == 0)
+	{
+		/* Fetch "full" datum by setting useFullKey = true */
+		state->base.mkqsGetDatumFunc(tuple1, 0, depth, state,
+									 &datum1, &isNull1, true);
+		state->base.mkqsGetDatumFunc(tuple2, 0, depth, state,
+									 &datum2, &isNull2, true);
+
+		ret = ApplySortAbbrevFullComparator(datum1,
+											isNull1,
+											datum2,
+											isNull2,
+											sortKey);
+	}
+
+	return ret;
+}
+
+#ifdef USE_ASSERT_CHECKING
+/*
+ * Verify whether the SortTuple list is ordered or not at specified depth
+ */
+static void
+mkqs_verify(SortTuple      *x,
+			int				n,
+			int				depth,
+			Tuplesortstate *state)
+{
+	int ret;
+
+	for (int i = 0;i < n - 1;i++)
+	{
+		ret = mkqs_compare_datum(x + i,
+								 x + i + 1,
+								 depth,
+								 state);
+		Assert(ret <= 0);
+	}
+}
+#endif
+
+/*
+ * Major of multi-key quick sort
+ *
+ * seenNull indicates whether we have seen NULL in any datum we checked
+ */
+static void
+mk_qsort_tuple(SortTuple           *x,
+			   size_t               n,
+			   int                  depth,
+			   Tuplesortstate      *state,
+			   bool                 seenNull)
+{
+	/*
+	 * In the process, the tuple array consists of five parts:
+	 * left equal, less, not-processed, greater, right equal
+	 *
+	 * lessStart indicates the first position of less part
+	 * lessEnd indicates the next position after less part
+	 * greaterStart indicates the prior position before greater part
+	 * greaterEnd indicates the latest position of greater part
+	 * the range between lessEnd and greaterStart (inclusive) is not-processed
+	 */
+	int lessStart, lessEnd, greaterStart, greaterEnd, tupCount;
+	int32 dist;
+	SortTuple *pivot;
+	bool isDatumNull;
+	bool strictOrdered = true;
+
+	Assert(depth <= state->base.nKeys);
+	Assert(state->base.sortKeys);
+	Assert(state->base.mkqsGetDatumFunc);
+
+	if (n <= 1)
+		return;
+
+	/* If we have exceeded the max depth, return immediately */
+	if (depth == state->base.nKeys)
+		return;
+
+	CHECK_FOR_INTERRUPTS();
+
+	/*
+	 * Check if the array is ordered already. If yes, return immediately.
+	 * Different from qsort_tuple(), the array must be strict ordered (no
+	 * equal datums). If there are equal datums, we must continue the mk
+	 * qsort process to check datums on lower depth.
+	 */
+	for (int i = 0;i < n - 1;i++)
+	{
+		int ret;
+
+		CHECK_FOR_INTERRUPTS();
+		ret = mkqs_compare_datum(x + i,
+								 x + i + 1,
+								 depth,
+								 state);
+		if (ret >= 0)
+		{
+			strictOrdered = false;
+			break;
+		}
+	}
+
+	if (strictOrdered)
+		return;
+
+	/* Select pivot by random and move it to the first position */
+	lessStart = n / 2;
+	mkqs_swap(0, lessStart, x);
+	pivot = x;
+
+	lessStart = 1;
+	lessEnd = 1;
+	greaterStart = n - 1;
+	greaterEnd = n - 1;
+
+	/* Sort the array to three parts: lesser, equal, greater */
+	while (true)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		/* Compare the left end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare lessEnd and pivot at current depth */
+			dist = mkqs_compare_datum(x + lessEnd,
+									  pivot,
+									  depth,
+									  state);
+
+			if (dist > 0)
+				break;
+
+			/* If lessEnd is equal to pivot, move it to lessStart */
+			if (dist == 0)
+			{
+				mkqs_swap(lessEnd, lessStart, x);
+				lessStart++;
+			}
+			lessEnd++;
+		}
+
+		/* Compare the right end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare greaterStart and pivot at current depth */
+			dist = mkqs_compare_datum(x + greaterStart,
+									  pivot,
+									  depth,
+									  state);
+
+			if (dist < 0)
+				break;
+
+			/* If greaterStart is equal to pivot, move it to greaterEnd */
+			if (dist == 0)
+			{
+				mkqs_swap(greaterStart, greaterEnd, x);
+				greaterEnd--;
+			}
+			greaterStart--;
+		}
+
+		if (lessEnd > greaterStart)
+			break;
+		mkqs_swap(lessEnd, greaterStart, x);
+		lessEnd++;
+		greaterStart--;
+	}
+
+	/*
+	 * Now the array has four parts:
+	 *   left equal, lesser, greater, right equal
+	 * Note greaterStart is less than lessEnd now
+	 */
+
+	/* Move the left equal part to middle */
+	dist = Min(lessStart, lessEnd - lessStart);
+	mkqs_vec_swap(0, lessEnd - dist, dist, x);
+
+	/* Move the right equal part to middle */
+	dist = Min(greaterEnd - greaterStart, n - greaterEnd - 1);
+	mkqs_vec_swap(lessEnd, n - dist, dist, x);
+
+	/*
+	 * Now the array has three parts:
+	 *   lesser, equal, greater
+	 * Note that one or two parts may have no element at all.
+	 */
+
+	/* Recursively sort the lesser part */
+
+	/* dist means the size of less part */
+	dist = lessEnd - lessStart;
+	mk_qsort_tuple(x,
+				   dist,
+				   depth,
+				   state,
+				   seenNull);
+
+	/* Recursively sort the equal part */
+
+	/*
+	 * (x + dist) means the first tuple in the equal part
+	 * Since all tuples have equal datums at current depth, we just check any one
+	 * of them to determine whether we have seen null datum.
+	 */
+	isDatumNull = check_datum_null(x + dist, depth, state);
+
+	/* (lessStart + n - greaterEnd - 1) means the size of equal part */
+	tupCount = lessStart + n - greaterEnd - 1;
+
+	if (depth < state->base.nKeys - 1)
+	{
+		mk_qsort_tuple(x + dist,
+					   tupCount,
+					   depth + 1,
+					   state,
+					   seenNull || isDatumNull);
+	} else {
+		/*
+		 * We have reach the max depth: Call mkqsHandleDupFunc to handle
+		 * duplicated tuples if necessary, e.g. checking uniqueness or extra
+		 * comparing
+		 */
+
+		/*
+		 * Call mkqsHandleDupFunc if:
+		 *   1. mkqsHandleDupFunc is filled
+		 *   2. the size of equal part > 1
+		 */
+		if (state->base.mkqsHandleDupFunc &&
+			(tupCount > 1))
+		{
+			state->base.mkqsHandleDupFunc(x + dist,
+										  tupCount,
+										  seenNull || isDatumNull,
+										  state);
+		}
+	}
+
+	/* Recursively sort the greater part */
+
+	/* dist means the size of greater part */
+	dist = greaterEnd - greaterStart;
+	mk_qsort_tuple(x + n - dist,
+				   dist,
+				   depth,
+				   state,
+				   seenNull);
+
+#ifdef USE_ASSERT_CHECKING
+	mkqs_verify(x,
+				n,
+				depth,
+				state);
+#endif
+}
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 7c4d6dc106b..5718911eb9b 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -128,6 +128,7 @@ bool		trace_sort = false;
 bool		optimize_bounded_sort = true;
 #endif
 
+bool		enable_mk_sort = true;
 
 /*
  * During merge, we use a pre-allocated set of fixed-size slots to hold
@@ -337,6 +338,9 @@ struct Tuplesortstate
 #ifdef TRACE_SORT
 	PGRUsage	ru_start;
 #endif
+
+	/* Whether multi-key quick sort is used */
+	bool mkqsUsed;
 };
 
 /*
@@ -622,6 +626,8 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
 #define ST_DEFINE
 #include "lib/sort_template.h"
 
+#include "mk_qsort_tuple.c"
+
 /*
  *		tuplesort_begin_xxx
  *
@@ -690,6 +696,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate, int sortopt)
 	state->base.sortopt = sortopt;
 	state->base.tuples = true;
 	state->abbrevNext = 10;
+	state->mkqsUsed = false;
 
 	/*
 	 * workMem is forced to be at least 64KB, the current minimum valid value
@@ -2559,6 +2566,8 @@ tuplesort_get_stats(Tuplesortstate *state,
 		case TSS_SORTEDINMEM:
 			if (state->boundUsed)
 				stats->sortMethod = SORT_TYPE_TOP_N_HEAPSORT;
+			else if (state->mkqsUsed)
+				stats->sortMethod = SORT_TYPE_MK_QSORT;
 			else
 				stats->sortMethod = SORT_TYPE_QUICKSORT;
 			break;
@@ -2592,6 +2601,8 @@ tuplesort_method_name(TuplesortMethod m)
 			return "external sort";
 		case SORT_TYPE_EXTERNAL_MERGE:
 			return "external merge";
+		case SORT_TYPE_MK_QSORT:
+			return "multi-key quick sort";
 	}
 
 	return "unknown";
@@ -2717,6 +2728,39 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 
 	if (state->memtupcount > 1)
 	{
+		/*
+		 * Apply multi-key quick sort when:
+		 *   1. enable_mk_sort is set
+		 *   2. There are multiple keys available
+		 *   3. mkqsGetDatumFunc is filled, which implies that current tuple
+		 *      type is supported by mk qsort. (By now only Heap tuple and Btree
+		 *      Index tuple are supported, and more types may be supported in
+		 *      future.)
+		 *
+		 * A summary of tuple types supported by mk qsort:
+		 *
+		 *   HeapTuple: supported
+		 *   IndexTuple(btree): supported
+		 *   IndexTuple(hash): not supported because there is only one key
+		 *   DatumTuple: not supported because there is only one key
+		 *   HeapTuple(for cluster): not supported yet
+		 *   IndexTuple(gist): not supported yet
+		 *   IndexTuple(brin): not supported yet
+		 */
+		if (enable_mk_sort &&
+			state->base.nKeys > 1 &&
+			state->base.mkqsGetDatumFunc != NULL)
+		{
+			state->mkqsUsed = true;
+			mk_qsort_tuple(state->memtuples,
+						   state->memtupcount,
+						   0,
+						   state,
+						   false);
+
+			return;
+		}
+
 		/*
 		 * Do we have the leading column's value or abbreviation in datum1,
 		 * and is there a specialization for its comparator?
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 05a853caa36..ddcffa5094d 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -30,6 +30,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
+#include "miscadmin.h"
 
 
 /* sort-type codes for sort__start probes */
@@ -92,6 +93,41 @@ static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
 						  LogicalTape *tape, unsigned int len);
 static void freestate_cluster(Tuplesortstate *state);
 
+static Datum mkqs_get_datum_heap(SortTuple      *x,
+								 const int       tupleIndex,
+								 const int       depth,
+								 Tuplesortstate *state,
+								 Datum          *datum,
+								 bool           *isNull,
+								 bool            useFullKey);
+
+static Datum mkqs_get_datum_index_btree(SortTuple      *x,
+										const int       tupleIndex,
+										const int       depth,
+										Tuplesortstate *state,
+										Datum          *datum,
+										bool           *isNull,
+										bool            useFullKey);
+
+static void
+mkqs_handle_dup_index_btree(SortTuple      *x,
+							const int       tupleCount,
+							const bool      seenNull,
+							Tuplesortstate *state);
+
+static int
+mkqs_compare_equal_index_btree(const SortTuple *a,
+							   const SortTuple *b,
+							   Tuplesortstate  *state);
+
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2);
+
+static inline void
+raise_error_of_dup_index(IndexTuple     x,
+						 Tuplesortstate *state);
+
 /*
  * Data structure pointed by "TuplesortPublic.arg" for the CLUSTER case.  Set by
  * the tuplesort_begin_cluster.
@@ -163,6 +199,14 @@ typedef struct BrinSortTuple
 /* Size of the BrinSortTuple, given length of the BrinTuple. */
 #define BRINSORTTUPLE_SIZE(len)		(offsetof(BrinSortTuple, tuple) + (len))
 
+#define ST_SORT qsort_tuple_by_itempointer
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) mkqs_compare_equal_index_btree(a, b, state)
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
 
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
@@ -200,6 +244,7 @@ tuplesort_begin_heap(TupleDesc tupDesc,
 	base->removeabbrev = removeabbrev_heap;
 	base->comparetup = comparetup_heap;
 	base->comparetup_tiebreak = comparetup_heap_tiebreak;
+	base->mkqsGetDatumFunc = mkqs_get_datum_heap;
 	base->writetup = writetup_heap;
 	base->readtup = readtup_heap;
 	base->haveDatum1 = true;
@@ -388,6 +433,8 @@ tuplesort_begin_index_btree(Relation heapRel,
 	base->removeabbrev = removeabbrev_index;
 	base->comparetup = comparetup_index_btree;
 	base->comparetup_tiebreak = comparetup_index_btree_tiebreak;
+	base->mkqsGetDatumFunc = mkqs_get_datum_index_btree;
+	base->mkqsHandleDupFunc = mkqs_handle_dup_index_btree;
 	base->writetup = writetup_index;
 	base->readtup = readtup_index;
 	base->haveDatum1 = true;
@@ -1531,10 +1578,6 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 */
 	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && equal_hasnull))
 	{
-		Datum		values[INDEX_MAX_KEYS];
-		bool		isnull[INDEX_MAX_KEYS];
-		char	   *key_desc;
-
 		/*
 		 * Some rather brain-dead implementations of qsort (such as the one in
 		 * QNX 4) will sometimes call the comparison routine to compare a
@@ -1543,18 +1586,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 		 */
 		Assert(tuple1 != tuple2);
 
-		index_deform_tuple(tuple1, tupDes, values, isnull);
-
-		key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
-
-		ereport(ERROR,
-				(errcode(ERRCODE_UNIQUE_VIOLATION),
-				 errmsg("could not create unique index \"%s\"",
-						RelationGetRelationName(arg->index.indexRel)),
-				 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
-				 errdetail("Duplicate keys exist."),
-				 errtableconstraint(arg->index.heapRel,
-									RelationGetRelationName(arg->index.indexRel))));
+		raise_error_of_dup_index(tuple1, state);
 	}
 
 	/*
@@ -1563,25 +1595,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 * attribute in order to ensure that all keys in the index are physically
 	 * unique.
 	 */
-	{
-		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
-		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
-
-		if (blk1 != blk2)
-			return (blk1 < blk2) ? -1 : 1;
-	}
-	{
-		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
-		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
-
-		if (pos1 != pos2)
-			return (pos1 < pos2) ? -1 : 1;
-	}
-
-	/* ItemPointer values should never be equal */
-	Assert(false);
-
-	return 0;
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
 }
 
 static int
@@ -1888,3 +1902,232 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
 	if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */
 		LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen));
 }
+
+/*
+ * Get specified datum from SortTuple (HeapTuple) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_heap() for details.
+ */
+static Datum
+mkqs_get_datum_heap(SortTuple      *x,
+					int             tupleIndex,
+					int             depth,
+					Tuplesortstate *state,
+					Datum          *datum,
+					bool           *isNull,
+					bool            useFullKey)
+{
+	TupleDesc   tupDesc = NULL;
+	HeapTupleData heapTuple;
+	AttrNumber  attno;
+	SortTuple *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	SortSupport sortKey = base->sortKeys + depth;;
+
+	Assert(state);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	tupDesc = (TupleDesc)base->arg;
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*datum = sortTuple->datum1;
+		*isNull = sortTuple->isnull1;
+		return *datum;
+	}
+
+	/* For any datums which depth > 0, extract it from sortTuple->tuple */
+	heapTuple.t_len = ((MinimalTuple) sortTuple->tuple)->t_len + MINIMAL_TUPLE_OFFSET;
+	heapTuple.t_data = (HeapTupleHeader) ((char *) sortTuple->tuple - MINIMAL_TUPLE_OFFSET);
+	attno = sortKey->ssup_attno;
+	*datum = heap_getattr(&heapTuple, attno, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Get specified datum from SortTuple (IndexTuple for btree index) list
+ *
+ * If the first datum is requested (depth == 0), sortTuple->datum1/isnull1
+ * will be returned. For other datums, relevant datum will be extracted from
+ * sortTuple->tuple.
+ *
+ * The parameter "useFullKey" is used for scenario of "abbreviated key":
+ *   false - get sortTuple->datum1/isnull1 (abbreviated key)
+ *   true - get the "full" datum
+ * If "abbreviated key" is disabled, useFullKey will be ignored.
+ *
+ * See comparetup_index_btree() for details.
+ */
+static Datum
+mkqs_get_datum_index_btree(SortTuple      *x,
+						   const int       tupleIndex,
+						   const int       depth,
+						   Tuplesortstate *state,
+						   Datum          *datum,
+						   bool           *isNull,
+						   bool            useFullKey)
+{
+	TupleDesc   tupDesc;
+	IndexTuple  indexTuple;
+	SortTuple  *sortTuple = x + tupleIndex;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	Assert(state);
+
+	/*
+	 * useFullKey is valid only when depth == 0, because only the first datum
+	 * may be involved to "abbreviated key", so only the first datum need to
+	 * be checked with "full" version.
+	 */
+	AssertImply(useFullKey, depth == 0);
+
+	/*
+	 * When useFullKey is false, and the first datum is requested, return the
+	 * leading datum
+	 */
+	if (depth == 0 && !useFullKey)
+	{
+		*isNull = sortTuple->isnull1;
+		*datum = sortTuple->datum1;
+		return *datum;
+	}
+
+	indexTuple = (IndexTuple) sortTuple->tuple;
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+
+	/*
+	 * Set parameter attnum = depth + 1 because attnum starts from 1 but depth
+	 * starts from 0
+	 */
+	*datum = index_getattr(indexTuple, depth + 1, tupDesc, isNull);
+
+	return *datum;
+}
+
+/*
+ * Handle duplicated SortTuples (IndexTuple for btree index during mk qsort)
+ *  x: the duplicated tuple list
+ *  tupleCount: count of the tuples
+ */
+static void
+mkqs_handle_dup_index_btree(SortTuple      *x,
+							const int       tupleCount,
+							const bool      seenNull,
+							Tuplesortstate *state)
+{
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	/* If enforceUnique is enabled and we never saw NULL, raise error */
+	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && seenNull))
+	{
+		/*
+		 * x means the first tuple of duplicated tuple list
+		 * Since they are duplicated, simply pick up the first one
+		 * to raise error
+		 */
+		raise_error_of_dup_index((IndexTuple)(x->tuple), state);
+	}
+
+	/*
+	 * If key values are equal, we sort on ItemPointer.  This is required for
+	 * btree indexes, since heap TID is treated as an implicit last key
+	 * attribute in order to ensure that all keys in the index are physically
+	 * unique.
+	 */
+	qsort_tuple_by_itempointer(x,
+							   tupleCount,
+							   state);
+}
+
+/*
+ * Compare two btree index tuples by ItemPointer
+ * It is a callback function for qsort_tuple() called by
+ * mkqs_handle_dup_index_btree()
+ */
+static int
+mkqs_compare_equal_index_btree(const SortTuple *a,
+							   const SortTuple *b,
+							   Tuplesortstate  *state)
+{
+	IndexTuple  tuple1;
+	IndexTuple  tuple2;
+
+	tuple1 = (IndexTuple) a->tuple;
+	tuple2 = (IndexTuple) b->tuple;
+
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
+}
+
+/* Compare two index tuples by ItemPointer */
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2)
+{
+	{
+		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
+		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
+
+		if (blk1 != blk2)
+			return (blk1 < blk2) ? -1 : 1;
+	}
+	{
+		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
+		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
+
+		if (pos1 != pos2)
+			return (pos1 < pos2) ? -1 : 1;
+	}
+
+	/* ItemPointer values should never be equal */
+	Assert(false);
+
+	return 0;
+}
+
+/* Raise error for duplicated tuple when creating unique index */
+static inline void
+raise_error_of_dup_index(IndexTuple      x,
+						 Tuplesortstate *state)
+{
+	Datum       values[INDEX_MAX_KEYS];
+	bool        isnull[INDEX_MAX_KEYS];
+	TupleDesc   tupDesc;
+	char       *key_desc;
+    TuplesortPublic *base = TuplesortstateGetPublic(state);
+    TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+	index_deform_tuple((IndexTuple)x, tupDesc, values, isnull);
+	key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNIQUE_VIOLATION),
+			errmsg("could not create unique index \"%s\"",
+				   RelationGetRelationName(arg->index.indexRel)),
+			key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+				errdetail("Duplicate keys exist."),
+				errtableconstraint(arg->index.heapRel,
+								   RelationGetRelationName(arg->index.indexRel))));
+}
diff --git a/src/include/c.h b/src/include/c.h
index dc1841346cd..f7c368cd162 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -857,12 +857,14 @@ typedef NameData *Name;
 
 #define Assert(condition)	((void)true)
 #define AssertMacro(condition)	((void)true)
+#define AssertImply(condition1, condition2) ((void)true)
 
 #elif defined(FRONTEND)
 
 #include <assert.h>
 #define Assert(p) assert(p)
 #define AssertMacro(p)	((void) assert(p))
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
 
 #else							/* USE_ASSERT_CHECKING && !FRONTEND */
 
@@ -886,6 +888,8 @@ typedef NameData *Name;
 	((void) ((condition) || \
 			 (ExceptionalCondition(#condition, __FILE__, __LINE__), 0)))
 
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
+
 #endif							/* USE_ASSERT_CHECKING && !FRONTEND */
 
 /*
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index e7941a1f09f..74a6a5ae5ce 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -29,7 +29,6 @@
 #include "utils/relcache.h"
 #include "utils/sortsupport.h"
 
-
 /*
  * Tuplesortstate and Sharedsort are opaque types whose details are not
  * known outside tuplesort.c.
@@ -79,9 +78,10 @@ typedef enum
 	SORT_TYPE_QUICKSORT = 1 << 1,
 	SORT_TYPE_EXTERNAL_SORT = 1 << 2,
 	SORT_TYPE_EXTERNAL_MERGE = 1 << 3,
+	SORT_TYPE_MK_QSORT = 1 << 4,
 } TuplesortMethod;
 
-#define NUM_TUPLESORTMETHODS 4
+#define NUM_TUPLESORTMETHODS 5
 
 typedef enum
 {
@@ -155,6 +155,23 @@ typedef struct
 typedef int (*SortTupleComparator) (const SortTuple *a, const SortTuple *b,
 									Tuplesortstate *state);
 
+/* Multi-key quick sort */
+
+typedef Datum
+(*MkqsGetDatumFunc) (SortTuple      *x,
+					 const int       tupleIndex,
+					 const int       depth,
+					 Tuplesortstate *state,
+					 Datum          *datum,
+					 bool           *isNull,
+					 bool            useFullKey);
+
+typedef void
+(*MkqsHandleDupFunc) (SortTuple      *x,
+					  const int       tupleCount,
+					  const bool      seenNull,
+					  Tuplesortstate *state);
+
 /*
  * The public part of a Tuple sort operation state.  This data structure
  * contains the definition of sort-variant-specific interface methods and
@@ -249,6 +266,21 @@ typedef struct
 	bool		tuples;			/* Can SortTuple.tuple ever be set? */
 
 	void	   *arg;			/* Specific information for the sort variant */
+
+	/*
+	 * Function pointer, referencing a function to get specified datum from
+	 * SortTuple list with multi-key.
+	 * Used by mk_qsort_tuple().
+	*/
+	MkqsGetDatumFunc mkqsGetDatumFunc;
+
+	/*
+	 * Function pointer, referencing a function to handle duplicated tuple
+	 * from SortTuple list with multi-key.
+	 * Used by mk_qsort_tuple().
+	 * For now, the function pointer is filled for only btree index tuple.
+	*/
+	MkqsHandleDupFunc mkqsHandleDupFunc;
 } TuplesortPublic;
 
 /* Sort parallel code from state for sort__start probes */
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 8be694f46be..094d22861c1 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -4273,7 +4273,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
      circle     |       point       |   distance    
 ----------------+-------------------+---------------
  <(1,2),3>      | (-3,4)            |   1.472135955
@@ -4310,8 +4310,8 @@ SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
  <(3,5),0>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (1e+300,Infinity) |      Infinity
  <(5,1),3>      | (1e+300,Infinity) |      Infinity
- <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (Infinity,1e+300) |      Infinity
+ <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,3),5>      | (1e+300,Infinity) |      Infinity
  <(1,3),5>      | (Infinity,1e+300) |      Infinity
  <(100,200),10> | (1e+300,Infinity) |      Infinity
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 5fd54a10b1a..a26f8f100a5 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -520,13 +520,13 @@ select * from (select * from t order by a) s order by a, b limit 55;
 
 -- Test EXPLAIN ANALYZE with only a fullsort group.
 select explain_analyze_without_memory('select * from (select * from t order by a) s order by a, b limit 55');
-                                        explain_analyze_without_memory                                         
----------------------------------------------------------------------------------------------------------------
+                                              explain_analyze_without_memory                                              
+--------------------------------------------------------------------------------------------------------------------------
  Limit (actual rows=55 loops=1)
    ->  Incremental Sort (actual rows=55 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 2  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 2  Sort Methods: top-N heapsort, multi-key quick sort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=101 loops=1)
                Sort Key: t.a
                Sort Method: quicksort  Memory: NNkB
@@ -554,7 +554,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
              "Group Count": 2,                  +
              "Sort Methods Used": [             +
                  "top-N heapsort",              +
-                 "quicksort"                    +
+                 "multi-key quick sort"         +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
@@ -728,7 +728,7 @@ select explain_analyze_without_memory('select * from (select * from t order by a
    ->  Incremental Sort (actual rows=70 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 1  Sort Method: quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 1  Sort Method: multi-key quick sort  Average Memory: NNkB  Peak Memory: NNkB
          Pre-sorted Groups: 5  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=1000 loops=1)
                Sort Key: t.a
@@ -756,7 +756,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
          "Full-sort Groups": {                  +
              "Group Count": 1,                  +
              "Sort Methods Used": [             +
-                 "quicksort"                    +
+                 "multi-key quick sort"         +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index dbfd0c13d46..edd2cbfffd5 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -146,6 +146,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_material                | on
  enable_memoize                 | on
  enable_mergejoin               | on
+ enable_mk_sort                 | on
  enable_nestloop                | on
  enable_parallel_append         | on
  enable_parallel_hash           | on
@@ -156,7 +157,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 6dd97e7427a..ad9e56c2548 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -703,3 +703,379 @@ EXPLAIN (COSTS OFF) :qry;
 (10 rows)
 
 COMMIT;
+-- Test cases for multi-key quick sort
+set work_mem='100MB';
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a | b  |  c   
+---+----+------
+ 0 |  5 | 98f1
+ 0 | 10 | d3d9
+ 1 |  1 | c4ca
+ 1 | 11 | 6512
+ 2 |  2 | c81e
+ 2 | 12 | c20a
+ 3 |  3 | eccb
+ 3 | 13 | c51c
+ 4 |  4 | a87f
+ 4 | 14 | aab3
+ 5 |  0 | 9bf3
+ 5 |  5 | e4da
+ 6 |  1 | c74d
+ 6 |  6 | 1679
+ 7 |  2 | 70ef
+ 7 |  7 | 8f14
+ 8 |  3 | 6f49
+ 8 |  8 | c9f0
+ 9 |  4 | 1f0e
+ 9 |  9 | 45c4
+(20 rows)
+
+-- test sorting on distinct values, in which mk qsort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a  | b  | c  
+----+----+----
+  0 | 20 | 20
+  1 | 19 | 19
+  2 | 18 | 18
+  3 | 17 | 17
+  4 | 16 | 16
+  5 | 15 | 15
+  6 | 14 | 14
+  7 | 13 | 13
+  8 | 12 | 12
+  9 | 11 | 11
+ 10 | 10 | 10
+ 11 |  9 | 9
+ 12 |  8 | 8
+ 13 |  7 | 7
+ 14 |  6 | 6
+ 15 |  5 | 5
+ 16 |  4 | 4
+ 17 |  3 | 3
+ 18 |  2 | 2
+ 19 |  1 | 1
+(20 rows)
+
+drop table mksort_simple_tbl;
+-- test table with abbr keys
+create table abbr_tbl (a int, b varchar(100), c uuid);
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+-- (see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+select c, b, a from abbr_tbl order by c, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+(50 rows)
+
+select c, b, a from abbr_tbl order by c desc, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+(50 rows)
+
+select c, b, a from abbr_tbl order by c, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+NOTICE:  index "idx_abbr_tbl" does not exist, skipping
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+                  c                   |                            b                            | a 
+--------------------------------------+---------------------------------------------------------+---
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 8
+(1 row)
+
+-- Uniqueness check of CREATE INDEX
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+ERROR:  could not create unique index "idx_abbr_tbl"
+DETAIL:  Key (c, b, a)=(00000000-0000-0000-0000-000000000001, aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1, 1) is duplicated.
+drop table abbr_tbl;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ae4e8851f8a..2de20ca1d0c 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -18,13 +18,13 @@ INSERT INTO empsalary VALUES
 ('sales', 3, 4800, '2007-08-01'),
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary |  sum  
 -----------+-------+--------+-------
  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
@@ -33,13 +33,13 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
  sales     |     1 |   5000 | 14600
 (10 rows)
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
  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
@@ -90,18 +90,18 @@ SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PA
  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;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
- develop   |     7 |   4200 |    1
- personnel |     5 |   3500 |    1
  sales     |     3 |   4800 |    1
  sales     |     4 |   4800 |    1
+ personnel |     5 |   3500 |    1
+ develop   |     7 |   4200 |    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)
 
@@ -3749,23 +3749,24 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
  empno |  depname  | rn | rnk | cnt 
 -------+-----------+----+-----+-----
-     8 | develop   |  1 |   1 |   1
-    10 | develop   |  2 |   2 |   1
-    11 | develop   |  3 |   3 |   1
-     9 | develop   |  4 |   4 |   2
-     7 | develop   |  5 |   4 |   2
-     2 | personnel |  1 |   1 |   1
-     5 | personnel |  2 |   2 |   1
      1 | sales     |  1 |   1 |   1
+     2 | personnel |  1 |   1 |   1
      3 | sales     |  2 |   2 |   1
      4 | sales     |  3 |   3 |   1
+     5 | personnel |  2 |   2 |   1
+     7 | develop   |  4 |   4 |   1
+     8 | develop   |  1 |   1 |   1
+     9 | develop   |  5 |   5 |   1
+    10 | develop   |  2 |   2 |   1
+    11 | develop   |  3 |   3 |   1
 (10 rows)
 
 -- Test pushdown of quals into a subquery containing window functions
@@ -4106,17 +4107,17 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
  empno |  depname  | salary | c 
 -------+-----------+--------+---
+     1 | sales     |   5000 | 1
+     2 | personnel |   3900 | 1
+     3 | sales     |   4800 | 3
+     4 | sales     |   4800 | 3
+     5 | personnel |   3500 | 2
      8 | develop   |   6000 | 1
     10 | develop   |   5200 | 3
     11 | develop   |   5200 | 3
-     2 | personnel |   3900 | 1
-     5 | personnel |   3500 | 2
-     1 | sales     |   5000 | 1
-     4 | sales     |   4800 | 3
-     3 | sales     |   4800 | 3
 (8 rows)
 
 -- Ensure we get the correct run condition when the window function is both
@@ -4468,14 +4469,15 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
   depname  | empno | salary | enroll_date | first_emp | last_emp 
 -----------+-------+--------+-------------+-----------+----------
+ develop   |     7 |   4200 | 01-01-2008  |         4 |        1
  develop   |     8 |   6000 | 10-01-2006  |         1 |        5
- develop   |     7 |   4200 | 01-01-2008  |         5 |        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
diff --git a/src/test/regress/sql/geometry.sql b/src/test/regress/sql/geometry.sql
index c3ea368da5e..1f47f07f311 100644
--- a/src/test/regress/sql/geometry.sql
+++ b/src/test/regress/sql/geometry.sql
@@ -403,7 +403,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
 
 -- To polygon
 SELECT f1, f1::polygon FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
diff --git a/src/test/regress/sql/tuplesort.sql b/src/test/regress/sql/tuplesort.sql
index 8476e594e6c..a7d11a146f3 100644
--- a/src/test/regress/sql/tuplesort.sql
+++ b/src/test/regress/sql/tuplesort.sql
@@ -305,3 +305,69 @@ EXPLAIN (COSTS OFF) :qry;
 :qry;
 
 COMMIT;
+
+-- Test cases for multi-key quick sort
+
+set work_mem='100MB';
+
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+
+-- test sorting on distinct values, in which mk qsort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+drop table mksort_simple_tbl;
+
+-- test table with abbr keys
+
+create table abbr_tbl (a int, b varchar(100), c uuid);
+
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+-- (see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+
+select c, b, a from abbr_tbl order by c, b, a;
+select c, b, a from abbr_tbl order by c desc, b, a;
+select c, b, a from abbr_tbl order by c, b desc, a;
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+
+-- Uniqueness check of CREATE INDEX
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop table abbr_tbl;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6de5493b05b..46359cb7968 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -21,9 +21,9 @@ INSERT INTO empsalary VALUES
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
 
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
 
 -- with GROUP BY
 SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
@@ -31,7 +31,7 @@ GROUP BY four, ten ORDER BY four, ten;
 
 SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
 
-SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
 
 -- empty window specification
 SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
@@ -1146,11 +1146,12 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
 
 -- Test pushdown of quals into a subquery containing window functions
 
@@ -1332,7 +1333,7 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
 
 -- Ensure we get the correct run condition when the window function is both
 -- monotonically increasing and decreasing.
@@ -1510,10 +1511,11 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
 
 -- cleanup
 DROP TABLE empsalary;
-- 
2.45.1

v20240609-0002-fix-add-GUC-to-sample.patchtext/x-patch; charset=UTF-8; name=v20240609-0002-fix-add-GUC-to-sample.patchDownload
From 79b2547a8678d7eaf80f2653f48482de57469a18 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tv@fuzzy.cz>
Date: Sun, 9 Jun 2024 13:26:45 +0200
Subject: [PATCH v20240609 2/4] fix: add GUC to sample

---
 src/backend/utils/misc/postgresql.conf.sample | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e0567de2190..f6abe07f824 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -413,6 +413,7 @@
 #enable_sort = on
 #enable_tidscan = on
 #enable_group_by_reordering = on
+#enable_mk_sort = on
 
 # - Planner Cost Constants -
 
-- 
2.45.1

v20240609-0003-fix-tabs-and-spaces.patchtext/x-patch; charset=UTF-8; name=v20240609-0003-fix-tabs-and-spaces.patchDownload
From f321d30fbfcfcb34ff547339de001709b86f4ad4 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tv@fuzzy.cz>
Date: Sun, 9 Jun 2024 13:28:02 +0200
Subject: [PATCH v20240609 3/4] fix: tabs and spaces

---
 src/backend/utils/sort/tuplesortvariants.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index ddcffa5094d..412e5b9b588 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -2115,8 +2115,8 @@ raise_error_of_dup_index(IndexTuple      x,
 	bool        isnull[INDEX_MAX_KEYS];
 	TupleDesc   tupDesc;
 	char       *key_desc;
-    TuplesortPublic *base = TuplesortstateGetPublic(state);
-    TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
 
 	tupDesc = RelationGetDescr(arg->index.indexRel);
 	index_deform_tuple((IndexTuple)x, tupDesc, values, isnull);
-- 
2.45.1

v20240609-0004-fix-pgindent.patchtext/x-patch; charset=UTF-8; name=v20240609-0004-fix-pgindent.patchDownload
From a2788b2f7dcceeeaa9229bfbf1ba2e5c413afb99 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tv@fuzzy.cz>
Date: Sun, 9 Jun 2024 13:41:54 +0200
Subject: [PATCH v20240609 4/4] fix: pgindent

---
 src/backend/utils/sort/mk_qsort_tuple.c    | 121 ++++++++++----------
 src/backend/utils/sort/tuplesort.c         |  26 ++---
 src/backend/utils/sort/tuplesortvariants.c | 125 ++++++++++-----------
 src/include/utils/tuplesort.h              |  34 +++---
 4 files changed, 152 insertions(+), 154 deletions(-)

diff --git a/src/backend/utils/sort/mk_qsort_tuple.c b/src/backend/utils/sort/mk_qsort_tuple.c
index 9c5715380aa..e44bc50d652 100644
--- a/src/backend/utils/sort/mk_qsort_tuple.c
+++ b/src/backend/utils/sort/mk_qsort_tuple.c
@@ -22,11 +22,11 @@
 
 /* Swap two tuples in sort tuple array */
 static inline void
-mkqs_swap(int        a,
-		  int        b,
+mkqs_swap(int a,
+		  int b,
 		  SortTuple *x)
 {
-	SortTuple t;
+	SortTuple	t;
 
 	if (a == b)
 		return;
@@ -37,9 +37,9 @@ mkqs_swap(int        a,
 
 /* Swap tuples by batch in sort tuple array */
 static inline void
-mkqs_vec_swap(int        a,
-			  int        b,
-			  int        size,
+mkqs_vec_swap(int a,
+			  int b,
+			  int size,
 			  SortTuple *x)
 {
 	while (size-- > 0)
@@ -56,12 +56,12 @@ mkqs_vec_swap(int        a,
  * a tuple array, so tupleIndex is unnecessary
  */
 static inline bool
-check_datum_null(SortTuple      *x,
-				 int             depth,
+check_datum_null(SortTuple *x,
+				 int depth,
 				 Tuplesortstate *state)
 {
-	Datum datum;
-	bool isNull;
+	Datum		datum;
+	bool		isNull;
 
 	Assert(depth < state->base.nKeys);
 
@@ -94,15 +94,17 @@ check_datum_null(SortTuple      *x,
  * See comparetup_heap() for details.
  */
 static inline int
-mkqs_compare_datum(SortTuple      *tuple1,
-				   SortTuple      *tuple2,
-				   int			 depth,
+mkqs_compare_datum(SortTuple *tuple1,
+				   SortTuple *tuple2,
+				   int depth,
 				   Tuplesortstate *state)
 {
-	Datum datum1, datum2;
-	bool isNull1, isNull2;
+	Datum		datum1,
+				datum2;
+	bool		isNull1,
+				isNull2;
 	SortSupport sortKey;
-	int ret = 0;
+	int			ret = 0;
 
 	Assert(state->base.mkqsGetDatumFunc);
 	Assert(depth < state->base.nKeys);
@@ -120,10 +122,10 @@ mkqs_compare_datum(SortTuple      *tuple1,
 							  sortKey);
 
 	/*
-	 * If "abbreviated key" is enabled, and we are in the first depth, it means
-	 * only "abbreviated keys" are compared. If the two datums are determined to
-	 * be equal by ApplySortComparator(), we need to perform an extra "full"
-	 * comparing by ApplySortAbbrevFullComparator().
+	 * If "abbreviated key" is enabled, and we are in the first depth, it
+	 * means only "abbreviated keys" are compared. If the two datums are
+	 * determined to be equal by ApplySortComparator(), we need to perform an
+	 * extra "full" comparing by ApplySortAbbrevFullComparator().
 	 */
 	if (sortKey->abbrev_converter &&
 		depth == 0 &&
@@ -150,14 +152,14 @@ mkqs_compare_datum(SortTuple      *tuple1,
  * Verify whether the SortTuple list is ordered or not at specified depth
  */
 static void
-mkqs_verify(SortTuple      *x,
-			int				n,
-			int				depth,
+mkqs_verify(SortTuple *x,
+			int n,
+			int depth,
 			Tuplesortstate *state)
 {
-	int ret;
+	int			ret;
 
-	for (int i = 0;i < n - 1;i++)
+	for (int i = 0; i < n - 1; i++)
 	{
 		ret = mkqs_compare_datum(x + i,
 								 x + i + 1,
@@ -174,27 +176,31 @@ mkqs_verify(SortTuple      *x,
  * seenNull indicates whether we have seen NULL in any datum we checked
  */
 static void
-mk_qsort_tuple(SortTuple           *x,
-			   size_t               n,
-			   int                  depth,
-			   Tuplesortstate      *state,
-			   bool                 seenNull)
+mk_qsort_tuple(SortTuple *x,
+			   size_t n,
+			   int depth,
+			   Tuplesortstate *state,
+			   bool seenNull)
 {
 	/*
-	 * In the process, the tuple array consists of five parts:
-	 * left equal, less, not-processed, greater, right equal
+	 * In the process, the tuple array consists of five parts: left equal,
+	 * less, not-processed, greater, right equal
 	 *
-	 * lessStart indicates the first position of less part
-	 * lessEnd indicates the next position after less part
-	 * greaterStart indicates the prior position before greater part
-	 * greaterEnd indicates the latest position of greater part
-	 * the range between lessEnd and greaterStart (inclusive) is not-processed
+	 * lessStart indicates the first position of less part lessEnd indicates
+	 * the next position after less part greaterStart indicates the prior
+	 * position before greater part greaterEnd indicates the latest position
+	 * of greater part the range between lessEnd and greaterStart (inclusive)
+	 * is not-processed
 	 */
-	int lessStart, lessEnd, greaterStart, greaterEnd, tupCount;
-	int32 dist;
-	SortTuple *pivot;
-	bool isDatumNull;
-	bool strictOrdered = true;
+	int			lessStart,
+				lessEnd,
+				greaterStart,
+				greaterEnd,
+				tupCount;
+	int32		dist;
+	SortTuple  *pivot;
+	bool		isDatumNull;
+	bool		strictOrdered = true;
 
 	Assert(depth <= state->base.nKeys);
 	Assert(state->base.sortKeys);
@@ -212,12 +218,12 @@ mk_qsort_tuple(SortTuple           *x,
 	/*
 	 * Check if the array is ordered already. If yes, return immediately.
 	 * Different from qsort_tuple(), the array must be strict ordered (no
-	 * equal datums). If there are equal datums, we must continue the mk
-	 * qsort process to check datums on lower depth.
+	 * equal datums). If there are equal datums, we must continue the mk qsort
+	 * process to check datums on lower depth.
 	 */
-	for (int i = 0;i < n - 1;i++)
+	for (int i = 0; i < n - 1; i++)
 	{
-		int ret;
+		int			ret;
 
 		CHECK_FOR_INTERRUPTS();
 		ret = mkqs_compare_datum(x + i,
@@ -299,8 +305,7 @@ mk_qsort_tuple(SortTuple           *x,
 	}
 
 	/*
-	 * Now the array has four parts:
-	 *   left equal, lesser, greater, right equal
+	 * Now the array has four parts: left equal, lesser, greater, right equal
 	 * Note greaterStart is less than lessEnd now
 	 */
 
@@ -313,9 +318,8 @@ mk_qsort_tuple(SortTuple           *x,
 	mkqs_vec_swap(lessEnd, n - dist, dist, x);
 
 	/*
-	 * Now the array has three parts:
-	 *   lesser, equal, greater
-	 * Note that one or two parts may have no element at all.
+	 * Now the array has three parts: lesser, equal, greater Note that one or
+	 * two parts may have no element at all.
 	 */
 
 	/* Recursively sort the lesser part */
@@ -331,9 +335,9 @@ mk_qsort_tuple(SortTuple           *x,
 	/* Recursively sort the equal part */
 
 	/*
-	 * (x + dist) means the first tuple in the equal part
-	 * Since all tuples have equal datums at current depth, we just check any one
-	 * of them to determine whether we have seen null datum.
+	 * (x + dist) means the first tuple in the equal part Since all tuples
+	 * have equal datums at current depth, we just check any one of them to
+	 * determine whether we have seen null datum.
 	 */
 	isDatumNull = check_datum_null(x + dist, depth, state);
 
@@ -347,7 +351,9 @@ mk_qsort_tuple(SortTuple           *x,
 					   depth + 1,
 					   state,
 					   seenNull || isDatumNull);
-	} else {
+	}
+	else
+	{
 		/*
 		 * We have reach the max depth: Call mkqsHandleDupFunc to handle
 		 * duplicated tuples if necessary, e.g. checking uniqueness or extra
@@ -355,9 +361,8 @@ mk_qsort_tuple(SortTuple           *x,
 		 */
 
 		/*
-		 * Call mkqsHandleDupFunc if:
-		 *   1. mkqsHandleDupFunc is filled
-		 *   2. the size of equal part > 1
+		 * Call mkqsHandleDupFunc if: 1. mkqsHandleDupFunc is filled 2. the
+		 * size of equal part > 1
 		 */
 		if (state->base.mkqsHandleDupFunc &&
 			(tupCount > 1))
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 5718911eb9b..d51d97b1136 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -340,7 +340,7 @@ struct Tuplesortstate
 #endif
 
 	/* Whether multi-key quick sort is used */
-	bool mkqsUsed;
+	bool		mkqsUsed;
 };
 
 /*
@@ -2729,23 +2729,19 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 	if (state->memtupcount > 1)
 	{
 		/*
-		 * Apply multi-key quick sort when:
-		 *   1. enable_mk_sort is set
-		 *   2. There are multiple keys available
-		 *   3. mkqsGetDatumFunc is filled, which implies that current tuple
-		 *      type is supported by mk qsort. (By now only Heap tuple and Btree
-		 *      Index tuple are supported, and more types may be supported in
-		 *      future.)
+		 * Apply multi-key quick sort when: 1. enable_mk_sort is set 2. There
+		 * are multiple keys available 3. mkqsGetDatumFunc is filled, which
+		 * implies that current tuple type is supported by mk qsort. (By now
+		 * only Heap tuple and Btree Index tuple are supported, and more types
+		 * may be supported in future.)
 		 *
 		 * A summary of tuple types supported by mk qsort:
 		 *
-		 *   HeapTuple: supported
-		 *   IndexTuple(btree): supported
-		 *   IndexTuple(hash): not supported because there is only one key
-		 *   DatumTuple: not supported because there is only one key
-		 *   HeapTuple(for cluster): not supported yet
-		 *   IndexTuple(gist): not supported yet
-		 *   IndexTuple(brin): not supported yet
+		 * HeapTuple: supported IndexTuple(btree): supported IndexTuple(hash):
+		 * not supported because there is only one key DatumTuple: not
+		 * supported because there is only one key HeapTuple(for cluster): not
+		 * supported yet IndexTuple(gist): not supported yet IndexTuple(brin):
+		 * not supported yet
 		 */
 		if (enable_mk_sort &&
 			state->base.nKeys > 1 &&
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 412e5b9b588..a41d4daa4b3 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -93,40 +93,40 @@ static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
 						  LogicalTape *tape, unsigned int len);
 static void freestate_cluster(Tuplesortstate *state);
 
-static Datum mkqs_get_datum_heap(SortTuple      *x,
-								 const int       tupleIndex,
-								 const int       depth,
+static Datum mkqs_get_datum_heap(SortTuple *x,
+								 const int tupleIndex,
+								 const int depth,
 								 Tuplesortstate *state,
-								 Datum          *datum,
-								 bool           *isNull,
-								 bool            useFullKey);
+								 Datum *datum,
+								 bool *isNull,
+								 bool useFullKey);
 
-static Datum mkqs_get_datum_index_btree(SortTuple      *x,
-										const int       tupleIndex,
-										const int       depth,
+static Datum mkqs_get_datum_index_btree(SortTuple *x,
+										const int tupleIndex,
+										const int depth,
 										Tuplesortstate *state,
-										Datum          *datum,
-										bool           *isNull,
-										bool            useFullKey);
+										Datum *datum,
+										bool *isNull,
+										bool useFullKey);
 
 static void
-mkqs_handle_dup_index_btree(SortTuple      *x,
-							const int       tupleCount,
-							const bool      seenNull,
-							Tuplesortstate *state);
+			mkqs_handle_dup_index_btree(SortTuple *x,
+										const int tupleCount,
+										const bool seenNull,
+										Tuplesortstate *state);
 
 static int
-mkqs_compare_equal_index_btree(const SortTuple *a,
-							   const SortTuple *b,
-							   Tuplesortstate  *state);
+			mkqs_compare_equal_index_btree(const SortTuple *a,
+										   const SortTuple *b,
+										   Tuplesortstate *state);
 
 static inline int
-tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
-								  const IndexTuple tuple2);
+			tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+											  const IndexTuple tuple2);
 
 static inline void
-raise_error_of_dup_index(IndexTuple     x,
-						 Tuplesortstate *state);
+			raise_error_of_dup_index(IndexTuple x,
+									 Tuplesortstate *state);
 
 /*
  * Data structure pointed by "TuplesortPublic.arg" for the CLUSTER case.  Set by
@@ -1918,18 +1918,18 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
  * See comparetup_heap() for details.
  */
 static Datum
-mkqs_get_datum_heap(SortTuple      *x,
-					int             tupleIndex,
-					int             depth,
+mkqs_get_datum_heap(SortTuple *x,
+					int tupleIndex,
+					int depth,
 					Tuplesortstate *state,
-					Datum          *datum,
-					bool           *isNull,
-					bool            useFullKey)
+					Datum *datum,
+					bool *isNull,
+					bool useFullKey)
 {
-	TupleDesc   tupDesc = NULL;
+	TupleDesc	tupDesc = NULL;
 	HeapTupleData heapTuple;
-	AttrNumber  attno;
-	SortTuple *sortTuple = x + tupleIndex;
+	AttrNumber	attno;
+	SortTuple  *sortTuple = x + tupleIndex;
 	TuplesortPublic *base = TuplesortstateGetPublic(state);
 	SortSupport sortKey = base->sortKeys + depth;;
 
@@ -1942,7 +1942,7 @@ mkqs_get_datum_heap(SortTuple      *x,
 	 */
 	AssertImply(useFullKey, depth == 0);
 
-	tupDesc = (TupleDesc)base->arg;
+	tupDesc = (TupleDesc) base->arg;
 
 	/*
 	 * When useFullKey is false, and the first datum is requested, return the
@@ -1979,16 +1979,16 @@ mkqs_get_datum_heap(SortTuple      *x,
  * See comparetup_index_btree() for details.
  */
 static Datum
-mkqs_get_datum_index_btree(SortTuple      *x,
-						   const int       tupleIndex,
-						   const int       depth,
+mkqs_get_datum_index_btree(SortTuple *x,
+						   const int tupleIndex,
+						   const int depth,
 						   Tuplesortstate *state,
-						   Datum          *datum,
-						   bool           *isNull,
-						   bool            useFullKey)
+						   Datum *datum,
+						   bool *isNull,
+						   bool useFullKey)
 {
-	TupleDesc   tupDesc;
-	IndexTuple  indexTuple;
+	TupleDesc	tupDesc;
+	IndexTuple	indexTuple;
 	SortTuple  *sortTuple = x + tupleIndex;
 	TuplesortPublic *base = TuplesortstateGetPublic(state);
 	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
@@ -2031,9 +2031,9 @@ mkqs_get_datum_index_btree(SortTuple      *x,
  *  tupleCount: count of the tuples
  */
 static void
-mkqs_handle_dup_index_btree(SortTuple      *x,
-							const int       tupleCount,
-							const bool      seenNull,
+mkqs_handle_dup_index_btree(SortTuple *x,
+							const int tupleCount,
+							const bool seenNull,
 							Tuplesortstate *state)
 {
 	TuplesortPublic *base = TuplesortstateGetPublic(state);
@@ -2043,11 +2043,10 @@ mkqs_handle_dup_index_btree(SortTuple      *x,
 	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && seenNull))
 	{
 		/*
-		 * x means the first tuple of duplicated tuple list
-		 * Since they are duplicated, simply pick up the first one
-		 * to raise error
+		 * x means the first tuple of duplicated tuple list Since they are
+		 * duplicated, simply pick up the first one to raise error
 		 */
-		raise_error_of_dup_index((IndexTuple)(x->tuple), state);
+		raise_error_of_dup_index((IndexTuple) (x->tuple), state);
 	}
 
 	/*
@@ -2069,10 +2068,10 @@ mkqs_handle_dup_index_btree(SortTuple      *x,
 static int
 mkqs_compare_equal_index_btree(const SortTuple *a,
 							   const SortTuple *b,
-							   Tuplesortstate  *state)
+							   Tuplesortstate *state)
 {
-	IndexTuple  tuple1;
-	IndexTuple  tuple2;
+	IndexTuple	tuple1;
+	IndexTuple	tuple2;
 
 	tuple1 = (IndexTuple) a->tuple;
 	tuple2 = (IndexTuple) b->tuple;
@@ -2108,26 +2107,26 @@ tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
 
 /* Raise error for duplicated tuple when creating unique index */
 static inline void
-raise_error_of_dup_index(IndexTuple      x,
+raise_error_of_dup_index(IndexTuple x,
 						 Tuplesortstate *state)
 {
-	Datum       values[INDEX_MAX_KEYS];
-	bool        isnull[INDEX_MAX_KEYS];
-	TupleDesc   tupDesc;
-	char       *key_desc;
+	Datum		values[INDEX_MAX_KEYS];
+	bool		isnull[INDEX_MAX_KEYS];
+	TupleDesc	tupDesc;
+	char	   *key_desc;
 	TuplesortPublic *base = TuplesortstateGetPublic(state);
 	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
 
 	tupDesc = RelationGetDescr(arg->index.indexRel);
-	index_deform_tuple((IndexTuple)x, tupDesc, values, isnull);
+	index_deform_tuple((IndexTuple) x, tupDesc, values, isnull);
 	key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
 
 	ereport(ERROR,
 			(errcode(ERRCODE_UNIQUE_VIOLATION),
-			errmsg("could not create unique index \"%s\"",
-				   RelationGetRelationName(arg->index.indexRel)),
-			key_desc ? errdetail("Key %s is duplicated.", key_desc) :
-				errdetail("Duplicate keys exist."),
-				errtableconstraint(arg->index.heapRel,
-								   RelationGetRelationName(arg->index.indexRel))));
+			 errmsg("could not create unique index \"%s\"",
+					RelationGetRelationName(arg->index.indexRel)),
+			 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+			 errdetail("Duplicate keys exist."),
+			 errtableconstraint(arg->index.heapRel,
+								RelationGetRelationName(arg->index.indexRel))));
 }
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index 74a6a5ae5ce..380a106789c 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -158,19 +158,19 @@ typedef int (*SortTupleComparator) (const SortTuple *a, const SortTuple *b,
 /* Multi-key quick sort */
 
 typedef Datum
-(*MkqsGetDatumFunc) (SortTuple      *x,
-					 const int       tupleIndex,
-					 const int       depth,
-					 Tuplesortstate *state,
-					 Datum          *datum,
-					 bool           *isNull,
-					 bool            useFullKey);
+			(*MkqsGetDatumFunc) (SortTuple *x,
+								 const int tupleIndex,
+								 const int depth,
+								 Tuplesortstate *state,
+								 Datum *datum,
+								 bool *isNull,
+								 bool useFullKey);
 
 typedef void
-(*MkqsHandleDupFunc) (SortTuple      *x,
-					  const int       tupleCount,
-					  const bool      seenNull,
-					  Tuplesortstate *state);
+			(*MkqsHandleDupFunc) (SortTuple *x,
+								  const int tupleCount,
+								  const bool seenNull,
+								  Tuplesortstate *state);
 
 /*
  * The public part of a Tuple sort operation state.  This data structure
@@ -269,17 +269,15 @@ typedef struct
 
 	/*
 	 * Function pointer, referencing a function to get specified datum from
-	 * SortTuple list with multi-key.
-	 * Used by mk_qsort_tuple().
-	*/
+	 * SortTuple list with multi-key. Used by mk_qsort_tuple().
+	 */
 	MkqsGetDatumFunc mkqsGetDatumFunc;
 
 	/*
 	 * Function pointer, referencing a function to handle duplicated tuple
-	 * from SortTuple list with multi-key.
-	 * Used by mk_qsort_tuple().
-	 * For now, the function pointer is filled for only btree index tuple.
-	*/
+	 * from SortTuple list with multi-key. Used by mk_qsort_tuple(). For now,
+	 * the function pointer is filled for only btree index tuple.
+	 */
 	MkqsHandleDupFunc mkqsHandleDupFunc;
 } TuplesortPublic;
 
-- 
2.45.1

mksort-test.shapplication/x-shellscript; name=mksort-test.shDownload
example.sqlapplication/sql; name=example.sqlDownload
results.pdfapplication/pdf; name=results.pdfDownload
#9Yao Wang
yao-yw.wang@broadcom.com
In reply to: Tomas Vondra (#8)
Re: 回复: An implementation of multi-key sort

hi Tomas,

So many thanks for your kind response and detailed report. I am working
on locating issues based on your report/script and optimizing code, and
will update later.

Could you please also send me the script to generate report pdf
from the test results (explain*.log)? I can try to make one by myself,
but I'd like to get a report exactly the same as yours. It's really
helpful.

Thanks in advance.

Yao Wang

On Mon, Jun 10, 2024 at 5:09 AM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:

Hello Yao,

I was interested in the patch, considering the promise of significant
speedups of sorting, so I took a quick look and did some basic perf
testing today. Unfortunately, my benchmarks don't really confirm any
peformance benefits, so I haven't looked at the code very much and only
have some very basic feedback:

1) The new GUC is missing from the .sample config, triggering a failure
of "make check-world". Fixed by 0002.

2) There's a place mixing tabs/spaces in indentation. Fixed by 0003.

3) I tried running pgindent, mostly to see how that would affect the
comments, and for most it's probably fine, but a couple are mangled
(usually those with a numbered list of items). Might needs some changes
to use formatting that's not reformatted like this. The changes from
pgindent are in 0004, but this is not a fix - it just shows the changes
after running pgindent.

Now, regarding the performance tests - I decided to do the usual black
box testing, i.e. generate tables with varying numbers of columns, data
types, different data distribution (random, correlated, ...) and so on.
And then run simple ORDER BY queries on that, measuring timing with and
without mk-sort, and checking the effect.

So I wrote a simple bash script (attached) that does exactly that - it
generates a table with 1k - 10M rows, fills with with data (with some
basic simple data distributions), and then runs the queries.

The raw results are too large to attach, I'm only attaching a PDF
showing the summary with a "speedup heatmap" - it's a pivot with the
parameters on the left, and then the GUC and number on columns on top.
So the first group of columns is with enable_mk_sort=off, the second
group with enable_mk_sort=on, and finally the heatmap with relative
timing (enable_mk_sort=on / enable_mk_sort=off).

So values <100% mean it got faster (green color - good), and values

100% mean it got slower (red - bad). And the thing is - pretty much

everything is red, often in the 200%-300% range, meaning it got 2x-3x
slower. There's only very few combinations where it got faster. That
does not seem very promising ... but maybe I did something wrong?

After seeing this, I took a look at your example again, which showed
some nice speedups. But it seems very dependent on the order of keys in
the ORDER BY clause. For example consider this:

set enable_mk_sort = on;
explain (analyze, timing off)
select * from t1 order by c6, c5, c4, c3, c2, c1;

QUERY PLAN
-------------------------------------------------------------------
Sort (cost=72328.81..73578.81 rows=499999 width=76)
(actual rows=499999 loops=1)
Sort Key: c6, c5, c4, c3, c2, c1
Sort Method: quicksort Memory: 59163kB
-> Seq Scan on t1 (cost=0.00..24999.99 rows=499999 width=76)
(actual rows=499999 loops=1)
Planning Time: 0.054 ms
Execution Time: 1095.183 ms
(6 rows)

set enable_mk_sort = on;
explain (analyze, timing off)
select * from t1 order by c6, c5, c4, c3, c2, c1;

QUERY PLAN
-------------------------------------------------------------------
Sort (cost=72328.81..73578.81 rows=499999 width=76)
(actual rows=499999 loops=1)
Sort Key: c6, c5, c4, c3, c2, c1
Sort Method: multi-key quick sort Memory: 59163kB
-> Seq Scan on t1 (cost=0.00..24999.99 rows=499999 width=76)
(actual rows=499999 loops=1)
Planning Time: 0.130 ms
Execution Time: 633.635 ms
(6 rows)

Which seems great, but let's reverse the sort keys:

set enable_mk_sort = off;
explain (analyze, timing off)
select * from t1 order by c1, c2, c3, c4, c5, c6;

QUERY PLAN
-------------------------------------------------------------------

Sort (cost=72328.81..73578.81 rows=499999 width=76)
(actual rows=499999 loops=1)
Sort Key: c1, c2, c3, c4, c5, c6
Sort Method: quicksort Memory: 59163kB
-> Seq Scan on t1 (cost=0.00..24999.99 rows=499999 width=76)
(actual rows=499999 loops=1)
Planning Time: 0.146 ms
Execution Time: 170.085 ms
(6 rows)

set enable_mk_sort = off;
explain (analyze, timing off)
select * from t1 order by c1, c2, c3, c4, c5, c6;

QUERY PLAN
-------------------------------------------------------------------
Sort (cost=72328.81..73578.81 rows=499999 width=76)
(actual rows=499999 loops=1)
Sort Key: c1, c2, c3, c4, c5, c6
Sort Method: multi-key quick sort Memory: 59163kB
-> Seq Scan on t1 (cost=0.00..24999.99 rows=499999 width=76)
(actual rows=499999 loops=1)
Planning Time: 0.127 ms
Execution Time: 367.263 ms
(6 rows)

I believe this is the case Heikki was asking about. I see the response
was that it's OK and the overhead is very low, but without too much
detail so I don't know what case you measured.

Anyway, I think it seems to be very sensitive to the exact data set.
Which is not entirely surprising, I guess - most optimizations have a
mix of improved/regressed cases, yielding a heatmap with a mix of green
and red areas, and we have to either optimize the code (or heuristics to
enable the feature), or convince ourselves the "red" cases are less
important / unlikely etc.

But here the results are almost universally "red", so it's going to be
very hard to convince ourselves this is a good trade off. Of course, you
may argue the cases I've tested are wrong and not representative. I
don't think that's the case, though.

It's also interesting (and perhaps a little bit bizarre) that almost all
the cases that got better are for a single-column sort. Which is exactly
the case the patch should not affect. But it seems pretty consistent, so
maybe this is something worth investigating.

FWIW I'm not familiar with the various quicksort variants, but I noticed
that the Bentley & Sedgewick paper mentioned as the basis for the patch
is from 1997, and apparently implements stuff originally proposed by
Hoare in 1961. So maybe this is just an example of an algorithm that was
good for a hardware at that time, but the changes (e.g. the growing
important of on-CPU caches) made it less relevant?

Another thing I noticed while skimming [1] is this:

The algorithm is designed to exploit the property that in many
problems, strings tend to have shared prefixes.

If that's the case, isn't it wrong to apply this to all sorts, including
sorts with non-string keys? It might explain why your example works OK,
as it involves key c6 which is string with all values sharing the same
(fairly long) prefix. But then maybe we should be careful and restrict
this to only such those cases?

regards

[1] https://en.wikipedia.org/wiki/Multi-key_quicksort

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
This electronic communication and the information and any files transmitted
with it, or attached to it, are confidential and are intended solely for
the use of the individual or entity to whom it is addressed and may contain
information that is confidential, legally privileged, protected by privacy
laws, or otherwise restricted from disclosure to anyone else. If you are
not the intended recipient or the person responsible for delivering the
e-mail to the intended recipient, you are hereby notified that any use,
copying, distributing, dissemination, forwarding, printing, or copying of
this e-mail is strictly prohibited. If you received this e-mail in error,
please return the e-mail to the sender, delete it from your computer, and
destroy any printed copy of it.

#10Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Yao Wang (#9)
Re: 回复: An implementation of multi-key sort

On 6/14/24 13:20, Yao Wang wrote:

hi Tomas,

So many thanks for your kind response and detailed report. I am working
on locating issues based on your report/script and optimizing code, and
will update later.

Could you please also send me the script to generate report pdf
from the test results (explain*.log)? I can try to make one by myself,
but I'd like to get a report exactly the same as yours. It's really
helpful.

I don't have a script for that. I simply load the results into a
spreadsheet, do a pivot table to "aggregate and reshuffle" it a bit, and
then add a heatmap. I use google sheets for this, but any other
spreadsheet should handle this too, I think.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#11John Naylor
johncnaylorls@gmail.com
In reply to: Yao Wang (#9)
Re: 回复: An implementation of multi-key sort

On Fri, Jun 14, 2024 at 6:20 PM Yao Wang <yao-yw.wang@broadcom.com> wrote:

hi Tomas,

So many thanks for your kind response and detailed report. I am working
on locating issues based on your report/script and optimizing code, and
will update later.

Hi,
This is an interesting proof-of-concept!

Given the above, I've set this CF entry to "waiting on author".

Also, I see you've added Heikki as a reviewer. I'm not sure how others
think, but I consider a "reviewer" in the CF app to be someone who has
volunteered to be responsible to help move this patch forward. If
there is a name in the reviewer column, it may discourage others from
doing review. It also can happened that people ping reviewers to ask
"There's been no review for X months -- are you planning on looking at
this?", and it's not great if that message is a surprise.

Note that we prefer not to top-post in emails since it makes our web
archive more difficult to read.

Thanks,
John

#12Yao Wang
yao-yw.wang@broadcom.com
In reply to: John Naylor (#11)
3 attachment(s)
Re: 回复: An implementation of multi-key sort

Hi John,

Thanks for your kind message. I talked to Heikki before getting Tomas's
response, and he said "no promise but I will take a look". That's why I
added his email. I have updated the CF entry and added Tomas as reviewer.

Hi Tomas,

Again, I'd say a big thank to you. The report and script are really, really
helpful. And your ideas are very valuable.

Firstly, the expectation of mksort performance:

1. When mksort works well, it should be faster than qsort because it saves
the cost of comparing duplicated values every time.
2. When all values are distinct at a particular column, the comparison
will finish immediately, and mksort will actually fall back to qsort. For
the case, mksort should be equal or a bit slower than qsort because it need
to maintain more complex state.

Generally, the benefit of mksort is mainly from duplicated values and sort
keys: the more duplicated values and sort keys are, the bigger benefit it
gets.

Analysis on the report in your previous mail
--------------------------------------------

1. It seems the script uses $count to specify the duplicated values:

number of repetitions for each value (ndistinct = nrows/count)

However, it is not always correct. For type text, the script generates
values like this:

expr="md5(((i / $count) + random())::text)"

But md5() generates totally random values regardless of $count. Some cases
of timestamptz have the same problem.

For all distinct values, the sort will finish at first depth and fall to
qsort actually.

2. Even for the types with correct duplicated setting, the duplicated ratio
is very small: e.g. say $nrows = 10000 and $count = 100, only 1% duplicated
rows can go to depth 2, and only 0.01% of them can go to depth 3. So it still
works on nearly all distinct values.

3. Qsort of PG17 uses kind of specialization for tuple comparator, i.e. it
uses specialized functions for different types, e.g. qsort_tuple_unsigned()
for unsigned int. The specialized comparators avoid all type related checks
and are much faster than regular comparator. That is why we saw 200% or more
regression for the cases.

Code optimizations I did for mk qsort
-------------------------------------

1. Adapted specialization for tuple comparator.
2. Use kind of "hybrid" sort: when we actually adapt bubble sort due to
limited sort items, use bubble sort to check datums since specified depth.
3. Other other optimizations such as pre-ordered check.

Analysis on the new report
--------------------------

I also did some modifications to your script about the issues of data types,
plus an output about distinct value count/distinct ratio, and an indicator
for improvement/regression. I attached the new script and a report on a
data set with 100,000 rows and 2, 5, 8 columns.

1. Generally, the result match the expectation: "When mksort works well, it
should be faster than qsort; when mksort falls to qsort, it should be equal
or a bit slower than qsort."
2. For all values of "sequential" (except text type), mksort is a bit slower
than qsort because no actual sort is performed due to the "pre-ordered"
check.
3. For int and bigint type, mksort became faster and faster when
there were more and more duplicated values and sort keys. Improvement of
the best cases is about 58% (line 333) and 57% (line 711).
4. For timestamptz type, mksort is a bit slower than qsort because the
distinct ratio is always 1 for almost all cases. I think more benefit is
available by increasing the duplicated values.
5. For text type, mksort is faster than qsort for all cases, and
improvement of the best case is about 160% (line 1510). It is the only
tested type in which specialization comparators are disabled.

Obviously, text has much better improvement than others. I suppose the cause
is about the specialisation comparators: for the types with them, the
comparing is too faster so the cost saved by mksort is not significant. Only
when saved cost became big enough, mksort can defeat qsort.

For other types without specialisation comparators, mksort can defeat
qsort completely. It is the "real" performance of mksort.

Answers for some other questions you mentioned
----------------------------------------------

Q1: Why are almost all the cases that got better for a single-column sort?

A: mksort is enabled only for multi column sort. When there is only one
column, qsort works. So we can simply ignore the cases.

Q2: Why did the perf become worse by just reversing the sort keys?

A: In the example we used, the sort keys are ordered from more duplicated
to less. Please see the SQL:

update t1 set c2 = c1 % 100, c3 = c1 % 50, c4 = c1 % 10, c5 = c1 % 3;
update t1 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (c1 % 5)::text;

So c6 has most duplicated values, c5 has less, and so on. By the order
"C6, c5, c4, ...", mksort can take effect on every sort key.

By the reverse order "c2, c3, c4...", mksort almost finished on first
sort key (c2) because it has only 1% duplicated values, and fell back
to qsort actually.

Based on the new code, I reran the example, and got about 141% improvement
for order "c6, c5, c4...", and about -4% regression for order
"c2, c3, c4...".

Q3: Does mksort work effectively only for particular type, e.g. string?

A: No, the implementation of mksort does not distinguish data type for
special handling. It just calls existing comparators which are also
used by qsort. I used long prefix for string just to enlarge the time
cost of comparing to amplify the result. The new report shows mksort
can work effectively on non-string types and string without long prefix.

Q4: Was the algorithm good for a hardware at that time, but the changes
(e.g. the growing important of on-CPU caches) made it less relevant?

A: As my understanding, the answer is no because the benefit of mksort
is from saving cost for duplicated comparison, which is not related to
hardware. I suppose the new report can prove it.

However, the hardware varying definitely affects the perf, especially
considering that the perf different between mksort and qsort is not so
big when mksort falls back to qsort. I am not able to test on a wide
range of hardwares, so any finding is appreciated.

Potential improvement spaces
----------------------------

I tried some other optimizations but didn't add the code finally because
the benefit is not very sure and/or the implementation is complex. Just
raise them for more discussion if necessary:

1. Use distinct stats info of table to enable mksort

It's kind of heuristics: in optimizer, check Form_pg_statistic->stadistinct
of a table via pg_statistics. Enable mksort only when it is less than a
threshold.

The hacked code works, which need to modify a couple of interfaces of
optimizer. In addition, a complete solution should consider types and
distinct values of all columns, which might be too complex, and the benefit
seems not so big.

2. Cache of datum positions

e.g. for heap tuple, we need to extract datum position from SortTuple by
extract_heaptuple_from_sorttuple() for comparing, which is executed
for each datum. By comparison, qsort does it once for each tuple.
Theoretically we can create a cache to remember the datum positions to
avoid duplicated extracting.

The hacked code works, but the improvement seems limited. Not sure if more
improvement space is available.

3. Template mechanism

Qsort uses kind of template mechanism by macro (see sort_template.h), which
avoids cost of runtime type check. Theoretically template mechanism can be
applied to mksort, but I am hesitating because it will impose more complexity
and the code will become difficult to maintain.

Please let me know your opinion, thanks!

Yao Wang

--
This electronic communication and the information and any files transmitted
with it, or attached to it, are confidential and are intended solely for
the use of the individual or entity to whom it is addressed and may contain
information that is confidential, legally privileged, protected by privacy
laws, or otherwise restricted from disclosure to anyone else. If you are
not the intended recipient or the person responsible for delivering the
e-mail to the intended recipient, you are hereby notified that any use,
copying, distributing, dissemination, forwarding, printing, or copying of
this e-mail is strictly prohibited. If you received this e-mail in error,
please return the e-mail to the sender, delete it from your computer, and
destroy any printed copy of it.

Attachments:

new_report.txttext/plain; charset=US-ASCII; name=new_report.txtDownload
mksort-test.shapplication/x-sh; name=mksort-test.shDownload
v5-Implement-multi-key-quick-sort.patchapplication/octet-stream; name=v5-Implement-multi-key-quick-sort.patchDownload
From 49e4c3804e42e524358545d9409f5bd905a60c6c Mon Sep 17 00:00:00 2001
From: Yao Wang <yaowangm@outlook.com>
Date: Tue, 7 May 2024 08:11:13 +0000
Subject: [PATCH] Implement multi-key quick sort

MK qsort (multi-key quick sort) is an alternative of standard qsort algorithm,
which has better performance for particular sort scenarios, i.e. the data set
has multiple keys to be sorted. Comparing to classic quick sort, it can get
significant performance improvement once multiple keys are available.

Author: Yao Wang <yao-yw.wang@broadcom.com>
Co-author: Hongxu Ma <hongxu.ma@broadcom.com>
---
 src/backend/utils/misc/guc_tables.c           |  11 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/backend/utils/sort/mk_qsort_tuple.c       | 711 ++++++++++++++++++
 src/backend/utils/sort/tuplesort.c            |  62 ++
 src/backend/utils/sort/tuplesortvariants.c    | 297 +++++++-
 src/include/c.h                               |   4 +
 src/include/utils/tuplesort.h                 |  46 +-
 src/test/regress/expected/geometry.out        |   4 +-
 .../regress/expected/incremental_sort.out     |  12 +-
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/expected/tuplesort.out       | 409 ++++++++++
 src/test/regress/expected/window.out          |  58 +-
 src/test/regress/sql/geometry.sql             |   2 +-
 src/test/regress/sql/tuplesort.sql            |  85 +++
 src/test/regress/sql/window.sql               |  22 +-
 15 files changed, 1641 insertions(+), 86 deletions(-)
 create mode 100644 src/backend/utils/sort/mk_qsort_tuple.c

diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 3fd0b14dd8..5aee20f422 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -103,6 +103,7 @@ extern char *default_tablespace;
 extern char *temp_tablespaces;
 extern bool ignore_checksum_failure;
 extern bool ignore_invalid_pages;
+extern bool enable_mk_sort;
 
 #ifdef TRACE_SYNCSCAN
 extern bool trace_syncscan;
@@ -839,6 +840,16 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_mk_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables multi-key sort"),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_mk_sort,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 2166ea4a87..e1bf50370e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -413,6 +413,7 @@
 #enable_sort = on
 #enable_tidscan = on
 #enable_group_by_reordering = on
+#enable_mk_sort = on
 
 # - Planner Cost Constants -
 
diff --git a/src/backend/utils/sort/mk_qsort_tuple.c b/src/backend/utils/sort/mk_qsort_tuple.c
new file mode 100644
index 0000000000..032c77794f
--- /dev/null
+++ b/src/backend/utils/sort/mk_qsort_tuple.c
@@ -0,0 +1,711 @@
+/*
+ * MK qsort (multi-key quick sort) is an alternative of standard qsort
+ * algorithm, which has better performance for particular sort scenarios, i.e.
+ * the data set has multiple keys to be sorted.
+ *
+ * The sorting algorithm blends Quicksort and radix sort; Like regular
+ * Quicksort, it partitions its input into sets less than and greater than a
+ * given value; like radix sort, it moves on to the next field once the current
+ * input is known to be equal in the given field.
+ *
+ * The implementation is based on the paper:
+ *   Jon L. Bentley and Robert Sedgewick, "Fast Algorithms for Sorting and
+ *   Searching Strings", Jan 1997
+ *
+ * Some improvements which is related to additional handling for equal tuples
+ * have been adapted to keep consistency with the implementations of postgres
+ * qsort.
+ *
+ * For now, mk_qsort_tuple() is called in tuplesort_sort_memtuples() as a
+ * replacement of qsort_tuple() when specific conditions are satisfied.
+ */
+
+/* Swap two tuples in sort tuple array */
+static inline void
+mkqs_swap(int a,
+		  int b,
+		  SortTuple *x)
+{
+	SortTuple	t;
+
+	if (a == b)
+		return;
+	t = x[a];
+	x[a] = x[b];
+	x[b] = t;
+}
+
+/* Swap tuples by batch in sort tuple array */
+static inline void
+mkqs_vec_swap(int a,
+			  int b,
+			  int size,
+			  SortTuple *x)
+{
+	while (size-- > 0)
+	{
+		mkqs_swap(a, b, x);
+		a++;
+		b++;
+	}
+}
+
+/*
+ * Check whether current datum (at specified tuple and depth) is null
+ * Note that the input x means a specified tuple provided by caller but not
+ * a tuple array, so tupleIndex is unnecessary.
+ */
+static inline bool
+check_datum_null(SortTuple *x,
+				 int depth,
+				 Tuplesortstate *state)
+{
+	Datum		datum;
+	bool		isNull;
+
+	Assert(depth < state->base.nKeys);
+
+	if (depth == 0)
+		return x->isnull1;
+
+	state->base.mkqsGetDatumFunc(x, NULL, depth, state,
+								 &datum, &isNull, NULL, NULL);
+
+	return isNull;
+}
+
+/*
+ * Compare two tuples at specified depth
+ *
+ * If "abbreviated key" is disabled:
+ *   get specified datums and compare them by ApplySortComparator().
+ * If "abbreviated key" is enabled:
+ *   Only first datum may be abbr key according to the design (see the comments
+ *   of struct SortTuple), so different operations are needed for different
+ *   datum.
+ *   For first datum (depth == 0): get first datums ("abbr key" version) and
+ *   compare them by ApplySortComparator(). If they are equal, get "full"
+ *   version and compare again by ApplySortAbbrevFullComparator().
+ *   For other datums: get specified datums and compare them by
+ *   ApplySortComparator() as regular routine does.
+ *
+ * See comparetup_heap() for details.
+ */
+static inline int
+mkqs_compare_datum_tiebreak(SortTuple *tuple1,
+							SortTuple *tuple2,
+							int depth,
+							Tuplesortstate *state)
+{
+	Datum		datum1,
+				datum2;
+	bool		isNull1,
+				isNull2;
+	SortSupport sortKey;
+	int			ret = 0;
+
+	Assert(state->base.mkqsGetDatumFunc);
+	Assert(depth < state->base.nKeys);
+
+	sortKey = state->base.sortKeys + depth;
+	state->base.mkqsGetDatumFunc(tuple1,
+								 tuple2,
+								 depth,
+								 state,
+								 &datum1,
+								 &isNull1,
+								 &datum2,
+								 &isNull2);
+
+	/*
+	 * If "abbreviated key" is enabled, and we are in the first depth, it
+	 * means only "abbreviated keys" was compared. If the two datums were
+	 * determined to be equal by ApplySortComparator() in
+	 * mkqs_compare_datum(), we need to perform an extra "full" comparing
+	 * by ApplySortAbbrevFullComparator().
+	 */
+	if (sortKey->abbrev_converter &&
+		depth == 0)
+		ret = ApplySortAbbrevFullComparator(datum1,
+											isNull1,
+											datum2,
+											isNull2,
+											sortKey);
+	else
+		ret = ApplySortComparator(datum1,
+								  isNull1,
+								  datum2,
+								  isNull2,
+								  sortKey);
+
+
+	return ret;
+}
+
+/*
+ * Compare two tuples at first depth by some shortcuts
+ *
+ * The reason to use MkqsCompFuncType but not compare function pointers
+ * directly is just for performance.
+ */
+static inline int
+mkqs_compare_datum_by_shortcut(SortTuple      *tuple1,
+							   SortTuple      *tuple2,
+							   Tuplesortstate *state)
+{
+	int ret = 0;
+	MkqsCompFuncType compFuncType = state->base.mkqsCompFuncType;
+	SortSupport sortKey = &state->base.sortKeys[0];
+
+	if (compFuncType == MKQS_COMP_FUNC_UNSIGNED)
+		ret = ApplyUnsignedSortComparator(tuple1->datum1,
+										  tuple1->isnull1,
+										  tuple2->datum1,
+										  tuple2->isnull1,
+										  sortKey);
+	else if (compFuncType == MKQS_COMP_FUNC_SIGNED)
+		ret = ApplySignedSortComparator(tuple1->datum1,
+										tuple1->isnull1,
+										tuple2->datum1,
+										tuple2->isnull1,
+										sortKey);
+	else if (compFuncType == MKQS_COMP_FUNC_INT32)
+		ret = ApplyInt32SortComparator(tuple1->datum1,
+									   tuple1->isnull1,
+									   tuple2->datum1,
+									   tuple2->isnull1,
+									   sortKey);
+	else
+	{
+		Assert(compFuncType == MKQS_COMP_FUNC_GENERIC);
+		ret = ApplySortComparator(tuple1->datum1,
+								  tuple1->isnull1,
+								  tuple2->datum1,
+								  tuple2->isnull1,
+								  sortKey);
+	}
+
+	return ret;
+}
+
+/*
+ * Compare two tuples at specified depth
+ *
+ * Firstly try to call some shortcuts by mkqs_compare_datum_by_shortcut(),
+ * which are much faster because they just compare leading sort keys; if they
+ * are equal, call mkqs_compare_datum_tiebreak().
+ *
+ * The reason to use MkqsCompFuncType but not compare function pointers
+ * directly is just for performance.
+ *
+ * See comparetup_heap() for details.
+ */
+static inline int
+mkqs_compare_datum(SortTuple *tuple1,
+				   SortTuple *tuple2,
+				   int depth,
+				   Tuplesortstate *state)
+{
+	int			ret = 0;
+
+	if (depth == 0)
+	{
+		ret = mkqs_compare_datum_by_shortcut(tuple1, tuple2, state);
+
+		if (ret != 0)
+			return ret;
+
+		/*
+		 * If they are equal and it is not an abbr key, no need to
+		 * continue.
+		 */
+		if (!state->base.sortKeys->abbrev_converter)
+			return ret;
+	}
+
+	ret = mkqs_compare_datum_tiebreak(tuple1,
+									  tuple2,
+									  depth,
+									  state);
+
+	return ret;
+}
+
+/* Find the median of three values */
+static inline int
+get_median_from_three(int a,
+					  int b,
+					  int c,
+					  SortTuple *x,
+					  int depth,
+					  Tuplesortstate *state)
+{
+	return mkqs_compare_datum(x + a, x + b, depth, state) < 0 ?
+			 (mkqs_compare_datum(x + b, x + c, depth, state) < 0 ?
+				b : (mkqs_compare_datum(x + a, x + c, depth, state) < 0 ? c : a))
+			 : (mkqs_compare_datum(x + b, x + c, depth, state) > 0 ?
+				b : (mkqs_compare_datum(x + a, x + c, depth, state) < 0 ? a : c));
+}
+
+/*
+ * Compare two tuples by starting specified depth till latest depth
+ *
+ * Caller should guarantee that all datums before specified depth
+ * are equal. The function is used by bubble sort in the middle of
+ * mk qsort.
+ */
+static inline int
+mkqs_compare_tuple_by_range(SortTuple *tuple1,
+							SortTuple *tuple2,
+							int depth,
+							Tuplesortstate *state)
+{
+	int			ret = 0;
+	Datum		datum1,
+				datum2;
+	bool		isNull1,
+				isNull2;
+	SortSupport sortKey;
+	TuplesortPublic *base = NULL;
+
+	if (depth == 0)
+	{
+		ret = mkqs_compare_datum_by_shortcut(tuple1, tuple2, state);
+
+		if (ret != 0)
+			return ret;
+
+		base = TuplesortstateGetPublic(state);
+		sortKey = base->sortKeys + depth;
+
+		Assert(base->mkqsGetDatumFunc);
+		Assert(depth < base->nKeys);
+
+		/*
+		 * If "abbreviated key" is enabled, and we are in the first depth, it
+		 * means only "abbreviated keys" was compared. If the two datums were
+		 * determined to be equal by ApplySortComparator() in
+		 * mkqs_compare_datum(), we need to perform an extra "full" comparing
+		 * by ApplySortAbbrevFullComparator().
+		 */
+		if (sortKey->abbrev_converter)
+		{
+			base->mkqsGetDatumFunc(tuple1,
+								   tuple2,
+								   depth,
+								   state,
+								   &datum1,
+								   &isNull1,
+								   &datum2,
+								   &isNull2);
+			ret = ApplySortAbbrevFullComparator(datum1,
+												isNull1,
+												datum2,
+												isNull2,
+												sortKey);
+			if (ret != 0)
+				return ret;
+		}
+
+		/*
+		 * By now, all works about first depth have been down. Move the
+		 * depth and sortKey to next level.
+		 */
+		depth++;
+		sortKey++;
+	}
+
+	/*
+	 * Init base/sortKey because they may not have been initialized
+	 * if the init depth > 1
+	 */
+	if (base == NULL) {
+		base = TuplesortstateGetPublic(state);
+		sortKey = base->sortKeys + depth;
+
+		Assert(base->mkqsGetDatumFunc);
+		Assert(depth < base->nKeys);
+	}
+
+	while (depth < base->nKeys)
+	{
+		base->mkqsGetDatumFunc(tuple1,
+							   tuple2,
+							   depth,
+							   state,
+							   &datum1,
+							   &isNull1,
+							   &datum2,
+							   &isNull2);
+
+		ret = ApplySortComparator(datum1,
+								  isNull1,
+								  datum2,
+								  isNull2,
+								  sortKey);
+
+		if (ret != 0)
+			return ret;
+
+		depth++;
+		sortKey++;
+	}
+
+	Assert(ret == 0);
+	return 0;
+}
+
+/*
+ * Compare two tuples by using interfaces of qsort()
+ */
+static inline int
+mkqs_compare_tuple(SortTuple *a, SortTuple *b, Tuplesortstate *state)
+{
+	int ret = 0;
+	MkqsCompFuncType compFuncType = state->base.mkqsCompFuncType;
+
+	/*
+	 * The function should never be called with
+	 * MKQS_COMP_FUNC_GENERIC
+	 */
+	Assert(compFuncType != MKQS_COMP_FUNC_GENERIC);
+
+	if (compFuncType == MKQS_COMP_FUNC_UNSIGNED)
+		ret = qsort_tuple_unsigned_compare(a, b, state);
+	else if (compFuncType == MKQS_COMP_FUNC_SIGNED)
+		ret = qsort_tuple_signed_compare(a, b, state);
+	else if (compFuncType == MKQS_COMP_FUNC_INT32)
+		ret = qsort_tuple_int32_compare(a, b, state);
+	else
+		Assert(false);
+
+	return ret;
+}
+
+#ifdef USE_ASSERT_CHECKING
+/*
+ * Verify whether the SortTuple list is ordered or not at specified depth
+ */
+static void
+mkqs_verify(SortTuple *x,
+			int n,
+			int depth,
+			Tuplesortstate *state)
+{
+	int			ret;
+
+	for (int i = 0; i < n - 1; i++)
+	{
+		ret = mkqs_compare_datum(x + i,
+								 x + i + 1,
+								 depth,
+								 state);
+		Assert(ret <= 0);
+	}
+}
+#endif
+
+/*
+ * Major of multi-key quick sort
+ *
+ * seenNull indicates whether we have seen NULL in any datum we checked
+ */
+static void
+mk_qsort_tuple(SortTuple *x,
+			   size_t n,
+			   int depth,
+			   Tuplesortstate *state,
+			   bool seenNull)
+{
+	/*
+	 * In the process, the tuple array consists of five parts: left equal,
+	 * less, not-processed, greater, right equal
+	 *
+	 * lessStart indicates the first position of less part lessEnd indicates
+	 * the next position after less part greaterStart indicates the prior
+	 * position before greater part greaterEnd indicates the latest position
+	 * of greater part the range between lessEnd and greaterStart (inclusive)
+	 * is not-processed
+	 */
+	int			lessStart,
+				lessEnd,
+				greaterStart,
+				greaterEnd,
+				tupCount;
+	int32		dist;
+	SortTuple  *pivot;
+	bool		isDatumNull;
+
+	Assert(depth <= state->base.nKeys);
+	Assert(state->base.sortKeys);
+	Assert(state->base.mkqsGetDatumFunc);
+
+	if (n <= 1)
+		return;
+
+	/* If we have exceeded the max depth, return immediately */
+	if (depth == state->base.nKeys)
+		return;
+
+	CHECK_FOR_INTERRUPTS();
+
+	/* Pre-ordered check */
+	if (state->base.mkqsCompFuncType != MKQS_COMP_FUNC_GENERIC)
+	{
+		/*
+		 * If there is specialized comparator for the type, use classic
+		 * pre-ordered check by comparing the entire tuples.
+		 * The check is performed only for first depth since we compare
+		 * entire tuples but not datums.
+		 */
+		if (depth == 0)
+		{
+			int ret;
+			bool preOrdered = true;
+
+			for (int i = 0; i < n - 1; i++)
+			{
+
+				CHECK_FOR_INTERRUPTS();
+				ret = mkqs_compare_tuple(x + i, x + i + 1, state);
+				if (ret > 0)
+				{
+					preOrdered = false;
+					break;
+				}
+			}
+
+			if (preOrdered)
+				return;
+		}
+	}
+	else
+	{
+		/*
+		 * If there is no specialized comparator for the type, perform
+		 * pre-ordered check by comparing datums at each depth.
+		 *
+		 * Different from qsort_tuple(), the array must be strict ordered (no
+		 * equal datums). If there are equal datums, we must continue the mk
+		 * qsort process to check datums on lower depth.
+		 *
+		 * Note uniqueness check is unnecessary here because strict ordered
+		 * array guarantees no duplicate.
+		 */
+		int ret;
+		bool strictOrdered = true;
+
+		for (int i = 0; i < n - 1; i++)
+		{
+			CHECK_FOR_INTERRUPTS();
+			ret = mkqs_compare_datum(x + i,
+									 x + i + 1,
+									 depth,
+									 state);
+			if (ret >= 0)
+			{
+				strictOrdered = false;
+				break;
+			}
+		}
+
+		if (strictOrdered)
+			return;
+	}
+
+	/*
+	 * When the count < 16 and no need to handle duplicated tuples, use
+	 * bubble sort.
+	 *
+	 * Use 16 instead of 7 which is used in standard qsort, because mk qsort
+	 * need more cost to maintain more complex state.
+	 *
+	 * Bubble sort is not applicable for scenario of handle duplicated tuples
+	 * because it is difficult to check NULL effectively.
+	 *
+	 * No need to check for interrupts since the data size is pretty small.
+	 *
+	 * TODO: Can we check NULL for bubble sort with minimal cost?
+	 */
+	if (n < 16 && !state->base.mkqsHandleDupFunc)
+	{
+		for (int m = 0;m < n;m++)
+			for (int l = m; l > 0; l--)
+			{
+				if (mkqs_compare_tuple_by_range(x + l - 1, x + l, depth, state)
+					<= 0)
+					break;
+				mkqs_swap(l, l - 1, x);
+			}
+		return;
+	}
+
+	/* Select pivot by random and move it to the first position */
+	if (n > 7)
+	{
+		int m, l, r, d;
+		m = n / 2;
+		l = 0;
+		r = n - 1;
+		if (n > 40)
+		{
+			d = n / 8;
+			l = get_median_from_three(l, l + d, l + 2 * d, x, depth, state);
+			m = get_median_from_three(m - d, m, m + d, x, depth, state);
+			r = get_median_from_three(r - 2 * d, r - d, r, x, depth, state);
+		}
+		lessStart = get_median_from_three(l, m, r, x, depth, state);
+	}
+	else
+		lessStart = n / 2;
+
+	mkqs_swap(0, lessStart, x);
+	pivot = x;
+
+	lessStart = 1;
+	lessEnd = 1;
+	greaterStart = n - 1;
+	greaterEnd = n - 1;
+
+	/* Sort the array to three parts: lesser, equal, greater */
+	while (true)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		/* Compare the left end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare lessEnd and pivot at current depth */
+			dist = mkqs_compare_datum(x + lessEnd,
+									  pivot,
+									  depth,
+									  state);
+
+			if (dist > 0)
+				break;
+
+			/* If lessEnd is equal to pivot, move it to lessStart */
+			if (dist == 0)
+			{
+				mkqs_swap(lessEnd, lessStart, x);
+				lessStart++;
+			}
+			lessEnd++;
+		}
+
+		/* Compare the right end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare greaterStart and pivot at current depth */
+			dist = mkqs_compare_datum(x + greaterStart,
+									  pivot,
+									  depth,
+									  state);
+
+			if (dist < 0)
+				break;
+
+			/* If greaterStart is equal to pivot, move it to greaterEnd */
+			if (dist == 0)
+			{
+				mkqs_swap(greaterStart, greaterEnd, x);
+				greaterEnd--;
+			}
+			greaterStart--;
+		}
+
+		if (lessEnd > greaterStart)
+			break;
+		mkqs_swap(lessEnd, greaterStart, x);
+		lessEnd++;
+		greaterStart--;
+	}
+
+	/*
+	 * Now the array has four parts: left equal, lesser, greater, right equal
+	 * Note greaterStart is less than lessEnd now
+	 */
+
+	/* Move the left equal part to middle */
+	dist = Min(lessStart, lessEnd - lessStart);
+	mkqs_vec_swap(0, lessEnd - dist, dist, x);
+
+	/* Move the right equal part to middle */
+	dist = Min(greaterEnd - greaterStart, n - greaterEnd - 1);
+	mkqs_vec_swap(lessEnd, n - dist, dist, x);
+
+	/*
+	 * Now the array has three parts: lesser, equal, greater Note that one or
+	 * two parts may have no element at all.
+	 */
+
+	/* Recursively sort the lesser part */
+
+	/* dist means the size of less part */
+	dist = lessEnd - lessStart;
+	mk_qsort_tuple(x,
+				   dist,
+				   depth,
+				   state,
+				   seenNull);
+
+	/* Recursively sort the equal part */
+
+	/*
+	 * (x + dist) means the first tuple in the equal part Since all tuples
+	 * have equal datums at current depth, we just check any one of them to
+	 * determine whether we have seen null datum.
+	 */
+	isDatumNull = check_datum_null(x + dist, depth, state);
+
+	/* (lessStart + n - greaterEnd - 1) means the size of equal part */
+	tupCount = lessStart + n - greaterEnd - 1;
+
+	if (depth < state->base.nKeys - 1)
+	{
+		mk_qsort_tuple(x + dist,
+					   tupCount,
+					   depth + 1,
+					   state,
+					   seenNull || isDatumNull);
+	}
+	else
+	{
+		/*
+		 * We have reach the max depth: Call mkqsHandleDupFunc to handle
+		 * duplicated tuples if necessary, e.g. checking uniqueness or extra
+		 * comparing
+		 */
+
+		/*
+		 * Call mkqsHandleDupFunc if:
+		 *  1. mkqsHandleDupFunc is filled
+		 *  2. the size of equal part > 1
+		 */
+		if (state->base.mkqsHandleDupFunc &&
+			(tupCount > 1))
+		{
+			state->base.mkqsHandleDupFunc(x + dist,
+										  tupCount,
+										  seenNull || isDatumNull,
+										  state);
+		}
+	}
+
+	/* Recursively sort the greater part */
+
+	/* dist means the size of greater part */
+	dist = greaterEnd - greaterStart;
+	mk_qsort_tuple(x + n - dist,
+				   dist,
+				   depth,
+				   state,
+				   seenNull);
+
+#ifdef USE_ASSERT_CHECKING
+	mkqs_verify(x,
+				n,
+				depth,
+				state);
+#endif
+}
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 7c4d6dc106..6dd21a2710 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -128,6 +128,7 @@ bool		trace_sort = false;
 bool		optimize_bounded_sort = true;
 #endif
 
+bool		enable_mk_sort = true;
 
 /*
  * During merge, we use a pre-allocated set of fixed-size slots to hold
@@ -337,6 +338,9 @@ struct Tuplesortstate
 #ifdef TRACE_SORT
 	PGRUsage	ru_start;
 #endif
+
+	/* Whether multi-key quick sort is used */
+	bool		mkqsUsed;
 };
 
 /*
@@ -622,6 +626,8 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
 #define ST_DEFINE
 #include "lib/sort_template.h"
 
+#include "mk_qsort_tuple.c"
+
 /*
  *		tuplesort_begin_xxx
  *
@@ -690,6 +696,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate, int sortopt)
 	state->base.sortopt = sortopt;
 	state->base.tuples = true;
 	state->abbrevNext = 10;
+	state->mkqsUsed = false;
 
 	/*
 	 * workMem is forced to be at least 64KB, the current minimum valid value
@@ -2559,6 +2566,8 @@ tuplesort_get_stats(Tuplesortstate *state,
 		case TSS_SORTEDINMEM:
 			if (state->boundUsed)
 				stats->sortMethod = SORT_TYPE_TOP_N_HEAPSORT;
+			else if (state->mkqsUsed)
+				stats->sortMethod = SORT_TYPE_MK_QSORT;
 			else
 				stats->sortMethod = SORT_TYPE_QUICKSORT;
 			break;
@@ -2592,6 +2601,8 @@ tuplesort_method_name(TuplesortMethod m)
 			return "external sort";
 		case SORT_TYPE_EXTERNAL_MERGE:
 			return "external merge";
+		case SORT_TYPE_MK_QSORT:
+			return "multi-key quick sort";
 	}
 
 	return "unknown";
@@ -2717,6 +2728,57 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 
 	if (state->memtupcount > 1)
 	{
+		/*
+		 * Apply multi-key quick sort when:
+		 *  1. enable_mk_sort is set
+		 *  2. There are multiple keys available
+		 *  3. mkqsGetDatumFunc is filled, which implies that current tuple
+		 *     type is supported by mk qsort. (By now only Heap tuple and Btree
+		 *     Index tuple are supported, and more types may be supported in
+		 *     future.)
+		 *
+		 * A summary of tuple types supported by mk qsort:
+		 *
+		 *  HeapTuple: supported
+		 *  IndexTuple(btree): supportedi
+		 *  IndexTuple(hash): not supported because there is only one key
+		 *  DatumTuple: not supported because there is only one key
+		 *  HeapTuple(for cluster): not supported yet
+		 *  IndexTuple(gist): not supported yet
+		 *  IndexTuple(brin): not supported yet
+		 */
+		if (enable_mk_sort &&
+			state->base.nKeys > 1 &&
+			state->base.mkqsGetDatumFunc != NULL)
+		{
+			/*
+			 * Set relevant Datum Sort Comparator according to concrete data type
+			 * of the first sort key
+			 */
+			state->base.mkqsCompFuncType = MKQS_COMP_FUNC_GENERIC;
+			if (state->base.haveDatum1)
+			{
+				if (state->base.sortKeys[0].comparator == ssup_datum_unsigned_cmp)
+					state->base.mkqsCompFuncType = MKQS_COMP_FUNC_UNSIGNED;
+#if SIZEOF_DATUM >= 8
+				else if (state->base.sortKeys[0].comparator == ssup_datum_signed_cmp)
+					state->base.mkqsCompFuncType = MKQS_COMP_FUNC_SIGNED;
+#endif
+				else if (state->base.sortKeys[0].comparator == ssup_datum_int32_cmp)
+					state->base.mkqsCompFuncType = MKQS_COMP_FUNC_INT32;
+			}
+
+			state->mkqsUsed = true;
+
+			mk_qsort_tuple(state->memtuples,
+						   state->memtupcount,
+						   0,
+						   state,
+						   false);
+
+			return;
+		}
+
 		/*
 		 * Do we have the leading column's value or abbreviation in datum1,
 		 * and is there a specialization for its comparator?
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 05a853caa3..d1c5efe575 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -30,6 +30,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
+#include "miscadmin.h"
 
 
 /* sort-type codes for sort__start probes */
@@ -92,6 +93,48 @@ static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
 						  LogicalTape *tape, unsigned int len);
 static void freestate_cluster(Tuplesortstate *state);
 
+static void mkqs_get_datum_heap(const SortTuple *x1,
+								const SortTuple *x2,
+								const int depth,
+								Tuplesortstate *state,
+								Datum *datum1,
+								bool *isNull1,
+								Datum *datum2,
+								bool *isNull2);
+
+static void
+mkqs_get_datum_index_btree(const SortTuple *x1,
+						   const SortTuple *x2,
+						   const int depth,
+						   Tuplesortstate *state,
+						   Datum *datum1,
+						   bool *isNull1,
+						   Datum *datum2,
+						   bool *isNull2);
+
+static void
+			mkqs_handle_dup_index_btree(SortTuple *x,
+										const int tupleCount,
+										const bool seenNull,
+										Tuplesortstate *state);
+
+static int
+			mkqs_compare_equal_index_btree(const SortTuple *a,
+										   const SortTuple *b,
+										   Tuplesortstate *state);
+
+static pg_attribute_always_inline void
+extract_heaptuple_from_sorttuple(const SortTuple *sortTuple,
+								 HeapTupleData *heapTuple);
+
+static inline int
+			tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+											  const IndexTuple tuple2);
+
+static inline void
+			raise_error_of_dup_index(IndexTuple x,
+									 Tuplesortstate *state);
+
 /*
  * Data structure pointed by "TuplesortPublic.arg" for the CLUSTER case.  Set by
  * the tuplesort_begin_cluster.
@@ -163,6 +206,14 @@ typedef struct BrinSortTuple
 /* Size of the BrinSortTuple, given length of the BrinTuple. */
 #define BRINSORTTUPLE_SIZE(len)		(offsetof(BrinSortTuple, tuple) + (len))
 
+#define ST_SORT qsort_tuple_by_itempointer
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) mkqs_compare_equal_index_btree(a, b, state)
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
 
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
@@ -200,6 +251,7 @@ tuplesort_begin_heap(TupleDesc tupDesc,
 	base->removeabbrev = removeabbrev_heap;
 	base->comparetup = comparetup_heap;
 	base->comparetup_tiebreak = comparetup_heap_tiebreak;
+	base->mkqsGetDatumFunc = mkqs_get_datum_heap;
 	base->writetup = writetup_heap;
 	base->readtup = readtup_heap;
 	base->haveDatum1 = true;
@@ -388,6 +440,8 @@ tuplesort_begin_index_btree(Relation heapRel,
 	base->removeabbrev = removeabbrev_index;
 	base->comparetup = comparetup_index_btree;
 	base->comparetup_tiebreak = comparetup_index_btree_tiebreak;
+	base->mkqsGetDatumFunc = mkqs_get_datum_index_btree;
+	base->mkqsHandleDupFunc = mkqs_handle_dup_index_btree;
 	base->writetup = writetup_index;
 	base->readtup = readtup_index;
 	base->haveDatum1 = true;
@@ -1531,10 +1585,6 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 */
 	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && equal_hasnull))
 	{
-		Datum		values[INDEX_MAX_KEYS];
-		bool		isnull[INDEX_MAX_KEYS];
-		char	   *key_desc;
-
 		/*
 		 * Some rather brain-dead implementations of qsort (such as the one in
 		 * QNX 4) will sometimes call the comparison routine to compare a
@@ -1543,18 +1593,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 		 */
 		Assert(tuple1 != tuple2);
 
-		index_deform_tuple(tuple1, tupDes, values, isnull);
-
-		key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
-
-		ereport(ERROR,
-				(errcode(ERRCODE_UNIQUE_VIOLATION),
-				 errmsg("could not create unique index \"%s\"",
-						RelationGetRelationName(arg->index.indexRel)),
-				 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
-				 errdetail("Duplicate keys exist."),
-				 errtableconstraint(arg->index.heapRel,
-									RelationGetRelationName(arg->index.indexRel))));
+		raise_error_of_dup_index(tuple1, state);
 	}
 
 	/*
@@ -1563,25 +1602,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 * attribute in order to ensure that all keys in the index are physically
 	 * unique.
 	 */
-	{
-		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
-		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
-
-		if (blk1 != blk2)
-			return (blk1 < blk2) ? -1 : 1;
-	}
-	{
-		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
-		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
-
-		if (pos1 != pos2)
-			return (pos1 < pos2) ? -1 : 1;
-	}
-
-	/* ItemPointer values should never be equal */
-	Assert(false);
-
-	return 0;
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
 }
 
 static int
@@ -1888,3 +1909,209 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
 	if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */
 		LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen));
 }
+
+/*
+ * Get specified datums from SortTuple (HeapTuple) list
+ *
+ * When x1 and x2 are provided by caller, two datums will be returned.
+ * When x2 is NULL, only one datum will be returned.
+ *
+ * Note the function does not check leading sort key (tuple->datum1 and
+ * tuple->isnull), which should be checked in other functions (e.g.
+ * mkqs_compare_datum()).
+ *
+ * See comparetup_heap() for details.
+ */
+static void mkqs_get_datum_heap(const SortTuple *x1,
+								const SortTuple *x2,
+								const int depth,
+								Tuplesortstate *state,
+								Datum *datum1,
+								bool *isNull1,
+								Datum *datum2,
+								bool *isNull2)
+{
+	TupleDesc	tupDesc = NULL;
+	HeapTupleData heapTuple1, heapTuple2;
+	AttrNumber	attno;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	SortSupport sortKey = base->sortKeys + depth;;
+
+	Assert(state);
+	Assert(x1 != NULL);
+
+	tupDesc = (TupleDesc) base->arg;
+	attno = sortKey->ssup_attno;
+
+	/* Extract datum from sortTuple->tuple */
+	extract_heaptuple_from_sorttuple(x1, &heapTuple1);
+	*datum1 = heap_getattr(&heapTuple1, attno, tupDesc, isNull1);
+
+	if (x2 != NULL)
+	{
+		extract_heaptuple_from_sorttuple(x2, &heapTuple2);
+		*datum2 = heap_getattr(&heapTuple2, attno, tupDesc, isNull2);
+	}
+}
+
+static pg_attribute_always_inline void
+extract_heaptuple_from_sorttuple(const SortTuple *sortTuple,
+								 HeapTupleData *heapTuple)
+{
+	heapTuple->t_len = ((MinimalTuple) sortTuple->tuple)->t_len
+						+ MINIMAL_TUPLE_OFFSET;
+	heapTuple->t_data = (HeapTupleHeader) ((char *) sortTuple->tuple
+						- MINIMAL_TUPLE_OFFSET);
+}
+
+/*
+ * Get specified datums from SortTuple (IndexTuple for btree index) list
+ *
+ * When x1 and x2 are provided by caller, two datums will be returned.
+ * When x2 is NULL, only one datum will be returned.
+ *
+ * Note the function does not check leading sort key (tuple->datum1 and
+ * tuple->isnull), which should be checked in other functions (e.g.
+ * mkqs_compare_datum()).
+ *
+ * See comparetup_index_btree() for details.
+ */
+static void
+mkqs_get_datum_index_btree(const SortTuple *x1,
+						   const SortTuple *x2,
+						   const int depth,
+						   Tuplesortstate *state,
+						   Datum *datum1,
+						   bool *isNull1,
+						   Datum *datum2,
+						   bool *isNull2)
+{
+	TupleDesc	tupDesc;
+	IndexTuple	indexTuple1, indexTuple2;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	Assert(state);
+	Assert(x1);
+
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+	indexTuple1 = (IndexTuple) x1->tuple;
+
+	/*
+	 * Set parameter attnum = depth + 1 because attnum starts from 1 but depth
+	 * starts from 0
+	 */
+	*datum1 = index_getattr(indexTuple1, depth + 1, tupDesc, isNull1);
+
+	if (x2 != NULL)
+	{
+		indexTuple2 = (IndexTuple) x2->tuple;
+		*datum2 = index_getattr(indexTuple2, depth + 1, tupDesc, isNull2);
+	}
+}
+
+/*
+ * Handle duplicated SortTuples (IndexTuple for btree index during mk qsort)
+ *  x: the duplicated tuple list
+ *  tupleCount: count of the tuples
+ */
+static void
+mkqs_handle_dup_index_btree(SortTuple *x,
+							const int tupleCount,
+							const bool seenNull,
+							Tuplesortstate *state)
+{
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	/* If enforceUnique is enabled and we never saw NULL, raise error */
+	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && seenNull))
+	{
+		/*
+		 * x means the first tuple of duplicated tuple list Since they are
+		 * duplicated, simply pick up the first one to raise error
+		 */
+		raise_error_of_dup_index((IndexTuple) (x->tuple), state);
+	}
+
+	/*
+	 * If key values are equal, we sort on ItemPointer.  This is required for
+	 * btree indexes, since heap TID is treated as an implicit last key
+	 * attribute in order to ensure that all keys in the index are physically
+	 * unique.
+	 */
+	qsort_tuple_by_itempointer(x,
+							   tupleCount,
+							   state);
+}
+
+/*
+ * Compare two btree index tuples by ItemPointer
+ * It is a callback function for qsort_tuple() called by
+ * mkqs_handle_dup_index_btree()
+ */
+static int
+mkqs_compare_equal_index_btree(const SortTuple *a,
+							   const SortTuple *b,
+							   Tuplesortstate *state)
+{
+	IndexTuple	tuple1;
+	IndexTuple	tuple2;
+
+	tuple1 = (IndexTuple) a->tuple;
+	tuple2 = (IndexTuple) b->tuple;
+
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
+}
+
+/* Compare two index tuples by ItemPointer */
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2)
+{
+	{
+		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
+		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
+
+		if (blk1 != blk2)
+			return (blk1 < blk2) ? -1 : 1;
+	}
+	{
+		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
+		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
+
+		if (pos1 != pos2)
+			return (pos1 < pos2) ? -1 : 1;
+	}
+
+	/* ItemPointer values should never be equal */
+	Assert(false);
+
+	return 0;
+}
+
+/* Raise error for duplicated tuple when creating unique index */
+static inline void
+raise_error_of_dup_index(IndexTuple x,
+						 Tuplesortstate *state)
+{
+	Datum		values[INDEX_MAX_KEYS];
+	bool		isnull[INDEX_MAX_KEYS];
+	TupleDesc	tupDesc;
+	char	   *key_desc;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+	index_deform_tuple((IndexTuple) x, tupDesc, values, isnull);
+	key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNIQUE_VIOLATION),
+			 errmsg("could not create unique index \"%s\"",
+					RelationGetRelationName(arg->index.indexRel)),
+			 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+			 errdetail("Duplicate keys exist."),
+			 errtableconstraint(arg->index.heapRel,
+								RelationGetRelationName(arg->index.indexRel))));
+}
diff --git a/src/include/c.h b/src/include/c.h
index dc1841346c..f7c368cd16 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -857,12 +857,14 @@ typedef NameData *Name;
 
 #define Assert(condition)	((void)true)
 #define AssertMacro(condition)	((void)true)
+#define AssertImply(condition1, condition2) ((void)true)
 
 #elif defined(FRONTEND)
 
 #include <assert.h>
 #define Assert(p) assert(p)
 #define AssertMacro(p)	((void) assert(p))
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
 
 #else							/* USE_ASSERT_CHECKING && !FRONTEND */
 
@@ -886,6 +888,8 @@ typedef NameData *Name;
 	((void) ((condition) || \
 			 (ExceptionalCondition(#condition, __FILE__, __LINE__), 0)))
 
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
+
 #endif							/* USE_ASSERT_CHECKING && !FRONTEND */
 
 /*
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index e7941a1f09..60eb77ee01 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -29,7 +29,6 @@
 #include "utils/relcache.h"
 #include "utils/sortsupport.h"
 
-
 /*
  * Tuplesortstate and Sharedsort are opaque types whose details are not
  * known outside tuplesort.c.
@@ -79,9 +78,10 @@ typedef enum
 	SORT_TYPE_QUICKSORT = 1 << 1,
 	SORT_TYPE_EXTERNAL_SORT = 1 << 2,
 	SORT_TYPE_EXTERNAL_MERGE = 1 << 3,
+	SORT_TYPE_MK_QSORT = 1 << 4,
 } TuplesortMethod;
 
-#define NUM_TUPLESORTMETHODS 4
+#define NUM_TUPLESORTMETHODS 5
 
 typedef enum
 {
@@ -89,6 +89,14 @@ typedef enum
 	SORT_SPACE_TYPE_MEMORY,
 } TuplesortSpaceType;
 
+typedef enum
+{
+	MKQS_COMP_FUNC_GENERIC,
+	MKQS_COMP_FUNC_UNSIGNED,
+	MKQS_COMP_FUNC_SIGNED,
+	MKQS_COMP_FUNC_INT32
+} MkqsCompFuncType;
+
 /* Bitwise option flags for tuple sorts */
 #define TUPLESORT_NONE					0
 
@@ -155,6 +163,24 @@ typedef struct
 typedef int (*SortTupleComparator) (const SortTuple *a, const SortTuple *b,
 									Tuplesortstate *state);
 
+/* Multi-key quick sort */
+
+typedef void
+			(*MkqsGetDatumFunc) (const SortTuple *x1,
+								 const SortTuple *x2,
+								 const int depth,
+								 Tuplesortstate *state,
+								 Datum *datum1,
+								 bool *isNull1,
+								 Datum *datum2,
+								 bool *isNull2);
+
+typedef void
+			(*MkqsHandleDupFunc) (SortTuple *x,
+								  const int tupleCount,
+								  const bool seenNull,
+								  Tuplesortstate *state);
+
 /*
  * The public part of a Tuple sort operation state.  This data structure
  * contains the definition of sort-variant-specific interface methods and
@@ -249,6 +275,21 @@ typedef struct
 	bool		tuples;			/* Can SortTuple.tuple ever be set? */
 
 	void	   *arg;			/* Specific information for the sort variant */
+
+	/*
+	 * Function pointer, referencing a function to get specified datums from
+	 * SortTuple list with multi-key. Used by mk_qsort_tuple().
+	 */
+	MkqsGetDatumFunc mkqsGetDatumFunc;
+
+	/*
+	 * Function pointer, referencing a function to handle duplicated tuple
+	 * from SortTuple list with multi-key. Used by mk_qsort_tuple(). For now,
+	 * the function pointer is filled for only btree index tuple.
+	 */
+	MkqsHandleDupFunc mkqsHandleDupFunc;
+
+	MkqsCompFuncType mkqsCompFuncType;
 } TuplesortPublic;
 
 /* Sort parallel code from state for sort__start probes */
@@ -411,7 +452,6 @@ extern void tuplesort_restorepos(Tuplesortstate *state);
 
 extern void *tuplesort_readtup_alloc(Tuplesortstate *state, Size tuplen);
 
-
 /* tuplesortvariants.c */
 
 extern Tuplesortstate *tuplesort_begin_heap(TupleDesc tupDesc,
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 8be694f46b..094d22861c 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -4273,7 +4273,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
      circle     |       point       |   distance    
 ----------------+-------------------+---------------
  <(1,2),3>      | (-3,4)            |   1.472135955
@@ -4310,8 +4310,8 @@ SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
  <(3,5),0>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (1e+300,Infinity) |      Infinity
  <(5,1),3>      | (1e+300,Infinity) |      Infinity
- <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (Infinity,1e+300) |      Infinity
+ <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,3),5>      | (1e+300,Infinity) |      Infinity
  <(1,3),5>      | (Infinity,1e+300) |      Infinity
  <(100,200),10> | (1e+300,Infinity) |      Infinity
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 5fd54a10b1..a26f8f100a 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -520,13 +520,13 @@ select * from (select * from t order by a) s order by a, b limit 55;
 
 -- Test EXPLAIN ANALYZE with only a fullsort group.
 select explain_analyze_without_memory('select * from (select * from t order by a) s order by a, b limit 55');
-                                        explain_analyze_without_memory                                         
----------------------------------------------------------------------------------------------------------------
+                                              explain_analyze_without_memory                                              
+--------------------------------------------------------------------------------------------------------------------------
  Limit (actual rows=55 loops=1)
    ->  Incremental Sort (actual rows=55 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 2  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 2  Sort Methods: top-N heapsort, multi-key quick sort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=101 loops=1)
                Sort Key: t.a
                Sort Method: quicksort  Memory: NNkB
@@ -554,7 +554,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
              "Group Count": 2,                  +
              "Sort Methods Used": [             +
                  "top-N heapsort",              +
-                 "quicksort"                    +
+                 "multi-key quick sort"         +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
@@ -728,7 +728,7 @@ select explain_analyze_without_memory('select * from (select * from t order by a
    ->  Incremental Sort (actual rows=70 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 1  Sort Method: quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 1  Sort Method: multi-key quick sort  Average Memory: NNkB  Peak Memory: NNkB
          Pre-sorted Groups: 5  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=1000 loops=1)
                Sort Key: t.a
@@ -756,7 +756,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
          "Full-sort Groups": {                  +
              "Group Count": 1,                  +
              "Sort Methods Used": [             +
-                 "quicksort"                    +
+                 "multi-key quick sort"         +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 2f3eb4e7f1..44840e7e5c 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -146,6 +146,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_material                | on
  enable_memoize                 | on
  enable_mergejoin               | on
+ enable_mk_sort                 | on
  enable_nestloop                | on
  enable_parallel_append         | on
  enable_parallel_hash           | on
@@ -157,7 +158,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(23 rows)
+(24 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 6dd97e7427..41d99793d7 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -703,3 +703,412 @@ EXPLAIN (COSTS OFF) :qry;
 (10 rows)
 
 COMMIT;
+-- Test cases for multi-key quick sort
+set work_mem='100MB';
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a | b  |  c   
+---+----+------
+ 0 |  5 | 98f1
+ 0 | 10 | d3d9
+ 1 |  1 | c4ca
+ 1 | 11 | 6512
+ 2 |  2 | c81e
+ 2 | 12 | c20a
+ 3 |  3 | eccb
+ 3 | 13 | c51c
+ 4 |  4 | a87f
+ 4 | 14 | aab3
+ 5 |  0 | 9bf3
+ 5 |  5 | e4da
+ 6 |  1 | c74d
+ 6 |  6 | 1679
+ 7 |  2 | 70ef
+ 7 |  7 | 8f14
+ 8 |  3 | 6f49
+ 8 |  8 | c9f0
+ 9 |  4 | 1f0e
+ 9 |  9 | 45c4
+(20 rows)
+
+-- test sorting on distinct values, in which mk qsort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a  | b  | c  
+----+----+----
+  0 | 20 | 20
+  1 | 19 | 19
+  2 | 18 | 18
+  3 | 17 | 17
+  4 | 16 | 16
+  5 | 15 | 15
+  6 | 14 | 14
+  7 | 13 | 13
+  8 | 12 | 12
+  9 | 11 | 11
+ 10 | 10 | 10
+ 11 |  9 | 9
+ 12 |  8 | 8
+ 13 |  7 | 7
+ 14 |  6 | 6
+ 15 |  5 | 5
+ 16 |  4 | 4
+ 17 |  3 | 3
+ 18 |  2 | 2
+ 19 |  1 | 1
+(20 rows)
+
+-- test create index
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 10 - g, g, g::text
+    from generate_series(1, 10) g;
+create unique index idx_mksort_simple on mksort_simple_tbl (a, b ,c);
+drop index idx_mksort_simple;
+-- try to create unique index on duplicated rows
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 1, g, g::text
+    from generate_series(1, 10) g;
+insert into mksort_simple_tbl
+	select * from mksort_simple_tbl order by a, b desc, c limit 1;
+select * from mksort_simple_tbl;
+ a | b  | c  
+---+----+----
+ 1 |  1 | 1
+ 1 |  2 | 2
+ 1 |  3 | 3
+ 1 |  4 | 4
+ 1 |  5 | 5
+ 1 |  6 | 6
+ 1 |  7 | 7
+ 1 |  8 | 8
+ 1 |  9 | 9
+ 1 | 10 | 10
+ 1 | 10 | 10
+(11 rows)
+
+create unique index idx_mksort_simple on mksort_simple_tbl (a, b ,c);
+ERROR:  could not create unique index "idx_mksort_simple"
+DETAIL:  Key (a, b, c)=(1, 10, 10) is duplicated.
+drop table mksort_simple_tbl;
+-- test table with abbr keys
+create table abbr_tbl (a int, b varchar(100), c uuid);
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+-- (see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+select c, b, a from abbr_tbl order by c, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+(50 rows)
+
+select c, b, a from abbr_tbl order by c desc, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+(50 rows)
+
+select c, b, a from abbr_tbl order by c, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+NOTICE:  index "idx_abbr_tbl" does not exist, skipping
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+                  c                   |                            b                            | a 
+--------------------------------------+---------------------------------------------------------+---
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 8
+(1 row)
+
+-- Uniqueness check of CREATE INDEX
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+ERROR:  could not create unique index "idx_abbr_tbl"
+DETAIL:  Key (c, b, a)=(00000000-0000-0000-0000-000000000001, aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1, 1) is duplicated.
+drop table abbr_tbl;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ae4e8851f8..2de20ca1d0 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -18,13 +18,13 @@ INSERT INTO empsalary VALUES
 ('sales', 3, 4800, '2007-08-01'),
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary |  sum  
 -----------+-------+--------+-------
  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
@@ -33,13 +33,13 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
  sales     |     1 |   5000 | 14600
 (10 rows)
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
  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
@@ -90,18 +90,18 @@ SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PA
  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;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
- develop   |     7 |   4200 |    1
- personnel |     5 |   3500 |    1
  sales     |     3 |   4800 |    1
  sales     |     4 |   4800 |    1
+ personnel |     5 |   3500 |    1
+ develop   |     7 |   4200 |    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)
 
@@ -3749,23 +3749,24 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
  empno |  depname  | rn | rnk | cnt 
 -------+-----------+----+-----+-----
-     8 | develop   |  1 |   1 |   1
-    10 | develop   |  2 |   2 |   1
-    11 | develop   |  3 |   3 |   1
-     9 | develop   |  4 |   4 |   2
-     7 | develop   |  5 |   4 |   2
-     2 | personnel |  1 |   1 |   1
-     5 | personnel |  2 |   2 |   1
      1 | sales     |  1 |   1 |   1
+     2 | personnel |  1 |   1 |   1
      3 | sales     |  2 |   2 |   1
      4 | sales     |  3 |   3 |   1
+     5 | personnel |  2 |   2 |   1
+     7 | develop   |  4 |   4 |   1
+     8 | develop   |  1 |   1 |   1
+     9 | develop   |  5 |   5 |   1
+    10 | develop   |  2 |   2 |   1
+    11 | develop   |  3 |   3 |   1
 (10 rows)
 
 -- Test pushdown of quals into a subquery containing window functions
@@ -4106,17 +4107,17 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
  empno |  depname  | salary | c 
 -------+-----------+--------+---
+     1 | sales     |   5000 | 1
+     2 | personnel |   3900 | 1
+     3 | sales     |   4800 | 3
+     4 | sales     |   4800 | 3
+     5 | personnel |   3500 | 2
      8 | develop   |   6000 | 1
     10 | develop   |   5200 | 3
     11 | develop   |   5200 | 3
-     2 | personnel |   3900 | 1
-     5 | personnel |   3500 | 2
-     1 | sales     |   5000 | 1
-     4 | sales     |   4800 | 3
-     3 | sales     |   4800 | 3
 (8 rows)
 
 -- Ensure we get the correct run condition when the window function is both
@@ -4468,14 +4469,15 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
   depname  | empno | salary | enroll_date | first_emp | last_emp 
 -----------+-------+--------+-------------+-----------+----------
+ develop   |     7 |   4200 | 01-01-2008  |         4 |        1
  develop   |     8 |   6000 | 10-01-2006  |         1 |        5
- develop   |     7 |   4200 | 01-01-2008  |         5 |        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
diff --git a/src/test/regress/sql/geometry.sql b/src/test/regress/sql/geometry.sql
index c3ea368da5..1f47f07f31 100644
--- a/src/test/regress/sql/geometry.sql
+++ b/src/test/regress/sql/geometry.sql
@@ -403,7 +403,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
 
 -- To polygon
 SELECT f1, f1::polygon FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
diff --git a/src/test/regress/sql/tuplesort.sql b/src/test/regress/sql/tuplesort.sql
index 8476e594e6..997c6c816a 100644
--- a/src/test/regress/sql/tuplesort.sql
+++ b/src/test/regress/sql/tuplesort.sql
@@ -305,3 +305,88 @@ EXPLAIN (COSTS OFF) :qry;
 :qry;
 
 COMMIT;
+
+-- Test cases for multi-key quick sort
+
+set work_mem='100MB';
+
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+
+-- test sorting on distinct values, in which mk qsort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+
+-- test create index
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 10 - g, g, g::text
+    from generate_series(1, 10) g;
+create unique index idx_mksort_simple on mksort_simple_tbl (a, b ,c);
+drop index idx_mksort_simple;
+
+-- try to create unique index on duplicated rows
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 1, g, g::text
+    from generate_series(1, 10) g;
+insert into mksort_simple_tbl
+	select * from mksort_simple_tbl order by a, b desc, c limit 1;
+select * from mksort_simple_tbl;
+create unique index idx_mksort_simple on mksort_simple_tbl (a, b ,c);
+
+drop table mksort_simple_tbl;
+
+-- test table with abbr keys
+
+create table abbr_tbl (a int, b varchar(100), c uuid);
+
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+-- (see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+
+select c, b, a from abbr_tbl order by c, b, a;
+select c, b, a from abbr_tbl order by c desc, b, a;
+select c, b, a from abbr_tbl order by c, b desc, a;
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+
+-- Uniqueness check of CREATE INDEX
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop table abbr_tbl;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6de5493b05..46359cb796 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -21,9 +21,9 @@ INSERT INTO empsalary VALUES
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
 
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
 
 -- with GROUP BY
 SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
@@ -31,7 +31,7 @@ GROUP BY four, ten ORDER BY four, ten;
 
 SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
 
-SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
 
 -- empty window specification
 SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
@@ -1146,11 +1146,12 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
 
 -- Test pushdown of quals into a subquery containing window functions
 
@@ -1332,7 +1333,7 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
 
 -- Ensure we get the correct run condition when the window function is both
 -- monotonically increasing and decreasing.
@@ -1510,10 +1511,11 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
 
 -- cleanup
 DROP TABLE empsalary;
-- 
2.25.1

#13Konstantin Knizhnik
knizhnik@garret.ru
In reply to: Yao Wang (#12)
Re: 回复: An implementation of multi-key sort

On 04/07/2024 3:45 pm, Yao Wang wrote:

Generally, the benefit of mksort is mainly from duplicated values and sort
keys: the more duplicated values and sort keys are, the bigger benefit it
gets.

...

1. Use distinct stats info of table to enable mksort

It's kind of heuristics: in optimizer, check Form_pg_statistic->stadistinct
of a table via pg_statistics. Enable mksort only when it is less than a
threshold.

The hacked code works, which need to modify a couple of interfaces of
optimizer. In addition, a complete solution should consider types and
distinct values of all columns, which might be too complex, and the benefit
seems not so big.

If mksort really provides advantage only when there are a lot of
duplicates (for prefix keys?) and of small fraction of duplicates there
is even some (small) regression
then IMHO taking in account in planner information about estimated
number of distinct values seems to be really important. What was a
problem with accessing this statistics and why it requires modification
of optimizer interfaces? There is `get_variable_numdistinct` function
which is defined and used only in selfuncs.c

Information about values distribution seems to be quite useful for 
choosing optimal sort algorithm. Not only for multi-key sort
optimization. For example if we know min.max value of sort key and it is
small, we can use O(N) algorithm for sorting. Also it can help to
estimate when TOP-N search is preferable.

Right now Posgres creates special path for incremental sort. I am not
sure if we also need to be separate path for mk-sort.
But IMHO if we need to change some optimizer interfaces to be able to
take in account statistic and choose preferred sort algorithm at
planning time, then it should be done.
If mksort can increase sort more than two times (for large number of
duplicates), it will be nice to take it in account when choosing optimal
plan.

Also in this case we do not need extra GUC for explicit enabling of
mksort. There are too many parameters for optimizer and adding one more
will make tuning more complex. So I prefer that decision is take buy
optimizer itself based on the available information, especially if
criteria seems to be obvious.

Best regards,
Konstantin

#14Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Yao Wang (#12)
1 attachment(s)
Re: 回复: An implementation of multi-key sort

Hello,

Thanks for posting a new version of the patch, and for reporting a bunch
of issues in the bash scripts I used for testing. I decided to repeat
those fixed tests on both the old and new version of the patches, and I
finally have the results from three machines (the i5/xeon I usually use,
and also rpi5 for fun).

The complete scripts, raw results (CSV), and various reports (ODS and
PDF) are available in my github:

https://github.com/tvondra/mksort-tests

I'm not going to attach all of it to this message, because the raw CSV
results alone are ~3MB for each of the three machines.

You can do your own analysis on the raw CSV results, of course - see the
'csv' directory, there are data for the clean branch and the two patch
versions.

But I've also prepared PDF reports comparing how the patches work on
each of the machines - see the 'pdf' directory. There are two types of
reports, depending on what's compared to what.

The general report structure is the same - columns with results for
different combinations of parameters, followed by comparison of the
results and a heatmap (red - bad/regression, green - good/speedup).

The "patch comparison" reports compare v5/v4, so it's essentially

(timing with v5) / (timing with v4)

with the mksort enabled or disabled. And the charts are pretty green,
which means v5 is much faster than v4 - so seems like a step in the
right direction.

The "patch impact" reports compare v4/master and v5/master, i.e. this is
what the users would see after an upgrade. Attached is an small example
from the i5 machine, but the other machines behave in almost exactly the
same way (including the tiny rpi5).

For v4, the results were not great - almost everything regressed (red
color), except for the "text" data type (green).

You can immediately see v5 does much better - it still regresses, but
the regressions are way smaller. And the speedup for "text" it actually
a bit more significant (there's more/darker green).

So as I said before, I think v5 is definitely moving in the right
direction, but the regressions still seem far too significant. If you're
sorting a lot of text data, then sure - this will help a lot. But if
you're sorting int data, and it happens to be random/correlated, you're
going to pay 10-20% more. That's not great.

I haven't analyzed the code very closely, and I don't have a great idea
on how to fix this. But I think to make this patch committable, this
needs to be solved.

Considering the benefits seems to be pretty specific to "text" (and
perhaps some other data types), maybe the best solution would be to only
enable this for those cases. Yes, there are some cases where this helps
for the other data types too, but that also comes with the regressions.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

mksort-v4-vs-v5.pdfapplication/pdf; name=mksort-v4-vs-v5.pdfDownload
%PDF-1.7
%����
4 0 obj
<< /Length 5 0 R
   /Filter /FlateDecode
>>
stream
x���M�5Kr�7���f}L
x �(hJ-R�eS$E����]�V����]�� �~k��"�>��v>YY���5|�q���s}M������������������_����_������_�k_�}Zn�������O_��:�c��?��������������?����~���/���_�����o�����/���?�_�����ul�:_}����������W_����������o����������_�0�����������������O������_���/��������_���?�����X��|�_�����������m�f���8���6��4��}Y��kx�2���:�u�7~����x���u��s���<o~��+��.WO�6�a<��kx���l����/E���u����u�S����k�yD��u���:������6�����m��_���l��5�����������8%��ZF
Po�E���<-��F���5g��}0�{�&�\�����}�6�����������}�6�|.�a>����9��P6��b��m�p���g��m�q{����z����
��CL��6{Ol_�s���A� �3�q�e ��>%Ep�!	�;���o��r��������V/�11O9&x$��u~���;���u~����yJO �a	��d;^��=)���v���A�����F� HO��5����l[����)
����@`���y��C�N�������a[�a��l��������������4.�SS��C<w?(�]	��d^�dg���<������@`��������/l[���:��-�0G�6[B���_�����5�r���l��������m�����nQ�1lO ��9����7{R���sz
{��~����-!�m��<��}�;3���%,z�C�~����� A��L�k�k���4��a����<�'�0�Az2�U�ul[���� HO�Sz�
#H�'��:W9;e�~?+Q�����F� HO��5�vg��5���Q����<�'�0�A�f_�]7���|,�u��e�-�=9��~��dD����h��$��m;��:�)
���������2��m���m6|	����yJO �a	��d_�b����<������@`������l[�����)
����@`���6��l[������-�0G�6[B����}M����m����_A�~����E� HO����v���5|N�E����<�'�0�A�j>�����u���v�����_�\�����i~����� p�A� �F��:7;���������Azb��lA� =Y�G������7��-�0G�6[B�����|���?����z�?� �0O��� A���k��������s����<�'�0��z�
����l��KX��m���
h!�m�����������z�?� �0O��� A���k�$��y|M���P~P�����k�d�B#F%�����/)e�����A� H?h)�l��qZ	93��:693e��]��1S��)���$��m�_��a������Q�����F� HO�UFK���5�/��� =1O�	6� A���k�RS6��cy
��-�e��"D����93e��=��8�Q�~��tF� ���0�����m�{�^�\�e��e��E�z[��O����q�pZ�7���8����PB�v#B%��D-]�~X�M8�C:B�"�{V�������@P����U
�	�A����o����m
��k��(HO�Sz�
#H�'��GK<l[��@�yDAzb��lA� =Y��4�)�����}�� =1O�	6� A��l�k^�:)�����.`��yJO �a	��d�_�.g�l[���Q��yJO �a	����^�)g�l[���M HO�Sz�
#H�'�����~����'v�VG�����TC���K�a��T���n�}2�H�I�k�����z���V�}�=x�� ����#�$
�	�Z)&�/� Q=��H18��R���i�A�J��
"@�6	���T�(��pC%H�2�� $�b��$��������+A��r�z��{%Xn#��& ����iF���b���J�(Tfv.&����aF�!�b�A�J�(�e�-&����OF���$C��`Q8t�(2�YL6�Y	���b��dC�� Q0d�(6�XL6�X	���1G���+Ab`��Al8��l��$
�������*��p��7~+wz&����)F��b�!�J�(et�A8�T,6<U	CbCG�d�K� Q0��(6�SL60T	c2bc6���
�T���p
���K1��L%��@;��<f��?|U"�.��H�_��_�x^����vO����WLL^������?~
_�����_����5�����k������5������o�%K�C8��m�	�����#���qf�w�2�?c���?�����v��U�;�����4�v���>Q���Q�"��|����^���q���?������?������Z���������9U��(�6g�5�v,��X�P���w����b�K���y��!G,�d���s|m��W9'�bO��=����c,}��K�~����2zj?��c�ZO����=`e;��u�z^s=M�����2-�O�=�_7��u���W�������c��������};�m�Z�����8��������?��_����e�{��o��6�����������_u������0
���?��=�w:��]���w������M������������.�k;�y8J����M�\�W���';�����r=�o�q[��,�m����������������������������_������s�~�����������};��������-�yr8��������<��\�a�mz�����nz������.����g��3^{�����������\�li��Z�l+����(6�?��6�?[����?������������������W��������^�F�m7:m�r��{���u]��.�������{�.�����!���_t{�T���Z��������n�xx�������}Z�%N�/�+e�r��p�~"ig9������V{]����A����~�T��zl���|����8K;|�����:,_�{4�1�7��s�����9���p��(���A���@���}�c��0�
���ml|8��p��qL�&�}Z��y:��k���v6
���2��z�����p��6�������������N���\��U_�hS{'lrI�	�������\vm�s�f�=��v��������6�Q�8���:3�d��M;	������
���+V����}L�-�x�/����v��w������0���;	�J�-��w}�Fw��!�=�h���w���<�vk��|�9�&<�h��]��S����v6����n�5�I�o����U$�u�F!������D����Y%Z��[yq�����q����e�o�Q�.�����]����w�w��
_o��[vZ�f����`���K�.�5w90���s�; nK�3������U�7]W�[�&����rC�|������#w�����
��O%s5k�M���zBD�&���	��c�n�}]��UO�������q�!Y{�x������."Gi���
��[M!+��t�|�.��l��{�dR�'�N��/��=d��������a_�'�d�J��St	��!f��z��.q�J��Ho���1��qj�9<6l�<N�����z?�#g��u��{��?�yue�c����c��#���q�z�>�y�v��}^5��]�5��>��~G��m��z��9�������:%�u�����ms���;�(%�zg�s� �:=���Q3wr��
v��������N���q�T��HI����>�������Io�?��{�@��b�a���]�I|��$������k����M����S�)������������K�1L���]���{��e����G
�Ho}�9x�gm>�3����1;Zva#{�]��d��w.��)o��<�dJ�L��i2��^*#t����tS�^��z��^�u��/�y���d���F�u;�����e��z���=�;����s������-���a������o���w�����m������s�cz���Z�����_�bf���5������q�����FBfn$�9T	���Y�������fF.�)U2�����hF��nV����5��9^�H�����HH�������M�r�Z���.��e�D���a3o�AJeg+����|P�I��c�����KA��D�Y�c&���i�%�-i����������"���P�ix��5y�����������y�����i��<�Re4	�
#���N�����^����s8-���������%dnv�c�~����S7ww~�!�;����9;.��C�p�d���~����v7JF�HY�:�����_�i�$zq\?���q�<���L������q7�������Q�����{����Kj>j|?��x�cX�Us�G����dU�$=����7�Y���Ij���m=w�����nNQ�����3��We�F_KO`��A�����?6��xm{�l���Y�����]�f<X���[���)o�2��g�U>�khs�I��I�9N��`0���a��^
3�:��c��7�
�(i`��t$�S��B����x�����w�����2����w�����<��e_o]�=F������$��:y�;�����������&_�lu��H����R�k�S-�U=?"��FV}l)��!�����x0%��o��q�_�09��
r>�%��:��R�����������o�^�y~�2�q�O�t]����o�L�;�����l�?d�z�i]�{����K�iz]��7��^?����~[�1���<=��A��z�Nw2��q��zN�^g"��?_��������k�7���y�'�G�}y�{5;n�y�{�\K�=/��F�kiy��Z�����^	vm��N�
���������
��.�r�x����<���R3'���.���q��g�:���?w��&>�)��z���w��-�}�{��p�g�=o��'�zk�;>,�7��7��_������<���f���i�.��7��^��p�+Z{R���;���]�[U���{�W
��'v�lp�y�w����=������+��xyL.���v��`�;�A.���On����F4{�|�3�=��������g�sn��K�R��se�� ��F2��s���7f��qb�%G���>��?�������8���Q���,fG����,f���<�^�],�/�X�������2��j��Y���\1�����p����}�]��������K�n���E�i�T����^�E����w:O]��'�d��o��n>�%�%�������]�9�'�������vCl���������NR�������zC��R0�K�'�s}���������1�����{���v����q7�i,��/\�?��� �K�-k��7����G��^*�n����P^���;.~eD�K��/���!�B�����b���+����}����;�w�'�������z#��?l���	��:�����������}6]&�I��L
9pu��k�~R����X��~��o��������qq�����t'�qm?�3��$qi'��v��S�����-}�j 3R?�/����E7��;�z��9!�@w����t
i�{�w'���6��G����s{�
�q�*=��>���}~���=�~���r���>?����]p���}�W�y/��v���9�f���{���,��g�^C�
��JO����7�K�H����dL�,Z�4�z�4�vS�d��B���(�6�����`Wa�{�u�X��s��{�(NqA��9/�������3S��t<�Zz|�w��eY�>���~u��E�}�Y$[z@�#3�����Dre>&��C�d�iv%;����q��kD�A�<NJ�����}BD}d��`PA�gg��j���-YQ&�YV�l�����������8��(���O���M��]����f���������k��^���!��WN]��f���{Q��B+�oOz���U^R_;g�e^��yg���)����c����{b��u4�xV����m�$i~v�v��������?��'��@�2����I����X����Xd{s�1�q������Z�C�+�9�6��h��vL��-Y�L�k��fm��x�b�~�������nv��)���>uuS[�C2�:�<25�;���R3m��$��m���#(`�l�o���eis���	�1YB����yZ�?M���i�i����n�Y�?����i;��3������&��������	��loG��
��7Y5f�#������2$�������n$v�ff�Uf����\��)3;�5L��&��.��N'f��y���M�Y���"�$Nm�Z�|����}Xf��[2���D2Us�5�J��@�j�������=��dx�9�9��O����k���T{i����.o0�����e]b���B=�;���q��u������y���-��Mt..�)x���������f`��+��������)�.�{%M��M7^�G�=0'���5�q�t>�!�:'+]Y���t:��|�8��X�
��6��5����v���s��2
��-x���l����x��2{��(�����w2����3);�z]������k���
Y2���x��3����F���;���u�s'm
�=Q��v����U��I���c��|�Q������Y��VX:�C�����g�]/�=^���w*��/��{��i~�������M���	�ig�F_��0�r{'��ii�������\{{���[�=8`|��nZoy3��G,[������{`&v������`����*��ay-��h�7YI�b����<*j��`�]��F�Kz����X�g���A�Kv�`U��t;�����NB�l��`����O���TR�{>�����N)�aJ'�NC��[����GI��Se<7�J�/�09�|I�N1br��|����3�gX����f������;��\s����\dg�g�����-������]������3�wO�ej�W?(0Q���"���7�,�6uY�7��=_a.�'+�a��N�q��{|0;	���xf��u�n���"������L1�;ms�|�`�w��_=�yA"��';��L�o�+�y��@�H����DO������y��������X������3��}BEG�p���/�t?^�p~�B�0�o�7lY�'���0��f{�t����j�����
��y7�G�Bj�{�u ����y���y��T�����������s9=(�����e�3�w(n2�R:q[J����8������]������uNOS�h���.���������,Edt������B���wd~w:��&����=6��N����>�nx��	�=���s���x\p�:������(}��02�s��q����/������S=��� �I�*[��������p(%�����?�>�U�]�{/�����M��E5�������xO�d�,���[�|���3~a��}��8�vot�i�����0}�=�����&u��/d�e�tQmf�D�������^�
��<���6��.Sl��m��,~�A�syL�)�\h�u���O�q4���/�"����z��.�5�`�����o�"����Jr�w�;#������/�33w{�O��6�(��.����F��U�/�fg���y�Q)&~�;�E��y{���\d�Q�o�"���u�Ts=�w^`e�w���f���P�Y������@
��= �y��+����!�N/{X�9���s�����������(S<o��(8S<�[��������}+?��JXe�Z�eZ�l���
�^*��;}E�{m�o���;����N��d��������y�G1s����d��1l$�ocv����R&�,y���7���7q3y�z*e2�W�m{W��y>$[��eMVnm����������\�P���=_��y���,��t���M�����3���6'=�U;=�V��i������P��f��\�qi��f���v&Y3���x�P�����N�a�p�s}�uh?� E������T����w�-�?u��5����n��[2�A����O�tw&��=M�x����H���j�������1 �,�����5dy�f��C|��D�,Ov����i]^�'����[���5��Xv���f��l��'Lg����`����/����xZ~|z6C:w:�����~��oSK�t�:��L����`��yt����-7�����eu�mI'����%���tK�0�w,|4Y`�2���C�qj������;2�����>�����rv�`���6^��������y�����sc0����<�_�����S??C��~n�}����>T^�`����>���O���g�-d��O�B�v�z�B������m|yl:g��F���o����irt���U�|��c�Z�����a�����4�<i���0�N���c�9��O��������~����
����&���j������i�y~ :�� �;M��XA�gvfyk��|��$Ov��k�L�����V�!3f���{�GV�&�f�v����?j��
�7v������46s��;%��n�!��Y�|D�;�c>jb�����Z�s��L�����s`���,9kk����y����)�����Y�=���������a�q��#{�W�5i;v6h;�;��&y ��>4��3���������H�N7�_�]�;�����Y�������4
B~!����������}�������������w������9��������9~�6�������^�9��3k��R�:=���w��zj�~�3c�G���=�U^�������&��E���q����$d�{#�}={9j	����^u�~�Z��"��Q����o�����g�Y����4���Xg�s�c�������I���������/o�d
E�?��Jl����_�`���'����#W�������a�=���)�/�y|������(&��n6�tQ�2��ww�������������|P&�[Z7�5�cE!����-���P'�9��������X��LM$vn�L-D^?07����o!�����tN��� ot�O���� ���������"r0? �/���}�E����V:|��B&g�fn�c�"�����7r��c����������3���y�:u����bL�����`j�������7U����]+���rvs4;�������}!�rL-������q10�w~F��/�O���q�bY+XGR8(R��s�b�����v�������Y���=-�]���j�c1���i�f+�}�t�s;�kx�~z&�j��_���Gg0_��{�09?,��Y���j��R�:u#��!NQ�-�R%u�S�w�a��.��5���u8��-��l�m�������&�\/��>����={��m�I���91�4��yDe\
/����{��O��i�X�������;����l�������T�/������m��j�������6����3�p��G���.ZV�^�y������Z.���5�cu�,������~o�
4���/�G�2}�V���!�7���#e����������l�a?�7po�����Bxe�N���zw�W���e�8��b������J����+������.�'�8����G�8�/��9<^�;L��������8��K���O.�'�������a�o�
�#G�8�f�{iFw��IO���|��1/�T��G�w'K0�Rwo)��wd�����Rq�����E����,3���<x>���������;���W������V��e������M��h��|���O��x�����Gy��T�x��t���B2����� �z�|�yd���:���Q�x�����o��A�N��dqq�8�;]�<
���,��<��� 289d��ZK��N]����]2�Q�l��g��!xM&c����{�@2�/?���.�;�|�z�w��O�`z�������o:1���;r������{��y{��,�t�S/����l���]>;a�F����ag�&vK��n^gq�����*�6��������S�t/{?�L������\�]��x�b*fmg�9��sg��Rdt�����tU���������p��H9���l��!�C��:�$�[�Ch���L���$������2��X>��l��W�����?��]{F����/�%�od���zc�j�{��.��sg{�����t�N'�����^����1��c�{�������fzb�L���:�T�To���G��1]P���2����Yw��gW�y��s�q#g������p��T��eI�v�q�8;3���u�D��������0O:�1E�z���*�i����q)���#yH����������\mI���b�%��_�����P���D-���>q����fi�{y������S�a��S����d?i=�,F�|p�����C��:47V���+�u�{���i�5�z���?�����?t{�|`Y����(�;�~�;��������8�9���]����i��G@w��`A��fe>j�q�A�v�J|%
y���}XP�3�{m/���$��l?�ey��+�d]��}(eZvw��rw��f���M���K����$+;w�N��LN���^���9�y_���6�����{��y�k3�;��r�^%�!�i�I���k�>���
����8��������x��Rg�q���L[���$'�s������
���e��sFz��1������:����g�x�0��Sjfg��:K����J��<C��7�a��O��aS�i��y�e�����B.�I���%��9�y���.�g�S�g��y��=��	��2�;��g�����y���\���~��%��F��P�ci���O����-��������5��|E�g���������J����vw�~Bb$���Y�g�qKg����9�����p/�"5w2�)�<.z�������������E��DZ��������Hd�_�z��Y���9r�����e�S��,�4�9��f���"���������+cz���jSG�m1F�y��'���~����@�d�o��NV���@���4�����}��?+p����[��dv��9���.���#�4^O��M ��z�0�8��[����\)�|���,��;�2����[(��=[������?�q�����#����{�Z�����+Nw�gc.�=��|��~��'���9QfF�'���L�=��������y���&��f{V���S�R��{��W5�M�U��A�~�.N��o���dp�7�N�����<0���ph����=�:g��.q�;����.mF��U�b��N"]7��������P�k{'�������){,�!#���?�(|e�v���������oLW��m���L�N��N��3q�s�j8���S>����{{����<�Z��}d���]_u�QgD������K�L��n��Z����V���}����s�����8��_�?L�������w�G�vZ�K��;���^��u9����0d����=5$i���8r4=������yK@�����%$Yfg�a!�o~>7���(G���"������d��Z?�|��d��Z��;���h���^�;8�[�w<6��jw��Z���}��R,������2�;���Q�Z~L�#���'�Z��T��>����
?:��a��2�B���A��o{�Z`&N^(����h��g���=��O.���]/�����t}��x�E�g�RJ;�=U��������A��:����zM? I����9���v���Mg1Z���X��2}m._�a9�������t�N_}�|^���>�#�7�����'{	���v��F,B2g����\���|~Dg�����xs�����k�`����J�}V�M�3����N���h��gs/���\VgM��B����m�����vL�2�����l+��;f�]�j	y}�����eb���:��goq�8��)��>�df���d�[��i�6�[���6�cB���_�L�v��C3���/�|��X�����=&jn�<�2Q���X�ej���7(fj��T��3�Wq���������qI���&�r��Gc�[������J�={�!������kH�'���N ����E���I��t�m�w����h���y����9����I���Q�����g{2���w�q	���r��$�v�����/��U����ai��������/_v�����;�
���H�H�}��7����<�xF��~[uLIp�d?�IY���hh�;��������n;G���L���2�����o�&{���{�_s�LQ/����O?��oX����T'_U�O�6�02���|��s�7�I3J&�T�{����pi�?=�w��A���`�sv{o` o���q������;5>�Y����d����`����y�����������q�W?�����u���e[��L��;�g����T�|��E��I���S����Z9���1��������~p�E��N����y�q *dv{���X�2O�r��%b������\�����3��t�3qp)�i�s�v������L���������#+ss��%241������i$��:��M�L���\��J����j������u�>��8{��z�����b����h��D��?~�^���9����>�T��N����I-N���/�}���Es����;;�Wj������������^f��w��j�����N�����^33_�3s7��e��0e����0
;mw���c���V1�y� �s�a
�������(|��c��no_C���,��v��l���$�L�N��W�t�p��x7]9u3=�C~�M����qe�w������E�������"��������Zm���L��E�������.�92L������H�v�#MNO���hL�_>�������45fg~8{p����-=����������s����mk���{{�X���H�G����o��������!6���5�V���#w kH���Oq�)O���):$m�����(����ud~���4����|�������������M��������_�5]#��g����<L���|^���G�i���@������'L�������s�m��$U�2n��v��)f�w�2��;-����$��0s�m��-�����4�C(�g�w/�x8���Q�p&�Y����G��>��I�C���3]����8�C���t�����|b���L��|}��k�&��C�p�3s��oq�����C����dy�'s�����p9{\��?'���Q���]���8��a����I������q5w��#����������yi���o�%�?.����u�k�~V���+3GV���fb@<�=1�����F�|��N3���o~����v����3����U&�'��a����<��^��JH����
�0���/����.<���:����/���	������������<C�fd��:nG
9���F�&�������f1���de�[�O����sYg���H��C�<�������R�s7������9��/���Y<7�]��j�foy�&�wy\��>�bR����F���H�]������$�2E�co|���u����j��Y�Q�2;��~�a��B'uqJ��&�~�?vL��������m
����5�8����g.�]���K������:,2K�5"�1��L����u:�����L� ��v���t���~Z�7 �S�v���ai{��������Y�u:�{Vy���,��������s�73��t�v]g��{]�k+��;����c��T~�������O�v������?|�|�?.F�~������N�YeE��]�||�����O���,3�G��R��l��dr���2����V3e�'v��=����"$|{���`�r����s�e���/���g\��]95�;nk�.�eb�&���o\�s���f�w���33�{�c���8�6��>v��Ps#]��J�1�<�pn\����J���o�������8��}^���3/������eb^���q�u^�[��pg�u���w��v����L����(��v__���0�}6�>��A����g������/����S�I����	/�?��i����{o��[���=�������������U��;�z�w���	p�y�5��C=��}��F�����q��.[�+w7��U�LM��S��v���L�U����6��]���~h�W��`��t�M�x��[�%�,��;HV�O���vy�������1\���
��IE!�L	���j��t���~^���=t�.�3]��3�����n
��+�L�}���V����`�^3w�w��p��^Y��:d��u��0�w�����"�T��`��n�:�����������zJO��q�]O�<\O��������������������V��������8��k����k������I��h�d�������c-�k��7^�������i�����R~�z������g��Y�3.�g�$��V���W�u���X��Q$R�n�/������	d���b�����}����w�?Gr{����#I��M�������j��C%i|�������2|_�eb�l���<.�-=��J���~����4�^��CG:��A�wS;���\�z��&���N�d����5�1[(������8���j���<����=_�*��o�������q����y#���������.��r��>�7��0����~������������g9^�����z~E��4���q�Z_��V�������<���*����na�-4Ou]����c������zb�7�"o�y����H�{���f�C��
��#},�#������s������y�f��-x�_����9����>5��.����?I��?��:���g<���I{h�/�)�*�q�,�m?��kz�?�����s�����O�[e�0��X�>{���������'���X��m]+��x�_��5O������r^�����u]����}z�U����oa���?|��/s,��^��H�z�����^�vZ�c|��d�z���D8��-�"����,B�}��F�^�t�^�,}���?�y����x.�MD����k��5����^�������|����5��9�p$���#1���O#��E9��������y��b���Z�Sc���tA�p[\��5�?���W�~�E�L�k�;m/�����{a����<����	B�h�y|m�����g�J� �e�
�Y�~rmG�vh���������Fw�p[\��5oz`��=BLA�-.��^jA��5�#���1����#��c��G���(�kX����7�1S�E�l�ks�v�_�����mqQ��������Pf
B�h�}z�zl��J�����]��5�7�������b�(����xl�*5���S��h��k��X����5�9�R5���M
a����7����y���n�D�����iX�i��CU���pEk_��R��i8_� {3���TQ(�2��xM��^;G�a����'�zM���U�E�L��8�+���U��
.����4��XE�X4�<��5��/V
a����7��I�ep�*l��XF_��e���!�u������i|��B�h�up�kZ�k�������z]������X5���^����5m�/VQ(����zM���v�Qo�U������i�|��B�h�}~
�����R�n�����5����)��Q������|�jS]�����.;��+Va�4�9��5��{xZ�a����7�z����U`�(��,��QW���mp1_������!,u������y�}��B�h�qy
���5n0k�z�6���`�4���b�(��+�|�>������]�l�'_��p;4�<�
6_��`5���`���
6/��Xa�4�2�
6/�{�	���`���
6_��7�(�FY_��k����=�l^W_���|��+� �u���U�����+
��Q��=t-QW���mp1_��}~�
a� �F�g_��}�%�!�u
����t����]��|���=�S�a���|������f
B�h�s|m��QV_��mqQ6_��a^���D���U��P	Rou��d:V�q���n��r�*����)��Q��W��a%	Ro/u���U1|��LA(�2����s%s]����U�,to�z�4�<�*���%H���-���V6S�E�,�k�{-1�(A�-.���VC4S�E����ce=6	Rou��dY��/+^�)�E�l��cX1�LQ�:v���ceY���K]���|����K�)��Q���1�/Q�0�u��W��������Ac���{-^�� A�-.�����2S�E����c��	Ro�u��d9��h
,eF_�0;\�a����7��a>�����ul_�0��LQ�:v���ceV�x����c���:��Bf
B�h�i�uS$J���]��Xs����Ac����t�����p[J���q����oPwMx��p�m�>���F�����mTt4�g~?�>�����h��H�l{����w�>���������F|�����G�����E��6x������E���]2���Q��a�{OE�F!|�O����{~7�� @~������w�>	�G��G�F!|��O�U)��w�>	Q���hp�n�gA��D���S��Q�D�aR����(��b���ph�T�n�'Q��D�K�X���B�$
��gJ�F|��O��YO�Fas��N����Fas��0-�����,
a��i:�6���������w��Y�:\y���(lN�l����������,X�l�"Sq�Q��Y}�1�&��7
��(��5$m����hl�wsV'���T�m6'A_q�����(l�����e�s�Q������������,
a���;��������]�1�&�����Y�:
��;w��I���mb��{��9X���W������$��+�a6�T�l�������������(�N�]����(lN����fKUw��,
a��:�6�������:w��Y�:
�l�8�6
��(��]���R�.�9�BXGa����n��9����e�M,���8gA�d�U������$`��zU�m6�(�D���E~���$����a61T�lN���~fKU���,
a�6Zy
�I��U0�lb�*��!��0�
Vq�Q��D�}#g��`@s��NW�*�6
��(��`��RU��,`�l�
Vq�Q������
Vq�Q��E!��U�����$��*�a6�����I�:\���(l����pP�EF,G�s�p5�0��6gA�Dp5��n��9���f�M,2b9�� �u���X��Fas�����X��Fas��N�{xR,2^9
��(��b��h��*6�(�DpU�"o��9	��*f�M2^9
�� ��b���RU1�9�BX'��by
�I��U1�lb����!��U������$�����6Z���8gA�d�U������$`��W������,
a���k+�6
��(�u"�:Vq�Q��E!��������$����a61w��I���1�l�x�6
��(�u"�:V�����9\3�F��n��9BX'��cw��I���1mb��X�s�N���V�m6'Av_�����c`s��NW�*�6�� ��c��� �m6� �(���U�m6'QV_�����c`s��u�0,�����,
a���U�m48'ANW�����c`s��NW�*�6
��(�u"�:V������������n`sd�u�\3��~�������y=�
����T�G�(;�]q��w������;�=��m��C��GE/
�]q����:K��A�������I��@zW�����7=�B�����z�m�:���;(�wp�����K��"�+�w"�����;J�w4���p��F1z	��;�^9#J�(���zG���w�H�`"�a�+��J�wtz�m�:��A"����N����zG	��&�;�U���A!����N�C�E�%�;�@�D��XP�)���z'F��(��PH�`!�aqGV�$�;�H�D��QzG	��&�;
�u���Dz����wT
����;����w�H�`"����w�6_`����%F��;8@�d��w�V_f����Nz�
��t�Rc���u�!��J�wtz'���(��t��c��`-F� ���Dz'���wT@�h����w�H�`"����(�f����Cz'���(��Dz�����w�6_w���p�D~3B�������<J�(�����U�w�H�`"�A+��;*���Q��l����������;\�Qz��&�;����R������:J� ���DzG���w�v_�����j��;H�w0��Q�|�RzGi�������e��A���N�]�RzG	��&�;\�RzW$�w�d�N����X�{������l����������;\�Rz��&�;
�u���t�
Fz'���)��Dz���W0�w�v_������ {U����Bz'��`J�(m����U0�w�@����mW���A"w���MW{��QZ\�!w���k�r7J�n4����j�r7H�n0���pO����W}��d�U�n���`"w�U�n�v_}���pO�������w�mW��Qw�	������r7H�n0���pO����V_��Q�|�Q�V$�n�d�MW��QZ]�!w��{xR��t��c�MW��A"w���MW��Q:\�!w�mW��A"w���MW��Q�}�1�F���G�$p7x��d���n���hw������
�L�n"�z�U�Fi�����)w�D����)w���
D�&�7f�%p7��� ��"���M�n"�:����������h0���
&r7\S�F��u�����)w����lz�U�Fi�u�����)w�D�����)w���:F�&�7f�[����q7
��c��(���w��1�n�N_���A f�E�%p7���DpuL����1r7�vuL�$r7���DpuL�%p7���DpuL�$23���Dp5H������$3��]��D8�
���)
�,�f",��0nVR/��DXg	!�
�-_
�����$_0}��������/
�]p���E���.!���`�`�����0J `�S�8G�cM�`*,���`����"�Re�C#�J!Xf)� +Z�aA"���K���#,�(����T�V
C����,U���dQ�2P�*w��!%�,sf���dx��"�,z��TY�1��<�.-U6wt�hQ�2��)@X4��D�E��*��ZT
�2K�Z*L���p-H�Ztl��J��-J[Um��e@�r)z�TY'���LQ:�b`h

YT���R��Y
�R��S���$�28�&�S����"�RE�** T��R�UeT����"�R��A%&���D�D�)@J4c��U�� �*�"1�$P&���L}uP�Di����&U\uP����.�&U�:k�RX�Y
lR���M���6nR���M���h,R����A"-�����!��Q���`�HW�A"1�������eF���64Re�;�R#H�F4����r#J�F�8R�U%GE2r��#U��8b�oJgU������H$?t���C��J���?*�
���]�?���B(���W��*��W�=D@��
���VU�@��
�(M�@*���� ��E���J�(-�>�����,�X���Tq�Ai$� ���T�����q4J��B*�
�D�]DB��
�LhM�9*�
�<�x��tL�}�P��D�."U��/1a,��ZU�:�l�B(�)�Q����*�rJ��;*�`b������vTqB�$P��eT87���@"���`FW��P���`h����e3��fh�Q���3�@g�<c�����H�3t����=P	
����hTqB
$���WT�q�X�W(�����
�
LBX(����EW!��P:|� d1P��,�HY�"fQ�U�,���BhQ�U%-�@ZhjQ��{��J{U!���*��H�$4��p�����H	\�JL}�PVBi�*��U\�PZB��*��*�#0	/�^b.U\�PbB����DW!��@"3���DW!��P51��*�7�rH�&t���j���q���:���9L�NL8��M�
���b2xb�2J�'P����Y�?�D�
�BL� A�b�X����c"�!
L�(Mh1�B�&b�]���@�R<RD�'�%*�\(��RD9���*�E�q����Jy
5*�Pe��$D�$ ��T(�����
5�*�Te;�����q�UD�V�`�������r�����dp�F�Q�Z����C���H�"JF5S,����e�E��p(�KY`4�"�����Fr*�*�*�e�FC-���@k1	��&������j�[�3�"�/T��q�q�J	����!�.c.����,��.v���j1L�]Lx�	�E_F���vT�D��sEwM��F_Dq�D��I�/t����
�%C00��OS�[q����h�
�����CmUM#���I]o��"���,��X��1��UY#�/+T\���h@FWV���$C����(�1��j�@Q|M��v�\>r|%P0C��|Ff����(�Kgh���b,F]���������)T|���)�hD��
��EJ)���(�1���>rQ|Y��]TS��jD�U����PgUY����+��
�l�����(>����1|qQhC��
|Fm��Uq���d����*.5j����������%�74nUq1X��P\��G�#������!����+�%R�_Y��g
�E8}�8���B�u�P��rD�����V|���9"���8����������%"��tD��E��i�:��P���B�u����uD����V|��*.�v�lUq�������Q|q��C������S��L;��"�G_\��.���G�#��������q�����+�%C=4�Ue1�c�#V�=�����+����{�#���W�����!
����{&j���PQ|q��B��G�#��y��c�}D?PHz����d��F�Q|q��C����c#@��gH_\��.�����Au��b >� w�Td�^AA���B�u�4>� n��� h���Ox��*.T~�PkU\	�������P!(�@tAq��H0$�/.T|��cC"���p���!������%D4���Pq���h�H__��C��k�W ��� �Y��<nQPJd�e� XX�A�`��<�};)�Q��	��O'�u��%B� k��M�,�a��	��&�l��%B� k��G��� k2��{��JTLQ�$��S��1NT�6��8u���yqG]���M��s(*6�.��$��&��u�\��Ai$'
�a+���c*��`�
���PaP�������J���RR0�_T�\��pI&������J��'!M!n�1*J��x4C0�_T�\�m�|=0��@QQrE��V�*F�`�Jq�Pgi(�+���B0���H*F�`���`b��1'�b� �t�}3t[T�\�
��P[U[�R01�����h�001��(�q�R�s*`LC�UAl)k|t(d�"+��0�e`L�U]l)������T�������H�`���.� c� lQq<M��'�M�}�l�F��|��_T��w�6
�U��L�d��I(�4�� �G�k����4���&��2�cT��+���(C]2A�4�Y�����	�&����Qq`N��d�c� �u���0��`�
��Pauj��cKQ�'F_0��G�h���)X���2��	V'��������._2�1PT�X2��,�`�b��'|b�?�l��KdC�w������=�����	�'���G
i����:|�l�f��|��_T
]�
��P{U$[��f�~�|QR(6_6�1PT�X6��$�|A�������}h�Ee�e���PF
a�
���B|��_%��&���!)�/*k,����H!�Qq�Q����:|�l����|�R_T�]�
>�P{U&[�'
�P���AT�/�(*S,� ���#�Qq�R��`��Z�B�R�_��U�S�c1F��K1��i����0��	�q��P���/}���t���4F��K1��i������	�)q�_��P��55�^��2��	�)�0�����._:�_2PT�X:5%��K��P�ww'~�PQQ*F_=muA��P�}�4�HAB��8gU+[J���K��J(C�06�!VO�Q
u�b��P=�G%��P�����T���:�Z�R�X=�G%Y(|A�W��u�;��P�J���K��J(�CHcTU����U)c�� �G�j�E�|V������V��1VO�V
uV�����z�J(��06�!VO�V
u�b��P=AZ%�QU��>��TP��"Ue����z�J(|+�� �|��> �q��V�����w����:�-����x����f����M� A0:k��DP�����f*ta,�A�,L��e�`l�A��y6wd
�"H���D:�0� �5����l�-���W��T�b'*Jg�X�,�X���tV\�73�X���YIg���1*���qu��0���`t�&�Y(�ba����b,5�
Y,CEE����b����Y�.���2PT�����|*d�4FE����f�b����Y�-���e��(���t��X����7���2TT���H:�X,�QqtV��;�����b*C�P���Gg�����X����Y1��h0� �����b'*Jg�X~-S9���R����Y
������L
��rTu��(���t��X����w�,���2�%S�,c��E��Y1��Tv_!��Yq��),��8:K�T�La��)�L��Pc���Y���h
�e����h
��b,��8:+FW4
�2P��d
��rV��,�d
��b,��8:+FW2
�2P��`
��b,��8:K#�,��*�-E��}�cT��q�
��X�����Y(���A���|��@Qc�:[a��Og�X~$SY|�lBg�����X�����)t��X���,���T_$��Yq��),��8:K�\�Ma��9�M�������b�eSX,Ee�eS�,�X���tV\�l
�e����l
��b,��8:+F_6
�2R�P6��R!��/*k,�Bg���1*�����/S9|�lBg����X�����}��PQ�c�:�0� (��/��b(*S,�Bg���1*�����/SY�B�R����U�SXl1F��Y1��i0���0��)t��Y���tV��t
��1*�����N����%�N��P���Gg��K��X���K��Y(���A���|��@Q�b�:�X,�QqtV�����e��(�����ba����b���`,#a�S�,����-E�,�Fg�����P�X=��R9|�lK��Bg���/*����WOa��9VO��Pc���Yq�N.,����tV��z
��1*����WO����1TO��E[|Q�t������b**c��Bg��U�l)k��Bg�����P�X=��R9}�lK��Bg���/*����WOa��9VO��Pc���Yq:Ke�jeKQ:{,�y���`����y��t�L�4�	c$Fg�S��`0�x��t�L��B �E� ��	t�l�-� �5�����X	��Y�Hg!����f�4�ma�����*U�
Y,�DE����B_�����f�B�@QQ:K#�,c�0F��Y1��x�2P���D:�X,lQqtV���S!�e��(��� �X,|Q:+��%��X���Y1��O�,���(��=��X,|Q:+��pc���4��B1cT������X����YIg���1*���qw��0���`tVLe���X�����}Q�PQQ:+FW
����b��QX,�DE����e*gU[���b4:K0���P�I��T��.���4��B1cT���#��2RT��d
��b,��8:+��C���+dC:+._2���Ggi���),���2��)t
a,|AP:K�XMa��1M��P���Gg�����X
�P�L��T�bi��K��Y(�ba����bt%�`,a���Y(�ba����4��R����R����La�0F��Y��`
�e��L�`
��B_�����La��1�L��E[�Q�tV��G2����� tV\�h
��/*K,�Bg���1*���H:K��E�!��/��b�����4�U��PQ�c�4:�X,|Q:+6_6��2PT�X6��B!��/Jg�����X�����Y(�ba����b�e�`,#aeS�,�X�����)t��X������2����� tV\�p
��/*����Na��9N��Pc���Yq��),���2��)t��X������2��*�-E�,�[U:��cT<��/�c)C(�Bg��U�l)Jg��K��X����}�4�HAXB�:�X,|QqtV��t
�e����t
��B_�����Na��)�N��P���Gg���;��X����Y1��),��8:+F_=
�2R�P=��R9�Z�R���ht�
Y,�
e��S�,�������)t��X�����}��PQ�c�:�0� (������b(*Jg�����X����}�4�HAC�:[a��Ogi��),���2��)t��Y������)t�
Y,�
e��S�,�������)t��X�����}��PQ�c�:�0� (�W��T��V���|;te�"�����������6Ql	Qo�5Gy(����Ro+�5K���aK�z[�,,��e� �m�����cs��l	Qo���Dl����c�r�L.�B_oO����Ty*d��|GS��B!y-�z[A�x��c*���^&]��P��[�`5,WD��
��B�pkqA��)�R����"N�
N��
�@kqA����G�Y&�}�F\��S!c�-����r�`���� �UL�?��W&�}��.�V(�V�-`��P+�U�	���q�Y�U-� �������*����X�R���P-� �:�����T�	�*��Ud��ToZ�/nR%C�o�U����5���x]\U)�����=�UN�*�������J'<���b��)qS)�"L���	J�b������|J��TvW���PT��z'������q�R�|ykS�w�O��W�������x�J&c�x�N�(-� ������0)���C]���R9}ykK�w�L� -� �������(���s]���B16Z\A���ETJe���!���g���P�b�**q�R�|qkS�vBH��W��|T<��	
E� ���	-��������2Jeq.n��xBAa�*�@Q(�@�-���1"J�p%.n��yB?�-��Q����	�D� ���	�`���� TL��	�D� L��	�B�Y\��"P���'�a�0��'����b���1�Ieq5.n��	��B�	[�P��{B1�YlA��K%�AO*�+rq[��x|��YlA����W=����9T=��P�6���V�)_�l"L�P��sB1�YlA�7O%�AN*�/s
<�8[U��g�� ��N-��M*��sq{uO�&����� _5��{�1�-����1�I�pu.n/��	��b����F��{�/'s�{�3��^W��0S<��	�D� L��	��b������J��T6_����q���	�,� ����&��U��=��'����]C����e��
Ie�Ea�O�%��U�����'��A�b�~EU\��	�D� ���	��B>Y\���J���,pa� _O��|B&�-����1PIes�.n���	�,�@���V����	�D� ���	��r�B��P��NR!�,�(��	��r�J��P�LB1YlA�O���W>a���9T>A�PH ���V )�$����5��#����4��i��|!�>N��.1A)���-��$$���K9Hx� LRL7���(A0*IS���&��'�%���#K� A02I�$�H��`lRL���6	STN�Iu����H�yWT�7�� I_Q����j
�����+jFPJ*��4FE�U���7��
Q���*��L�}WT�w57p�BEE�U�����$}Q1d���%�JyWT�wU7���Q�wE����@NI_T]�m����EE�5#�%�J�����q�����BEE�5#&"K�����qw���� SM��.rK�������EC�**���]e$��-4��k��K�yWT��/]S�� �yWFbM
���!u�4�i�Q���"���l�
Q&�Q�wE���H��)*s]2
pR!��-*����_����B6����%��&}Q�wE�8U%������K��N*`��Ah����h��@Qc�4�I�|������jtE���a�K�QOS��@��%�L�T�9i���+�FW2�:-P��`��B�I[T�]Q3���W������j���'�Q�wE�8U��������P* ��AX��|�4�i��2��i8��O���U���������`LT]�h5_T�X4
�R!�1*���AFM9|�l�F�����P������q����P�9�MR
��E���|�4$j��2��i��
�(}AP�._6��Z����l+�B4JcT�]Q5��I<j��0��i���Q�Ee�e��)"R������<6��e�!8U�/��I����+�F_8
�Z����p>�\J_����N��(*S,��P���}WT��`�)kU([��+j��*�Na��{WT��t�Z� �tN5��
eK�wE��K�T�������NBT��%�N��THQ����+�F_:
�Z����t\��J_����N��(*S,�FX���}WT����S�PQ�wE����aU������WO�U��!TO����U�l)��(�������P�X=
��r�b��P=
�R!c�/*���}�4�j��2��i��
P+}A��������Z�����j���p+�Q�wE���'��E
���AX(�\���{W��cU=
�Z����z�5��jeKYc�4k
�+�
e�����)�/�
a	���,X�������WO��**s��f���A�����){U+[��+:��k�d%_c�����f����M� A0:k��DP�����f*ta,�A�,L��e�`l�A��y6wd
�"H���D:�0� �5����l�-���W��T�b'*Jg�X�,�X���tV\�73�X���YIg���1*���qu��0���`t�&�Y(�ba����b,5�
Y,CEE����b����Y�.���2PT�����|*d�4FE����f�b����Y�-���e��(���t��X����7���2TT���H:�X,�QqtV��;�����b*C�P���Gg�����X����Y1��h0� �����b'*Jg�X~-S9���R����Y
������L
��rTu��(���t��X����w�,���2�%S�,c��E��Y1��Tv_!��Yq��),��8:K�T�La��)�L��Pc���Y���h
�e����h
��b,��8:+FW4
�2P��d
��rV��,�d
��b,��8:+FW2
�2P��`
��b,��8:K#�,��*�-E��}�cT��q�
��X�����Y(���A���|��@Qc�:[a��Og�X~$SY|�lBg�����X�����)t��X���,���T_$��Yq��),��8:K�\�Ma��9�M�������b�eSX,Ee�eS�,�X���tV\�l
�e����l
��b,��8:+F_6
�2R�P6��R!��/*k,�Bg���1*�����/S9|�lBg����X�����}��PQ�c�:�0� (��/��b(*S,�Bg���1*�����/SY�B�R����U�SXl1F��Y1��i0���0��)t��Y���tV��t
��1*�����N����%�N��P���Gg��K��X���K��Y(���A���|��@Q�b�:�X,�QqtV�����e��(�����ba����b���`,#a�S�,����-E�,�Fg�����P�X=��R9|�lK��Bg���/*����WOa��9VO��Pc���Yq�N.,����tV��z
��1*����WO����1TO��E[|Q�t������b**c��Bg��U�l)k��Bg�����P�X=��R9}�lK��Bg���/*����WOa��9VO��Pc���Yq:Ke�jeKQ:{=�����"�]��1�me�f�G��M[B��Ff�Q��` �����e�R�,b���*�l�6{[�ma�����4[B��Bda!��@[<���X��?���������)U�
�+��V�UWA�PH^���V+����
�+��V�5),c��[��������7������F��w��p������@�a�"��0�+��|�z����U-�s�#�"��+U7�2�6���U=r�/S�O�+U����w���������&v���M�L��D��TW���5�^UM�����m�H����M�L��������7){V�X�+��|���W�NU-�s+"��b���*��|�������Z��U�����V�������7xU��\���|��T5�C=C�|�WJ��;w�<k��SEbOW�n�e�3k�NUS���*U$�t�
�&_��GZ����~�EEf�����M�I=���0UO�G����u�J���������+UM�{-��HD���M�L���&U��|�����7��<k��LE"HW�n�ej�]��Z��c_;�R����*��|��*5��q������v��+V7�&���JT=�v0�"�+�?��"3�6T5��w���@����������9���E����X�g-��H���M�I���|���3|(2s�`?W�n�M��6�S=����P�x��*���<XO�x��A���y���L���<O�������v�X���6���96��o�yP�&��x���{��r�X��������;F���<�����bp�/Ss�`7�S�������\������{��)�������\������5��k���M�I�����<cx�/Ss�b3U�?�j� 7M~s�.@n�ej�<���M�L����T���������\������{���)p��Ag��^���L����=�K�x���d��[�X����g�'CZ��@n�ej.����M�L����T��|����4���������K��kp����4�����g-��H$���M�L����TO����R�~r��g�Jd��=rR5r�/Ss�`&W�n�ej._��j��{-4�"��g�o�M��-��z
�k� (M~s�.���v��.r��j��&M~c���Y�1)
�bp�/Ss�� �S�������\���B�%$M>s�.@n�������?������������W����' ���Z�2-q���"���������8�!"���B�rD���2�N�o�.��c:"!��5�_:�#Z�!$��:�#Zl"�>a"�����cJ�"A��e*�������9U"SdT�MV���Lf�7d��:DQd"��/�����<d�l��<`2��bK�(B�\J��w�
����6�o�z��R���T���\f��Ye�r.&S�����Id��"������@Z*=�� ����\������y���)O�*�AI��p��p�@TE�|����i��Y#?p~��������(�"��1�R����%��?�)�|zb�@�lL�WR��@�-bA��������?�d������i�����D�.)��4\fUq} cO�8�N���>�9(����{���9?�1�GF�rs���3�fK
'���@������z�w
OE�&;;��6+�i���Yy����Yy�����t�������i�'�X#s0Ge�=M���)�w sS��E������
`.��s�b���(��D�,)�7�l��27e�74\�
U1p&�mS�y����)�p ���i2�N��&{8�9+��c����E6q sV�yCA��TA�������#���-L�\�qN�p�3TE����%�����q�O��2�����g(�Q39�q�?��@��|f�g94�����q��������q1U�KC�����2�����`(���b2�+�"�M��,QZ��jYd�Xd72�e�3\2U�c ��{L��Y���C0�e��J�e�PG2�e�3<m�������9iv�,Yp�|�q'���BU��S>S���������8�'(��*�.�9.&��5�%������.��6.�] s\��<�SL���
�TL���G�-3���*��,�]�g[�*���,&��Th����2����i�Xdge���e�3\�
U�V ���9����q����2����	M�N�-b�	���L,�v�/r�g�O����'�9.&��;�%��@�(�EH�%�P�2�e�}�n�2��dT��	M�P���9�2��gh(�9.����)��d�p�f�g�1J��}�q`-���4��q`-���L���S<�x�#TE&��q1�U�9.�Is\�y[�E1J s\����S!��R	d~r1U���*Fd��������oErfm������������:�_�>���9������b�V�O�%�N�	�����)��k�9�he
��Z2��������)��k�D
�	���_o\��D+�g��Z��abE��&�Q�u�M��H�<����%G�[sQ�(������6)�d�(��k��N.*�m�@�Ht�bM������0�nrU�(���[(N�5��6���t����FQ��~�X79��7�"x~�,��\pC�9_��gsQ�(������\)�d�(��{��1W�j�E�I7��+��E������Er�
����"QW�57�"8G��UMx�(�cs�M�8����F80�4������4�s���t��7i���}&q�����������H$��M���~f�5����d��H$�bM������4����8���L�4��<>=�8g����<z2q"N�6M�<��}M�45��h>r,N$2M�&�FQ�h�����m�L��3�myE�=�8'��p
67�"8&3b�E�}0qFN$ZN�&�FQ}L������������S���Q���6��r�j����y9{9�
��Rs2��\�����Cs�@��`�y�(���(M�s"/N�!5Gz��&�M���S���Q�A��3����8A���M�=���E<����sU��g3��"��k��t�����sQ�G�&����)W��������s.jr����:��B�<�=�8Tg��+yn5�8VgbS�\�W�&N���*�d�(��:b`��3yp��9�0��57�"8��\�����!;[G�
��Rs:!]������v"��
67�"�������"�Q���6���+�d�(����XG7��t����o�+�E���cv
67�"8�3�M�5�8|'�\�q�(�s:�/]�����<L�
�Rs:�/]�����sx"��
67�"������j�E����w
67�"8�3.�Mn��8�g������FQs*��*T�r����<��������<��P���E��	��&G�'����*W�G����wr�P5�������<��E������ZO�[Vu���E�o�	���&�^O�3�m[yE^��8�gb�����z����o��x�z���H��rM������UuU�����D|\O�<���:�g������F����3v��������y��M��Z�V��b��Mb�4�@D����eY^V�vu���������H�3�?��&��g����ad����]��>6��c�z�}=3`c�Y+ob����Q��bEl^Wj��Ef}<6�vUM�(��-�H������H��Kl����9�X����*�E�Z�nb����Q�������*���w�;�fUM�(���r;V�
�FQ����"XW��+B��G���nu��U$nu�
�FQ���U�XUO�(��=�H������H}��l����9���}�H��J���H�Y�MUO�{-[���]�
��P���q�HUK�[����o.����+�j �z��������w��x��������X�(��g�X������T�H��J���H���&��o�����;8�+����A�����]�G����u�J���������+UM�{-��HD�����H���&U��|�����7��<k��LE"HW�oEj�]��Z��c_;�R����*�E��*5��q��Q��v��+V�7�:���JT=�v0�"�+�?��"3�6T5p�(Rs��B�X�q�(R���s\?��"3T��Z<HQ�(�+�E��5����g�Pd���~�X�(���m��z
�y1���+U��y���)������\���
��y��)p������\��Q��yq����o�yP�&��x���{��r�X�(������;F���<�����bx�(Rs�`7�S�������\������{��)�������\��Q��5��k�����N�����<c���H����T��|����4�����Q��{��+V�7�"5w/S5���vRS$
s�
�FQ����_���Q��)b{�R�3e&2s��.US�Q��)o�bx�(R���i���Q��|0�+V�7�"5�/�R5�������7���FQ��.Ml*W�������;�����|��"��+V�7�"5��R=�Z>K������+����d�I����H����\��Q��|�������|���@J���FQ�����H��������7���j�`'M�"W����j�D&R���Y�1)
�bx�(Rs�� �S�������\���B�%$M>s�.@n=n��>����?�����X�#�������.)`#��9�6����D�t�r�n)+����z��T�@�$2����C��@��C2�2���D������"j;����!���i������DB��P�L�_Cd$S�gE��4��t�	��2����d�
XU&$Uib3�X�e�\���T5�YQ�~�0��t�I�%c���,�2EMpV��s�C$)l����}�k`O�\��K�����LQ�MP������&<+��{�����j���	�a�XY:��gE���\�2E,2:��5��t�	��28G1�2UMpV���������dhnc�ez���(��'���6�WgE��XS�p�g2b3�����Y��6M�2l���~f�lf���dFp��g:��gE<?��|�B^�XN��dFj:��gE|l����&����N�M�
�v&u�F3r3EM�=��&��6�YQ�h�q���m��X���6�W����ib��`�epL�]g�
�`�~�Xv:��gE�
�l�xEpV��9�Q�6�Y��`�z����3&�D���p�L����LQ�{Oft�H���M�YQ���!�9� N��9�Q��5y�hF����:��gE�
����q�L���
u�	��&xl���&G��
����sM�H��%��&���hRiQ�
@�25g3^4EM�=�q�&V�6�YQ�lZ�����f3�4D~4�&���8S+R���(����!�9� ���9����5�YQ�pF������}j"]�\T��t����	��&���3���&<+���er�k�+���	����8U��8+���N��4��tF������"8+����T���(�s:-Q�T�Y��jb��\�epNgDj��=���&����^ejNglj��<z:cXM,Tl���~�;y�j����(�s=�Ul������ZMS�[�gdk�o��+������H�:xAn��Q�!�9��Y��jb��\�ep�g4k����Q�&R���|e�;���ZS�gE����`�ep���k�
�k=#aE�\�k2��&x��3�5UM���Q�!�m+����366D���r����
�����Z�(YX����(�s=#aS������5��u��,S��
�l[yEpV�����7�
D���lw{7��M��Z���*);����`����)@;������j);����z��]������+����
��
X�*Tv6��C�z��]�&��H��7��uO�Y����U��,R�3��]�����:h;+�`�ag|���2�Ebg����U������7��uU�Y�D�b�k;��w�#��uQ�Y�����:��v���f��X����"��/x\�����:h;+�`�ag|��<.�UMhg����U����~�kn�������E�bk2�,�s�b]��v��������Ehn#\�{���"�>-���A�"��+;k �b���$���w��+B;����H\��M��E�3��b����'vV$.V�&��"�>(�|�B^�Y��d��*�d�Y�d������'vV�2V���N�����uQ�{�&��H\��M��Ep�fd��
�����5�myE�=���"q�
6v�1���.*p��	;+�X�ag��5�l�xEhg��	�`�ag|l�	��&�L�Y�X�
��"5'.�EM�=�������`�ig\�M�s"/�,Rs4�b�k�������U�������|�H^�Y��l��*�d�Y�m6�b]��������U�	�,bs6�b]����	;+b�\�Y��l������gvV$.V�&��"8g32�Mn5���&v��5y�l�����*�d�Y���&�9�v�9�p��5v�9�p��jr�p���X�*W�v�9�p�.j�������U�������M^�P^�Y��t���`�ig������N�Y��6�W�v�9�p�
6v�9���n*�������U��������uU���vV�2V���H����uQ�GO'��H\��M��E�3���b]��v���p�
6v�����n*p����5�m[yEhg��5��U���z=agM�s,/���vV$.V�&��"8�.�UM�^O�Y�X�
��"���p�.jB;��\O�X�;��\��X7��z��.�rM��u���'\����{=agM~�V^�W�'���]�����	;k��cy�����"q��5v���p��jr�z���X�*W�v�egM>�V^�������M�������
hg:����e�J
��&�~"X 2ve
��&����e�Z
��*$;��#cW��l2���F�����B����
��M������bW�	�,Rk�M�b���v�egE,c�+@;����lb��&�������*�d�Y_����uQ��Y�lgE�bk2�,�k�M�b]��v����X����"����b]��v���&v�6��E���Y .V�&���=���&�������*�d�Y��5��uU�YmgE�bl2�,����[���@�,B��f��X��;��E�XW5��Ep,cd�b`g����&���O�&�m���
��H�����3	;k��v����:h;+�`�ag��&�X759������U������&���v�9�p��5v���6�p��j�������U���S�m4�b]����	;+�`�ag����}2agM~�@^�gO&��H\��M��EpLfd��
�`�����*�d�YmgM>�<^�Y�`��*�d�Y�`������vV�2V���H����uQ�{O&��"p�+�d�Y��d����;��M�X��<{4agE�bl2�����&�9�v�9�p��5v��c�M�XW59z6cg�b�k;���M�X5y�l���X�*W�v�9�p�.jr������U��������uS�[�&���]�sM^=���"q�
6v�����w����Ej'\�rM��Ep'\���=���"�����EjN'\���<z:agE�bl2�,���e��6�W�v���6�p�+�d�Y�tF������v���
���EpN'\��M��EpNgd��
<k:agE�b�k2�,�s:�b]��������U��,Rs:�b]����	;+�`�ag��wr�XW5��Ep�'\��M��Ep�gd��
�j=agM~�V^�YcgM�b� �^O�Y����������U��������uU���vV�2V���H}�;9\�����"8�.V�&��"8�32�M���������\�ig�o�	��&�^O�Y����W���	;kb����z�����X^�g�'��H\�rM��Ep�'\�������"�����Ej�Y����W�v�u��{�f�"c_�����v6��;h=[���@�l2�'�"cW��lB��
X�����B���92ve
��&��ld�J
��*d;+`�P������.v����"�V��.�=Mhg\vV�2V���H���&v�.jB;����H\��M��E�5^o�X��u�vV$.V�&��"�6��.�UMhg<��U�	�,b���.�EMhg\�ob�`�Y����b�k;��s��q�.jB;����H\��M��E�=_��XW5��u�vV$.V�&��"���e��
��"��j��U�����Q��uU�Y�2F�*Vv���p��iB;����l�������`���d��]��>���&�m������"q�
6v��l��uS�c�L�Y��X��;����l��y`g���	�\�ag|l�	��&��L�Y�X�
��:u�F.�EM�=���"q�
6v�1���.*p�'v���
�y�d�����*�d�Y�dF�����&��H\�bM��u�v�������Ep&\��M��u��
&\���<z0agE,c�+@;���L�X5��d��.��M��Ep}H6y�����H����u���GvV$.V�&��:h;k��#y`g���	�\�ag<����uU��g3vV .V�&�������uQ�G�&���e�rhg���	��&��M�Y��X�;�����X7��l�����:����	;+�`�ag\�/�|�L^�Y��p��*�d�Y�p�������	;+b�\�Y��t�������vV$.V�&��"��_6ymCyEhg|o�	��M��EpNgd��
�j:agM~�P^�Y�t��*�d�Y�tF������vV$.V�&��"8�.�UM��N�Y�X�
��"5�.�EM=���"q�
6v��x'��uU�Y�z��*�d�Y�zF������v���m���U0v��.V�r����5�����ZO�Y��X��;��\O�XW59z=agE,c�+@;��w������	�,�s=�bl2�,�s=#c�T�^�	;�\��5�v�����p��jr����5�m[yE^����&v�
^�[�'���o��x�z�����*�d�Y�z�������	;+b�\�Y���5�l[yEhg�����vv���o�w�������l���������]���	-;+`����
�������);��{����*);�����e�B`g:?4�g��j;��Z}�X�4��Ep�Y�X�
��"�>3�����	�����"q�
6v��x�-c]T v�!�Y��X��;���|�XW5��E��AD .V�&���}�?2�X5��Epm��]��Mhg<�j��U�	�,b�������	�����"q�
6v��|��b]��v�A�Y��X�;��g����.*;����Y$.V�&��"8G.�UMhg��X�Y��6����	�,�����o�+B;������+vn�L���|�]�"�������*�d�Y?��.�MM�}2agE�bk2�,�����g.���EjN&\�rM��u��M&\���<z2agE,c�+@;��}M�X5��h�����*�d�Y�hF����m�L�Y��6�W���	;+�`�ag����>���"q��5v�A�Y��6�W�v�9�p�
6v���6�p��j�������U��,Rs2�b]����	;�\�
6�v��!��9'���"5G.��&�M�Y��X�;�����w����Ej�&\�rM��u��f.�UM����Y��X����"6g.�EM=���"�����Ej�&\����{6agE�bl2�,�s6#c�T�V�	;kb�\�W�&��H\��M��Ep}�l��3y`g���	�\�ag��	��&G'���e�rhg���	��&��N�Y��X�;���~���
���u��M'\�
6�v�9���n*p����5�mCyEhg��	�`�ag������N�Y��X��;���N�XW59z:agE,c�+@;���N�X5y�t�����*�d�Y?��.�UMhg��	�`�ag�����ZO�Y����W�vV��Y�X/���v��;��<k=agE�b�k���Ep�'\�������"�����E�;���b]��v���p�
6v�����n*p����].v��L;��}[O�XW5��z�������"�^O�Y�X/���v��7��<k=agE�b�k2�,�s=�b]��������U��,R���|���"����������Z�}_��;���^����#8e,"��B���%J7��+g$�W�"���#26��������-����:a�*��k:�b��5�W5:�+Z,c�Q�W5:�+Z,c�>�"�>�����.�o�����:������9-*SdT��M��m���I���)��PA^|� C���)kdP
��D�z�0EQ*�����)�k�)LYe�
��-.C�k]��K��
��0�	
<�)��PA���Ly_Oa�*+T���qZ_�-]��T��|�iB
��DU�'�{�P�����d���������4\�U�� ��BE`�IN��z�}��?fN��%9Q�	2gc������� c;�/���DOl%��G��1����mB`+�eT�1�OM�Br�sFB������dF4�9.5���J��'p�A��d�U%���
��DUd%��q��?2��RDU$��2���+|o��r~Lc������SD�%��}�����Y�d��2\��R���d�[�gY#�0G%�=�mS�A��Uhx�@4��%A��(�4Y>/M4|��mS�A���g�D ��M�����6j�3�0MF�i�d50g%�=�d�� ���a�%S5r~�b������,Pz sY��3\UQz&2qI-2��s\������g	<E����3���J�3�>��H��'Bd��0s��m\��@���������J`~Z	A��n(����b2�+���M��,�g(z������d.��i����*����?2��s\������gY7����q:��teh�<����S���������p`�K��*���;��1�o�_��@����qLP�,U�g s\Lf�k�K����v�c�$��m�y2�e80�Sp����<6���-���/��,C\~�e��2�n�b2�N��&/��,!�9��l�L>��qe���b�@>�=s����6.0^ s\��2<���������9	{N)�"k*��4�6�R
U�T s\Lf�w�K4���K(A��R(����2lS�wh*�9.&���Rh����7��|�q����2l�����d��g����I�#��"�%��6.C��q�[��2Q�o�%	�TQ�&	UqK s\Lf�o�K����,����%�9.C�&�UvK �����ZUqB s_L�U���o�>�'����}\(K���I?���J
�!s���@,���>��3|�Dj)���:���h��)���{���D*)������)T �	��_��3,�
5�mb����+"�4�mb�<�gbG�\��c������sQ��KPg�Lb�l�3|���m����V	��It�bMx����7CB��\�����_W1�pR�I�c�����EM`�<3$����&0W�}�l��\��+�����=5��JPg�Lb�l�3|��ks��&�`	��I���M(�������E��:$�D])��g���u��&�`�e��=�
��14��=Mp����w*C~� ^�W���3|�`+vn�L�_�%�cMp�/A��3�S�	}���d
��&�>�9�g	�X�4�_�����9����L�4���_��m2!�\�����3|&�i������hB�����G3g�L"�lB/����4����3|!�m ���'3g�LrbO�&s����sQ�c���3��S�	��%�3|!�m��s0��lB���cLh9W5y�`�����rp���9��r.jr���>h�l2���R�<�D^���cj�&��sM�=�9�g��`��u�/�;G��Ss6���k�3|	�_�g�+S���M>���26gz�EM=�9�gb��\��cj�&N�����g3g�Lb
l�3|���*tS�[�f����:�����3|&1�
6�td�<���3yr���9�0��5�>�p�����=�9�gb��\��cjN'����<z:s��$�Q�&����B^�P^����t>#W�������utS�[Mg�������"8����N��S�	�%�s:#0�T�Y��3|&���5�>�t�_������3|&���>��t�_�����3g�L�/lB��g���_��	T(�s=qbO�&T����uS�[�g�������"8������X�*xAn��9���cy���9�g�\��cp�'T�����9�gb�\��c�;���B]�g����{
6�Uep�g���
�k=s�OVu���3|	����UuU�{�g�������"�^����UU���z=s�/�7��<k=s��$VU�&<���\OXUW59z=s���G��+�3|L�g�B>�V^��{��}��
����������&t~�g�X���Mf�D��e�2hgZvV@2�-`g��]������M�=^Y�X���U�vV@2����	���s\�BM`g�Z�o"��&��.;+"�\�Y��gf��5��u�vV�.��&��"���dl�
��:d;+b�X�ag\�o"��&���?���:�v���G��&����7��M�	�,��W�v��5��E�9_p��5��u�vV�.��&��"����]l����:h;+b�`�ag���\26EbgZ_5���:�d�Y�(����	�,�c-c+;�������4��Ep}Z6�m�xEhgW0v�`�X�.�m�I�Y����W�v�A�Y�X�;��g6������O&���]�cM��Ep}P6������H����u����>����MU�GO&���d�shg��o���&�M�Y�X�;��M�����	;k����<{2agE�bl2�,�c2-cST��vV�.��&��:h;k�������"83.��&��:��3.6UM=���"�����EjNf\l���{2ag��U������&�9�v�9�q��5y�h�����:�d�YmgM�s$/�,Rs6�b�k2����6�q��jr�l��
��:�v�9�q�)j�������u��,Rs6�bS����	;+b�`�ag��i����M�Y�����z6agE�bl2�,���e����;����X��;�����T59z8agE$c�+@;������5y�t�����:�d�Y���&�m(�����m:�bl2�,�s:-c�T�V�	;k�������"8�3.��&��"8��26M`g����\�ag����&GO'���d�shg�����&��N�Y�X�;��g������	�,�s=�bl2�,�s=-c�T�V�	;k�������*;k"����z���|�X^�g�'���]�sM��Ep�g\l������"�����E�;���bS��v���q�6v�����i*p����]$.V�&��:x��3.6UM�����&�m+����v�D.��r����5�����ZO�Y�X��;��\���T59z=agE$c�+@;����&�m+������������I>��&t~�g�X���Mf�D�@d����Mh�Y�X���UHvv=G��L��d�����UI�Y�lg,c*;����y=���P�Y��������	�,����X�*W�v�����.�EMhg����U�������m���������*�d�Y�������	�,��"q��5��E�;������	�,�k�M�blB;���U�@\�rM`g{�<.�EMhg����U������k��&�������*�d�Y?�5��uQ��Y��W�"q��5v�9�p��jB;��X��X�
��"4�.�=Mhg\��M~� ^����5��]�p�gv�������u�vV$.V�&��"��Mv�njr��	;+�X�ag\�M>s!/�,Rs2�b�k2����m2�b]����	;+b�\�Y���h������GvV$.V�&��"8F32�En�d�������"��L�Y��X�;�����X8������U�������|�y�"������U����>����uU�G&���e�rhg���	��&��L��E�bW������&�9�v�9�p��5y�h�����*�d�YmgM�s$/�,Rs6�b�k2����6�p��jr�l��
��*�v�9�p�.j�������U��,Rs6�b]����	;+�`�ag������M�Y�X���z6agE�bl2�,���e����;��N�X��;��N�XW59z8agE,c�+@;���N�X5y�t�����*�d�Y���&�m(�����m:�bW��������uS�[M'���o�+B;���N�X�;�����X7x�t�����*�d�Y�t�������	;+b�\�Y��t�������vV$.V�&��"���p��jB;��\O�X�;��\��X7��z�������"��
�����*xAn����&�9��Y�	;+�\�ag��	��&G�'���e�rhg���wr�X5��Ep�'\��M��Ep�gd��
�k=ag��]�&��:x��.�UM�����&�m+����v��.V�r����5�����ZO�Y��X��;��\O�XW59z=agE,c�+@;����&�m+������������}��{<
hg:����e�J
��&�~"X 2ve
��&����e�Z
��*$;��#cW��l2���F�����B����
��M������bW�	�,Rk�M�b���v�egE,c�+@;����lb��&�������*�d�Y_����uQ��Y�lgE�bk2�,�k�M�b]��v����X����"����b]��v���&v�6��E���Y .V�&���=���&�������*�d�Y��5��uU�YmgE�bl2�,����[���@�,B��f��X��;��E�XW5��Ep,cd�b`g����&���O�&�m���
��H�����3	;k��v����:h;+�`�ag��&�X759������U������&���v�9�p��5v���6�p��j�������U���S�m4�b]����	;+�`�ag����}2agM~�@^�gO&��H\��M��EpLfd��
�`�����*�d�YmgM>�<^�Y�`��*�d�Y�`������vV�2V���H����uQ�{O&��"p�+�d�Y��d����;��M�X��<{4agE�bl2�����&�9�v�9�p��5v��c�M�XW59z6cg�b�k;���M�X5y�l���X�*W�v�9�p�.jr������U��������uS�[�&���]�sM^=���"q�
6v�����w����Ej'\�rM��Ep'\���=���"�����EjN'\���<z:agE�bl2�,���e��6�W�v���6�p�+�d�Y�tF������v���
���EpN'\��M��EpNgd��
<k:agE�b�k2�,�s:�b]��������U��,Rs:�b]����	;+�`�ag��w�g\�����"8�.V�&��"8�32�Mn����&�m+������&v�
^�[�'���w��x�z�����*�d�Y�z�������	;+b�\�Y����.�EMhg��	�`�ag�����ZO��E�bW���������uU�{�'���o��+�����5��U���z=agM~s,/���vV$.V�&��"8�.�UM�^O�Y�X�
��"����g��+B;������f�"c�����z��M��Z�V��b��Mb�4�@D����eY^V�vu���������H�3�?��&��g����ad����]��>6��c�z�}=3`c�Y+ob�������X�����)b�Y�M�]US`i��c+��b�`�ck ����9�X����* ����W�V�X�u�H�
D��T�W����4�j
,�:zl]M�X+�����r�`]��=�k���K���V���+ ����V�U�X�u�������XYV�X����e�bEd}_,��R$X��j��;v��l��v�+�?C�"2�
"U-n�o��&��h`YU�D�����~��+�j���v��t��"��+V@6�=��V��)p�{�*q�R$R��j����������3]�r���B5y�y���;�S�����iO��o�W���Z<�S���+ q�{SiR����}�`MM~s�.����T$�t�
H�����@zT-����l�H��J�+eOT��g��X�t�|���]�����$5y�q��Z;R�����G��{����;��E�B�X�Q����<���3�(2s�`A+�����]�R���5����g�Pd���~�X�P�D��|��]��6/&T �s�
��"47�S5�y��"v�+�?S�"37�S5��y��"��+V@��=��&��q�|����4��T���6�S$�s�
�z�'���;F���<�����b$<��i��#w�Z=�N������:��������=xN�X�+ ���HN�������yo��y�
�o�z��5v��o�{��&�9s`�����vs�
�k�'Z��;v����{��"Q�+V@J�=1�&�9s�������\���2��{P��)�������\������4y���Kb����|0�+V@�=�&��t�|����4����K^�'����r��j� .M�c���Y�m)I�b$-�gi��Cw�Z>K������+����d�I�X�r�|����\�2����4y����{-4�"��g��%%z�(M^s�.������7���j�`'M�"W����j��7�����|�"��+V@Z�=��&v��)p��AI��@���L!���&�9t`��=�����&�b��<~6��L���V���*)#����b W��dB�I
�A����B���9re
�K&��l<�J
�L*d3)`�P������*r���N"����2�=MxV�%(E,$�+@E����jb#��&<+��-�H���M�YQ_����tQ��J��*Eb&k2��"����r�UMxV�����I��@Y"���`(]��gE\�nbI�`�E��"W �R�&P��=�S��&<+����Hd��M�YQ��5��tU�u�S$�R�&��(����[[��@D&B��]�xK������Q��tU�Ep,c��b 4����&<+����k�������
FkHc����3	�i��v������v�"Q�
6gE��&�L759������T��8+����k��y`9���	��\�qV���6����j�������T�m�S�m4!7]����	�)��`�qV�1�q�.*p�'����
�y�dB|��s*�d�EpLf\��
�`�~�Dv*�d�u�������YQ�`By*�d�u��
&����<z0aBEl>�+@���L�O5��dB�.���M�YQ��c����'��M(P��<{4�EEbAl2��:h3j��#y�F���	�\�qV��c�M�PW59z6cH"D�kG���M(Q5y�lB��X�*W���9���.jr���+�U��8+�����Q7��lB����:����	g*E�`�qV�����w���8Ej'<�rM�YQ�pB������	}*b]�\
T��t�������U$�T�&��(���d��6�W�gE|o�	q��M�YQ�tF������:���
��YQ�tB�*�d�EpNg$��
<k:�UEbQ�k2��"8�"�UM��N�U�T�
P�"5�6�EM=�0�"�
6gE��wr8UW5�YQ�zB�*�d�Ep�g���
�j=![M~�V^�U0���zU�r���r5�����ZOHW�8V��������fuU����U��U���H}�;9\�����(�s=�[l2��"8�3��M���������\�yV��������jr����5�m[yE^����&��
^�[�'���o��x�zB����*�d�Ep�'$������"����Ej�Y����W�gE����D���=���	��A��2V%bg�Y?,�2hgZvV�2V-`g��]���+Sv6��xe#cURvV!�Y�X�
��&t~h^�p�+�v���&v��iB;����"�����Ej}f6��uQ�YmgE�bl2�,���z[���@��C��"q��5v���&v��jB;�����@\�rM`g��dp�.jB;���|�X���"x~�,�\�Y�����uQ�YmgE�bl2�,��������	�����"q�
6v��x��k��Eh}�,�X��,�s�b]��v��������Ehn#\�{���"�>-���A�"��+;k �b���$���w��+B;����H\��M��E�3��b����'vV$.V�&��"�>(�|�B^�Y��d��*�d�Y�d������'vV�2V���N�����uQ�{�&��H\��M��Ep�fd��
�����5�myE�=���"q�
6v�1���.*p��	;+�X�ag��5�l�xEhg��	�`�ag|l�	��&�L�Y�X�
��"5'.�EM�=�������`�ig\�M�s"/�,Rs4�b�k�������U�������|�H^�Y��l��*�d�Y�m6�b]��������U�	�,bs6�b]����	;+b�\�Y��l������gvV$.V�&��"8g32�Mn5���&v��5y�l�����*�d�Y���&�9�v�9�p��5v�9�p��jr�p���X�*W�v�9�p�.j�������U�������M^�P^�Y��t���`�ig������N�Y��6�W�v�9�p�
6v�9���n*�������U��������uU���vV�2V���H����uQ�GO'��H\��M��E�3���b]��v���p�
6v�����n*p����5�m[yEhg��5��U���z=agM�s,/���vV$.V�&��"8�.�UM�^O�Y�X�
��"���p�.jB;��\O�X�;��\��X7��z��.�rM��u���'\����{=agM~�V^�W�'���]�����	;k��cy�����"q��5v���p��jr�z���X�*W�v�egM>�V^�����{+|�������n�oC�����T���
-���<�}������YdyW7���������5��z^���y�T��\�`���x-`S�N��X=K�:�jt��t������,������\�+"_�;)��-a�kE��+��L��D5;Mkjd^�|�����g2[n45��/�4��Ab%K��Y7�&��s��M�=)�y�3�["�t���$�����������i� ��������S��G~��I��-v�P�H��}A�����hy��X�<�k��s���L�e&�{�:��6�����4�
����[�e-�c�	��Ntd���D�R�x>o�cBw�-a�L�9��6�=G �`�4��R�i�s8o,bFw���D8.`O��a��\��d�l���V����h��i��m�f�+���`ta���"�0�����-��;F��C3��f�<`z,
t�l2�g6�=�!V��X��>h��
05R�~�o��{f�^s"�:��Y�%��c0�V/����d�@<�m�=�a��
�sx�����R#w�����b���K������M&7��}�6���m�
�f��������mo�
{�m����w@�7�S��F��sxCl���P#Y0��7��I��'������>����>D��}���fK���n�@F,�8���X�5<�����f����Z����w��}�B��|f�g_����������a���is!���Nw��4<�����l���X�}��
{�}����k���v�.��|�}���f�r�������}�a�������l	���x�����f��J���B�.��SH�F����-�R5[6*=�S��q��F���\��)����KUL�=Ss!(��^c!l��|���n���������T�Nk�Y$<�����~7�l)���!�}��a��H�m(��N[��#<�}��a�*B����g���j�<Qz,�>�=�w����B�����f��I�<���J����a��A�,��o*uw���F'=V<s!�j��c!�w���J����fK���v`.�.
������AEcvz������;�ywi�g.D��\�]jvz�������a�	�9*&���X����s4�5"
`.�
�����
�����������`.%J�o,����B��Q��YRc�0�w����=�.�3���#��)���������z��Q�h����6�;A������cl.)`{�����'+S��:��X���@,�C�\���PV�@<2���F���@L�C:�f`��P����~�P�)+�$>����%B,R����<���X�(WJ����
Ct�-EMp�-A�k3��Q�	��1������
X�$�sm&�3�5��6�����qU�kc��W8"i�k���w�#��qQ�kc��m����\�����5�5��a�9_p�bKQ�kKP��L"~l�sm��k���&8������D�(���������E,�:I�$H�&<���Eh W5��6�2��c"������&8����{�!�m���)�sm2K+vn�L�\[�w��+�sm	�\�I���Mx����l�`rS�c���k3�aR�	��1x��a�g.���bjN&d�rMx�-��6��U�j�����6;+�
�|%u�F��EM�=�9�fo�`�kcp�f���
��������6�W����sm&>��`�kcpLfT��
�`�\�IT�bMx�-A�k�l�xEp���9�b
6����`������3��L���+����L�15��d�\���
6��<�0�9'���15G���&���k3�iS�	��%�sm!�9� ���9��m�5����m6a�\������6�X7����16g��EMp���9�Qo���cj�fN�����g3��L��l�sm����sS�[�f�����9�����sm&�y
6��6��������L����S�	��18�3��R������6+B�
�\Ss:�]�����sm&��
6��6���ymCyEp�-��6���+�d�kcpNg���
�j:s�-��
���6�t���Mx���9��n*�����6��
�rMx���9����jr�t�\��E�r�3���	��&����k3��T�	��1������j�sm���)6��\�s=�7�T�V��sm!�m+���9�sm!V�
^�[�g���|�X^�g�g����s*������	���&G�g���Xw*W����x'��tQ�kcp�gN�9������}���Z��k�?]�&�\[��m=�P]�����sm!�m+����3��B,R� �^��k�����Z��k3�MU�	��18�B�UM�^��k3�16�
@�2u�k�l[yEp��u��R�:*{|��<
hg:����e�J
��&�~"X 2ve
��&����e�Z
��*$;��#cW��l2���F�����B����
��M������bW�	�,Rk�M�b���v�egE,c�+@;����lb��&�������*�d�Y_����uQ��Y�lgE�bk2�,�k�M�b]��v����X����"����b]��v���&v�6��E���Y .V�&���=���&�������*�d�Y��5��uU�YmgE�bl2�,����[���@�,B��f��X��;��E�XW5��Ep,cd�b`g����&���O�&�m���
��H�����3	;k��v����:h;+�`�ag��&�X759������U������&���v�9�p��5v���6�p��j�������U���S�m4�b]����	;+�`�ag����}2agM~�@^�gO&��H\��M��EpLfd��
�`�����*�d�YmgM>�<^�Y�`��*�d�Y�`������vV�2V���H����uQ�{O&��"p�+�d�Y��d����;��M�X��<{4agE�bl2�����&�9�v�9�p��5v��c�M�XW59z6cg�b�k;���M�X5y�l���X�*W�v�9�p�.jr������U��������uS�[�&���]�sM^=���"q�
6v�����w����Ej'\�rM��Ep'\���=���"�����EjN'\���<z:agE�bl2�,���e��6�W�v���6�p�+�d�Y�tF������v���
���EpN'\��M��EpNgd��
<k:agE�b�k2�,�s:�b]��������U��,Rs:�b]����	;+�`�ag��wr�XW5��Ep�'\��M��Ep�gd��
�j=agM~�V^�YcgM�b� �^O�Y����������U��������uU���vV�2V���H}�;9\�����"8�.V�&��"8�32�M���������\�ig�o�	��&�^O�Y����W���	;kb����z�����X^�g�'��H\�rM��Ep�'\�������"�����Ej�Y����W�v������`g��}=�����n6��h=[����9f6�����gd��MdyYk���?��*"+��#a���'��{��Q��b��U�FV�ve�g��D���������Ef�����Z
��R����y]���"������U5rwiR��"q�+V@�=V�2�j��#`���n]������&v��)�;P�:��h]��}��#hV���L�-7�cU�@�@e���X �u�
@�"���q��j
���l[E�VW��\+{�ZM,V�S w�&e�*��bp�*S��*���e�bEd}_,��Rp�*Ss�`S�S�^��*`��B�3�*"s� R�R�V��j���vr#�R��r�g��o��A��|��]�g-|�H������L}f���j
��A��D��T�����Hk����������3]�r���B5y�y���;�S�����iO��o�W���Z<�S���+�{]��M���������o��x��A��D��X�������Q�����v��"q�+U��&eUj���vr;,Ss�`EW�n�M�����z
<j�`HE,DWj�Ef�l�j
�k� G�
=cx;,S���s\?��"3T��Z<HQ�(�+�;f��5����g�Pd���~�X�1���m��z
�y1���+U��y���)������\���
��y��)p������\������4y����[m���E�b^�y��"��+Vw�2���5����g(Od��Ap�X�U��\=�M�8j� ;E�6Wj��Df���j
<j��9Eb5W���ej}Mk��3wr�mR�m��3�X�y�����L�����=�M�����;o�����bp�-Ss�b1U�??k� 5E�0W���ej���z
�{��"��+�?Sf"3w�R5�{0�"��+Vw�2������)��s���c�bpw.Ss��+U�?�j��/M~s�.@��u*����r��j� .M�c���Y�m)I�bp/Ss�`(�S�������\������xO��TM�����\>��+�;x���/����^�M���+��|��o�#���Z>J��������4��\��Z>�I��X�~~��AL�DC�X����\>8H�8j��$El Wj��Df	I����t���������@�������m@�������UR F2�5��@�L:������TKXI�d%�s,����L�=^�xH���T�fR�&R�p�	�\�3T�
5��Dj���e�{���(�KP�XH*W�������F�EMxV�A[J�HI�������m1��Q�YU��L*�d�Ep���������(������5��D�;���P��	��"�V�����&<+���E�@<�rM�.{�<��EMxV�A�K��J������k_��&<+���H���M�YQ?�5��tQ��L���"���5gE��u��&<+��X��K�
@h"4���=MxV��I���
��Y���4��\�p�gb������YQ�6E�2l2��"��M��njr��	�)��X�qV�����3��r"5'RS�&�����m2�5]����	�)b��\�N���hBn����G�S$~S�&��(�c4�8]T��O&���o�+�������T��8+�����N8������T��8+����g��+�������T��8+��cLXOW5y�`����|*W�.�9�P�.jr���]�s�������&�9�N�9�P��5y�hB����*�d�u�f��;G���"5g*T�&�����6����jr�l��
D�*���9�P�.j����&�U�E)Rs6�E]����	W*5�`�qV�9���n*p���05�u���g�T$�T�&��(���c����q��NxR��������*uU����T��T��H���/uQ�GO'�H���M�YQ���&�m(���:����t�������<uS�[M't��o�+������@U��8+����HT7x�tB����*�d�EpN'D���=���"����^EjN'l���<z:aXE"Tl2��"���p��j������VU��8+��\��U7��zB������"<+�`|��������	�j��cy�����"q��5gE��	���&G�'���U�r(_���wr�V5�YQ�zB�*�d�Ep�g���
�k=!a�s]�&�����m=�]]����	k������z=acM,_� �^OY����������U��8+��\OHXW59z=!fE,b�+@5��R�&�m+���~O�;�@d���<|7��M��Z���*);����`����)@;������j);����z��]������+����
��
X�*Tv6��C�z��]�&��H��7��uO�Y����U��,R�3��]�����:h;+�`�ag|���2�Ebg����U������7��uU�Y�D�b�k;��w�#��uQ�Y�����:��v���f��X����"��/x\�����:h;+�`�ag|��<.�UMhg����U����~�kn�������E�bk2�,�s�b]��v��������Ehn#\�{���"�>-���A�"��+;k �b���$���w��+B;����H\��M��E�3��b����'vV$.V�&��"�>(�|�B^�Y��d��*�d�Y�d������'vV�2V���N�����uQ�{�&��H\��M��Ep�fd��
�����5�myE�=���"q�
6v�1���.*p��	;+�X�ag��5�l�xEhg��	�`�ag|l�	��&�L�Y�X�
��"5'.�EM�=�������`�ig\�M�s"/�,Rs4�b�k�������U�������|�H^�Y��l��*�d�Y�m6�b]��������U�	�,bs6�b]����	;+b�\�Y��l������gvV$.V�&��"8g32�Mn5���&v��5y�l�����*�d�Y���&�9�v�9�p��5v�9�p��jr�p���X�*W�v�9�p�.j�������U�������M^�P^�Y��t���`�ig������N�Y��6�W�v�9�p�
6v�9���n*�������U��������uU���vV�2V���H����uQ�GO'��H\��M��E�3���b]��v���p�
6v�����n*p����5�m[yEhg��5��U���z=agM�s,/���vV$.V�&��"8�.�UM�^O�Y�X�
��"���p�.jB;��\O�X�;��\��X7��z��.�rM��u���'\����{=agM~�V^�W�'���]�����	;k��cy�����"q��5v���p��jr�z���X�*W�v�egM>�V^����������e�?���3�,"�@�Y*V���,�O�+�?��"rzYiXu�����,+�gK����d�x���
V�s��#����CV������c���2bc�9W>D��-r�/S��5�yUj��e��x"�����7)YX;W�
�&_�^�%�qu��l����u�Rp�/S�������@n�e����V�
D�2����Y]S 7�2uny��cr�/S�X�*U ����|��W]S 7�&%�jb��X����{����)��|��g5�UU�n�e�3^e9U���V�����X�*U7�25g-6�=��l��r�
��Q���q�HuK�[�[�j�o.��M�JY�,��B�|�W.J5D���Z��T�S�
�&_�>�F��5�}��RM,N�*��|�:?��|���s,*3s��L+��|�zl{a������S�R��g��d���������/����T������x��n��o��������]�g�]����bp�/Sc��G��?�io�#��m���)�S�9���O���}75_]F��
��Ar�����Q�`\���qr��d0b9b3��K[*"7)��o�B�&�k�����/S����F������k�T��c`��KC*B4R�6�(3����j����.�h�T�-f�w�e�9AM����������K����/���h��N��
#��(���C�����3bp'�L�]�����u�L���g��e�v^ZO�10Y��	��~
���y)<5��h���SD�3bp'_�j��ujL�=X���L�S1�u^zOY����|�z.�&9J��v*Ofj����������zi75��l���S$�f��m�Nfj������^zNY����|�z.�&Yk�����o�����g�������{���o�{)7���T�@�����{�1#fw�e���,������^JM)����|�����Rs��{�3E�^F����d��^�K�10Y�������������&���y'_�j����������|���o�|�/����^@��W)��$a*#�`��Kq��(M���5_jKI����|�����Rs��|),E�OF����d�(��)'5�@�����|i&#fw�e�6�������5_j� )%[������k�4��c`��KA���E�Vk���I�EF��`��j2�Y���k��"��3�;�2U�/�����RI����T�
!��#$����^@��w���O�@��q�=��z.����c1�F23��H�2�$3��d9��bV2Ba%�vZ��1/�������1��d�d&�DF��d����
�BN`'�j�-"�9N�V��&(�HHF�%R��UDFR��`�he)������ep-�[bR���TH�2H���9�ZQ[��HNj��e�y �d��@Y"v�'�9�ZQ[��HR*�kE|.�HO9'P��-u���� 'X+�A�� )+#��kE��>O_�QN�V4�r�ARYF�	��2��}.m�ARd"�.�Io1'\+�`-E�K�r����fL}1��n���'X+�`;�9�B|#X+����1[�}MBl�]/�����f�T�t����u�l�&9������>3bN�V��v�+���|��H����������U&��F9��2�:��mF�m�RcW����d�����~3�N�V��R��85���W&�����Y�2!>������e�Tf�N
20��	�$eg��p�h%@E�����2X�3�N�V4�SW����d���	
"�9t�H����� '�W&th#��-���e���,�"_�(R�4�@�s�xiB�IA'\+�A�Q�����E��&Th��p�h��6aC5������4@
��9�#E��&��9��6�I�H�F�E)R�6�E5����	W$�h�p�(��6S�j���j�TD~T9'��&�i�T�t������"G��q�T-Nx��9�ZQkqB�j�����4�ti�P�"U��T��L^�p�AR�F�	��2�.&��]Q�����U'�i:)kE����T�V���"gW�okE��	�A'\+�`�����d`���V
�5rN�V��Z���d���\
"�9��H���M� '�W'k��t������NU��`�(��=�U#��kE���jU�����"g��okEL�*"��2x{B���,_�b�	�$k��p�(��=�Y5����	�D�5r(_�:�+9\�9�ZQk{B�F�	��2X�3��&�=!a�sm9'e�h�aE��+���2X��UA'��'l���k_���	!+r��|��'�l�4��s��������('��'�l�����E��Y����7�;����z���������~^6�w�G�"�;�6�nNI������D�����^sF���FED��
��WL��
�<�y�F�z��ble������)��B�*�^U�q��"��>�E"���H���|
������#_9J:���L���z�+&I�<�f&>,��)�CY��x�\�[�rLT�X���\�)��)�By>��T��A��%�
r_�e(n��R�I�
r�'F�k��#L9J
��<Sy/�G�r�*�sGI��&���.��� K����������� [�Q�V�����(yO�Xr�)��6`����d/;
7��%'���x��0+S������� �6��n��9�=AJw�^�������� ��i��	�]��V6"�!��V�V��@(\%@���LvtEY)�'3���lj�RV��>��i�1�9R� �P��i65�Q)+A��=�nRF5��Q)Ebd�x_Y���HF��4��U�U
�8EL�d����������V Aj����lJ�R2��Z��e�n��1�j�p�d���8F��)����I2��	��Q��@J5(K3���
�]�@
��N�w�
�D F���N�w���N����H��@L�������AR� �V��`���A�+�nRG�]�@���f�wq
�F��	��F����K*=�Z.\�(�����r�wq��r������/��w��B��K�������H-�2�1x�$�P�V��Q�Y7JR�E��H�v���6K.8�L.AL�X7����Ro��Y7�J�\d�&u�Q�%=@-.Ek�
�����r��^
��2L�<���u��U�A�g"{W.�~��*���g �
���o�kW.�g"[W.�~�
�*�Q�g �\D����K�3�g%3��"����H-�z������$/�g�3��S�����4^ �Y�MN��Y`�D��YD��Gpa��@m.
L��-����f�79
��F����kf�������H-�z������$i*�g-3��/�#�8H�*�V�)U�L*�H)�JMR�E��:j����5}��U~bMJaPj*�Z.����GW.�T �\D���Ja�4�Q^4��/���4H-�zg���J�$�%�ga3�T/X���$2v�Ro'*�v��R���NT���n)��)Un'*��$�J�R�E��:k��[����{b�$aP�%�Z.�v���I�(�%�z�"RF5�Q��@j����j��f�%|�v��e�
���S�Y��6�\/F��{����<���Z�W�)���m���ak�Z&��=�)�Z�V�)�q���n�e�l�m�E�V�)�|����j'X����Q�B��w�X[���tP�p�^��g(hi��8�E#��z
�S���Q4c+w�����ez��*��'E�	�(������I��w���Q���.)BN�</S�G�H�w�X��GI$�����{.��tHr�ey�Z�>�%x�w����5�*RNxG��me/���'��h����4P�r�;�fl�^����/3mQAi�"��w�X�5�'�q2���+�����E��e�����8�~���gWhow}b��.��]�������GWkod����Hi)'��h��2F�Jc��]�a�]��"��w�X��c�����Z��������Q4bS�;�.�q2�}�Ev
�vE���Eh����1N��������\�r�;�f����KS]�ai]��k�7��}�uu
�"�H9�E3��KQ�)��������dr�;�FL���]���Q4c����"��w��T�nLs�L}�a]2c2�Ut*}-�1N������pb-���Q4c�S�b���������VS���7V�5�N-RNxG��i�\��6�������N���Q4bs�<�4�q2������v��9�j�L���F�'S�yX)��$Z�p�\�J�aQ��8���"���E�	�(���y��4���w����S���w�5�.RNxG��M+�7�1��q*��)'��h�J�a��8��������"d��2Tz�Nc�L}�a1\)�"��w�X�,��k�7�;�Fl��m�RN�E3Vz/������
�g�row�X�=�u���Q4c����i����=�|k �_�����+���9N����������������'S�{X��@��H9�E3��5�Os����+��Um�r�;�f�4_�B�10���5n�����(�b��-�4a�^��7V�8j����o>�mk 
a�����+�=�9N�����������|M��'��h�J�a�Z�����+��~Qc�}�a-� [�I��h���|0���d�����]����o>,a ��2����k�Zt/`����H�)'��h�J��%j���o>�[k@��"d���2�V����{#����}��wm ���}�ur@����mlK@�i$3����T�-c�N2C��%%dL1+���m;5d�����V�l��bf2BR��"#dn2C��k���l!'��H����'��(��P����*J�������9�E���V2�N�E\����� �*���j2bN�Elu.";�QNxGQ�7RPF�	�%bGy�AQj��Q�V�"��
:�E|��HQ9'P��-u���� '������A�VF�I��(�[��),5�	�(��$f�t�tR�(��^����H��P��$�e���;�"XK�R������fL1��n���'��(��TW��
�����-�^S <f�����I�M����7�;�*(�$]f��;�"��I����d�+�3H
��9)wE������!_,'R�2a5#���QT���L�M�r2yeBv�����N���4a75����	�$g��;�"XJ3%�����9��|#�W&�g��tR�(�`����d`��3H���9)wUPTd��������������rGQ��0�=5����	D�3r�B���	��ANF�L��F�?[�I��(��Yd���D���	����K^4Hj�:)wUPjT��%��F���	9'���
�]mB�j���k3i�4��sG�X�M8Q
r2ym������R�jmB�j���k�4H��:)wE��f�QM20Xm���H�*�d���4
��4�N�ElW�E�Z�/��Z���sR�(�`-N�R�r2{q���/��
T�juB�j����5H:�:)wE�]MY��|#����[W�0�-���Q�Z�iO5��`�	�*rvE�FxGQku��F�I��(��:��j����^5Hj��9)wE�V'L�F9��:aW���F��*R�:�S5����	�$�j��;�"��WrHU�r�;�"X�^5�N�E���nU�����"g��o�w�`
W�������U��e�kOX� )Y#���Q������d���{
"�9��H���U��������o���rGQk{�s�$��',l#��-���QT��kOxW�r2z{����]W����:VD�5�/d����9kY����N6H*��9)wE��',�F9��=af���F��,R����]W��Qt���~��6�2v[?�>;����s
:�%cc�����i�H�2hg3��l���bv6Bag�v���1;�������1��l�dgH�F��l�����
�BN`g�j�/"�9Nhglv6�dl���"���E�b5�	�����A��F�I���eK�j����
��I1'��"�:_D.V����"��	�.6rN`g;��.V����"�:_D.VA'��>�������Y������j��Yeg������bg��>O�QNhg��
�.6�N��Ep/�\2V���E�]j�.6bN��E��"\�F9��E�4c�����E�v#\��8��E��-��]!���L;+2��^���$��������UPv6H��:)v��N���$'s_���A��F�I���e��6���E�V&\l��;���U&\�F9��2ag�H�F���RcW�p��d����
�.6�N��E��f�X
20��	;+rv�F�L�� �b#���YKe��� s_���A��F�I��
����]=��Yka��F�I��
N]a��j���v6�dl���"U+.V���^���������jgl'�"K��;�T-M�X��,^���A��F�I��
����$_�,R�6�b#���Y��6�b5�����v6@���9��E��&\�9��6ag�H�F��,R�6�b5����	;$]l�;�`�����d`������U����	;$]l�;�`��,r��|��H�������bg��	�QNf/N�� ���3@;�T�N�X
r2yu��IA'��"��/��]Q��Y��:�b[�I����L�I�N�Y��+�7B;�`�N��:)v�Z�)c5��b�	;$]l��;�`�N�X�r2{u�������Y�ju��j����v6H��:)v������j��Yk{��F�I����L�IkO�Y����7B;���"r�|!��'���Q��,����A��F�I�������('��'�l�����E�(��p���v���p�tR�,��=S�j����v����sR���c��p��d����9��|#��'���\l_���	;+r��|��'�l�t��sR�,��=�b5����	;D26rhg�jvVd���������B�v�����r�0��z�A��dl1�v63�A)c[��l���
 S��F(�l�N�2`g3��=�26������l������s����b[�	�,R��E�b5�	�,��������Y��9��\�9��UPv6H��:)v���o�X
2�vV!�� �b#���Y[����j��Y�7"��F�	�,bGy���j��Y[����*��v��Rs�t��s;��Rwx�X
rB;���l�t�tR�,�[���b5�	�����A��F�I���e�K�j�����K�A��F�I���R���('���fL1���n���'����e��+�7B;��igB�������9�^|#��
��IA'��"��Ir���d�+v6H���9)v�v�,���|��H�������bg������('�W&�l�����Uj�J.V���^���A��F�I����L�A��2agE�� ����	;$]l�;�`����d`�v6H���9)vVA�Y����7B;�`-L��:)vV��+L�X�r2ya�������Y�je��j���+v���tR�,��$Yd��`g���	����Kv6H��:)vVA�Y������E��&\l��;����&\�F9��6��H9'�������� '��&�l�����E��&\�9�6ag������bg���2V�V���"r��9Y�6ag������bgl��E�Z�/v�Z�p��sR�,��8�b5����	;D26rhg���	�AN&�N�� �b#���Y��e��+�7B;���U'\l:�v�Z�)c5��`�	;+rvE�Fhg��	A'��"X�3e�&X�:ag������bg��	�QNf�N�� ���3@;�T�N�X
r2yu��IA'��"��Wr�X�rB;�`mO��:)v���)c5��`�	;+rv]�Fhg#�vVD.6�/d����9jY����v6H���9)v���p��d����
"9��H��.V����"X�.6�N��E��g�XM20Z{��6�rN��Up��.V�������"g��od���������=agE�Z�/`����
�.6rN��E��'\�F9��=ag�H�F��,R����]W���c�^SS�>��b��3���Ye��mS"6FH3�H{3�l��m	���4-��%ac�X��	)�l��m	p��l��)`c�����m���1��s��l�������� ��)N�wV3v�����F�El���qIW�q���1I���F�	��j�V�b�VM1�62��
�l�������J ��9N�wV3���h��Y#��5S�G���g5c����*�g5c�����z5BN \3��}�rUc����FL���4��r������������g5b��
�S���������2��b kf���R�F�	��j�J���j���o�t�m[&52�V3S�
US�}�A�8�B{#�;�O,�jl�Am�0t-���j��,}�A�6��4RN�wV3��1��d��&����r�����vF ����E�P�;�H9��Y��T��Ts�L}�A�6 Y!��k���j���o<��R�F�	��j��x)I5�����i��k�7��}c�@��H9��Y��.�������,m �h�����FL�4��u���Y�Xi;8�H9��Y��T�BTs�L}�A�6 !��*}�1N����F�RN��Y�X;?
��~{��*�����o<8�R�F�	��j�$D��^|h�J��}F�	��j���y���d�;/Eh�N�!'���*���1N����@����*������'c�y��
����������K��1���`>Hs*�d�;���t��r�����v�6�Q;�@yf���f�����f�����8�����l@f3B�:3TzZSc�L}�As6�N3RN�wV3���X��{#�;��j��f����wV3Vz/]������pv-�F�wV3Vz3RN�wV3Vz/��X����l f�����f�����8�����l@�2B(33Tz�Rc�L}�Ad6��2RN�wV3��5�Rs����f�4|e�����f�4_�J�10��{��z������X�����z!C�|���Zt/`�����T��r������|����d������������d�I�q������|���r������|i%5���7,��$[�I�;�k��Gj���o>��g�sod��r2�Ld�^��7�d���X����l %d�����f�4��8�����l@�1B($3�|d����7���z��kR��R?��g�g��z����d1�F23��H�2�$3��dI��bV2B�%�vj��1/������1��d��&HEF��d��3��
�BN`'�j."�9N�V�f(��HF�%R��UDJR��p�����A�JF�IY+��Z����HU��\e�T�sR��"��\DvR��p�(���)(#����<��(5�	��"�j]D�RA'\+��s%7@���9��Dl�;<U�9�ZQ�/�������V�������kE����2�N�ZQ����-5�@�L���� ).#���E��"��F9�ZQK3����M�j7B`j��E�����]!��m����1[�}M�l�]/��UPr3H��:)kE��$�LMr2��	�$�f�������^��6���D�V&�f�����
N]eBlj���+�3��f��v*5v�	��ANF/M� )8#���E��fJN
20��	�)rv�F�L�� ):#���E�Tf�N
20��	�$mg�����
����]=��E�&�g����
N]aB{j���*4��g���"U+�S���^����@����V�v�,���|p�H���U����	/$5h����
J���$_�(R�6�B#���Up�j:T���^��H���8R�jm��j���k�4��h�P�"UkbT���^���A��F�IY+�`�����d`���1� U����	i$i�������"G��q�T-N���9)kE��	W�QNf/N�� ���3@��T�NS
r2yuB�IgA'e�(��j�����ZQ��:aN[�I]+�`�����d`���O9��|#\+�`�N�:)kE���U�,V���AR�F�IY+�`�N�T�r2{u���M���U�juB�j�����5H�:)kE��+9��F9�ZQk{��F�IY+�`m�t��d`���m9��|#\+��"��|!��'���Q��,����AR�F�IY+�`mOxV�r2{{���k���W���J��AN�V�����tR��"X�3��&�=aa�tm9'u���c�����d����9��|#��'t���k_���	#+r��|��'�l�T��sR��"X�V�����0�Adb#g�j��fE��+�����t�X��������6�,"�hm������,����-�o��"�xY���1��N+�H���-	�"�v:Y$��?�`cD��FV�0�!`#�o��E�>M����-c m,3O�'	��)r�(S��	��~"����8IhW�1�kE3VD�5b�V�����0���oK�f$��tk�`�(SO�'	��9r�(S����HH���Q�G�Y5�@�e���$�X3�kE���H�F�@
W�����W5�@��T�V����ZQ����%V5�@��TxVY���ZQ������jJ�-���s�XDB5R�V��ZkiS5��h�&�*.5B�v�UFj��H���[z�$gm��kE#%���9����o�T�I��k/`��K�*"{1X+��^��:�s�w�RE$N#ekE�zNi����|;-*3����F���fj��.����������.�T�
{���k�t�c`��Ku*"Q1X+�Ti<iRM��eM�}��T)��]*S	���ZQ�J�I�jJ�=�m��TDn4R�V4S�J����^@�e��]Z���Z�LM]�����]R������G��}�6Tc��w)G��
m1\+��s��d)���E����T1�5^JQ)���Z�L�Mr���������K�1X+�����T��c`���	���������K��1&����"�<#�oC�2S;/��������vF���2U;O�Sc���:/�g���X���{��rF���2�\�Mr����T����K�1X+�Tm����c`��K�)n3R�6T'3��Rlj���z/=���f�`�(S�e�$k���kE3�u��>��p�(S��d35����Rn&9k���\+�T����3���L������~{��K�)"�1X+�T�����c`��K�)�2R�6d&3��R]j���z/M���e�`�(S{yMNi�9r�(S���XF���2U�O�Rc����/�e������J�]&	S1�5_��$Gi:�^��R[�HRF���2U�/
�����RX����T�
]��Q^�SNj��\+�Tm�4�3���L������~{��KM$�d��Z�L�]�����5_
�$g-��Z���L.2bk�T�I��t��X��������e�6_:H�10[���	�~B��GH&�k����$�v�����?~�q�����������6��eNI����J�D,�i�	���2���*�@�r�9�� *��<i�1g��L��}�U����)��B"*�U����)2�	�3L$�����x�!P� O�2��|�#G�G�<*����Q���$)I��<���(XS��2d�N��L�[rL�D�����]d�5}�A)A��d*?�#�D��S�����>�C!bNJE��<1��L�G#r��"���L��}��Qr� ��%�b�I��1'�"�RwxY��i@�J1��US
>&���EB�e��)6�Q�A�����R���~��������	AJERk�������%)���������4| �	S��T���H�F$�"�E�B���Ba�j�����]�@�����W@lf�����>�
H�G�q�dH-��2`�{���9{c�&eTss��N$4[��rQp�:��9�bj�w>������V��Z���V`�@j��������w �V�vQ�u���J�c���u
�H���p1���0I�
�6A��j�f�0(��HH�Lq����u
�H����0`�m�Nd�:��7��N�����H�h7L����Rn�*�x7����R�6�-GA��<gUL���:�f�I��R�	l��R����T#u�Q�%}@-�r�`��aP5��+��NPp��R
d����\��aNZ5�Z.u������H-�2�qi�$�P�V�=U�5	�A��@j����a���fI�����%L����f���6�Q��@��{L���:j��!��Rn��1JGR��.���,L����R�j*��,����r�+�6��Q��@�KtL����+h.��+��"���Z9
����H��rI��\�cFwz;�r����RW�|�&�T<����,X3S��
�6K]F'�w�]%rt�"RF=�
�d�j����=�����D��Y�2��MRaTj+���f�et�[W.0W �\�2���c�$I'��1�{��=�������)U��	>�	��<��r���Z.)��e�
�d�~��R��	]��@��r)���I�PGy�,���r����R��|�&� <���8��0F9Id���.X\�r�F��R�	�]��$�R�,X|�F�L��"RG��\�'�r)�
���Q��R�	>
��$�@���H��F���"r�Y�[d�����L�g[7\��������Uj1�@�[�L��Gi�Z�W�e��\ KS`�Z�b�Z�NM�2�z-3[��i�b��_�����%���`������
K�BN��
��))"�q��`[�D�(r��
��!G�[� '��h��-H��:�E\���p� ��M!-h2K7E�	�(�`����t�F9��b��J�N�s��m��I��AN`�l���nR�	�����)�"��[���l��*�Z�$�U����nu����('�`�J� i�"������s�+
2���j���*bNxGQk)B]i�X0K3�"����7�j7��i��Q���yH��+�7��[�\�&��^���$���]/��Q4�Z$%X���1��I�`��d�+�������4�g#E���/k����	�9'��h��2!�4�����qAd�"g�k�����L� '��&��I�A'�r��L��A��2�6N��
��,^�X$W�E�	���2S�i���/L���Z.bNxG�j��������c�&�\���ep�
ZN��L^�X/D^.r�b�Z��r�d�����F��Z�I1|�OQ�,�"_V�!UKzO9'��&�I�A'4���9�����
:�jmB�E�	�(����M�=�r2{m�:�)�"�+���	��AN&�M,�"�9\N�T�M��� '��&V�ISA'��(��6Sj���j��Dd
�s�zmbe]�4�tB��`[b r��|X^�T-N���9�E���u�d���"� ���3�evH���t� '�W'V�I�A'���%"kW�o�2�[W���-���Kku�u�$�U'���]Q��Q��Z�XfA'����L��I�N,���2rNxGQku�_j����K��H`F��!U��R��L^�X�$�e�P�2��Wr�K�r�`mO,����Pk{��$��'����]W��QT�\�'"�2x{ba��Q��,��X�$Uh��������
�('��'����.�C�(��P��we��'�E�	�*��=S�j����K��Um9'������U�('��'���]W����k�DdU#�BoO,�9kY�������U���Q�������d����� Z�9\��T[�'�w]�FxG��~�	v����������l��k��-C���L{G�@���1@;��fgH����
;��S������le����!`g#$;@26B`g3��4�m��r;�Tk}�X�qB;�`��A$c#g�v�v�,"�ANhg��
�.6�N��Ep-�[2V���UHv6H���9)v���"r���v���H�t��s;��Q�dp���v���"r�
:��E��� ]l����"���.V����*(;$]l�;��V�y�X�rB;���l�t�tR�,�{����d �,B�Rs�t�sR�,���b5�	�,��S�F��,B��b5�	�,��lY��
�����`�Y���-���&agE�bsB;���l�t�tR�,�{�$�IN��2ag������bgl'�"{m�;�T�L���9)vV���L�X�r2ye�������Y���4�b5����	;$]l�;�`)���d`�+vV��
��,^���A��F�I����L�A��0ag������bg����z|#���������bg������('�&�l�����E�V&\�9�2ag��mA'��"�N�E�Z�/v�Z�p��9Y�4ag������bg��9jI��Y�jm��F�I��
�]m��j���k3�l�t��s;�X�M�X
r2ym�������Y�jm��j���kv6H��:)v�Z�)c5��`�	;+"����kv6H��:)v�v}Y��5�`g���	9'��"X�.V���^���A$c#g�v�Z�p��d����
�.6�N��E�]_Y��|#��
n]u�����jg���2V�V���"gW�o�v�Z�p�tR�,��:S�j����v6H���9)v�Z�p��d����
"9��H����� '�W'�l�t�tR�,�{y%���('���������bg���2V�����"g��o�v6�igE�b#�BoO�Y����X�=ag������bg��	�QNfoO�� ���3@;��Q^��b5�	�,��=�b#���Yk{���$��'�l#p�-���Y��=�b5����	;+rv]�FVoO�Y������vV��e�kO�� �b#���Yk{��j����v6�dl���"�����u����^��v������9���l��k��-C���L{G�@���1@;��fgH����
;��S������le����!`g#$;@26B`g3��4�m��r;�Tk}�X�qB;�`��A$c#g�v�v�,"�ANhg��
�.6�N��Ep-�[2V���UHv6H���9)v���"r���v���H�t��s;��Q�dp���v���"r�
:��E��� ]l����"���.V����*(;$]l�;��V�y�X�rB;���l�t�tR�,�{����d �,B�Rs�t�sR�,���b5�	�,��S�F��,B��b5�	�,��lY��
�����`�Y���-���&agE��������A��F�I���u�\�&9�����
�.6bN��E��(���!_�,R�2�b#���Y��2�b5����	;D26rhg������ '��&�l�t�tR�,��4S�j����L�Y��+�7�xe��IA'��"X*3e������
�.6bN��UPvVd������"X.6�N��Up�
.V��L^���A$c#g�v�Z�p��d����m.��T;�`;IYjE��Y�ji��*�d����
�.6�N��UPvV��%�`g���	9'��*8w�	�QNf������F�	�,b�6�b5����	;D26rhg���	�ANF�M�� �b#���Ykm���$��&���\�rNV�M�� �b#���Y��e������E�'\l��;�`-N�X�r2{q�������Y�ju��j����v6H��:)v�v}Yd������*�u�	��N��E�Vg�XM20Xu����]Q��Yku��F�I����L�I�N�� �b#���Yku��j����v6�dl���"U�.V��L^���A��F�I�����.V����"X�.6�N��E��g�XM20X{����]W����������=agE�Z�/`����
�.6rN��E��'\�F9��=ag�H�F��,RGy%��� '���������bg���2V������������jg�����('��'����u�Y�=agE�b#�BoO�Y����X�=ag������bg��	�QNfoO�� ���3@;�T��"{��o�v�~M]`gH���a���f�����1�@����w
��m��jv6�dlL1;���m;el�����V�l��bv6B��$c#dv6C�Is���m!'��H�����'��6;D26rhg�j��"r���vVA�� �b#���Y���%c5�@�Y�dg������bgl�/"�QNhg|��H9'����I�ANhgl�/"���Y�K���F�	�,bK���b5�	�����A��F�I��nu����('��
��IA'��"��}.�A��"�.5I1'��"XK.V����"X�1el���"T�.Vs���"���E����l���b/`�kvV��z����*(;$]l�;��^'��j����L�� �b#���Y����^���"U+.6rN��Up�*.V��L^���A$c#g�vV��+M�X
r2zi��IA'��"XJ3e�����9��|#�W&�l�t�tR�,��2S�j���/L�� �b#���YegE����,��0�b#���Y��0�b5����	;D26rhg���	�ANF�L��F�b[�I����d��V���E��&\�rN/M�� �b#���YegE�Z�/v�Z�p��sR���sW�p��d��L; ]l����"Vk.V��L^���A$c#g�v�Z�p��d����
�.6�N��E��f�XM20Xm�����*�d����
�.6�N��E�]_9jM��Y�jq��F�I�������('�'�l�����E�V'\�9��:ag������bgl��E��(����[W�p�-���Yku���$�U'�������E�V'\l�;�`�����d`����
�.6rN��E�V'\�F9��:ag�H�F��,R�:�b5����	;$]l�;��^^��b5�	�,��=�b#���Yk{���$��'����u����`�Y������vV��e�kO�� �b#���Yk{��j����v6�dl���"u�Wr�X
rB;�`mO��:)v���)c5��h�	;�\l�9�vV��kO�X�r2z{����]W����vVD.6�/d����9kY����v6H���9)v���p��d����
"9��H5;+�w]�Fhg����w���������l��k��-C���L{G�@���1@;��fgH����
;��S������le����!`g#$;@26B`g3��4�m��r;�Tk}�X�qB;�`��A$c#g�v�v�,"�ANhg��
�.6�N��Ep-�[2V���UHv6H���9)v���"r���v���H�t��s;��Q�dp���v���"r�
:��E��� ]l����"���.V����*(;$]l�;��V�y�X�rB;���l�t�tR�,�{����d �,B�Rs�t�sR�,���b5�	�,��S�F��,B��b5�	�,��lY��
�����`�Y���-���&agE��������A��F�I���u�\�&9�����
�.6bN��E��(���!_�,R�2�b#���Y��2�b5����	;D26rhg������ '��&�l�t�tR�,��4S�j����L�Y��+�7�xe��IA'��"X*3e������
�.6bN��UPvVd������"X.6�N��Up�
.V��L^���A$c#g�v�Z�p��d����m.��T;�`;IYjE��Y�ji��*�d����
�.6�N��UPvV��%�`g���	9'��*8w�	�QNf������F�	�,b�6�b5����	;D26rhg���	�ANF�M�� �b#���Ykm���$��&���\�rNV�M�� �b#���Y��e������E�'\l��;�`-N�X�r2{q�������Y�ju��j����v6H��:)v�v}Yd������*�u�	��N��E�Vg�XM20Xu����]Q��Yku��F�I����L�I�N�� �b#���Yku��j����v6�dl���"U�.V��L^���A��F�I�����.V����"X�.6�N��E��g�XM20X{����]W����������=agE�Z�/`����
�.6rN��E��'\�F9��=ag�H�F��,RGy%��� '���������bg���2V������������jg�����('��'����u�Y�=agE�b#�BoO�Y����X�=ag������bg��	�QNfoO�� ���3@;�T��"{��o�v�z��yg�R���gZ��z�A��dl1�v63�A)c[��l���
 S��F(�l�N�2`g3��=�26������l������s����b[�	�,R��E�b5�	�,��������Y��9��\�9��UPv6H��:)v���o�X
2�vV!�� �b#���Y[����j��Y�7"��F�	�,bGy���j��Y[����*��v��Rs�t��s;��Rwx�X
rB;���l�t�tR�,�[���b5�	�����A��F�I���e�K�j�����K�A��F�I���R���('���fL1���n���'����e��+�7B;��igB�������9�^|#��
��IA'��"��Ir���d�+v6H���9)v�v�,���|��H�������bg������('�W&�l�����Uj�J.V���^���A��F�I����L�A��2agE�� ����	;$]l�;�`����d`�v6H���9)vVA�Y����7B;�`-L��:)vV��+L�X�r2ya�������Y�je��j���+v���tR�,��$Yd��`g���	����Kv6H��:)vVA�Y������E��&\l��;����&\�F9��6��H9'�������� '��&�l�����E��&\�9�6ag������bg���2V�V���"r��9Y�6ag������bgl��E�Z�/v�Z�p��sR�,��8�b5����	;D26rhg���	�AN&�N�� �b#���Y��e��+�7B;���U'\l:�v�Z�)c5��`�	;+rvE�Fhg��	A'��"X�3e�&X�:ag������bg��	�QNf�N�� ���3@;�T�N�X
r2yu��IA'��"��Wr�X�rB;�`mO��:)v���)c5��`�	;+rv]�Fhg#�vVD.6�/d����9jY����v6�>w]�B��E��'\�F9��=ag�H�F��,RGy%��� '���������bg���2V������������jg�����('��'����u�Y�=agE�b#�BoO�Y����X�=ag������bg��	�QNfoO�� ���3@;�T��"{��o�v�~^���
��=����hg3�\��m��b �lf�;�R�����5;@26����P����2�e��ff+{6el1;!����2;�����m�����Y�Z����j��Y��
"9��H�sf�X
rB;���l�t�tR�,�k����d ��B��A��F�I�������('��>oD�����Y���$��� '������U�	�,�����b#�v����t���vVA�� �b#���Y�����j��Yeg������bg��>��� igj��������bg���QNhg,��26b`g���9Nhglg�"gW�o�v���
��m�0�5	;+rt��Fhg��
�.6�N��Ep���b5���W&�l�t�sR�,��DYd�
�`g���	9'��*8u�	�QN&�L�� ���3@;����&\�9�4ag������bg,��2V�}e����]A���+v6H��:)v�R�)c5���&�l�t�sR�����"{W�o�v�Z�p�tR���SW�p��d����
"9��H����� '�W&�l#p�-���Y�I��R+���"UK.V9'��&�l�t�tR�����"G-�;�T�M���9)vV���M�X�r2{m��
�.6rN`g��	�AN&�M�� ���3@;�T�M�X
r2zm��IA'��"Xk3e�&�6agE�b�s�zm��IA'��"��/��&_�,R�8�b#���Ykq��j����v6�dl���"U�.V��L^���A��F�I�����"kW�o�vV���N��tR�,��:S�j����vV�������"X�.6�N��E�Vg�XM2�Xu��I9'��"X�.V���^���A$c#g�v�Z�p��d����
�.6�N��Ep/��p���v���p�tR�,��=S�j����vV�������F0���\l_���	;+r��|��'�l�t��sR�,��=�b5����	;������E�(��p���v���p�tR�,��=S�j����v����sR���c��p��d����9��|#��'���\l_���	;+r��|��'�l�t��sR�,��=�b5����	;D26rhg�jvVd����������������Y���l��k��-C���L{G�@���1@;��fgH����
;��S������le����!`g#$;@26B`g3��4�m��r;�Tk}�X�qB;�`��A$c#g�v�v�,"�ANhg��
�.6�N��Ep-�[2V���UHv6H���9)v���"r���v���H�t��s;��Q�dp���v���"r�
:��E��� ]l����"���.V����*(;$]l�;��V�y�X�rB;���l�t�tR�,�{����d �,B�Rs�t�sR�,���b5�	�,��S�F��,B��b5�	�,��lY��
�����`�Y���-���&agE��������A��F�I���u�\�&9�����
�.6bN��E��(���!_�,R�2�b#���Y��2�b5����	;D26rhg������ '��&�l�t�tR�,��4S�j����L�Y��+�7�xe��IA'��"X*3e������
�.6bN��UPvVd������"X.6�N��Up�
.V��L^���A$c#g�v�Z�p��d����m.��T;�`;IYjE��Y�ji��*�d����
�.6�N��UPvV��%�`g���	9'��*8w�	�QNf������F�	�,b�6�b5����	;D26rhg���	�ANF�M�� �b#���Ykm���$��&���\�rNV�M�� �b#���Y��e������E�'\l��;�`-N�X�r2{q�������Y�ju��j����v6H��:)v�v}Yd������*�u�	��N��E�Vg�XM20Xu����]Q��Yku��F�I����L�I�N�� �b#���Yku��j����v6�dl���"U�.V��L^���A��F�I�����.V����"X�.6�N��E��g�XM20X{����]W����������=agE�Z�/`����
�.6rN��E��'\�F9��=ag�H�F��,RGy%��� '���������bg���2V������������jg�����('��'����u�Y�=agE�b#�BoO�Y����X�=ag������bg��	�QNfoO�� ���3@;�T��"{��o�vv�������sP'x\l&�f�������P6q��M�~�������Qm���_��>����O}@3�_��>bmk&��/�p��d�+[#����[��}�e�����j��c��~�����o:6��L�Dm��_Tl�e��� �45�M�Y�k(=��^��9�aE1$@NiV��9
�����(�o�4�}���n�H����i����yl�7c���_��9�
��K�<��3Wli��*
��},�s����7�le�`��-����s��&-��� ;�td��X~q�����@Ni����R* �l]	T}�5@
�H3��,d�u��'�9gWU8�#�q��a��-~��J!$������whE�	�s�V������<J��vR�!A�iF��u���!�������
w8��#�9[�
�
y����VC���w���"�)k�U)�� �4���a;�����5�tCL9�n�"0H��9�Qu_d^���� �4Q�M���t9Gs����R�!A�i�������r��l]7Tcd�+`1o
=v���P�!�<j���R�!��������<�9��'���C�kAJ?$�9��}�m���
��:jC��4D���$�7����������N-�^��]C�=�f����H�9[�U�)
Q�XC�:�"��s�9���oB,H����-������0e��!<�7��C���$�7�����.�:jC��4D�����7�4D���(�/�*@i�����o�j�����o��Q��U����/B���k���vh(����
�MU)
QS��6D�R��a��<S��!��
R"A�y�����(>"�l~C�}�JAJ?$�����*�����:�~h���N
P�!�<���M���0g��!4�7�d�k`������(Hi�9��:_�O����"�Ly��7��C�������7��4DQ5������p9� �4����)
�s��!��	R�(����E�8��S��!��	R"A�y��1��'��<@S��|�2;���$���k��`v��Y�������P-9G �4��M��#W�!�5D-_T���)g�aY����%�`��X�i�}�J6��s��"��I��	^�������?�t��e�nG�����j��������������������?�������i��e�����3��_?��?����_����������;���?���O�)��?~�6x�R��c7��g��/�r:����o����.�����/��O��)�~N��9����+�"�}�����<�&�������>����=ro���{+�D�x4q�Q�e������� ��"��F����m������G��}���_����$���<�����L� ����'�
:�[�Q$��)�F�@���H�<�8H>������$yN=�9H�h|Q>����	�h��&hO� �lL�G4H>�"�#Z�3����F����D��?���*Q���~����#$Q���c��ExD��#
�Q
��4�(�FR��km3��f��V~�@����K���q6~��E"1���wi�C�����c��p�����:���u���c�o}}�u��CQ?���w>����G�Y��5�c���FA�%�!:/?E]\_����\�}���:<�w����\�:^��\h$��{�>�������,�t�/Y������=��?��s�c�$�C��^GK�qA��;m�u"��/G�������K~I���u8>C��]�����m����!�2�Gy��c�9n�������g��t������8��e��w?�z���/���w�����u�������?�a�?�����������_�2��N�bc�������3��u;�i�~��_��g���g�c�4|�u��r]���i�l����?�6����u_1n8s�~h����)6������=�u�b�t|�Q?���u�~�9��Ug����o�����7G�|���z�Z��=��?��>o>�4����S����w����������C��Y��t�����1�q8�]����?����'��I�:�-����2���El���?��8]�����k�������>������}�y\����?�?������Q����<�qb�3��i���������6��u��k����1�~��57�i��s�����
�����k��v���<������y������~=h�o��\���9�w�����sl_��Iq��:���N=O�y���:��3�X�q]�S�;v���f����?����_������=w��;~����G�����.��C}�g��9�������^������������0jG�������&�k�3b�#�9������<[��k����q?z�
Sy�������h����[�0l��A?���>���C����<@�^�����zu���x,���~>m��;�{k�~��t����4������C~����������*��]�/X?���q]�����W���������D~��/�u��O�}�}�o�����0��4^o���L+�y���q~�����3/���p?�u ??�0�'�p�8���6��S���D���l�{���\��yz��g����}�[��A=?�����7]W��5�e�#�?�9��v����}���T���l�v����y�v���`���d��lPS�|^>�0O�;>c������3���{��J��o<�?O�a�L���G����K��S��cw����v�>��g�|���?�Qs}4p��<�����n#�k�����a?�����_�a���3���-�����d�o1���z���s�����{k�^���,�gXc��/�)^��8��q��t,��ly�GU=����`s�c��>��#q?��h�R�������
����6�����Z�R��g^V=(�s$��.�|@�s��M���5.��������}w}2q\���}����H���0�[�9��?�cm�y:���s���#���Wwu�o�V�������	���~�q��d����������:�^��p�C}�����|,K���Cf���,�u�~��f�.j��_>�����L������N��^!�8��������c�v���g��3_��"�\�^b�r���e?k����5u�/�S��q?y�����uab.�zn]j���������|��%����\���#,�q?�#\�V��5���<�|?������g��W<(�U����w��6�r�����_��6i�Z�=/���>w�Z�"���
��{����=��5�a��c������g>����|l��U����
�y[��U���n>p�|��%ze:��r��<l����������3�K�7��������������z�~��������3��������o3��s�m�}��z}X����z���c�t`��g��/��������r���?d�\'0Gv�������9�����n��e�1�����������W�i����:��8���c`k>��%�i��`c���/��]��
��;zv��t�,�4y��������y�{y�l�.�z=�:����x���������q��������q��s:z�}�h�����������R7��_|�Na�.q�����>���:���&?�+����y�7�g������~��~1�&����]�z�?>�����W\�9����m8����3�i��S������/�w��s�P�c9����z#�^��z��:�g��(~��u$�kZ^��A�����)������;��\�3�<�����uY��6nzi���:��]����k�1Mg^~��c[b��N��a�>8�u�~�����9�Q�j`jM���������������*�\�j\V=)��3��������<���|��++<��]��Y��Z�����?_����{�����l���6��P���.^��{��k�}�H�r�CS�K�-����z����o����c]�q������	�w�:	������
�r������<�v�^#���q�]��'�C��^G��jW_Bf��;�u���.������#���3������?�ky�N�a�,�������z�4-;���x�8L�s�,k�0�u�=.��/������m;���w�9p�-�9��)�|e�N�����O��n���6
;�l��u�T�����g���o�����]w������~{.\Kx�j��Em��������N|���-�������C|�
�_P�sa}{.��*�<]�-T{*\+���V���\/;������/����a�����a?G=��9��������s�9���<8�\�K���y���vL���\>�O�iW�l]W�t}f�j��V��Ft��.z~�����z_�d�.��n��d�>[�9�h\��p��\���_����"�x�T���u.���>������M����t����s�7D�����}>�\k��.���������U��i�6��	e�,}���6����r�T��s;���
��x_o<����g���w�����e�>��B����U<��1���h����2
z��N�4��$����j���v��~��C����z������������
��x�@K\�����z���z�5.�gM������(��������9�-��<����������<3�?��'��y"\�@�t���<3�>����r����U���	�#���z�|��vr����)�~�[�����Nh���5]p�'�7�v���j�vd��8�����\���������}�b:��G�>�}�1��u�������{�o���[y�[���vP���v-����r5����i��^���d�)�������4o���,�?���*���~�c�{��<Y��u<�e��������0f�������;��m�}24k�d^?�������9j�L���k8�K��<;��4�y}`�'��\����k;n�t�|���)�?�p����u)��� ��e}�X5�G���������-��*���,%w='�����!^�U?���o6������}4#���a�l�T��a�������g
��u���
���2N����{s��=��KS�+N6�������\r��u�>?�0�� ��kY@��o`�w����g��_��*u�E����9U��(��_�;�u��K
W$�?�9�:��23�u�����SP��D!~II�E��B/����6��%��$�)��+[l@����B��+M��	����p������JL�/��z�
�����pIQ}B�*v+�����wF����uO�R�����}�eJ17=Z����<���/�����Zr��Y^D�~����j�D�e�P�+3�>�{o���r�yc|�)m�d_���:�R�p'�����g���2z9�zm�d��3��l���g���V�,RV�9���%��#����r��axg�8��	?r�i�8����Q��K4�|�Rg�6"T�s���E�d���r���h��z+A���6�	���+�[��
q��\w�w�m2mQfu���O���~!�o�m���$���k|��r��m|Q�����+;�/tIH&���������j/a/�$���/�������]��4&3QYv
���E��W�	������y����fN�s W�65#����J�	;��.6�R���H��b]J�r3D�(u���z+��3g�"Qo��%���+����[y��p��L��\��*\�@������}�D�:�N���L��I������W��������t��ddI�	
�lu����b�V�]��Vh�s�	`���JL%_��i�4���mp��N��|8^�Yz_��q�~XkT��F�Rn^�kz6�N�����L��5UB�AF'qA�I�
_I]�x9T��(L].�Q������F���b�[E���6���8����4����4$�d=�P�*e��[PY�-���� ��u5�w�����0s�M�����Wb�0��4LmW��U����f�f��J���82L�� ��bY�����cf|5�J�)GA'H�:��j���zx�B���`�oE�E��N`�����ac"�� 3Q�V��Q��F�3���+�)������O��b��>#8��P�h�}��g>N�����&�E��b�Bz�l	H:-��
��(���4�u��/|y��s {�A	B��Li3�q�t�tC\��s�E��	��K�5��E7��sqt7A��
_?L���N�`�-3����!��f�F�a��X	W�8��P���f��llX�<H�������^t�9^��[�P}o���2�[Ak\g�!'��(�1�})Z�[���8%+^I�9����AZ
@�J����9�?�?n�;"��}?��6�ANW�t��^(���3{K��s��)��������
r�E\�����=�Tr��!=m(G	Ug<�Q�b�0��Q���^�5U��5~1)���mmdg�U��:c�S�67����b��d�Y�);��tx_��<�$�D�v+b����B����G;���=c�.���d�e���p��\��p,�
3�Fy:@-f`H��n���z,��w�~~?�&v��l;���
_��Ly���y>`������V�"�.Q�����T��^-M���E������W���:@	�T�F�{�������7{�pQp��l�m�x���t%
oa��n������h�Tf09�T^E�8��	��yI�������klY�CQ�>��kA�H�yk�tnO{�_��?�����������������(���u�\	����6���\��U�z��;��^��k�o��l�$�uu���]�jp�d���l}��<�:��o��cv?����%�?o�7~���b7���u\��.���~u����������3M3W]��M1t�u��N��\%r|�D^�m{<��g��F-/%�Hu�F�VR���	PHn�
*��w�H�t"��GO�\�9����{m�����v����}�g.��Q�8�M�Ld��K:�<�gL��N��9>}]���~H�q�Pq3?�����U���u���U�X%�_���QD���G����OI��!&�y6����Y��q?���}����'Z�_R�kG�'�=0�Z�(��"���	7��p9����	w=_�y�vWg/�|���Dw�V�p��,�vu�#�����9fw��������T�������y�~j���|���
�	�wY���3�Z`������
���+�vnN��i�M��q�d'�x������p��N���fZ\�xu��j��
�f��N���2��%G�����j���8��@;��������wY*���h���H��j�4�]LB�^NnL5\�v������gM�����}/����=�!w��
�z�f-;qsW,����%���q��������W��
�o�������G;cvo�y;��u��v���e�m�L���������DIN������i�����n?^��!���G=&��&T�[Fg�/���B�����)Eq_��Fg��8�j�E�t�f�;�r�7'�gcZ�5L��;�g!����r��#��������}�f��r�'��T���EV���T1A{e����d	���U��M�k�����s��Q/���\�j�������G�Z^��N�L@{�}�zg7r#��X����Vf��e��s�xY��B�$����u����Lg��A80�����C�����+rrW���GyOn���"���;_o�{��������%~�������nh|���\U����=eyj��T�����������TZL��r��CF��������!�t�u��;>HQt�+�4;�E��i���PKn{��y����o��S�G������
�~��0:�[I��y��������|=c���>���=��7��_t���S|������9��;�3����R��1��\�0�jb���7M' ���IDVv�ua�3J2sp=�&��L���y���S���,L�W�t���ok9��9$��%T����gE�w���$i��\3YDS���2�W�:#Y���zu�L��C�	c&���8�<Q����O#�l={>��������r��F>�lj@P��������7����i�[(4�30��,�{�,rk�Y�j��������5���	�c�
����_<����d�����;Nid}]_��i������b���x��Y"���{��%�r�,�Q���tEu��3V�zt������B�$&T��Xk<�?�
���\�#����������{�$������zFU4|q�v|����Dx��+�}��
�,���C�r�7��jqw���E����z�7~C��&���dL�;67�Y�*���r��H9q+t����L>�2��+l������U�L���vqj��y��&�����I*�5�����S5i]9�/W/��9���F��c�H�Vx
�K^�Xo���Q)R?�L�{�S�����������|+[����`�7�B��	�i�$���L��b���9��\��&b��h��������+��,�nzK����f�&�C�8+��&���Y)>82��?q�3�5#��Y�/WobW,���8 ��WH�J�_�L-�pn���M�|����(��'�~�L��sE�����	��v(/���o���q����	w%Fj���!Y�������^d���8;�s�
��-��)���t
����g��d2��	�M*��c�G�b������,n�]�rY��������Y%�Yddo����c��������I)��������u����o��������?�N��%��6�1�.�!I�4[K��v� ��+B���� X���fg�_H�����6wTI)�"����G���q�7-�����E�f���S�q{)n���������z�����9L����N�Q�rv�K����dm���~��h�J���_���c>�w�7i��"���r�>=�;��������P[J~S�N�)�� �5���r�CN����M���e�Y�a�[6��Ny�d;���!�4J<ugwb������8�v�ls�P��qr@T�-�-�:�$�g7%Nn���6���]�����M�|�y�s��\�L��&�O-�:o��C�{��k�m��n*���kU�6��]��
���S8��u��������:��-�ez�|��27 ���w��S�B0�-_���(�P �;X���hJ�AEH9�vx��P~Io�1N�\<�IS�
�EIi!�+����^����3vA�~�4]e!-��Y��H����O�o���������h}�������k3P
�Tk����do0,/���3��Y�%��\�zd�wI��kv��.��Z
�������=��oXq��d�^����!S��t�*F���{���d��@�����v'Q2N
z:(�	�[�����A�F�E_v��=��:Ug�N��G%x�?n�.~ovw��f��"�.��r��zS����#j�}DR�=o�>���EJ'DV��!�h�`'u��\�.Q����y�f������Q������us!b��	��p�<n�/��(�d>��5�5<��}�x���a.P��M��/���;Dmb%%GjZ�������
j�6�a����,b�Fc�5�QIQ��k��Ul���:{�Uv|-Nn�\X�����<N�~;�������E��h�a�(�_7��c���v��)����@q����Z�e�{W��@������$12�#���; ��d�h�	���������)���f��Vt����\��f��7����%��`i�_��KN�������^xoQ��V��iGcS5nXF��%���C�'���s�����70S����Z�O����'iO�c:���.uK��eIz��wi��]9%���N����5�c�E�^�]�J�.�I
��O�����9�V6�(�B�#�G�t,&:�$����e������=?���-#r�����p��E�r�1�.�&���<�����K9I9�C�w�����/FV
�,��A��.�n=�����Ve����\�NVg������#B����N!`MU�v}y���k9�6b xP�E�z��h�����K(��,�Ng�<��)y��Xf���	Ksc�2�����=���[��y'��&��\L�76���?�4ws�������C�����������^,��8Kr���{�����L���(�����0Z�TgS�uI�o�2?�.tqo��~����
��@��BD��_��d����9�IO�up���xq�����������r9�e]��Q���q0$����7K~�xS�����4�����?/�75O�}�.�uR�`����2_��!���<q�=����h�q�V39�/��XY$���d��@��K�Q��V�������h��V����5~
kT����%�&�h�m����r�,���W7������]S��DN6M�N�d��N��	J�(��d���>�"�[p{�WBgb�{��^[j��.z�.g���z�O�o�+m���S;\!��h������Y�k���s8t�v&����M���9/���w3(��A��nKz��HE��$<'�y�@���S����2�@kaI���d�b�QD��,�EG�)�������"mfiM���[�2��5�-�Rw%�?3�
d���S���I���l?l�NFX�bu���4����%���y�|�N���kLi�m���'�|^�����	P�,����Uu�
���yk����v0��w,s�
�mW�LF�	�����z���P��n�9c���d�/V�
c
7&'\��a������Tv��������2����|k���/�@y�2
�M�:��d86FB�Y�!7����Q6hs�����dC%sX���o��-�������32dh��2�	�����>�^~�V�
�qL,m�-�g����/��z�Y��8,�������u����UN�>�$yf����)P0.Q��������e
�Dq�s���\�&sx�w*1'(/
�1)t���gXw3�ZA�A��K��2��m�	�;X�+J��g:?k6?���8�������S\c"�Dq>Ur�8T�p4�6�'�3�<3��I��C�����4�*6�R�r\��z`>��~���"����_'Bt�H���i�3������V�n�)���r�#�e������n"����L��\��Z9������a�o1���)�m$�����{gq��b����A�
�!��S^ 8��Of�F�L����@���r?P�H��+��0{~=�zg�%�Y�/��0X�:������]������7�].p�I��l���7-d��A� 0����n2d�v4���K��n�hr(:�7���4�������h��0����v�$)�E���ZvO��"��v��fz��$S��l���s�*�u2@����9��
�O���6���F�8�!S#�#��������=����OE!t�Wa�.
G�te#H�#�s
;�+�Q����e*eh���GF�����_�|'B\��R4S90�MU:N�;�u�O�� �k����7�����m��G.�:kW�as�����>[�S;�v�����3;������9�3����~��^[�G��8X4�\M����p�0�'�x�nb�=�L�����6r�
�V�G����-?h��[����Q^�U���:�������O��:x(U-"|�2z�L
�z��5Y�Y�,O�U��9���9��=	<c��`�+�M����T=-����Q~�FJ#qa|��L�yVf��]�|�yf"��$����(��1w�mQ];���n�
��$N�k�(o�_�:��!�F�Z������w9-��,�r���F���|��5��|[@���vRN:���gf�g����ij����2�K�T���'������-L���C���a;��!L�[/���ey�{��b]DB���a���y�W�]Q����3x��L0�+����wPP��l����v�k\4�n�,
^��i��+JI�(��r�sw'���_%&;����o�5����)��x�+6TE�j����z�.�/�Y����,��t6qC�]���tP�� �K�����gu6��&�N8A[z�f
����'�� F ����D�1������g1�!pw	�_�c��N�o��4�����I�=��Kf7���'��_)S���S��h��3��X�@�%G�(���(���-:{����}28��"���!jm<�:}��{�-�+,=�EY����j(js,�A�2`w�2����*�K����PN�����\i}�H_���w ���=���������LnQ�����*�������n��1"��cj�������2�&
�@v��8��C'E�Kx'0=U_�l������~����t8�k��nVy��Y�� 8�\n���t�j	�d`+����&�;��3�[(��PX�R�O��::T����]@S� ��dp!�����iY��s
wb�{��b���$_m$��w��H��o�{�O������
���O�b)&t��d���,@��q��k���]�+�L�9PAB6�q�P��v��}��]R�2\fk�>8�Q���@-=c����C�I�nMWe�}�n���Hv�������Y�}�f��=�f�n��o�?��9T�9��$��������[�=(�H,�Ql%8�x��I9	�2�YU�L|��
��u���$7���K��N���N\P����_�K�cv�5���5�+�"g�k�ZA��*����C��_j���8���,���p�n�����-�|������S����P�E
U���UiM�V��8[��AU.#�e!A�_=�F��|�4����2�����-��������t���2��N�C�5,r��Q�eHF����q���U��aJ��hp,�5Dtx@xBZN�cI��t��:9��;�"�nC�Hg�7��P�wt ���W6��jB��$c5r�g���\�$����L}���-�h_�h=2��E�l-\I!���g��P�I�J��>B�dV���[B�F���x�3�����'J-*i+x���\�t�k
v�]��j�<���m�r�z`�6/��.MF���
-��f�����,�����~��~�?�����Z_�W�v�A�v��/�7E���H}�g��1�����8�

��{)M����h:7Y���q��_-�{A���*%E����'���2=�>�"���WB���&{�8�uL4%��	������y�	�s�����#e��cL�	�:��DO!��F���a���>�������U�K�'PT����5yBc����*�RY��J�����h������eGb�/?
��$��QcK�A�o�d�3����"`�CUl1����1�2����P�|������uq���������j|Q2���N�
��[^K���<���Zu�
i6�oHT��C�%�B(9��5&	M)lL�����|�ua�I��FC�B���0"������K�e$6���\j���d����%�TD���TFb��T��k��,s/�p����L����SS�O���$��|��Dm�����5�������X�uy���GH���C<F���3�}g�0�����=�6�K�����u%��$
��C3b�k���ydO�����$�=Cb~\Q��=�Vr�U,72��}T��%����k\JO�Y�� _��	�I�t�r�������:!��cv���$��	u���fv�!59x"gi�0>��o����yv&�O���������r��t�z�f%�M��Q�7�����F����$��af�mxH�m�,J��&�toI�u*��3���C���={g`��%X�P��@M� �����8���x8Mt~a/�gL!Ya&�9#���������-�
t�2.���%���
�q��N����mTu��Fc�q���}\XSc��YD����}DYL_Z,��
��d4Z�KC��+FgB��-���Qb��%v����|�BQ��zd������t�l�Y����~��A��Y����$ec0"���y���:r�����	��\�l�6���Yy���vkij�;J9�0��b�~)
��]7�z�^�@��FE�3�6v�`i�5��G���H����[����0�S�n�F���P
�F��0�K��gp�3�������c�����������-g;`-�\:�|�m}+�LY�i]a������}�"�_V��`LY~����f\��A�y^����m6���VH��'Qt^���=+��K���c�y���MA�Hc���P�B26���L9�������3��������W��U'c�<0�����q�R^�u�Q�3I����}�g���
�Y��LPlV"�8�
�Ao�p����@G��'�`9��22yz��y�J=�����$7�.�XGQ�T�5�,g�Z��")`*���$N2"v���;g��#�F�!��S#��&�kB���\��A�*�*N�Vn���	�Rvo����-_��w�S,�d��RV�t���z���7�r������D4�W.���%���a�r�D'@A.���!�;�"9���C�$�����f#Rgj�p���|���������
w���)�(�~�%�i`�@@N�r�wE��@�F�I��*ba��f���a��TQ����aDrA-h��O}�43'&'K5�e`��ql����[�EzD�{�pd�����������������(iA)�m�+���n�Mm�ts����N���SU��v`��<h�qu�������lN��:�Y�!p�����9����y���+�O�p��Y����^�'?n�������n.�4��m�����?�6����B�������9��@���i_x�}�����[�d)O�m}q����kr}~ON.��){�d:4"�Q���S�f�Q�=�������z��lBv~vG��0�^^�YF�L�H0����^k1��X�}��o2�M�����`�����~ye��8o;����3r/�U��E�lI\����S��i�\77����7/������@�0;��IL|�d
}�tD9(�d�op����~��������������31[G������K���=|�i�X�����S�}�:�{i=6��Q���������������X)a8�u\��N�p��'�;V��)>�n�I�[u��;J�#��2�~*!�~���2������f�[���r8�8���w�^����=��nN���vO�,G��~i�����[+���R1#�{QN���\��d�EX�1�gb$o�I/�N2���=��o��!�v����w�q:��c�a:[�~�=-�S�O��;��t��������Vf��2[3������c�A�=Kvu2CE���>U�c�����6��n>�q�����������g���	q�T���$��0eo��m�m�
�]�����o$�]������a�oW�%�����FOf����%=u��|��w�@�b��yV���_o�m��%���mN�;�'�
�d��W���$��N��
~����|�:��;���vA��H�����
j�/�f���5�i9Ur���=T^�����>�P���Y��e��UxB9���}<��*E�6�����5��p��Dh��@�@+��Z��e?�T�G%W����>�������f'����Ar!9z
i���[�1�.���(��d-G�q�$����
�M����$��p�� ��%���	�^��DK��W��7���f[�����M<)�x-�i�O�Q|T��rVY�i����O��(V�^����oA'�
��+N�s�.����`@�L�@7z������p���+��c�P�VV��>��#�lQ���:J����y��,�Q=�
�#�]��lQ���������ZA�y5.Nl����D�����Rr�~�g��Q������G6��������n���w�����\���I��_I��IYN�JyY7<Zvo2��U6�����*?yN<��i��m��)����=�����������{k��ws�|��7�$��F�.]Q=�O��;�Q���@�b���Y%���)���wMG:zSmG}���d���W�H��L��G��#!��U�����<�����������[���\/]!lO�r����\�	ad2��6������}*�����������FJ���](�7���Or�p9�������.�#���s��g�
��>3Z������$����8c�6%��8��;h[�������4'���b���-����l�{K�O��������]�����v?
��%���c����_�z�
u1A6�7�Z����Ww�a�fh�p�c���%����K��s:�!����c���k��g�h"�����
����=E1����FX��5
�S��,��U{^���H���&d��O�^���$����}��4��_�����m�-n��P�����i�v��{�X�6��Q��g��S�fG!G�l�^r>�ef����������C��d����C*W�G�29�h����v|T�Q�yc���;n'�n�3�����/��(����=?�nd3�w/�����/e���t��;42'�'w"ZU�vw��Z�� �,I��_�#���6u�Ho�H�[�*rJ�����i�45a��&W9���L�H���)_������r5����i��ST[c�F�����2��g8����)6�R;����&�o	{a��~�����-�=-w>�z��LQ��zTN^�b^A�F�=�d�n�b��5��H����FI������k����&�~CI=��U�i��_�t��
���@6�7%[P���k=:��������(=����\m�A�����������IL7�&�);Sz(w�{����"?|���mF�u�o��7r�[>�~
�l'i��r��`#j��DPd�H14��a��W�_��n�9��PdT��cgd� �G�#1�BJ1��FFg@uO�e����=�UTf����j����K
7�bw�C�����'C���g������a��,����Do��k���(�n�'��<;p-x����k8���(����0��U�gM����[�������/{�S����oOD������XG�N�7}����;
�ge�������|�8�`x�J";����VG���	_M4�5����fc�Q���=�Q��U�K��G�'����0�S�Ap%k��h���r��y�_�#&��4�!:�)�C��R+�U=^����n������������w
��e�����~���a%Y�b����b|���d�C#sN����*�J���3_eT�
��a\�
�dMU�{Y&]���M��q�.��AM�L����EegJ�F����D~��p�I����7��-���$�mN���UqZ��Z���l�������0���d�I2Fc;�'�R�w!��DF��n���:J;=�;Y}�f�v8������������{tH��g��G:~t��cz$��8_�F�.1��Q���������[����y���'T0�����ZFK�%�"�N����T$�P�'aR���[�w�F��5�
L�� �8�(�wZ�=.���EC�c$NZ]a��G�)�}J�=�v�'�":�)�V�:h��fXS��n[O����&����|�|�g#H�[�����yEd���/H�y8�K7r1�^�����I�d����Qin.��.Od`I��f�b6��F��%�E�4��O���u��e���`�<�����<�2�De��s����x�&�%�����I����+�s�:�n���MPfo���t�[U�Q������?�&�L�?��.{���,A�I��|����.����%�����n3���i�e��
��]����9���;��m��<�y��b� LUEW��D�rX��������%;����B����(���&�h���=o�/�aA��7�cO�3������:�r����������I������F'��\6���o����^�G8�/G���~k�o��S�+R�Bz�
������Kq;��t�����Nr�����c\s�Y-����], �m.,�����aM�j6���ubw����x�c��_���Gj��=e:�b4dW�*��/�O��`�Vn3����Gy{����B����HE�Ku���%�<U���S;-���|��S������������o�H�Gl�%��W����9�����#���Y9��B8u�0�me���/c+**�z����<�;�@�L���)/;��-��=;�g]<��'�d"���w�Z<,mt�]��J����F���GZl��S"kv`27J}3��J�K?y���0��E;����&��;������g��g�VpQ�f����2�e{o�7#|K#�<�S��5����C~���jKC��X\	C�������c��i���7v�����YT0e�C��\&L��:�j{�'�:Fsly��|�rU�,��/~�W�yj\4�� )�ZW���H�A(��zM�R����|:[���8����=-�T�yF�	���g���Y��!��&��U��4_��1����j(��B>G����%z�������������O��cQ	�k��n��/5��)��3���K����?�E����F�a�<�
��0�mt�J�k
@#Cf���������m���fy�r���F��vd��J��\F����F)�8+���=JO�]�
����Z���%�b4����k��V�hg�J��:vr�O�v
D+����q�j����G���������j�E�^CB^�I��Q5K^{a�)@`�>��W����g�N��@�f�lR��X�%9������?����_�}]@U8]����5bQ���L ��p$��_Lw���F?.2���
���_�x�A�W4>Z�WI��Q$�t�B9S%��
|#�eXws���Z{�e�v�a���s�dS�������@��m
�0�'h���>s���vx�A��V�����Nf�(���([�jK4�G����[�"�����<,�=�y��cev�v����Y������W=w8����7W�6gMp�����|(q�j�����.���J�h�~�s��{�0�]�^��4�l������5*�Jk.H������5'8�Y��P���b�O�J��	w�%�J���~��^�ChsVx&���9�����K�������is>�2z���{��L6!�h���z�Z�*����n�I��1�GO�����F8��eY����+�CI�u�<��D����_�1����nn������'��
	�%��)�{R��w��6-��D�kd�V%#e4�f[������d�)������e��
(:�r���&��50����v����Y�GT���6Q;a4����J	:��z��n%m���,��s�H96���R��CQ��0�Y�V��<���c�����a�S����q��(PM����
1VnP�����4�5�:s5�T�r�8�j�\�L��L-i��s*4�N�:[�j:�3�������TH�d�EL�Efa��K�BS�M]^d����.bHb��vX��[SuJ`������c*�`M3���a*����p5��a=jF�����W���u^|�G��wGu���	|�����{�:s��rB����������y]�E5�
	g���S����e���o���R'�c�/��*:++uF��bk<�Fz� ��T[���n��l+_�����F�n�sN#�g�?!?������x�<�H99�N*����{�m�a��A6����{/A��V��1��o�4�`�d�g���G@t����������cp���g@�k������s�_�/����C^����A�C��.���#4k�W��$�^MA�U.rIn�������@�%��e��e�\���Kj#�F�������z������������+
g*;_@�5�(-f�fA�����[��Z��q�Y�?�2�V������"H�������JI�P 0Y���J���������na���z����]C�<����C+����8��e$7/�kBO
g,��
��^(k������$�z"��PGu�$J�^��{�S�r"�3WI�Pt�Wu��'�8�b�9�Pt��C(��ie4�s�]H��(�O�[Q���$������^���
,�~���5��4�1F
zw%\��6�Vz������������J �HT]����E��$�.�h�=��p��������H[I?s��(
����&_�t2��K����T�1	��������Y@�^��E�-��:�4��m������1��rP�f�R�.i~jC��(����x�����J���v�:�f���xq^��#�z����#��X�n*l�|F9�L�B���*")���P�}���7�5H�m'XXh2
v&f!��H�H3�s���d�WG]���
bfShH2Wh�O4�VX�����h���0s��jqR	]��}H��L[����47��\&����T}�
s�Y�@�.���e%���%�����a=���$�f	��E��7b������u����T�VF�U�bSr�i��1xA�lt!s�fS1���)�����!E����;*����:�Xebfp��JON��������a�<���5��s�����R�
'����'jn���--�E�(:IqB�\L3��&�47�kPo�N���/)���1B�;��
�nu7y������g���:����Y`�T��W,Z|<��H��������gr�Q����p��j�`�Uy����1��]2_�rL���?�<����Y���>D'�
J���
{`H��n������f���������v$;� �L��K�Vp�s����)b��f6�N�>��R�;t�N�5a������u��Z!�
F�K0(bd�����-���iI��\Z�*;���u73���3���/p6.t|?�����3z# �k������9�EA�-�H�W�Q'���L�U��^s��U�B/@�U���LM_���4����I6����l�J��jVel0���Q.'�)��U	��,f�aG���ess�a�}&�"j���N���w'�1��i�J���y�M,d"2T�%%G�T��)��z��q��(JGy��F���O�B�b��<�U������S7��M7?5���h��&������.���:��5��/}I�}W�U��tK���
N����L��<r�,mfd�Y����e��bUr���)y8�"=�M�^px �������Q�U��1R8���z��;-�"I@)�R�$�>R	r���}H(�
=��qd�x-�X��a��i���� UO���Z�UV2*5g���>`������x��x!`f���?~��;����_y�O��=��T���(��R\��
�BJ�
��o���&a�{OT����dmn�n����������!����Ns���&[.=��6
Y�����l;��h���WB�W���h���:x���x��8:�|t�h���������#:����m�.H�V�N�����I�4���i[�u��<��qw������������/��uG/H�
��T�zF�����������Y�3!��=�F����cQJ�L5��.���tN���tu���������0�/O:r�^g_D�/�#L'�-u\dy[�)���V�����d���<��D����v.�HX���H~�,	yQ��[�vZ��	39��#�����5��_-K���%��q����X4�>0z����u�O����b�5�b�J�W���U�_�1o-���&���?�^Q
����O?���o	�}�C��T� !�Vw�	���X�C��o���Tb����������)�������h�S�u�&V���}C��bT�f���Zg�"AW����5Na�I����T���FB�V�P�E���H�g�v�h�N�lc��#���{C'�~!7&4��1)=O/�[���4k������nLz��������	�I���7�u}�,y
JA���R��n�Wi����������
U=���Z��ri���&�U�(����l8��V9����>���wl��8Ji������=������_.��IV�r���L)-FZ���;9!�@A�(�T��<����%�,����S���dt�Q�0��E��%E���WuW��a��
B��g���)��SG���k p�+TQK����M'�����=���v�I�2E�~!�����Nm)�m����(/F7�	�A��-B.)
z�/�/A6�
�w�5��
y��|�"�[�#���Y���/��������_��_��_�����������*:��v�����hW����������q���!\�3H��]���������������������������?�l���o
�+�������������t�_~�#����/�����?���uedz
endstream
endobj
5 0 obj
   99354
endobj
3 0 obj
<<
   /ExtGState <<
      /a0 << /CA 1 /ca 1 >>
      /a1 << /CA 0.14902 /ca 0.14902 >>
   >>
   /Font <<
      /f-0-0 7 0 R
      /f-1-0 8 0 R
   >>
>>
endobj
9 0 obj
<< /Type /ObjStm
   /Length 10 0 R
   /N 1
   /First 4
   /Filter /FlateDecode
>>
stream
x�3S0�����8]
endstream
endobj
10 0 obj
   16
endobj
13 0 obj
<< /Length 14 0 R
   /Filter /FlateDecode
>>
stream
x���K�-�q�9?�b/\��c���@=�n=P�@M%V���R��}#W�������}T  ����3�������������������k����~����1���/���7?|������~��|���O�m��4��s��k^�x�����_��~;�v��~����������/������������������������:�i��6��~��/\����7�/��|������}������|�����7�/����?��/����������_����|���:����/����������?;�ux����.�q;�=O_�k����F:����|m��H���������q.��|
�sZ�}���}�Z��:N_��:���|��f��y��o�r����:��:
�u��m�^���.�y���}l��:����u�k�yF��u�wd�u����/l�����?.����O_����l��5�����������8%��ZF
Po�E���<-v�F���5g��c0���&�\����c�6����������c�6�|.�a>���9��P6��b����p��f����q{���z����
��CL��6[O�X�s��A� �3�q��x ��>%Ep�!	�+���o���x����sR�[1� ��<��@�)@� HK����������k�(HK�SZ�;F� HK��u=1���m;���s0����@��$��cy��h-)���cy��i�yJK p�i�y��C�N����k�����������VL����2��^�qy�������A�e� HK���Mve�m;����yAZb���1�AZ�l�������e}S��v���w[B���
_�����5������P��}{r�c���o�m��c��@`�$rF����lI����k�����������V����{2c�{��E�u�������2H�%���^���x�^��EAZb���1�AZ2�U�ul�����`A�����w� A�����\���m���]�����<�%�c	��d;_�nOfl����5j�@�����w� A�����r���!0�k��}Y�������k?N{3���Q	�f��k�������|��k�x�sB��A�`�d��6���6w|	��i�yJK `�i�<���j'�m���:4w HK�SZ�;F� HK���H�`�v�l�i�� -1Oi	�A� -���!��m��6��-lK;�Q���-!�mi����S�L�������� �0Oi�A� -9��>�����sz-�7�%�)-��#H����������Q������=~�O�k����_0��y:�cX� H��h>^�fg����x�z�C�����w� A�������Ol�����-�0Gi���������O�l�^��������<��_	���X_�������<�(HK�SZ�;F� XK��z��+�m���1l[;�q��v����b�^�bW����z�?� �0Oi�A���'��8I�o����/����-����I$�F�J�w�m�_R"����e]��A�v�RNH�����"re��ulre���v�~�LQ�v��4w� A��l��������m}��i�yJK p�i��Jo��_�����8�(HK�SZ�;F� HK��5Y�)���cy
�i-�e�;E� H+��5/re�����_�<GA�a���1�A�����lve�m��������-���(��[����|�t��kGt���z���1^J@�nD�y������k�	�rHC(\$`w���v���Jv��J�-A� ���7�����m�����=
����@��$��e{��%�m�WG�yDAZb���1�AZ���i�S�m�����
���<�%�c	��d[^�*�I��o���A�����w� A����k����m��>{�AZb���1�AZrL����S�m���;M HK�SZ�;F� HK������_�)o "�x4����k���B#D%����#�������	d<�@����m7_-<����~��{��7|A�3�GN%H�F1�RL_*A�z0�a�b2pR	�0!�(��`1�D��mdQ	��Q&���J�(���AH��PA%Ht�3�u��u�W�D)=���b�.�J���F���M�A^	��b]��d��� Q���(��\L�1]	��b]��d��� Q���(��[L�!\	���b��I�P+����Q�s�����$
��:&���.+A����Q�S�����$J��c��uV��@g�Xw^1Y�_%Ht�1�u��u�U�Ea��	V���MV	�S�bX�d]\� QJ�����X�{�$:������:�*A��S�Q�����c�,
�d��l���������b�.�J��)�v~Jy���?}U/"�.�H�_��_�x^���v��?�wL^������?_���������^��?������_����������2�d<�C��F����G������P�����e�?����n����]��F��k�yzM�j�����5.rm�����J��L�1W����[�?������JM�^���Y�+|������.E���1�����cym�����%����5L?\�t-�#~
Y?b�%SF���k�?�5�{�@���a--�
c�����{�����R���n��ZZ��u��	+�����������h(�^�m�iy~���zx��]�`�j��^�4�v���u?��yl��z��
��Y�����������8��|,����|������5���������k������i����7����Q��������F�>j�6]/��������Z�������(���?7�����������:�G�^oB������m����������������������������?������~~;����_��?��>��k���JK~�����-�yr:��������8��7�]�a�ez�������q�O�����?[��m��x�����gc����gs����gk�����������X�lj��\�l)���l+��?;��?o�O���?_����_�^�z�)w:m�{���/�!�����^����r�����?��v�����0rH����T�R1��k����.����]O��un�v����:/q�_)���k������e?���;�g���+�r��Y!�6��]C}_��Y��zI����v���{��Z��kyw�Ztc{�������T������
���_���z6�����X�������
l������B��>�mY7=\�K�y�}xd���
�h��}��c��}@�<NzU0�|��r��Q�T�0�m{�	�q���AS�(l\Ik���#��>v{���c���}<�y��><j�(������M;+��YqAi��q�\������g������Z��gb�[�S[}��x�;;�x6V��/��1M����h*U=��6_a��<�����uM�9���� �zl��Au�{L�n��S��'N�l��E����<K�:<���0w8����@��p�<���=��r���)���:�u8�S�p�v����#9hz��/�}p��?����HYf���+f���a�S�s[I�_�b���l������?DV�S�H���Oh��I���+;�o{���d_�N�|����q]W�|��y���\SD���]I���+�W�u:��%_g���a|��.���G�w9H���;O
$����w�#����b&��[
9�]��1K�������r(��\4�q�3���I��9��9�}+g�g�T�y���?�p�U�s��y������K�{7�s��s?�%}��yB�t�8y|,��/�c�4{�l��c��I������AsI��9�uI����8�;���Y*�^k�eKem�R�dTnC�R�z��<@���}V��%'o��x��O��Kf	�~!vU9{�d�o��O�F" �n�w������D�o��7n�;�4���J/�E\x�i��9f�p��K��9����mdQ�����,�.u���/��u?����?��n7/K>���OH�-�����u�U��K�H�%�Z�O�dI/�9��9}#�&r����7��g&�����_����\~�������x?R$�z��dsI��;��;�-��f����Ul��%�:m�[��T�L���*S��w���piW2'���9���]��Rr&9�%gx���Sfy��n�������N��n���|~�]�����+�|��-���mW�i}�@����O�?I����s�}u���\��P�@r�R�0��h)�=6jn����y�e������G�:�1o������V���w�mo]����w���N�u��i^G���q�
m_�c���u]VW:��L��kP����'��+���y�������]?`��V������<���u\o���:���~���.�[���H�����N���m�����N��0�5�4���|�w@dbbc&���Ll_�;�uc?9�5�������24S��p��ny!�?���������!�n���5�������L���0-�q:��\{}�L�c�f���G�}?y����� t��$���c��������y�?mt��)X�ZOGI��(C�w���.�����l�g�}�z�B~e�	{�;^����� ���0�r�A��N��Xn��Z��i�'�2�${p�7cg��$��I6��<������7��s�;?�-��F�7g�~<�r���i�\�����/����F�:��CL����Y��8_�\?�)����5{n��&O�nt��
���`�a�3��P�:��������7����b���[��em� I�@T���38�{r����fiz��n/7�Q���Y�-�7���!��L��-��E@h�����l�)W{ZpI��y��|XC��x`]o��>�����K�K��4�����eS������~y������0?��d"&v��������e�-t{�i:M�y�yz���{��O��?�����Ij�����9�d�)�����r4ix�q�[���w��������w@y�(^�+��+����Df�4��f&c���I���q�^�x?�B��%b���}��wt>�,�r��!GO��w����;A
{����6��K����r�V��L��L���]������)���g>��e7OFS���t8E���x��?^s�v������
��	9����2���K	����ZL�lw�'���C���=�dQ�f�3y��9�l����?l��������O\���_����!�X�c����h��d���"w��g����Ur��i��w2����uA2��7�R����+���2�>�r;:�~�<
WZ���<������e�� fO��
���\�u�M���l�K�M���}�[����r��|1�
Qg��M����&�_q]���^�2�{V�Q��3��fjvw���J}��[�YR��Z����E�~"6
�2����+=[V�s\n*��V���c���H���{���PZ��c���]4������u>��c���������CX?��{�%3�������tI��������5o�`�BAv�o�m��`�S�]���D����2�ib�,��(���R�N#����!������j>1{}�����������������y�d��2��L$��C�{����L�������;2��Z�L)my�e���]�SK�F���(��Qr�S_�����I�'*c~�:#�Q-�����3�=�>Z��<-����!��r�������g����H�W��~n������O��pqs�O{:|%�.3�����;RU���#������'3,�;W��Q�g���h\��s qg>>��V�����bk���^��C�����R���4������i_u�������&O�9L�����9�������_�4��=�kA��=;�F�e�����C��?ox/���k���������!{o��={�J�������|������k=p<�������j\������?��q`���>�H��>���:v���^Kr�bzkw���:C�L��m���Yb.r�,O[�=p��eyv�?�������Un<���Aw�xd�H��y�aG�q��vC�t�_sg��*��*���)��>��CJ=��%=AJ��;|�R��?����I���?��^��J+H�N�L���;4i��j�x��q�k�[H�����,������c�d�t�c�s����q�Q��!���
���G���	���=[�2�_��2��oQ�����,���3"Y�k^>������g�m��y2��7@�tz��)�����;6�0�[��8��`��c�O��y�xo[������M|����c��x���._A���0.��q4���7>�i��9Fn�p^Wy��j�T���8d^���K����@�N�;�����YO���M��8v/�S�������\�o���������Z�z�Yo{o�DI��u���bR>�u����;�R�����3S�G}�J\]Ff����0#�k��&
��G�~ZO�|K��!�����U���Z*������l6�h?-��|<�d����i������{F>1#;���
��;GG\��<�1{8r���t#+����
���
H���O��w�;������c��c�>�6����k�;9U��'�
�Yc=��~H�3+q���IO��,M�|���r��-]R5�mA�i����<}b��=
����w3O�<.�4}p���F�i��'���k��Z�in���k��bf��Z���{;`���6b�>33q�:��������IeS���>��3������^��w��H\$����b�)��Y�Y�i������;���e�����@�%g,����Km�����|X
�w%;�����[�i_.dJz�B������E3�*��������\Y?����;�e�V��|���%�yoQT��=��tk7=N�L��'J���cZu}�+�C����	��:��s�J�tR���p�p�����9�/�.������sw�:������:��LW�������*,�~n�L����j���?�I�����|���������],,���?��&g,��{pCw���VK����a�(�=�s������J�_������(�������Ov���m�t�t9S��������O�|���V�<o��$�_���f
��#�-jU���nJ��?L>�t��O�������s����i8�K���3L��{K�2����?��7���}�����gn����Y�p|mK�����l[^�z����Q�8������z=x�{w�~-�n1�{uo����m���
�����	?��.��^u��������[#B���������7�Ov~��agP���j;�x
�Mr���������}W���;y��W;N����i<^�pv��5�[.���:���-���uw���/�^��<O?����`�{���n����E-��E��$����zK/�7���I�g� ���{$�������)���~���S\#���;Gc�/��=7�c���:�s��)��InD�c�GLg�$���G�	������#��L��'�����{q�?`J��^��`�S����i��9�]K�H��Z�FJ��Av����>�kR��.�z�/�;��t/���lI�k��4�_�����)����5/����I|_��u��j�����8���3�y-p%{������(�lE��
%x <�ug �qZ=/���������)����y��l,V�����[�\gAq�������������������W<����cu�?VK��%~�E�:}	����Y_H�}`nt8�zv�y#/'�);i���T~��|����1q�
`������=�]k������c}�����su'��v[���>w�=q���z��_���4��������7���
�����]p������,}��Sh��p��:]����5�f8�1|�7��y��mo�
oA�?�@����k>��7_���5R���~O+Y���|ZXo����.3��,�U��=����;�/]���W��8�Z�>�;r��Fov�>������p1��80�:�oC�.���G�j�O����}������1S{�N��m����9+�*�\Mw�[����9����h��a<D��a5����e��Yo=;����K&fr��,��DN�'���G
����q~'�c��m�%q$M��2�y`���L��/������Q��k��������� ��w�����-��U��]����=/M��y�{cs��i�Q�$����@~����[S� ������?y��Q�9�����Y��:�k��=����|$v{�aR�������}p�_���/���3����;s=���]�\���	��u����sz����3����^	��wo�{����O�@�^]{����s�s�����?�����G�)�� 0��S��&���o� ��}�c�,���L�����R�X ������;�g������l,�R}���[����&o3������-7��e}�zu4s��|tI��d�%��e�\�G��k>27��8����Y�lu|���jw9R&}z|��e���^�d���;��&������	������\!�n	�6[��u�G�{-�{���R���:��)_$-���</���������Kj��v�F�3�Wt��;h'���b�T7{}S�R����1�����d���2����
��{���4Y<z������N�;e���^��u����z|
t���gr���$�%����Q��EX<	2;�E0H�-�!��I��w�/M��.��Ww�����;OeK�=[6�chZ����%���/_�
���Nw�N���,��8�/�K��K%#g��Y���@1���h�|��8�:?�r:��rqz���^n�s`����2HP�$J�q��_\>E��}��25q[��+A]�h�a��9Vr����c+��Yn���c�������W�.�.����X�3���f����������4����)�on��n���-��D��k%�
���1�^�9{9��w����{caC�("��S�Y!9��7��o��bgv&�QJ��m�:��c�N�����E�_t��	���Y�+�g]W/u�-��0[��F�0��}���pYG�}n�-���O[�y�����uT1���?�Q��;����O��E���/K��f|�r?oy����O���[�e����>�n�9���u��b������y���cr��������3&9�=��xB��-�-�QR����c���u�
�A��-�{�����Qz��^_ �;�����w�k~u��������Y��!�����>���=��QG��������S�����R>ww����/��R���k������4s�7%����f9�`�AeL������O9�9�{���l���?�
��w���������2z��_Nm�+dt���s;�Xe�<�bw��JF����,y�q���H��=p��%��F�����w��n��T��{��
y���(O��E�+ty(��z[��B���w��
�k��@�n]��������Bj�Ca�q�}�T=���v�<,�Bm��=�dA�^���B��}L��	������������Yv�������a|oZ���� �b�2��^�2m�p���^:��������1������8{�w�M(���X��a=v����^��-��@W~@�-uY���6���[�<��Rp�S��_����Z��n.�l��>���M��g��������0��������$�3�k���}/g"76��2�S�rM���k0����]z���x�����f�l~q���GX��v�w�*n_h��M�������f+������_h�ouX�'vK��6YY������������{�&���.����������T��=�T7{ ���A������z+����j�������v��[f]��}$����d��7y�o�(C���|���c��Eb����2[���f�+3_;�����~�V�%A�fv��G�^����K��u�&W��T����5*��}�0X������'���h�5�*�r7�ic��vW�
ir���kd��^���4&Iv������l>2#=iF1l����^���i63�m��j�,?}�+�S�C^5�+�Vn2�s��d��6���
�%��U���a��8��b��	�����f����	��<fl���&u!e;���z���f��"gs{��"k���[�i��ycfi�M������h�8�S79�os��Z�������c���%?,��a�(����z�
s{o.��aM��c��Q^��BBv�w����cr2��cj��a>���|l���"�����{�3�����1��� ��>6������L�yJ�|XJ��[V�od��d|R�o��q��H��������;xr{0m��B��.��Y����������6
/}L���	�I�Uv�7�9>C6�����]��GT�$�/m�S�Z���$��6d��7`�m��<?��/>������e��vV�������n<X������?OV�Z��d�k�����|M�d�d��(_s{��|�������b��;����lL�,�s@��2rP�@P]n�c+�����2��p}��P�7�����O����	�}��MF�����.b����L^e��f���x9�n����\��Xd|~�������������Y�k�`E��~;SI�������Y=�U'z����������h���Y����S;�hn����H����{H��Nw
jz������@���\���Mh�����v��/�0����lA����S���_�'�*:v|���
�|k	�]i~�����@�����������jI��-�������%�~�B���
���h�Y������#<��AY���������� �����1T��a������#C,���O���K�u�P��������B�w.[��������v�9/�������\�|�?s3����|>{����u����|]����7���]�����;>�c�L������^��t��2���d[����(&@�����hDg����;�;��tf�Y�n^S`�������eK�q?.��uG��,Y�~j�,Kt�'5���5�e�A�c�3���Hq��g�vz��~y3{d��������R��p�I����������nMC	X5�[�����v��m\�U�
X���������C@��=�7(2�������#�����o���o�5=fp��v�p:r�	�ieov$p�N�{���=w��H���v��"�q�������'�~#���%4~�#���y���#�����F
c�}v��������f���?��������m����c��p�~d.d��o9�B����Y*$��D�	��{s�Q��e�����������x���h�_���lj������R�sk�N�S��A����7E<oW]^p�~r�������AJ��'ce;�����J���u��U����%�*�3��9V����*9��1
��g+�w�]J]�%��ka��jI�g��w:�-7��wGf����r�m��'37���s9oz��ld.��cgK���'&sv�,���w����Ps9�[.�A{fR�}hX��(+i�]���^;���/�������� ���e)��%��t�}�q��Mw5��]�#�=������y���������Y�;W�Y�]�K"������������Ss7���������Y�\��^G�3>��n^<A�������~�1����u�����+���:�����H������c �w�}�H������uF�/S3��<�|�6�O�0� 7/�Y��[�����O��z�&��A�_n>	�ZY�I�_0|�o�a�M�����Av�z���i�_���O|X����q�r�����\���<h\����]�D8�t����-����p>
:�
�� ����������!W�����m<
��w&x��uZ-�nKy�M�M�������.��9!�;��<@Bw.Z���H�����nY:���0�;'��&�|���g,�����/yu:'���)q��kn_�������dZ�����������q;����f�f?�
�G���:����;#����}�,���Z���v������s� ���=/+�y�j�`[��7U�<��i'f/7�#{
��]/[1�5�<�������=��Y�C,���8ScM����VvV�����F����g�������;�����0�,�t,#�J!@xaTs;��=�h�0�H�y�Q.����?'��� wgF�����.E�\��7��V�������l����Z���������go�2��b��������g�xng o��=��y>1�����3Yy������������HB�o7���\��7T�jy3�����&�5��?X�"���/�Z6�;��T)/��/�Kc������=;�o��/��di�0b�����a��#wc���=[�	�o��0����#R��Jyy���%�wY;�OA�w�������~Z�}���`)�>=sO����� ����c����0[��\���G�P���1���#���mw�PS;���������oY����w:���'��e�sA�E�-��&v�2s;uw�"3;5w:8���E�NfZg{���mY�v[R�!g�Y�!����Z>�����JV�����D��8J}��@���'���^����<�<�yg5s�y�^�.w�����)Y��;�yz=����������J{���H���9��������l�]�8�������S\�u�.���>];�sk�����5|��E�{��d���*���FK�����i���;��}�Q�O�[�����K�t��u���q����s���Y��������Y�����(�e0�?�w�c�@j�oL��	|c��E�k��e�{W���`{$v{��s�f�,svp>��|��la�N��9u?�|���U����;��;�9��v�hK��!�S��PJ"g�~���b���;W���������������s��w�pv���| ��K�~�N�L�9�[�_J���'�H���}����]�a�F�����<�����v�������;���3�`�>hk������g�p�2�g��[�M��M���Rb�G���23��=Qs�}��)u���]f���g��X���GF���3�7��N�eLV#�nd�l16����>finh�������0��|���AFg��O�gb�NDU;o:�g�S�K��+y�i������%�{��Ci���Gvwv���$��L���w��u�'����8�������������0;�t���[�n������-����Xp�i��������Oc��-�NHa�v.U�C����;�~a�fg�����1G�;g�&v��������Q�����^�,G2���G����-Z��s/�g��k/�=�i����n���@�ir5�����w
dw��'rUW��J�W����_G�^����������v�w~e;=|����\k:u3�k�=0��N��kw���;��5[�������������WC,k�����q�����f�G0���v�M����F0u���N��\���r7�][_�f����������v�1Y�]w�Y�����q�XN����.��Tm����=�L=�%�;CK��=�X��������g�2po��%l����wK���{���i�{k�1��|5��z>L���wBgB����,ftn������\�q���w�a!�����:�=-BZ��~����������j<��p����A���n�!�s{�^�f��������1�;o����;����g/�H��umw�"���f�Y�����`����m�d��g����>���$�+��/�<�3���rn�{���[gyrpX�!��Ms�h/���s2����J����s�kJ?[j��neR,�;��?��;;_�^�0"4mpomA�xr=�_<d����u�1�3���n��c�7����aV�m�-e��n�-����)�<��u.�s��r��}{$h���I��Cg���>�����t��$d
�� y�&w��E������o��;�`�������yw�����������vo�,�:�3��8��C?��N����C�
y}��/���5����d�w�:g��L�t�Le�~
����\��;c^���Y�����ZVg�����N�{u�Y��;��L���`q��|���gZ����2����1VCZ�������n���#���`�0+w��F|��N�q<	����a������_������%����2:����@F����~BB�'+_�����{��#���,�e�{��1$n�f�6�5�\�:o��P�T�K?���,Y�1��t��_LL�����N{����=[��=��Zv�Z���is����m~4�3���2S�s�;3f�����Efy������|�u����t����������e���)L�������f��?�z�k{���,�1P���s?�$���'H������f��n�X5�|���O8#�;��* %���^��w]e~����%������!2?��>���OO��ag���P�y��������)�����#���e+]?��y�[L��
����T`O���{0�� �����wa5K{���X���-��dft�s����{��[k)�8F%�4��������h���;T��6s��.�	J�������%m���0lX�<s[������!g��UR��(M���0�k����HY��/�_"��Un~�i��7��2�K����9���_�O_��wv�^��h��F�w����K��M��.��E��R.O�N�����O^t<:�.�.�����|��gB�����y�����v<��������������y���e��y[�5Ys���#`��r�~������a:���6�ab���5���2���5�$�7��g��m��w/��l���w�Z����t6&{n����do���w[����;l����-������Lo��WIZ�3Y�;c������vB���][�@��=�,��Ow�Tg�>:>?�KL?���_��0��I�?��d�����n�jj��Z������1�5�yN�����r5�;������CCY������!�KO7|?[�3{�W$�3[��Nq=�H�|g�4���>����a2�W��x�������������}�9=m�g����s�����c\��+�S99�l����>���w���O'u���ch�����k}|���JOE(%;%O�2w���"L���/�49[��(���|��$i�-G��,���q'G�`5/$�4'��g�l��b|:�����u^J��t�������x).��iA�KZ��sq��uz�%y�����l����Bfw��A0H���=Yr;��;=���z{��&<��o�|�b�:���TvG2Q��qYa����)�����;����f�V_MzB��Z�_��&�?����?���){X�}w�gR$v��)�����kbrpW��J�y�����_��
��l�H�u������������=E��{�3����;��m�^�m���u��<~�����%���e����8���(&�������\7�������"(;�����}��\�xn/!�4(O��]�$�;[O�^�S������	����?���x��/Y�r��������������]�+�;�y^�rX������������=�=M�k�����O��^������������3�_���>��/���e^�tp7_���!�+�:���Xn�����~��}z�O���oa��������9�Ql�}]$@���s�o�s;-�1��q2C�]�^"���X���"�E������k�&�G�6��������:9��rU]	Po����4�1��{�=s����k����`��8���m��_\w��8^�>i� ��x4�2���(����Ac��kYN������	�mqQ���l�E����j��P,e�^���i{iF������k��h��u�~3O�E���k;�h��=UF����(�UL-�r
	�{;
�Cc,�@=�e}��4F���(�k���.�{(���p[\������krg2�Cc��kqgv=^�����mqQ����}��o�j� �F����������#Q�p[\��5nzD���V���P,e�^���}���$F���{aM��1���=�	B�h�c�Ej<6_��X���o�P���*%�z��{�p�T���kSC�ju��:�}��>5��"Q�ap%k�kuZ��P���/\����W����uk��4��L��+U�E���+^�x��� A���u���^�4�b�b�(��:m���j�o�����5��/VQ(�2O�zM���UC���u���me\�
���1����iY}�jc]���q�kZ_��P,e\�����w&1����^�_��5��/V
a����7�zM���U�E�l��^�v��]c��sU���p�k�'_��P,e�_��G���T5������vM���`� �F9&_��c���!Lu���f���t��X��b�����t���o	���^����5�/VA�E�\��FY\������X|������������W��k�`-WQ(�2.�����z�g
Ro���l�&_��P,e�}�/���!�u��F+�<O�`E�vh�y�l�7_��TW��o\���U��]c}������	�XW�y�|�����(�FY_��uq+nu���W�y=����)k]���ql�_��P,e[����p%+n���5l����K3�X4�>�6��/Y
a�k��7���sL�h��b�����|l���$S]��c�Ul>G_��P,�_��j����p[\��W�e|�
,��W��auU+nu��d:V�q���n��r�*��'�)��Q��W���V	Ro/u���U1|�LA(�2����;r%s]����U�|��<�v1h�y�U��� A��*����b�p���P,e_�>k�U�����l��a�r3�X4�:�:VV� ��P���O�E�������p[4�6�:�U"����c���:V�O���ul����?�
���P,e�}��;%s]���qu�,b�z�4�1�}�b	�������aR���P,�}+�$%H�=�u����X,&U�X$�:���a��D	�X���o\�Hr1a���:��a���0�u��W��0?���K]��?quc���b�(���WH� �u�������So����������/[
���(������_��n8��'�>4}<���1��m/Z��h���~�}�����#�����#��F#|����������'���M���w�>���m�:���w�>��g��d��w��=0�'�������B�$
����k��n4�gA��d{�'OE�F!|��`-���B�,
���R��F#|�?n�����(�������s��w�>��'�����Q�� ���|���(�O���p�X�H���I ?�����,���;����B�$
��:Z���Y"?6wn=���I ?
�u�8z7
��(D~"82���h�OB�'���=W�n�gA��DpE��w�>���:E^'_�
�� ~��JUE�F!|d���`�X�w�(�O���b����w��_�Q��l��U��Q�9|�2XG��w�>�B�'�/O�F|�OW�*z7
��(D~"�?�"�GC|dw��`����gA��(L�zU�n�'Q6_���E~5���,��G_�*z7
�� ��_��R��B�,h�l��S��Q��9}�1�&�����Y�:�u�m4:'!
��mWw*�6
�� �u�hq�m:'Qv_w����;�s������NE�F�se�u�P�Xv��,x������<y��I�:\����(t�Q��p�����
�I���mb�j��9}�1�&�����Y�:
@m�8�6�� ��>��PU�9B\Ga���"o��9����C�&_}�,i���T�m:'Q6_}����>�Y�:�v��"o��9	\Ga���"o��9�B\'��>{
�I��Um4�u��� �u"��S��Q��E!������Y���$��j��61T5t�����jXE�F�se�5�P�X��r4<gA��d�U����B�$p��W����B�,
q�w�X��r:'QV_�����Ut�Q����X��F�sduU�@���r:'AN_�����b�s��NW�*�6�� ��b��PU1�9B\'��by��I��W1Cm�u+x������by��I�:
��by��Y�:v}�V�m:'Q��Dpu�"o��9�B\'��c{
�I���1mb�6
�� ��c�`��m:gQ��Dpu�bo��9	r�:f��G�F�s��NW�*�6
��(��c���R����,p�l�����(tN����hKU�@�,
q���U�m4<'A6W���A��(t�A�Q}���(tN����hKU�@�$�����6X<y��Y�:\���hxN����hCU�@�,q���U�m:'Q��Dpu�bo��9\'�7hCE�@�$����%�:f�?��������z~g����=�HQv������l{-��;�w�{�;�>��*��^��� ���u�&��~�����������=��moz��A�����u�{�wP������}��wE0~W��D�'9�w���h��8G�b��<�w��,rE��Q��	��p-�� ���D~'�*'V�����(����u4��D~����G#���M�w"L�!��B~������;J�w4���pj��rS��	�N�z�QzQ����B~'�����;J�w4��Q����$�;���D���~G	��&�;
�u���D~����[�wT
����;��=�w���`"��)�w�6_������J��;8��d��*�w�V_�����Nz���t��e���u�!��J�wt~'��h)��t��e��p-�� ���D~'�/�wT��h��U/�w���`"����)������E~'��z)��D~���W/�w�6_����p��~yB�������_��(��~���/�w������mWy��Q:}�1~'��<�� ���D~'�V�wT
����;�vuG�$�;���(��b�������;\�Q~��&�;
��;��(m���a�'��;H�w������<��(���~'��<���d������p�@~1RZ\�!��mW{��Q:}�1~'��=�� ���D~G���w�W}��d�U�w���`"��0����������;vd����;X��Dp�G���W�w"�����<�w�����;J�w4��Q�|�Q~��&�;\�Q~Giq�������>��(���~'��>�� ���D~'���)�{R:\
#��mW���A"����NW���Q�}
3~'���)�{B���N�]S~G	��&�;
��b�� ���D~'���)�{RZ}3~Ga�UL�]�����;\S~GiuU��N��nN1��������;\S~��&�;\S~G�pU��N�]S~��&�;\S~Gi�U����W1�w������mW���A"y���M�]��J�(m�y�U %o�H�`"y�U %o�VW�H�d�mby�t�
d�
A,B�(����&��@J�(���q��#o�H�`"y�U %o�_�����*��7H o������OI%o�v_�����*��7H$o0����*��7J��@$o�}�61����[1y�0�
�����+��7\R�F�����6X��Qy�	�MW���Q:]"y�mW���A"y���MW���Qy�	�MW���Ay��M�o�&�7J��@F�DpH����W�$y��]���D8�Q��sR�7X�DXFa�
�
�o"�xN*rQ��`!�a��B�����G�@7���@!���O�pP��hDa�%)Z���aM�y��(G��f.�4U�s�8F� ���������U�F	,�\�i���d4
i]�i��r���Q)<�,���F�5Hdat��2��h�0z��T9��UF	<�\b��TL���@��$�
z��)=�P���!Se9�z�*f.`1S��h2.�\�.�1U6w���A"���PK�]��Q-*�j��`-�C�"\�][���d��VC[�h�l��-\!P�Ei�J��-U�I�b�[�����B�U<B���e���Tp%A$�)���T�� |�
��y�TquA	$*���T���	��(��.R����R*H�TtS�2������V�U��?���_j�@�h�2a��AY%�*sV����Oi$�*���Tp�Ay����Tq�A�$+���T�� ��JaVf)�JW�ZA"�����`*��[Q���`�JW�\A"s���I�]�[J� �:�������(�;��IW�<��\��T�"�	��(-�6>��j��'JgU?��j��'H�Ot@��D�(H�Gt�\D��#J{U ���0{U� �C������(mU}0����J� �"�������(�#� ����%I�H��"JR��eI������{�+	�DDQ�*w����G���0HW!�A"
��8HW!�Q��
a@H���ML���"D��
�F(�&d.@!Sv_#�
A"��XH��NL��+�����j�2J���:*�]hbA�����uTq5B�$r�vTq5B�����\�P��l�.�U\�P�Ci�j��S_#��@��	�G�B	$��xT�������V��<����)]�<�������kA�
7�H����z���$��X��{Tq5Bi$���jTq5BY
���kTq5Bi
$���kT8������^�6�����
]D6�������k��
7��M����e������6�@\��B��&a.��\�����J](��F���j�rH�.t���j��J /�zQ��e/��^h|Q��-b}��W5���*�F(���x0��:�I�	G�Zpn �)&C0&,�1C�B�����a�b2c�:kS��P"��� B!/t��@1S,�bL���C�0&��]���@S<�cD�'m�.��$2���B��"���PF�e���T�4b��e�����(�IFf`42#���[��I`34��P�1��jFg�3:#��j �3����e|F����T�)2DC#�(w�T�[��Hih$��t���t�Q2N�qQ���P\���HTCd�\Eq����h�F���s(.�
����e��$ ��lD�� a6���g�F_6��C3nC�V�r5U���
L$7"���������nDY'w{Cq�f�����"�50	�1	�&|!Q�C�|�pDq�D�I�8t����	=E�q`4�#���K]o���}51�#��&�r����`���&T\�����U�������E����*(�tL#���PG��=*��&���G�#��(
vL;��"dG_P��&�����(���1	x�&�|9Q�C�|x�����(������W�����B5��aQv����B���G�#�/(�zL#����G_P�h����Q�_e�z+>��k�|MQ�c�Y�a>���B�5����}�����(.����P�1WQ|���,D�}���,��2�#��,T\����U�����Be1!���(2��>b *[UY��&	��H�(��P���h$�W*.�� ��rw�����P�/.��D��E�5�B��*�I��h���bH]o��"��hH__�
�F8D����/T\���h|H����h(AD�(�H__��P��/F�D�;���.@����'��*�I��`4T$��/T|����E"�������"������$CF4�U}1Bd�#��F�����(6�f�>�F���YG�5���[U_��+��G0=��*>����$n^�$����B�BbD�$�H4�"����@��D����uT�EX�(��Pq����#N�p;�I��U}�$��/T\��)�hLI__��P��/��D�)�z���	X*>KT���P��B�-�H��(�.(.��%��D����u��b�I__1Q3��1&Q|}���d��Fb&Q|}��B�4�G�$�
���d�^�aM���B���u~��
t���D����-
J������A�`��<��X*�q��	��&3��6�8�AhLovdAO�6�gsg�k�1H�6�t/�'QH�`
���'��W��-����"�J\�,M!jb��(��q�qk�/2�H]��M!jb��h��T���2�cT����7i��M{�:3.�G[T@�\���{�����2J���S�\�!`b;\�	�b��(���KS��h��01�}�\�!`b[�	7l�@QQF���$�a+���7������0��L*F�`�
��s�n.���(���4�����4����4�V���4M��2�-����k�00��J�i�A�i��*�-e����Y$�`����L�i����-��4���|����4����\���i$5F��\�L@6	d<
��84'�������B6�a�._2����9���4�V��2��	�&������2�XM�i�1M@6	�U�l(���MCs��.��u��
dKYb���P�`��c|bt%��a�&h��12[T��\�
��P{U[�B>1��ik��h���)����2��	�'�H����
S�/��(*c,� J _1F��B1��6e�%�!��0u��)����,�h��I(�|0F��Bo���_$��S�/���
V+tM*QC�U�l)s,�!J$���EE���|�X�@Q�b�A�P���A&����M���1�MD	e���8�(F_6��E
��&'�8���Ee�e$RBt�1*�_���)�P�/�
�����N���E��5��H
�We����p�DJ(RG�� ����K�@G��K'H��2�cT��E"5�Z�������*��/�1*\�PCjj��W��0��	��q��P��BB}���t
��1*����N���%�N0M�c������&�����P��9�N@M	E�	_d���|��O�Y��L�t��e�P(�Q�w�\��G5�VU���TU��z

�1*����WOc���!TO�Q�sV������T���2
cCb��P�/�
a	�+JC��E�1�&�����V��9VOpV	E�
_d����$��@Q)p�5�pV	eH��8:+F_=��2R�P=�Y-� ���
��6	�UC�U�l)c��X~QC�U�l)k��@���,�
e���VC��X6�%TO[�ct�����&d��Hg**s��@��x� �tBu]�V��U�l)����t=u�Y�l�n�k��((�5���m�X	��Y��7�"�-� (�5S��c%Bga�-�c�'Bg���3k0A� t&�Y��0A�����e[Xl1EE���J��B�8QQ:+�Bg������*�����2PT���H:�X,�QqtV��;�����4��B1[T�c��T�b**Jg�x��@0_T����pI&,����tV���S!��1*Jg�xw5C0_T���m�'�X,EE�,���P���Gg���sn,����t�F�Y(�ba����b��9'�e� �S�j�b,��8:+F_��2TT����UF���A���|m�8QQ:+��k��Y���t���R�-��0�eR�,����-E�,���P���Gg���Hd����.�Bg���-*�����P���
������La��E��Y��d
�e��L�d
��B_���5VESX,Ee�ES�,c�0F��Y1��i0���0�%S�,��*�-e�%S�,c�0F��Y1��i0���0�S�,c��E��YIg��Uyl)Jg����X���,�SU0��2TT�X0��B!��/Jg��K��X��K�����-��x:+��#���KdC:+._4����%M��P���Ggi$��r�"������Ma��E��Y��l
�e����l��`,����/��b(*S,�Bg�������eSX,Ee�eS�,c�0F��Y1��i0���0��)t�
Y,}QYc�:�X,�QqtV������dC:+._8���Gg����X�����Y(���A���|��@Q�b�:�X,�QqtV����Z���t���*��b�1*�����N����!�N��T��P���b��SX,�QqtV��t�e� ,�t
��b,��8:+F_:��2TT�X:��B!��/Jg��K��X��K��Y(�ba����b���\X,CEE��}�cT����c)C��Bg��U�l)Jga4:K�,��2��)t����eCXB�:�X,|QqtV��z
�e����z
��B_����pOra���b��SX,�QqtV��z�e� ��z
�-��������4�U��PQc�:K��jeKYc�:K�,��2��)t����eCXB�:�X,|QqtV��z
�e����z
��B_�����Y*{U+[���cy��(t�c������f����M� A0:k��FP�����f*ta,�A�,L��e�`l�A��y6wf
�"H���D:�0� �5����l�-���W��T�b'*Jg�X�,�X���tV\�73�X���YIg���1*���qu��0���`t�&�Y(�ba����b,5�
Y,CEE����b����Y�.���2PT�����|*d�4FE����f�b����Y�-���e��(���t��X����7���2TT���H:�X,�QqtV��;�����b*]�P���Gg�����X����Y1��h0� �����b'*Jg�X~-S9���R����Y
������L
��rTu��(���t��X����w�,���2�%S�,c��E��Y1��Tv_!��Yq��),��8:K�T�La��)�L��Pc���Y���h
�e����h
��b,��8:+FW4
�2P��d
��BK[T�X2��B1cT��+�c(s]0��B1[T���t��^����tV��`
��1*���8USX,CEe�S�,�X���tV\�d
�e����d
�-���b����b,?��,�D6����ESX,}QYb�:�X,�Qqt�F�Y*�/�
A���|�_T��q����X������Y�b����Y���),���2��)t
a,|AP:+._6��2PT�X6��B1cT��/�c)C(�Bg�����5�M��P���Gg�X����L6�����SX,|QqtV��p
�e����p
��B_�����Na��)�N��P���Gg�X����U�l)Jgi���),�����}�4�HAB�:K��
eKQ:+F_:���Gg��K��XF
�J��Y(�b�����b��SX,CEe��S�,�X���tV\�t
�e��L�t
��b,��8:+��=���2TT����WOa�0F��Y1��i0���0��)t��Y����tF��T�bal(C��Bg��X6�%TO��P���Gg�����X������Y(���A����$�@QQ:+F_=���Gg�����XF
�������-��x:K�XUOa��1VO��T��V��5VO��T�bal(C��Bg���X6�%TO��P���Gg�����X������Y(���A���
���W���(�����+�`������l�,w4��bK�z���9��@���z[��Y
��@[b��Bea�-�aoK�-L��;��`K�z[�,,$�`���k��gr��z{� 4V<��S!{E� �{��(
�kq��
b�S~S!vE� �w7�"��b������pJ"X
 ��Ro�������
�~pS\��S!kE� �������Z�+^�t�<��0A�+8�*��
+lA�7<5�+���bZ�96��0AX��7t��B1�ZlA��;%��V*��|�S���B1�ZlAe�8���T��6�*��_��jqA��)._���"N�P��B K-�z[��X|q��(AB}�J���!�����2�J�6����r�T���5�%T:��P��[@S5a*�S�	�\�;A�P�W��P�c����[��*_���[���tMU�`�8A�B�~
�����m����U�V�0AC�t
�@i��_�8���I���z'�����[CXB�d
�i����8���G����j'�����
�~K�.�R*�/n
��8{U���[�aQ�c�����[C�B�B
�@���m�����Nh(�a�N�hQ��� ��I-��Q*�+pq[��x|�

[�P��B1ZlA�I%�Q*�+qq[x�x|��YlA
�8C����5�9�<#��{W�����<���)�<��P�<���V*_�x"L�P��B1�YlA�7L%��O*��qq{5O�'�N�����'��Q�b��*qzR9\����<�����b�~�T\��	�D� ���	��B�Y\���N���'`a�0��'��Q�b�y*qrRY}�k�������'<���|S�����:��P�nR9}�k��Tq��'�����J��TW�����@M(�0�-�aTq��'�q�0��'8
�eq��
3�����K�	����L(�-�-���0&�������j���|B,�-����1�Ieu�.n��	��r�B��k�p��BRYlQB�pI�p�.n/��	��b����h�q�YR9|�ks�|,��OW���R<�{&�D� �WX��+���b��*qTR�\���c�|�)�"P���}}����|B$'c�|(����5�5T>��T�"�-
C�|�&������%T>�PC[������O$�a�O�$����H��I*�/t
�>�:��kd%_�����KL�G���i�m�G	��x�R�� ���$)�A2J�J�T�$�I!�	�qI�l���C2H�L�2I$�� ����+�
E���tR]w�60��DE�����T$�� Ju�?_M��@Q���f��B(IcTt��Ww�&-P�*�TI�d����\Q5����I�+�����I��b�Rm�K2#�(*2WT�wU7���Q���j|w�R ��/*�.���NRi��"sE�zI������\Q5n���WZ���\Q3�aR!��1*:WT��;���(�j�;v��[��+�F_
]Z���\Q5��H|I[h���F��'*2WT��/]S�� ��+
#��	c����L�4���bK���f��B�IcTt��w	4�"Ee�K�N*���EE������k��+dC0��._2
j��+j��*��5-TT�X2�uR��/B;�5VE�������O*��4FE����M2N��.�F=M9��R�X2
|R!��1*:WT��d�uZ� �u�4�I������\Q3���W����\Q5��i�����\Q3NU�4�i��2��i$�
�'}A�._2
}Z����d�b������j���������MC������aQ*��4FE���d�����`lT]�l
�/*:W��sU6��Z����l��R �/*�H����!Q�)�M��T�E���Ru��i\�Ee�e�X)�Q��sE���&��E
���SS�G��5�Mc�T�Hi���U��yl���dC0p�._8����+�F_8
�Z����p>�\J_����N��(*S,��P����+���3���*�-E���q�J��S�������N�S��!�N����U�l)2WT��t@�1*:WT��t�Z� ,�tV�B�J_Tt��}�4�j��2��ip�
`*}A��._:��Z��L�ta�B�JcTt��w�$7�j��"sE����aU��sE���'��E
����VS��V��+J#y�)��46�!VOC���X6�%TO��T�X����U����Y-TT�X=
�Rj�/_�u�'��V�+�F_=
���+�F_=�\-R�P=
�B1�
_T�\Q3�U�4�j��2��i(�����-e���h�)��46�!VO����X6�%TOC�TH`����U���a-TT�X=
�R��/�f�u�YS��V��+:��k�d%_c�����f����M� A0:k��FP�����f*ta,�A�,L��e�`l�A��y6wf
�"H���D:�0� �5����l�-���W��T�b'*Jg�X�,�X���tV\�73�X���YIg���1*���qu��0���`t�&�Y(�ba����b,5�
Y,CEE����b����Y�.���2PT�����|*d�4FE����f�b����Y�-���e��(���t��X����7���2TT���H:�X,�QqtV��;�����b*]�P���Gg�����X����Y1��h0� �����b'*Jg�X~-S9���R����Y
������L
��rTu��(���t��X����w�,���2�%S�,c��E��Y1��Tv_!��Yq��),��8:K�T�La��)�L��Pc���Y���h
�e����h
��b,��8:+FW4
�2P��d
��rV��,�d
��b,��8:+FW2
�2P��`
��b,��8:K#�,��*�-E��}�cT��q�
��X�����Y(���A���|��@Qc�:[a��Og�X~$SY|�lBg�����X�����)t��X���,���T_$��Yq��),��8:K�\�Ma��9�M�������b�eSX,Ee�eS�,�X���tV\�l
�e����l
��b,��8:+F_6
�2R�P6��R!��/*k,�Bg���1*�����/S9|�lBg����X�����}��PQ�c�:�0� (��/��b(*S,�Bg���1*�����/SY�B�R����U�SXl1F��Y1��i0���0��)t��Y���tV��t
��1*�����N����%�N��P���Gg��K��X���K��Y(���A���|��@Q�b�:�X,�QqtV��{��e��(�����ba����b���`,#a�S�,���g�o�\Y�Y��F�#��'g�u�����
�~o������R�,���"��
�����*xAn����&�9��U�	;+�\�ag��	��&�^O�Y�X�
��"���p�.jB;��\O�X�;��\��X7��z��.�rM��u���'\����{=agM~�V^�w�'���]�����	;k��cy^����"q��5v���p��j�������U��,R����V^��?�?��D����}�L7���
���bW��3���i`���3�?��&����5����ae��]���gd��M�3^�(�U�?��*b#+`�2�3|l"�����zf
��"�V���U-r�/SK�������L���xlb�����7)[X�8�+��|�z����U-�s�#�"��+U7�2�6���U=r�/S�O�+U����w���������&v���M�L��D��TW�^�5�^UM�����m�H����M�L}�������7){V�X�+��|�:��l����9���}�H��J�M�L�Y�MUO�{-[���]�
��P���q�HUK�[����o.��M�JE�����~��+�j���v^�t��"��+V7�2u��S�x�{�*q�Rp�/S�#��1���aQ��{g�bp�oR�m� L�S�Q{*b]�R�3��3�m��JUS�^�u*Q�bp�/Sc��I��?����55��y���;(S��+��|�{=������l�H��J�M�IY��s�.@n�ej�����M�I=���UO�G�����J��������
UM�{���"P�g�o�ej}@5y���gxQd����*V�U�)*�bp�oR6�&�1q����<��+��|�zn��������P�x��*���<XO�x��A���y���L���<O�������v�X���6���56��o�yP�&��x���{��r�X��������;F���<�����bp�/Ss�`7�S�Y��)b��R�3U'2s� 6US�Q��)��bp�/S�kZ������|��l��y�
�&_����f�f���An����]������=x�+��|��������U��)��bp�/Ss��/�S�Y��)b{�R�3e&2s��.US�Q��)o�bp�/S�xO��TO�����\>�+��|���_����V�}i��Cwr��Sq�&6�+��V�qi�K���Z>hK�H�+��|���C���Z>K������+����d�I��M�L����\������|�������|���@J����7���|0��)p����4�����Z>�I��kp����4�����W-��H4���M�L����TO�g-���
�J����,!ir���������w�������������������o'^h)���a�}K�������,�a�p7"����-��pJHD��E�Nq��L��!��_]d��tDe|�+��1*t�G��D�}�DB;f���@E��;�T�
��iYe	r�D����:
$��$�����o���
�E��&��I���������y����5������4�RL.TlD�)��m7C���\
=�� ��?�q.3�����X9����n��$��n���y�O�	-����E��#S�]��S'����Df0)��4\FUq� ����\������9����i��Y#?p~��������(�"��1�R~���%��?�)tZA�X�������L!����)�jk�o��E,����4������l!���7<���CQD���M������d��8�h���b�@��nL��Yu�5���k2�
��|(��3ylk2�?~�5��9?�1��o���M�mP�_�4;E���@>���������C���\���4�~������A�
��CU���MM�?Y��;�9'�oj�-]��<���ph1���d��8�h�d���@��g2�N��&8�9(��b����Evp sP�ICAI�TA�������#��57%&`n����f�mS��@������K��*:�D&-�EF�2g��K3�K
'�mY��@��t��"{���4��,��`�g[85��,&���hh�V��2������Xd�f���e�4\&
Uqk sYLf���Xe12a��3\&Uqc ��{L��Y���=0�e��J���PA2�e��<�q�#9��c���������`�����f�c�E�����8�g�����@������AI�TAs��}1�U��/1]�u���p���}����2��.����.��/&��;�%�`���3�b�p�(�d��8�xl��e����dT��M6_�c��s�b�P�(�
��u�]�g�h+��/����T�����b2��s_"�N�������o��+{'�x�.��U�����b2��lBU����q�-������Qu:'4YB|������f��/�P s_��7���P2��dV}��DF������$�Rer��e�}<��l�D�������S�*2	d������}�O��2�����G(�Q��2��
�U�J s_LF����d�p
!fvE$���c��d���������"9��@<������4�����#z�(RI��Kf����D+S�'�Z'����Rg����Q�2p
.��xes�M%pN!���mR�N�%t���z�mZ�&8���m�&�4�����p"vM���8��/K�X5��	o&u�g�D��l2n&E�=^o�&��8�|0N$��k2n&Ep���������fR��f��R�	�!�����\��7�"�~������&�����f��+����b���G[��	o&u�g�D��l2n&E�3_���sU�L��O����)�d�L��1^sK0��9��1�(0����I����&������`���9��6����	o&Ep�^����M���C�H�����3�ct&�m�o&u�'�D��l2n&E��M6jnj��'��D��k2n&Ep����1��LRs2���k�8z2q����
�y�d�d����rx����6��i.jr��|�x�HN�)�d�L����9����3v&�m ���'��D"�l2n&EpLf���
<���Y;��9����I�q;�c��+��I���v
67�:����UM=�8w'b��\��CjN&$����{2q�nH�l2o&Ep�6��kN��	<��hB�9�����Cx"�|
67�:�sx&�9�'����	Y�\�q3���m6!]������x���5��<��lB����g��Dl�+�cyH���,tQ�{�&N��D*�����9�9�����M�3�tt���g'�D"l2n&EpU0����8���NHG����I��	���&�N��uT���!5���EM=�8�'��`�q3)�����{�+��I�l�	��M�������tS�[M'������"���9���
67�"8�3�M^5�8�'�\�q3)�s:�/]������|"���a>��t�����<z:q�O$*T�&�fR��N��&�����P�
67�"8�3.�Mn��8�g������fRs���*T�r���?�������?�(U����I��	���&�^O��SU���!���P�.j��I��	��`�q3)�s=�T�T�^��#�@��\�y3���m=q��UM���8�g������{=q���vV�r����?�������@��Y����I��	;��&�^O��U�"���V^�L�{�}���] 2����:��t����@��*vU��1�I����=#�3�l"��
X����VVY��	{F�g8�$>����]�3��"6��+�?��&r~L^���g�l,2k�M�^�R7�"�T����J�������&���)��I����s]�����x�m\��?G�:b+��Rx3)Rk�M�Z�S7�"u��!��R ^��G���)��I�Z[nb��X�L�����@�J�pE�5_��U����N������X�L��g����)��I��g�U]����c��v�j���XY��D��T�L���5�T����E�
��������7�T����������]�L�R��r�g��o��A��|��]�W-|�H������H���T5���A��D��T�L���Hkr�}�gXTd�����X�nL�����z
<j��OE�KWj�=u��-\�j
�k��NE"JW�o&Ej,^4�Z����w��&�9o�U{e*A�bx3)Rc��G��??���-�]����)�R�c�����H���]�������vP��)����!�]���~��w���)��I��{z�
��I�ZPM^c��^��x���x��A��D��X�L�����wL\?��"37�s�
�fR����A}����6/&T �s�
��"47�S5�y��"v�+�?S�"37�S5��y��"��+V�7�"57/�S5���6����S���<xO�X�+��I�Z_��|���3�'2s� 8W�o&Ej���z
<k� ;E�6Wj��Df���j
<j��9Eb5W�o&Ej}Mk��3wp3�S�m��3�X�q3)Rs�b3U�?�j� 7M~s�.n&Ej�<�����H����T���������\�����{���)�������\���2��{P��)�������\����c�'CZ��n&Ej.������H����T��|����4������T��K��kp����4�����W-��H$�����H����TO�g-�����J�����|�{2��j
�fR����L�X�L��\�xI����Z>h�E %�X�q3�S�m�`$�S�^�Ai��Cw��|��&v�+��V�5i"����������\�����|p��)�������\���B�%$M�9t �N����#r���'`�����|$B_��Y��%l$�9�\�R�p��N�@�-b%ZVR������D�3^Y{H���tHf�@&��q��}p�sT�BMb'�:�;D22=MpV��SP�HH:W�����k��d����h���&��6�YQ�����LQ����*Ml&k����k"9��&8+�������5��d�;���P��	��2x�z�$e�MpV���/r
�)�ku��k��6�)j���	�^�XV:��gE�����2UMpV4A9L+K���(��x��-ST�"����]{K����(�s�.S�gE�h}�X�M��6�_��	��2x~�
�m�xEpVTAk���1��}&#6C��.^�MPn��*��&<+��1�d3����Of��}�cMxV���So�1��r25'3R��&<+��c��x�T5y�d�u��m:W�3��6���)jr����4��t�	��28F��3En�d�z�����"����O{N���(�c2�:ST��f���e�cMxV4A	��c��+�������t�	��&��3�3UM=�1�&2���ejNf�g���{2�CEb?l2��2x~>y���q�L���M���G3Z����&<+���h�w���ej�fT�sMxV4��6����j����!5�u�I)cs6�DS����Mj"-�\�R��l������g3���j��&<+���M��4��lF����&����gjbE�`�e���8�;g�D�25�3���&<+�����T5y�pF��H�:W��9���)j���	�tF�:��gE<�LyoCyEpV4��6��
6gE��iy���������6�WgE����`�epN�%j�
�j:�UMlQ�k������HMU�gOg���d�s�W������&���VU���(��x'�SMU�ep�g���MxV����V�i*p���l
�m[yEpV�A���U/���3�5�;���j=#]M�X�k������fMU�g�g���T�s _���w���5�YQ�zF�:��gE��i����Z�HX�8W�����	����vMU�{�gTl�o��+�����
�|u���z=#dC~s,/���3J����&<+��\�H�T5y�zF��H�:Wj��S���V^�������M����>��>
hg:����e�J
��&�~"X 2ve
��&����e�Z
��*$;��#cW��l2���F�����B����
��M������bW�	�,Rk�M�b���v�egE,c�+@;����lb��&�������*�d�Y�����uQ��Y�lgE�bk2�,�k�M�b]��v����X����"����b]��v���&v�6��E���Y .V�&�������&�������*�d�Y?�5��uU�YmgE�bl2�,��x�-c]T v��U�H\�bM��Ep�"\�����"8�12V����m��uO�Y��e��6�W�vvcg
$cW������5�n�xEhg����U������.�MM��d�����*�d�Y�e�c.���EjN&\�rM��u��M&\���<z2agE,c�+@;��}M�X5��h�����*�d�Y�hF����m�L�Y��6�W���	;+�`�ag����}0agE�bk2�����&�6�W�v�9�p�
6v���6�p��j�������U��,Rs2�b]����	;�\�
6�v��!��5'���"5G.��&�M�Y��X�;�����w����Ej�&\�rM��u���&\���<{6cg�b�k;���M�X5y�l���X�*W�v�9�p�.jr������U��������uS�[�&���]�sM�=���"q�
6v�����w����Ej'\�rM��Ep'\���<{8agE,c�+@;���N�X5y�t�����*�d�Y���&�m(�����m:�bW��������uS�[M'���o�+B;���N�X�;�����X7x�t�����*�d�Y�t�������vV�2V���H����uQ�GO'��H\��M��E���p��jB;��\O�X�;��\��X7��z�������"��
�����*xAn����&�9��U�	;+�\�ag��	��&�^O�Y�X�
��"���p�.jB;��\O�X�;��\��X7��z��.�rM��u���'\����{=agM~�V^�w�'���]�����	;k��cy^����"q��5v���p��j�������U��,R����V^������7�
D�>�}E�j@;�����-cUR v6�����+S�v6�eg,c�RvV!����2`g���W62V%`g����U��lB�����BM`g�Z�ob��&��.;+b�\�Y��gf�X5��u�vV$.V�&��"���e��
��:d;+�X�ag\�ob��&���?���*�v���G��&����7��u�	�,��W�q��5��E�5_��X5��u�vV$.V�&��"���y\�����:h;+�`�ag<�kn�������E�bk2�,�s�b]��v��������Ehn#\�{���"�>-���A�"��+;k �b���$���w��+B;����H\��M��E��Mv�nj��'vV$.V�&��"�>(�s!/�,Rs2�b�k2����m2�b]����	;+b�\�Y���h������GvV$.V�&��"8F32�En�d�������"��L�Y��X�;�����Xx��	;+�X�ag��59�y�"������U����>����uU�G&���e�rhg���	��&��L��E�bW������&�9�v�9�p��5y�h�����*�d�YmgM�s$/�,Rs6�b�k2����m6�b]����;+�\�Y��l������gvV�2V���H����uQ�{�&��H\��M��Ep�fd��
�j6agM�b�k�������U�������M�s&/�,Rs8�b�k2�,�s8�b]����	;+b�\�Y��t�������vV$.V�&��"��_6yoCyEhg�l�	��M��EpNgd��
�j:agM~�P^�Y�t��*�d�Y�tF������vV$.V�&��"8�.�UM�=���"�����EjN'\���<z:agE�bl2�,��x'��uU�Y�z��*�d�Y�zF������v���m���U0v��.V�r����5�����ZO�Y��X��;��\O�XW5y�z���X�*W�v��x'��uQ�Y�z��*�d�Y�zF������vv���k2����m=�b]����	;k������{=agM�b� �^O�Y����������U��������uU�g�'���e�rhg�Zv���������������x��������m@;�����-cUR v6�����+S�v6�eg,c�RvV!����2`g���W62V%`g����U��lB�����BM`g�Z�ob��&��.;+b�\�Y��gf�X5��u�vV$.V�&��"���e��
��:d;+�X�ag\�ob��&���?���*�v���G��&����7��u�	�,��W�q��5��E�5_��X5��u�vV$.V�&��"���y\�����:h;+�`�ag<�kn�������E�bk2�,�s�b]��v��������Ehn#\�{���"�>-���A�"��+;k �b���$���w��+B;����H\��M��E��Mv�nj��'vV$.V�&��"�>(�s!/�,Rs2�b�k2����m2�b]����	;+b�\�Y���h������GvV$.V�&��"8F32�En�d�������"��L�Y��X�;�����Xx��	;+�X�ag��59�y�"������U����>����uU�G&���e�rhg���	��&��L��E�bW������&�9�v�9�p��5y�h�����*�d�YmgM�s$/�,Rs6�b�k2����m6�b]����;+�\�Y��l������gvV�2V���H����uQ�{�&��H\��M��Ep�fd��
�j6agM�b�k�������U�������M�s&/�,Rs8�b�k2�,�s8�b]����	;+b�\�Y��t�������vV$.V�&��"��_6yoCyEhg�l�	��M��EpNgd��
�j:agM~�P^�Y�t��*�d�Y�tF������vV$.V�&��"8�.�UM�=���"�����EjN'\���<z:agE�bl2�,��x'��uU�Y�z��*�d�Y�zF������v���m���U0v��.V�r����5�����ZO�Y��X��;��\O�XW5y�z���X�*W�v��x'��uQ�Y�z��*�d�Y�zF������vv���k2����m=�b]����	;k������{=agM�b� �^O�Y����������U��������uU�g�'���e�rhg�Zv��������~~�=�7������<����	��A��2V%bg�Y?,�2hgZvV�2V-`g��]���+Sv6��xe#cURvV!�Y�X�
��&t~h^�p�+�v���&v��iB;����"�����Ej}f6��uQ�YmgE�bl2�,���z[���@��C��"q��5v���&v��jB;�����@\�rM`g��dp�.jB;���|�X���"x~�,�\�Y�^���uQ�YmgE�bl2�,��������	�����"q�
6v�c����.*;����Y$.V�&��"8G.�UMhg��X�Y��6����	�,�����o�+B;������+vn�L���|�]�"�������*�d�Y��d��&�}2agE�bk2�,�����1���"5'.V�&��:��&.�UM=���"�����u���&\����{4agE�bl2�,�c4#c]T��O&���o�+�������U��������uQ��>���"q��5v�A�Y�c��+B;��L�X�;��cL�XW5y�`���X�*W�v�9�p�.jr����].v�L;����l��y`g���	�\�W�&��H\��M��u�v��;G���"5g.V�&��:��f.�UM�=���q��5��El�&\���<z6agE,c�+@;���M�X5��l�����*�d�Y�lF�����fv��.��&��M�Y��X�;���~��;g���"5�.V�&��"8�.�UM�=���"�����EjN'\���<z:agE�bl2�,���e��6�W�v���6�p�+�d�Y�tF������v���
���EpN'\��M��EpNgd��
�j:agE�b�k2�,�s:�b]����	;+b�\�Y��t�������vV$.V�&��"x�wr�XW5��Ep�'\��M��Ep�gd��
�j=agM~�V^�YcgM�b� �^O�Y����������U���v���p��j�������U��,R��N��&������U��������uS�{�'��"p�+�d�Y��z�������v���m�y�z�����*xAn����&�9��U�	;+�\�ag��	��&�^O�Y�X�
��"�����m���}|������������������x	�?�S�"";+�nZ�t��rFb}mr
\t���2�Y�ep��p�XDdg�N��+]�ec���Y71��������-���m�X�uEd^S����c�kx�WVY�����?2�N��&�X�����c�$�9�+��cM�CE��������y���W
�s����5X5"�Y�EQ� ��2���5Z�=� ��?�aBOa�*+T��5�S���S���
���I���FK��'����������S���
�dyOT��.a��(T��|�iB
��DU�'�1^���?��YO��o^���I��$'��=A�l{����dl����8Qc�	0�#�=�m?`=A�>:��)��m#���m�B��A������C���%QS	2��1�������k2��������9?�1�Gf�1�$�`�Iz��DQL��c[�!?���T�����z����)&�dSip�%�=��d�M%�g�U�hx�D4�/�E	a�oF�9(�.���F�1(0�A6�5��sNB��l"��M�L5a���	�"�\��	
�DU� �1��LF���d)0%�=�d�� �����S)r~�b�������HA��)!��m�)27e�=��Q)h"���"�j�;TE��,�*:��c[�<�c6��H��'*d.�pr��mY �@���������2`.K{Nu�"�<���,��.u���<��,&��Tn�����0\��Rn���9��c������H8�9.!�Y�
E�p s\�K<�q��9��c��������`�K{N��"K8�c�����rCU$��w�c��
���
�d����z�}�9�������=�m_`�@��f�<�b�@���������3��/!�Y�E1g s_�<�}�93�n�b2�NQ�&�3���%=Kn�(���u�Z~�}����2����[�����b2��s_���$���9	{~��@w��Q���*��[�����b2���BU4���a����@S��}1U��B�5�w��������
d���M�KJ�*�
d������}����K�c�$���J4��}��a�O��*�%	�TQ�&	UqK s_Lf�o�K�����,����%��/C�&�UvK s_LF����d��g����I�sl��2�����������
>(O���}�(�����?[���m2�����L�&�����Dj)���:�����)���g��9����M�,��m�Br��_��3l�
5��b����&�4��b�<hb��\�d��
��&5�Y�u�$�I�&�V���m���vV	�,�IN�)��g<�$���UMp���_{1��R�Ic�����EM`�<�$����&0`�}�lq�\�0�^���rQ�u�$�J�&4`~�k���j������)��g<�kn	��Vi�L��k����Q�sU�4�2��)V .����p`�i������������G��[�p�g2gC|���&8������)��g<f�����<���Y@��4��P�1x�Zf�1��, Ss2���k���	>���OsU�GOf��X�)Wg��o�	���&���4��?�P�18F3j�En�d�,`�o�+����Y@��9���18&3n�E��`�,�I��bMx0A�9�y�"�|����?�P�%����UM=�9hb��\�djN&$����{2sP�o�_���+�!�9� g���	��\�W�f��D�)���0A�����9���M�B���,`�:rl#yE��	]�\�xG��lB����g3gMl�+���L���,tQ�{�f��D*�����9�9��������tt���g3gM"lB��y�!�;g��, Ss8!�k������ttU�gg���:*Wg���	���&����4�tT�	�%��i���6�W�2��6����l2�28�3�Mn5�9������, �s:�/lB����L7x�t�,�I��rMx��9����j����Y@L�
�, Ss:q��EM=�9h�`�d���P��j���\O�P���28�3.�Mn��9������,��>b����z�,`�w��x�z�,�I��rMx����P��j����Y@;U�
�, S��N���&8��\O(U���28�3N�M���9(��rM�Y���z������{=s0��m�y�z�,`����������!�9��U����&���5�Y@�z�������3gM�g�+���L�gC�m+�����>u��V������hg:����d�K
��&�~"X�2V���	-;+ ����
���g�Xe
��&���e�K
��*d;+ �P������9.V�&��H��7��MO�Y����u��,R�3��\l����:h;+b�`�ag|��[26Ebg����u������7��MU�Y�D�b�k;��w�#��MQ�Y�����&��v���f�X����"��/�]l����:h;+b�`�ag����.6UMhg����u�����5��MQ��Y��W�"v��5v�9�q��jB;��XF�X�
��"4�1.6=Mhg\��M~� ^����5X2V�p�gv�������u�vV�.��&��"x�&��45y��	;+b�X�ag\�M���v�9�q��5v���6�q��j�������u���S�m4�bS����	;+b�`�ag�i���}2agM~�@^�WO&���]��M��EpL�el�
<������u��������<^�Y�`��:�d�Y�`������vVD2����H����MQ�{O&��"q�
6�v��!��5'���"5G3.6�&�M�Y�X�;�����w����Ej�f\�sM��u���f\l��<{6cg�b�k;������5y�l���H�:W�v�9�q�)jr������u��������MS�[�&���\lrM�=���"v�6v�����w����Ejg\�sM��Epg\l��<{8agE$c�+@;������5y�t�����:�d�Y���&�m(�����m:�bl2�,�s:-c�T�V�	;k�������"8�3.��&��"8��26M`g����\�ag����&��N�Y�X�
��"5�3.6EM=���"v�6v�c������	�,�s=�bl2�,�s=-c�T�V�	;k�������*;k"����z���|�X^�W�'���]�sM��Ep�g\l��<{=agE$c�+@;��w������	�,�s=�bl2�,�s=-c�T�^�	;�H\�rM��u���g\l���{=agM~�V^�w�'���\�����	;k��cy^����"v��5v���q��j�������u��,R����V^�����M<ag��}������lB�w�z��UI���d�OD��L�������UK�Y�dg�sd����M�3^��X���U�vV�2V���	���3\�
5��Ej���]�{���"����e�rhg�Z��M�b]��v�A�Y��X�;��{����.*;����H\�bM��Epm��]�����"x� "�\�Y���\�����"�6��.��&���_5��*�v��|��b]��v�A�Y��X�;��g��q��jB;����H\��M��E���e��
��"��j��U�����Q��uU�Y�2F�*Vv���p��iB;����l�������`���d��]��>���&�m������"q�
6v�c6�����s�L�Y��X��;����lr�����H����U����>����uU�GO&���e�rhg��o�	��&�M�Y��X�;����X���	;k�����z2agE�bl2�,�c2#c]T��&��H\�bM��u�v��������"8.V�&��:��.�UM=���"�����EjN&\����{2ag��]�&��"�>$���D^�Y��h��:����	;+�`�ag��5������H����U����>����uU�g�f��@\�rM`g��	��&��M�Y�X�
��"5g.�EM�=���"q�
6v�9���n*p����5��u���gvV$.V�&��"��_6������H����U��������uU�g'���e�rhg���	��&��N�Y��X�;���~���
���u��M'\�
6�v�9���n*p����5�mCyEhg��	�`�ag������N�Y��X��;���N�XW5y�t���X�*W�v�9�p�.j�������U������.�UMhg��	�`�ag�����ZO�Y����W�vV��Y�X/���v��;���j=agE�b�k2�,�s=�b]����	;+b�\�Y����.�EMhg��	�`�ag�����ZO��E�bW���������uU�{�'���o��+�����5��U���z=agM~s,/���vV$.V�&��"8�.�UM�����"�����Ej�Y�c��+B;����z�f�"c_���G�������l���������]���	-;+`����
�������);��g����*);�����e�B`g:?4�g��j;��Z}�X�4��Ep�Y�X�
��"�>3�����	�����"q�
6v��x�-c]T v�!�Y��X��;���|�XW5��E��AD .V�&���}�?2�X5��Epm��]��Mhg<�j��U�	�,b�������	�����"q�
6v��|��b]��v�A�Y��X�;��1^s�X��Eh}�,�X�ag����&���e��U��,Bs�b���v��i���
���]��Y����}&agM��.^�YmgE�bl2�,��l��uS��>���"q��5v��A���y`g���	�\�ag|l�	��&��L�Y�X�
��:u�F.�EM�=���"q�
6v�1���.*p�'v���
�y�d�����*�d�Y�dF����sL�Y��X��;����������Ep&\��M��u��
&\���<z0agE,c�+@;���L�X5��d��.��M��Ep}H6y�����H����u���GvV$.V�&��:h;k��#y`g���	�\�ag|n�	��&����Y��X����"6g.�EM=���"�����Ej�&\����{6agE�bl2�,�s6#c�T�V�	;kb�\�w�&��H\��M��Ep}�l��3y`g���	�\�ag��	��&�N�Y�X�
��"5�.�EM=���"q�
6v�����{�+B;��g�N��l2�,�s:#c�T�V�	;k�������"8�.V�&��"8�32�M^5���"q��5v�9�p��j�������U��,Rs:�b]����	;+�`�ag<�;�+.�UMhg��	�`�ag�����ZO�Y����W�vV��Y�X/���v��;���j=agE�b�k2�,�s=�b]����	;+b�\�Y����.�EMhg��	�`�ag�����ZO��E�bW���������uU�{�'���o��+�����5��U���z=agM~s,/���vV$.V�&��"8�.�UM�����"�����Ej�Y�c��+B;����=x�������O=��&r~�g��U�?��&�~X "�������,/+`
�:�gXYEde�s$����d����3
vU��0����
X�������1y=���������7�{UK��bG�U����J�������&���)�4�����s]�r����5�qU�����Ht�J�~eO���]�z
,�:z�^"ZW��+B����U5�v=��&v��X�u�H�
D��TW�^�5�^UM��[G�m�H����keOT����z
,�:z�YEbUW��,+{,Y
�T��?G�"��/�P]����_5����Z��U�����V�������7xU��\���*z"U
�P�P?����R5��]��Z:�T���+ ���T�S�x�{�*q�R$R��jr�}�gXTd�����X9T�D��|��]�G����u�J���������+UM�{-��HD���8e����4�Z����w��&�9o�U{e*A�b$L�c_j =������l�H��J�+eOT��1��,Q:z�m�`EW��,){"IM>s�.����T�Bt��g�Qd���������rt��3V�j=0�&�1p�/��\<XP�
�j� EE�@W���({bDM�c��>��y��+V@6�=��&�������	��\�������TM�Gm$����J��T������TM�{m��Hl����dO���kl\?�j��>M,:+�������\������4����g(Od��Ap�X	O��w�s�.��V�S�ns��g�Nd��Al����v�S$Vs�
�r�'���=g�,�9z>���g�������4y����[����o��Xjs������\������4�����W���H����dO���1g�<k��3El/Wj��Df���j
<j�`2E�-W��<&{�1M>s�.������-�����dO��{,]?�j��/M~s�.������4��\��Z>�K��X�~~��A[�DR�XIK��Y�|��]�g-�����J�����|�{2��j
,Y9z�m�`&W��L%{"*M>c���^�M���+`I�8J���p����4�����Z>�I��kp����4�����W-��H4����dO����z
<k��$El Wj��Df	I�c�X:�_���p��w����/�_
�#:������J
�H&��|���)@'��r�v�j)+����z��\����|�+��0�
�L
�D*Tn2����z��\�&��H��6��tO�Ep	JI�
PQ"�>���H��	��:hK))�`�qV��x�-&]T ��!�J��I������57��tU�E���B ~R�&P��}�?2J5�YQ���XR:��gE<����T�	�%b����T��	��:h{)Y�`�qV��|��+]��gE����T��8+��1^skK��Dh}�+o�X�qV�9�P��j����e��T��&Bs�/���gE\�tM~� ^�]�hMi���}&!6M��.^�u�nS$*S�&��(��l��tS��>��"���5gE\�zM�����9����5gE|l�	���&��L�N�M�
�v:u�Fr�EM�=��"��
6gE�����}2a=M~�@^�WO&��H<��M�YQ�d�u���sL�O��N�����Z���<^�Ep&���M�YQ�`�z����&T��S�t�H����tQ�{O&t�"��+�d�Ep}>6y���p�H���u���GZT$T�&����6�&�9�n�9�P��5gE|n�	��&���R�Q����"6gJ�EM=���"����(Ej�&�����{6�JE�Fl2��"8g3z�Mn5��&���5y�l���D�*�d�Ep}yl��3y N���	O�\�qV�9�P��j����>�.U�*Rs:�K]����	�*e�`�qV��e��{�+���~���8]�&��(�s:#O�T�V�	�j�������(�s:!Pl2��"8�3�M^5���"���5gE��	���&��N�U�T�
P�"5�6�EM=�0�"�
6gE<�;9������(�s=�Ul2��"8�3j�Mn����&�m+���*�jb�����zB��|�X^�W�'��H�rM�YQ�zB�������U��U���H}�;9\�����(�s=�[l2��"8�3��M���������\�yV��������jr����5�m[yE�����&��
^�[�'���o��x�zB����*�d�Ep�'$���<{=!fE,b�+@5��R�&���W�gE����D����~���	��A��2V%bg�Y?,�2hgZvV�2V-`g��]���+Sv6��xe#cURvV!�Y�X�
��&t~h^�p�+�v���&v��iB;����"�����Ej}f6��uQ�YmgE�bl2�,���z[���@��C��"q��5v���&v��jB;�����@\�rM`g��dp�.jB;���|�X���"x~�,�\�Y�^���uQ�YmgE�bl2�,��������	�����"q�
6v�c���5�
��"��j��U��A;��E�XW5��Ep,cd�b`g����&���O�&�m���
��H�����3	;k��v����:h;+�`�ag<f�]���<������U������&�\�;���L�X��;��c�L�XW5y�d���X�*W�v���6�p�.jr������U��������uQ��>���&�m ���'vV$.V�&��"8&32�E��`�����*�d�YmgM�m��,�s0�bl2����m0�b]����	;+b�\�Y��d������'vv��l2�,��C��kN���Ej�&\�sM^=���"q�
6v�A�Y���;���M�X��;��s�M�XW5y�l��
��*�v�9�p�.j�������U��,Rs6�b]����	;+�`�ag������M�Y�X���{6agE�bl2�,���e����;��N�X��;��N�XW5y�p���X�*W�v�9�p�.j�������U�������M��P^�Y?�t���`�ig������N�Y��6�W�v�9�p�
6v�9���n*�������U��������uU�gO'���e�rhg���	��&��N�Y��X�;��1���b]��v���p�
6v�����n*p����5�m[yEhg��5��U���z=agM�s,/���vV$.V�&��"8�.�UM�����"�����E�;���b]��v���p�
6v�����n*p����].v��L;��}[O�XW5��z�������"�^O�Y�X/���v��7���j=agE�b�k2�,�s=�b]����	;+b�\�Y���59���"��������s�}_��{��oC�����T���
-���<�}��������)o� ���S�" /+���;�r�|JXde�Ns��\=/��{;Y��m�`����x=ad�N����<�}�����t+"����W��N
ftKE��Z�#�
p*Sf$Q�N���W<��11����N��yW���L��5[�5=��o����A�%K��Y7�&�;x��M�%)�������m��M�)�w�o�w���:=���M��n�����Gr���9F�7�F����b��C���vjP������drw��r�����Ww��-k�kL�c�:��A�%Ka�����	�h��ejl0��������K�1��F��L��%����������K���#ftR�o�A�����@?�)��Kf)K<�I���f�*���`�da�i�"�0w�w��-��F��#3��v�]�^����Uk�\bj,
�.��������E���3��fvj��H+���-��
;"j$>���w����/5��x���[f�~s��2��Y����c0�./����d�?<�]�-�f������@w�&�[f���qsx����s���8�������R#U����6��r�������a�$��Kp~�aFz�^c���<�������6D��m���fK���v�@N-�X6�e��c�f�[\����a����4�c��>H���.
`��u
��}�J��@%fvz�����y��u
;�jd�>�>�^W�e��c�0��*,��\���������l9��Xz�_�1�����c!l��<�7��-���.����5���p~����v3;�Vjd��<7�7���Z5�\�����l9��Xr���1�{��d��-07�z)�=6�N
���qL�>�����)���I�l���XG���V
����������j��Sjl��F�&��cnD4��w��$��9�F
��oR5[�(5�F�x��
�a��qFs#x���C��0�A��a[����S&tj�onD\���;u7���^(=Es#(|���I�����]���07�����8�����w�{����cnD����]j�Nz�t�FP��}�F�������	�]*&y��������4��-���
�����������`n%J�ol����F��Q��YRc�07�w����=�.s#(O�N��<�7�2�;G����-s#B�����	�/�]*y�ot�00������J
�� s�F�@����A�<g`����0��qz�EY�11�|�+��p.)��N�X�(T �����E=C��P�X������qO��c�<gb%�\����Y��qQ��KP��L�el��q���m7��<	�t����9����<�"���UMp:���_1��Q�I�c�����EMp:���w.B�{l��q���
D�(�$����|��|\�����8�h��t�����,\���t\�:g��`��c���M��
XG1t���IL�bMx:��9��I�j��q�e�QR�@�Csa�����<[1��
���8}:.@fi�.�m�����n�xEp:.A��3��R�	O�1x�&�*75y����q&1U�5��8�_T9�B^�8/��dBW)����|l�	c��&�����3��R��WR�m4��\������q&>�`��cp�f$��
��������6�W�����q&�`
6��8�dF����s���3�S�	O�%��q!�6�W������p6����`������3��Ll��+O���L(65��d�t�,�
6��<I1�5'���15G���&����3�mS�	O�%��q!�9� ���9�w�5�����l������g������k���M<5��8�lF�)W*��9��x.jr����8��<��t�s6}.Mn5�9b'�\�w�fN��D*����<�?�;g�D025�nP�&<��N�AW5y�p�t���r ���	G��&�����3�&T�	O�1x�~�{�+��q	~���t\�&�t�s:#�T�V���q!�m(�N�18��Q�&<����H7x�t�t���R�&<���NHHW5y�t�t��E�r�3�����p)j�����8�HM��t��x'��tU��cp�'���Mx:������n*p���������W�����+N/���3��B�s,/���3��LbK�k��q���0uU�g�gN��X�*W����x'�5uQ��cp�'���Mx:������n*p����8���k2N�%x��3g�R������q!�m+����3��B�c� �^��������Z���3��U�	O�18�Z�UM���9gb5�\^���q!���W����S��K"c�������lB�w�z��UI���d�OD��L�������UK�Y�dg�sd����M�3^��X���U�vV�2V���	���3\�
5��Ej���]�{���"����e�rhg�Z��M�b]��v�A�Y��X�;��{����.*;����H\�bM��Epm��]�����"x� "�\�Y���\�����"�6��.��&���_5��*�v��|��b]��v�A�Y��X�;��g��q��jB;����H\��M��E���e��
��"��j��U�����Q��uU�Y�2F�*Vv���p��iB;����l�������`���d��]��>���&�m������"q�
6v�c6�����s�L�Y��X��;����lr�����H����U����>����uU�GO&���e�rhg��o�	��&�M�Y��X�;����X���	;k�����z2agE�bl2�,�c2#c]T��&��H\�bM��u�v��������"8.V�&��:��.�UM=���"�����EjN&\����{2ag��]�&��"�>$���D^�Y��h��:����	;+�`�ag��5������H����U����>����uU�g�f��@\�rM`g��	��&��M�Y�X�
��"5g.�EM�=���"q�
6v�9���n*p����5��u���gvV$.V�&��"��_6������H����U��������uU�g'���e�rhg���	��&��N�Y��X�;���~���
���u��M'\�
6�v�9���n*p����5�mCyEhg��	�`�ag������N�Y��X��;���N�XW5y�t���X�*W�v�9�p�.j�������U������.�UMhg��	�`�ag�����ZO�Y����W�vV��Y�X/���v��;���j=agE�b�k2�,�s=�b]����	;+b�\�Y����.�EMhg��	�`�ag�����ZO��E�bW���������uU�{�'���o��+�����5��U���z=agM~s,/���vV$.V�&��"8�.�UM�����"�����Ej�Y�c��+B;�~��<~��D��_��?�L7���
���bW��3���i`���3�?��&����5����ae��]���gd��M�3^�(�U�?��*b#+`�2�3|l"�����zf
��"�V���U-r*SK�������L���xlb����5)[X�8�+�P�z����U-�s�#�"��+U7�2�6���U=�����������*���w�;�fUM������r;V�
�&U����"XW��+B��G���@nRM��U$nu�
�&U�>�U�XUO�����=�H����ee�%����Z��(VD���"�+U7�25g
6U=��l��v�+�?C�"2�
"U-n�o��&��h 7�*�j �z��������w��x��������X�T�X�X����s�;�T����*��]�ZiM��o�����;8�+��]�zl{a����;�S�����iO��o�W���Z<�S���+��]��M���������o��x��A��D��X������Q����}�`KE�FW���M������vr?,Ss�`EW���M�����z
<j�`HE,DWj�Ef�l�j
�k� G�
=cx�,S���k\?��"3T��Z<HQ�(�+�{f��5����g�Pd���~�X�P�D��s�.��6/&T �s�
��"47�S5�y��"v�+�?S�"37�S5��y��"��+V��257/�S5���6����S���<xO�X�+�;o�Z_��|���3�'2s� 8W���ej���z
<k� ;E�6Wj��Df���j
<j��9Eb5W���ej}Mk��3wr�mR�m��3�X�M�@o�������vr��7g���\����c�X������XL�����=HM�(�+��s�������=�L�����)3���u����=�L�x�+ ���hL�����;x���c�bp/Ss��+U�?�j��/M~s�.@��u*����r��j� .M�c���U�m)I�b$-�gi��Cw��|�"��+�?SW"������)��|���3�bp�/Ss��%U�?�k��)��<cx�oR�m�`$�S�^�Ai��Cw��|��&v�+��V�5i�K���Z>�I�h�+��������Z>(I����)$�YB���Cwr#���������>~~���o����/m�l���1���)_ re
�I&�����Z
�J*$+��c!W��d2����C����B6�6�
��L������"W�	�$Rk�M,#���gE\�R�BR�T�H���&6�.j����R�DJ*�d�E�=^o�I��t��R$fR�&��(�k�M,']��gE<���T�	�%b�����EMxV���&��6�YQ�/r�)�ku��k��1�.j����^�DV*�d�E�3_��JW5�YQ�0E�,l2��"x�����E"2Z_���[*�d�Ep�"������(�c�/+����F�K�4�YQ�']��6�W�gEW0Z�@s�.�m�I�M����W�gE����T��8+��1�l3����O&�H|�bM�YQ��^�c.���DjN&��rM�YQ�d�k����'�S�nS���N�����tQ�{�&��H���M�YQ�h�q���m�LXO��6�W���	�)��`�qV�1�q�.*���S$�S�&�����&�6�W�gE��	��`�qV���6����j����	��T�](Rs2�>]����	���
6�gE\��M^s"/�(Rs4�@�k�����U��8+�����w���Ej�&T�rM�YQ��l������g3�T BT�&p�����uQ�G�&4����r(J���	/��&��M�R��Q�������uS�[�&�����sM�=�p�"Q�
6gE\_�|�L^�S��p��*�d�Ep'T���<{8�OE�K�+@����N�R5y�t���D�*�d�Ep}�l����������m:!NW��<+�����S7��tB������"<+���NT�������DuS�WM'��H,�rM�YQ�tB������rU�2U���H���MuQ�GO'�H���M�YQ��N���&<+��\OhU�������ZuS�[�'d��o��+���
���X�*xAn��P�&�9��U�	�*��\�qV������j����z�jU��+R��N���&<+��\O�V�������ruS�{�'$�"p�+�d�u���'�����{=�bM~�V^�w�'l��������	!k��cy^��P�"1��5gE��		��&�^O�Y�X�
P�"�����m��Y����bg���������	��A��2V%bg�Y?,�2hgZvV�2V-`g��]���+Sv6��xe#cURvV!�Y�X�
��&t~h^�p�+�v���&v��iB;����"�����Ej}f6��uQ�YmgE�bl2�,���z[���@��C��"q��5v���&v��jB;�����@\�rM`g��dp�.jB;���|�X���"x~�,�\�Y�^���uQ�YmgE�bl2�,��������	�����"q�
6v�c����.*;����Y$.V�&��"8G.�UMhg��X�Y��6����	�,�����o�+B;������+vn�L���|�]�"�������*�d�Y��d��&�}2agE�bk2�,�����1���"5'.V�&��:��&.�UM=���"�����u���&\����{4agE�bl2�,�c4#c]T��O&���o�+�������U��������uQ��>���"q��5v�A�Y�c��+B;��L�X�;��cL�XW5y�`���X�*W�v�9�p�.jr����].v�L;����l��y`g���	�\�W�&��H\��M��u�v��;G���"5g.V�&��:��f.�UM�=���q��5��El�&\���<z6agE,c�+@;���M�X5��l�����*�d�Y�lF�����fv��.��&��M�Y��X�;���~��;g���"5�.V�&��"8�.�UM�=���"�����EjN'\���<z:agE�bl2�,���e��6�W�v���6�p�+�d�Y�tF������v���
���EpN'\��M��EpNgd��
�j:agE�b�k2�,�s:�b]����	;+b�\�Y��t�������vV$.V�&��"x�wr�XW5��Ep�'\��M��Ep�gd��
�j=agM~�V^�YcgM�b� �^O�Y����������U��������uU�g�'���e�rhg���wr�X5��Ep�'\��M��Ep�gd��
�k=ag��]�&��:x��.�UM�����&�m+����v��.V�r����5�����ZO�Y��X��;��\O�XW5y�z���X�*W�v�egM�m+����v��o;+`����7���7�~��U��l3������E��������^�@V�s��#����vE��8Y$>����U��#����):$`������X>V���+S 6��s�C�^�R 7�2u�X�W��g�Xf���!���)��|���5�sU�n�e�=^bW����M�3��m�~����9����v�
�$W4p�P���#g�9�X{���J:�1��+���11�#�m��H��X�*w�e���"r����������V��xe��^G�Y=&@���������:�������cV��pe��������;�VJ���nU���/S��([�zN���o��YMlU�;�2u�GYN�S�m+VF���&�J��|���V6�s��l��r�
���V��V"�S,�o�U��{������R���C��^��+�ZD��G4]�T�S��N�L�}�������+�jbq�T����������-���2�����bp'�JmS��0��[�]�S�R��m����S��+��k4^�S�R��N�L���&��y{����i�w��pD��25� U,���T�;�QO����������*w���Ti����
�;�2�����bp'�JmS����[�]R	Q��m�Qfz��
��k�]�Q�R�#�w�e�:A-r���������+�X�#�����bp'�J��y���������+��X���R��y�>='��gj`��T�����+��1�����&r�J��P����+��1�����&�����|���g��1���W���D�c�y�=Ml9�;�2u]�-�j%���<���W�S���/S���nzN�=Z�d����R�6T'3��JlzL�-z�<����bp'_����E��n@���R����g�X�����{��3o/�{%7�He:����T�������|���g��1���WR��
S���/S���_zN�=z�t����R�6d&3��J]zL�-z�L����bp'_��vL.i�9�N�L��+c�X�����|��3o/�|�/��{������Sv�Ed*K�D���,�jM��G4_iKKJ��N�L��+C�9�h��&��J�������1�����;�2�����bp'_�z��Kz���F���))9bx'�J�S�����k4_	�"�^t7��Wv��\�b	�h�R�E���r���+1ib
�X�����|� ='��WJ�DR�yB��KH9{������s}�>�e#(��>~w����.�j��QC�������@�L:�

')`�)`%���e!G&�de���-�!`&����T(�d����
9BI`'��mb�9I�V��!(E,$�@E��8}5����$X+ZA[J���
&�ZQ�������*��)3�X�ep�����G%�ZQ���'�Ke�����`(=(	��28Z�����$X+��u!W�<�rI�.;�^����`�hm/EJV*��kE|���|�G%�Z�
�a���T0	��2x��������Dh\�)o�X�e��"��G%�ZQ[3��T,�&B��/='	��28�tM�S!��U����4����e�I�M����wkE+h�)R*S�$\+���'�fzR�}�LN����%�ZQ�Y�����r"�+RS�$\+Z�m�LxM�J�ee�u��m*����u*M�MJ�fiBx���T0	��2�J��X����4yOyG��L�O���
&�ZQ[e�����\���"%;K����59�z�#X+�`/L(O�p�h��0a==*���	*b��\�P�zeB}zP�5+:t��L���28��M�^�7N�^�P��%9�4�EE��*��kE+h3j��%y�F���	�\���>�&l�G%��6��
�U.	)b�6�D=(���	M*b-�\�R�zm��zP�5k�T����I�V��^��G=)��	ajb?�\�G�&��H)R�p�(��������
�8E�'<�rI�V��^�P��d���>�.U.*R�:�K=(���	�*R�T�$\+����l�����`�h�SuB��`��V��^�%O=)��	�j�����`�(��:!PL�����,��I��NhU����%�ZQ{uB�zT�=�rU�2U���H���M��$[V'�H	U�p�(�g;���zT�e��'���I�V�����V=)��	�j�����`����[M�W�!K�'������
8�=!]E��*��kE��	��QI�lO�W�V�P�"�jGr�VJ������nU0	��2�����'X�=!a�s�$m�h�aMSW��e��'���I����&��
��%�B�������P�"e`�K���������${�'���E�r�f�j�������N���������^�o=���u9��\2��w�apkJ)�������
E.��6�.����;�0�5��n�K�"�;�
]S�t��u���;��5��n�g{dy'_���b�el��s[$t_�Ad^k\,��)(�����|�(�X���2�K����b�u,��)`f���b�|� �X�����+8�+F��y���=x�~�+�H�h�gej	���T
��*.C������S��v�~�\�K�r�*��y&��^��0�(+T�����&�BC�bN	T����L���/a�QV�&Z�Y)�5WpS�*�
���y�k����U��lnvk�+99F��Z���V��
��A�=Azm�;�>����i��{�
]�c,=z{pAf����� ��r��������R!����Wq�Br��H�.�lxI*S	���7�5��$��S��6�7�^��T�\�zL��>��mR���	WV�
/�Ae*M��M��i
�S��T�\�zL=|o&�KLb�M���SV�VX��$�T�<�����V����d���$���B�`����)+8t"F�`i������D��^�u�u�f�&bP�E���o)kxNm�����RVp(@�*)r�C�Iu9@L�������.�A�� �P�}`��Q�� �)S���:z�����%���S�@
��N���
�J
���Uj�6j�;�*�g�%����[
��Y �@�>���E�sJ���f��o5|N���������&Y��f���b��� �<���,����C�aT�<��,&}���8��7B����PnU�����/��^�\J��r�:D���0�$H/�~�U�s*H8���S�����&Y��~�z�b�r� K8�s��~�U���0�$�����s���d5
����I���R��ZF��V{O�s����,Upx2�*s�����z�~)s���K��'��2g �_��R
��_`�L^S���Q�(�$�3�~��%�fCnaP�.����N
�S�@w��~�w8r�Jw��~1���_Jw\k��*�b��_��D�*��v�S�KnaT�.��/&m��RU�
��K�3��k�h*��/&m�e�0��
��v\�W����
��K�3���RU�
���I���R�
�Z���V��IJ� h*�u��~gR��$q�����9�n'jx�$�*������z�~)�������I��rK �_��D
/��QvK �_L��K%a����\�-�+vN������W���'�R�k�,�q�:�\	����alZiD�Z������]�h$p�3c�����X82Zxm��X����g-���X�72^�7���	���\���6a�F$	��Uh|�D�b�S�������VR(��Uh|S�N�c�����y��%��J�;�V����B�S�r?e��o�Z��P���b�$�R��w&������]NJ�$X�W�_G�Q�w&������e�SIpg��]���v�(��`y_�������$�3�b^�7@I(�����{�G��yN��T1/��L�RIxg���|�m�<%@-���X�0@Y,�����k���9I���j%����R&�U�����$Y�~�">��Thww&�b��O�rW#s����~O�5��9������)��w&����XyL�}�;�����BIxg����O
���n��U����RIxgR���wP^��d������R(��)�������$��xX�7@-�S*	�LZ16^)3O	�L}�%z�����1���
P�L�$�3i��w��<%�>���
P�L�$�3�b^�'pN�vGpg�����
<������m��`�<'�6���
`��P���P�;H6�I��}��x�a�$������_��X�W��x�kN%9���
���)��w&U���^��nV�U�u��RIxgR���y0s��d�;�����s
%���J������$��yXq7���B���B����<&�:w�
P2N�$�3i�Z���:�	����uv�yN%y���Ev��S*	�LZ�����*�c`�]�Z���)��w&�Xk=H<�I�����uX�)��*�z��c�ls�aQ�%��J�;�Vl�	xL-wGpgR����`G*I�3i�Z�������{XI'��Z�����k���T���b����yL�c�=�����RIxg��������$��{X;7�}�B�t�B���L�c�ls�a��%
�J�;�V��1��s����k�]�T���b��JzL�en>��xO=wGpg���r��J��en>��x�����|X#7@�F�����k���9I����:nKF�pq\�^<&�0zL���b����J�;�V�5_�E�	����5q�Y�$����������s��s�a5��{��;���K��&��!��|X'��Ew����
n���J%��I+��N�s��s�a��6�
���
��o��sww&}<^O��t��������%����u�V����deF�P
rd�IV���W)	�)`%����!G&�de���-�!`&����T(�d��3��
9BI`'�nb�9IxgR����T.%R����J������ZS���T0I�3)���x�LzP�R��U���T,I�3)���Ml'=*	�L���C��rI�,{��%��I�nbK�`����J�@�J��@]"v��T�%��I��)[�`�vgR��1/a�QIxgR-1E�Y*������=����D&B���H�K���;�"�K��������f,�XM�z7B`zN���q�k��
�����#X^�@s�n�2�$���k��;�;�:h�)R.S�$����}�u�'%�����)��X�vgR�i�����r"�+VS�$���nSeBlzT�-+�S�rS���N�Si�nzP�5K�S���I��Il�Y���,seB{�����#GV&��H�N��;�"�*�d������)��X�vgRm@M����L�`/L8O��;�:�M�	��QI�,L�P�O���"�+�����Y����@�`�~gR�	���+���"�K��$G�&��HiP��;�:h5j��%y�F���	�\�vgR��6�C=*���Y�T���rI�H��	'�AI��MxR{Q�P�"�kb����Y���"�FL��L�`�����`���15� u.�#k�T���I��IW�M^�&o�)R�8!J�K��L�`/N�R�J�gq�����*��^���d���D)g�`�vgR��d��T�w�w&u�9U'��&�w&E�Wg�SO
�Du�������#�3)��:aPL��L�`���������W)��\�vgR{u��zT�=�vU�6U���H���N��$[V'�HU��;�"x�#9��G%��I��	��`�vgR{{�[��K�'l��{��;�;�*X���~U��d{����zY��#��U�$�rI��I��	��QI�lO�W�V�P�"�jGr�VJ�;�"���U�$�����,��I�hOX�A ]G.I�3��������d����5yO]yG����&��
��%�F�������p�"�`�K��L�`oOXX�J�g{�����*�j��fM��+��L�|>~����%c�������V���m�X
	Pv�2��%cG&�l���������
������#v�2���������
��
X�*v�B�I�����$��H��7����$��;+b�\�Y��9��]�%��u�vV�\��I��E��o�X
Pv�!�Y�r��%iv���&v���v����@�X����"�j/2�XJB;���|�X���"x]j(�\�Y��������$�������U0I��>�c^.�����:h;+R.V�$��"x���2����Eh\j)�X�fg���QIhgl�X2V����n����$����e��T�w�vv��H���
X����5yM�xGhg��)�`�fg<�$�XOJ���	;+R.V�$��"8N�M���7v�^�p��%iv��m�L�X�J�ee���X�*�v��u*M�XJ�fi�����U0I����,�A��2agM�SA��#+vV�\��I��E�Uf�X
���	;+R.V�$��:h;krN�xGhg��	�`�fg�������$[&���e�rhg���	�AI��L��A�bG0I����d��W�
��E��&\�sI�,M�Y�r�
&iv�A�Y�W/�;�T�M�X��4;��>�&\�G%��6��
��U.	�,b�6�b=(���	;+b�\�Y�zm��zP�5kvV�\��I��E��f�XO
�Dm�����:����	;+R.V�$��"8�/��zM��Y�zq��*���Y{q��zT�=�vV�2V���H������$[V'��H�X�4;����l�������:���.v�t;�`�����`����5yOEyGhg��	�`�fg��Y2��Q���"�b�K��,��:�b=*���	;+b�\�Y�zu��zP�-�vV�\��I��E�lGr�X�JB;�`oO�X�4;�`o����`����5yO]yGhg,;kb��
Y�=agM^�,o��	;+R.V�$��"��.��������"�����E����p���v���p�
&iv���%c=)��	;;\��%�v��ujO�X�J�f{�������#�lO�Y�Xo���	;k��ey�hO�Y�r��%iv���p��d������U.�,R����SW�����{�/;;@�����ahg+t]���e��(;[���`���#�v�B��
X�jJ�Y�dg�v���	;[�g{dK�jH�Y�lg,c
;[���yl���P�Y�F����zN�Y����U.�,R����.�����:h;+R.V�$��"�h��e�(;����H�X��4;���|�X�JB;���FD�\�rI`g{�\�%��Ept��]��Ihg�.5��U.	�,bG���zP�YmgE��*���Y��1/�QIhg��)�`�fg<�cn�A��"4.5���U,I���R����$���f,�X�Y�z7��zN�Y����{*�;B;;�eg
$cG�,sM�����^�#�������U0I���}�]�'%�����)�X�fg'�&go�;�T�L�X��4;��6U&\�G%��2agE,c�@;��:�&\�%Y�4agE��*���Y[i�����\���&�� ����	;+R.V�$��"�*�d������)�X�fg��59�z�#������U0I��nSa��zT�-vV�2V���H������$kV&�� p�#���Y�I���+���"�K.��$G�&��H�X�4;��������
��E��&\�rI��up�j.����Y�eg��*�v�^�p��d������U.�,R�6�b=(���	;+R.V�$��"�k�d�'X�6agM�b�K�����)�`�fg��M^�&o�,R�8�b�K��,��8�b=*���	;+b�\�Y�zu��zP�-�vV�\��I��Ep\_6yLEyGhg|N�	;�I��E�Wg�XO
�Du�������#������U0I����,�I��N�Y�r��%iv�^�p��d������U.�,R�:�b=(���	;+R.V�$��"x�#9\�G%��E��'\��I��E��g�XO
�D{�������#��
��5��U��,����&�^�7�����)�\�fg��	�QI�lO�Y�X���"�jGr�XJB;�`oO�X�4;�`o����`����.v��t;��:�'\�G%Y�=agM�SW��G�'���]��7d����5y���G�'��H�X��4;�`oO�X�J�g{���X�*�v�agM��+�����SK�^��b_���n	�f��.@�M�X�Pf���f��.
;�e�Zvl[�jFX�����6K��D8YG�|<K�jD����������u�:K�6�^G$	ll�F���zJ��������wU(El��������$��YU�v�2�J%��Y����}��(����%[J���Z�Q�6���g�b����]�U�$��z�u��1I�wV+6�\����$�;��.����
%�p�������gU1����*��g�b��(�V��$�;��Y�PNU�$�;�;�(��zJ�R����(��P�����Z�K��$��l�V��M�2�V+��
�S�,s�A�
��B�#�;�W����ePG�,S���
��Z�#��t���;U*	��j��6���c��S���P�T�$�;�g����B��`L�J���������s�ls�A�`Y�P�S���x0��d��t���J%��Y��$��X���4xO�vG���`L(=�T������J�zJ�}j;����*��gU1�R�s��;���Z��vp�J%��YUl�m!�9I��� H�U(�h�Z���zL�u�;��@��T��wV+6�O��o7^�B���@�Jr��':@	P�����*f!*��
w�C+�:�S�$�;����� >='�>w^���]�S�$���j���1I����@��T(h�Z�AwzL�u�<���u*��g�b���tzL�e�<�OkN��<������RI�wV+6��
�z��(�
����T*	��j�Z��mzN�}n=��l6
@�Y��{���d�{�s�r�J%��Y���J+��Z�����*����9RI��Y�X��r�`�{nS�=����Y�X�=XL�����V��^9L�	p���9@	L�����V�����$�������R���j�q�1I��� 2(k�T�����<&CYzN��������T*	��j�Z�������|��������������T��,s��[
�z���cn>X�JQ*��g�b���'='�>7|���
������d�I�I���Z��|��J%��Y�Xk���`����P�#����U���|����d��~R�=��y��9)`��
Y����x�����|����T*	��j�Z��@zN�}n>��
@!Y��#�������{�=&�����{�Y�=}d���������e$+3�|�R�#�N�BCJ
XBjJXI��%�vi��	/Y�g{dKDjH�I��&�"
7Y���ul�E�P�I�F���FzN�EpJI�PQ"5�_M�$=(	��:hM)RVR�$m�(���x�LzP�R��U���T,I[+���s�I�J���^o0JP*���W{�AQzP�Ep���-��I�V��J�@�J��@]"v��T�%�ZQ�/E�V*���E������kE��)g�`��V��=����D&B���H�K������R����$\+�`k������D�w#��$�ZQ����{*�;���#X^�@s�n�2�$���k��;���Zn���T0I[+���'YgzR�}�LN���%ikE��&go���T�LXM�����nSeBlzT�-+�S�rS���N�Si�nzP�5K�S���I�ZQ[i�����\���&�� ����	�)R�S�$m�(��2KvzP�}.L�O����%ikE�59�z�#\+�`/L8O����nSaB{zT�-*T��S�t�H������$kV&|� ��#���Ep� ��"o�(R�4�@�Krdi����U0I[+��������
�E��&\�rI�ZQ��6�C=*���Y�T���rI�H��	'�AI��MxR{Q�P�"�kb����Y���"�FL���"�k���'X�6aLM,H�K����4)G�`��V�q����k�@�"���T�$m�(��8�J=*���	*b_�\
T�zuB�zP�-�U����I�ZQ��d��T�w�kE|N�	s:�I�ZQ{u�=��KT'|��{*�;������AU0I[+�`���������W)��\��V�^�0��d���]�MU.�*R�:�S=(���	�*RFU�$m�(�g;�C�zT�E��'���I�ZQ{{�[��K�'l��{��;���
�p5�_U��,��p�&�^�7����u)��\��V������d���{�kU.�+R�v$�l��$\+�`oO�V������,��I�hOX�A ]G.I_+��:�'��G%Y�=�bM�SW��G�'t�����7d����5y���G�'��H)X�����������${�'���M�r�f�n������`���l��Y����
7���hoK�j��m3���n@�"vD�mxYD./k 
��vYYG����%�����d�x���
V#��2����H�*3o��E��4Y�e_G&@�Xf��/"��)j�(S��5�yUj���e�:=."��1j�h�daM�\���L=�C,��)��lE$`M�[�
���L]�^D��s�ZQ�~�eX�*��+C��:*��1j�(SW��cu,@�e������RJ�2t���z�c�Z�J�����*kE�z�G�b�s�Z�J�����*kE�:��,��)��+#��bU�`�(S����zN�5��r�@.U�y��*#��J�zJ�%���j�wo�PkE��T-0���2�\)�"��k7���+�jb{�X�e��c�N=&�>�]�T�S�`�(S�)m���[n�Ee��]9S�`�h����J�zN�-�����t�R�6�ie�����zL�5����E�b�V���x���2o��2����S���R�&���ZQ�Z�Y�z����mW���nT�X+Z)��"g/�PkE��mWVT�X+Z�mj�R��`��+Cj"!���
?�L����`��+9*R*t�p�(S�	j��\n�e�7^YP��x%EM�@�����-�j���C���W�S�X+Z�}��R��`���	5��T*@yP�z�����[t^IP9O��m(Pfz�����kt^�O�N�`�(S���:=f�^��J}��t,�#:����-�b�V����m�W+��.��Lo����ZQ�z�����{�^�N�M��m�Nfz�����[�^yN[M�`�(S�e�"�^s7���V�9�^�����L������y{��+�Y��k��ZQ�z���T,��2�{��c��#z�����b�V���{�/='��W:�D�R�y2���{�.=&��W&���R�X+�����%-='@�e�7_K�`�(S���+=f�^��J_y����V�)��"2��%X��J\y����#�����%�b�V���|e(='��W��D~R�y���W;&�����V���|e&���L������y{��+M)RRr�p�h�����HzN�5��e�w/����+;YD.R�K4_��"��t�}D���4��T,��2��������JI��@*5oCH2s	�"g/����������k������?��
�+��O@�kE�����,�{�	-���%fXb���hh��R��%!�ZQ�K\b�M����N�Z�A����2<�#���B�x����g�H����H<��H��g���^����#A.���/i�.�IV��y&3�K��� 9�R�D6�R\�)8,$F��y���*O�_�1�R���
i��A��r� �����_�R��SR��v��.����e�r5&S�i��$r��"��f	!-3*sJ.�\'�L�ge
/��Q�&2����L�a1�#��?�m]����Uz�l�S��@����t2����
!�A�Azm������9`	AZwp5��e1����G�=��c��B������h��{*��A,��bJ�_9�o��"iw�Z�J���2��G���q�MHo���Qp�=�*�r��1�K����I	?��&�>�bC�aP)?�mj�����9�	��u
������<&Y��S�����]�����@��P��?
^����z�������B�����W-
U�Q%�@Z�p�������;�^'���bC�aP	<��Jq���9�	Ho��hQp�6�*�r�C�Iu	7L�����n�jv7�����W(�V���@��)�~Iu�N)�;�����=u
\H����Pp�7�*g"�V�A��a�0�\��$X��B����:
�����Yd�0�|Ho��B��95�Ho�6��h�d�����M���hd�f�����&
�����f1��.
�Qc ���>�a�0���uq��_�G�z�������*6t� ���W��S����\���%m�e�0����K�;����8���������f����\�����	Jj�(h.��/&}���K�.��B3����{��.��/}9���[U����I���R���K����0\T���K_�gxN��e�������D&�|���v�S�a�0�����u}
��s�h+��/}
���TU�
���I���R�
��N����f��_��D���jk�/_�Qe�@z���QC6aT�'��/}���k�(��/&m���0�
��v���f��/�P �_��7���0�LH��>����d�e���=H�|�A�Q&��/}�����8�&I�
���`��RGU2	���I���R>	��K�����GTF	��K_�fx)$��T��b�F]	���.!������8����Cz����}�m���
P�h;��g�~���D@�E���Uf|pd��D#�+�*4V�	�iJ��SHk��v���	�Up�y�G�V�iH��S���l�
��p�>&9�a�F(	��!5>-bb��9I`���D����qH�K�X5yP����^'R�I�$�V>��m��Aja�C^'�{�bIxgR�gGL��<*	�L���i�W�%�9�^�Em�AI`��!1��r0	���f�W�%�B9��������$0`�Z9��V
&�c���Z�QI`�*�s"e�L�;�2x�������9��2�R`�%��I���QI`�l�XL�X>�P�F80�I�;�28>Wi��
��<��C-�3����5�et&����LZA��)��`�����O�Q��$�\�XN'R>M�$Ts��X���!o��!�+>M�$�3i��2��<*�����u"j���:���4��<(�����u"��N�$Ts��,5�A��2����=�9�2��N����I��l�Yn���sab��H�9������r;�s��;��`/L,�S0	%_��0!�<*����uw"�|���;�zeB�yP�5+�����$�28>�ir���X��T/MH>��Y�X�'R�O�$���:<�W/���xH���,T.	�LZ�}�M�B�J�gm�z<����%��<�zmBzP�-k��Dl��eyH���,��$k�&V���,T0	�#��6k1�'X�6�<�����$��M��)��`�K�R�W���ezH���tT.	�L�`/NHG�J�gqb�����r�\�^����d����=���
&��dp,]0yLEyG�/+����r��;�2����'X�:�x��=���I��	�`�P{u����GT'����T.	�L�`�N�K�J�gub)���r���^�X��AI��N��)�`�������B=*	�*��=�BLB��`o�r��`����>����ww&u�V��X�*xC�lO,�3y���G�'����RU.	�L�`oO(U�J�g{b�����r���W;�C�zP������P�
&��e��g9UO
�F{b�� P�#���������X��QI�lO,�3yO]yG��X�gb;��
Y�=���������X(RvV�$�3)��=ag=*����e�"����B@��B@�s��;�;�~�����P2v�����v�B�5hm[�jH�����(;2hg+4���e����UHvvl������y�G�d����U�vV�2V����N��6\�%��Ej���]��$��Ep�Y�X���"5��M�b=(	�����"�bL��,���x[�zP��������U,I����7����$��^oD��*�v�W{���zP�YG����:��v��R�@�X����"v��\�%��u�vV�\��I��E���r���v�A�Y�r�
&iv��=�����,B�R�H�X��4;�`/E�X�JB;�`k������E�w#\��$��Ep�-���B�#��#Xv�@2v�n�2�$���]�cIhg��)�`�fg<�$�XOJ���	;+R.V�$��"8N�M���7v�^�p��%iv��m�L�X�J�ee���X�*�v��u*M�XJ�fi�����U0I����,�A��2agM�SA��#+vV�\��I��E�Uf�X
���	;+R.V�$��:h;krN�xGhg��	�`�fg�������$[&���e�rhg���	�AI��L��A�bG0I����d��W�
��E��&\�sI�,M�Y�r�
&iv�A�Y�W/�;�T�M�X��4;��>�&\�G%��6��
��U.	�,b�6�b=(���	;+b�\�Y�zm��zP�5kvV�\��I��E��f�XO
�Dm�����:����	;+R.V�$��"8�/��zM��Y�zq��*���Y{q��zT�=�vV�2V���H������$[V'��H�X�4;����l�������:���.v�t;�`�����`����5yOEyGhg��	�`�fg��Y2��Q���"�b�K��,��:�b=*���	;+b�\�Y�zu��zP�-�vV�\��I��E�lGr�X�JB;�`oO�X�4;�`o����`����5yO]yGhg,;kb��
Y�=agM^�,o��	;+R.V�$��"��.��������"�����E����p���v���p�
&iv���%c=)��	;;\��%�v��ujO�X�J�f{�������#�lO�Y�Xo���	;k��ey�hO�Y�r��%iv���p��d������U.�,R����SW�����~�ag({<~^�W��
]���m�!��Vf�#�d�����������vV!���]2vd��V�����vV!�Y�X���V�:i�p�#�v���&v����v�agE,c�@;��8g6����$�������U0I��>��m�A��:d;+R.V�$��"8:��.�����"x�(�\�Y�^�E�AIhg�ob�`�Y�K��b�K;����r���v�A�Y�r�
&iv�g���zT�YmgE��*���Y���[�zP����K�"�bK��,���b=*	�,��K�*v���p����v�q�l��
�����`�Y����\���&��������"�bL��,�g�d�II��2agE��*���Y��������"�+.V�$��:�M�	�QI��L�Y�X���:�N�	�AI�,M�Y�r�
&iv�V�%c=(�2W&���{*�;rde�����U0I����,�A��0agE��*���YmgM����,��0�bL�����T�p��d������U.�,R�2�b=(���	;;\�&�v�q�lr�����H����u.���	;+R.V�$��:h;k��%y`g���	�\�fg�������${�f�Y�r��%��E��&\�%��6agE,c�@;�T�M�XJ�fm�����U0I����,�I��M�Y�X��<�6agE��*���Y��e�W��;�T/N�X��4;�`/N�X�J�gq���X�*�v�^�p��d����)�`�fg��MSQ��Y�Su���`�ng��Y2��,Q���&��(��,��:�bL��,��:K�zR�#�vV�\�rI��E�W'\�G%��:agE,c�@;�T�N�XJ�eu�����U0I����H�QIhg��	�`�fg��Y2��,����&��+����egM�b�!K�'������
8�=agE��*���Y{{��zT�=�vV�2V���H���.�����"��.V�$��"���d�'X�=ag���$��:�N�	�QI�lO�Y����w���	;kb��
Y�=agM��,o��	;+R.V�$��"��.��������"�����Ej�Y�s��;B;�{L=`g(�x�,�3�l��k������eg+3�P2vd��Vh�Y�XM	;�����.;2`g+�l�l�X
	;�����e�B`g+t�4�m��J;��h}�X�IB;����"�����Ej�3���zP�YmgE��*���Y������eg��)�X�fg�ob�QIhg�����U.	�,b��"����$����7��u0	�,���f�r��%��E��x�XJB;����H�X�4;���?��b=*	�����"�bL��,�g{�-c=(@�Y���f�r��%iv�^�p���v���%c;�P�F�X�IB;��8[6yO�xGhgG����d����e�I�Y����w�v�A�Y�r�
&iv��O����$�\���"�bK��,��D���
y`g���	�\�fg�������$[V&���e�rhg�Z������$k�&��H�X�4;�`+���`�+v��=�9�2agE��*���Y[e�����\���"�bK������&�T�w�v�^�p�
&iv��m*L�X�J�ea���X�*�v�^�p��d����.v�t;��8I69zE��Y�zi��:������)�`�fg��5y�����H����U.I���Sm��zT�=k���@�X����"�k.���lY���"�����E��&\�%Y�6agE��*���Y{m����K�&���]�sIY���"�bL��,��������
��E�'\�rI��E�'\�G%��8agE,c�@;�T�N�XJ�eu�����U0I�����&��(������:�bG0I����,�I��N�Y��T�w�v�^�p�
&iv�^�%c=)��	;+R.V�$��"��.����Y���"�����E�W'\�%��:agE��*���Y�v$����$������U0I����,�I�hO�Y����w�vV���&v�
��%�v���������"�b�K��,��=�b=*���	;+b�\�Y�^�H�AIhg��	�`�fg��Y2�����������\�ng\������$k�'���{��;�����5��U��,����&�^�7�����)�\�fg��	�QI�lO�Y�X���"5���9u����������}?�~&����u
Z�����le�;�J��L��

;+`�)`g���%cG&�le���-�!`g����U(�l�����
;BI`g��ob�9IhgvV�2V���H�sf�XJB;����H�X�4;���=������C��"�bK��,���M�b=*	�,���r��%��E��^dp���v���&v�&��E���,P.V�$����/�AIhg��)�`�fg|���\�G%��u�vV�\��I��E�l��e�(;����,R.V�$��"�K.�����"���d�b`g���9Ihgg�&�������5�����5	;k��z����:h;+R.V�$��"x�Iv���d�+vV�\�bI��Ep�(���!o�,R�2�b�K�����T�p��d������U.��S�T�p��d����)�`�fgl�Y2��,se�������#GV&��H�X�4;�`����`�vV�\�bI��u�v��������"�.V�$��:�M�	�QI�,L�Y�X���"�+.����Y�������`�ng'�&G��;�T/M�X��Y���"�bL������&�^�7v�^�p��%iv��}�M�X�J�gm��(�\�Y�zm��zP�-kvV�2V���H������$k�&��H�X�4;�`�����`����5��u.�#kvV�\��I��Ep\_6y�����H����U.I��������${'���e�rhg���	�AI��N�Y�r�
&iv�q}��1���u�9U'\�&�v�^�%c=)��	;k�������"��.V�$��"���d�'8�:agE��*���Y{u��zT�=�vV�2V���H������$[V'��H�X�4;�����p���v���p�
&iv���%c=)��	;k�������*Xv��.V��d{����zY��#�vV�\�rI��E��'\�G%��=agE,c�@;�����b=(	�,��=�bL��,��=K�zR�5�vv���K�������p��d����5yO]yG����&v�
��%�v���������"�b�K��,��=�b=*���	;+b�\�Y���59���#������;P2����+�l��k������eg+3�P2vd��Vh�Y�XM	;�����.;2`g+�l�l�X
	;�����e�B`g+t�4�m��J;��h}�X�IB;����"�����Ej�3���zP�YmgE��*���Y������eg��)�X�fg�ob�QIhg�����U.	�,b��"����$����7��u0	�,���f�r��%��E��x�XJB;����H�X�4;���?��b=*	�����"�bL��,�g{�-c=(@�Y���f�r��%iv�^�p���v���%c;�P�F�X�IB;��8[6yO�xGhgG����d����e�I�Y����w�v�A�Y�r�
&iv��O����$�\���"�bK��,��D���
y`g���	�\�fg�������$[V&���e�rhg�Z������$k�&��H�X�4;�`+���`�+v��=�9�2agE��*���Y[e�����\���"�bK������&�T�w�v�^�p�
&iv��m*L�X�J�ea���X�*�v�^�p��d����.v�t;��8I69zE��Y�zi��:������)�`�fg��5y�����H����U.I���Sm��zT�=k���@�X����"�k.���lY���"�����E��&\�%Y�6agE��*���Y{m����K�&���]�sIY���"�bL��,��������
��E�'\�rI��E�'\�G%��8agE,c�@;�T�N�XJ�eu�����U0I�����&��(������:�bG0I����,�I��N�Y��T�w�v�^�p�
&iv�^�%c=)��	;+R.V�$��"��.����Y���"�����E�W'\�%��:agE��*���Y�v$����$������U0I����,�I�hO�Y����w�vV���&v�
��%�v���������"�>u�
iv���p��d������U.�,R�v$����$������U0I����,�I�hO��A�bG.I���S{��zT�5�v��=u�yd{�����*xC�lO�Y�w/�pD{�����U.I��������${�'���e�rhg�v����������.yg�J�����|$����u
Z�����le�;�J��L��

;+`�)`g���%cG&�le���-�!`g����U(�l�����
;BI`g��ob�9IhgvV�2V���H�sf�XJB;����H�X�4;���=������C��"�bK��,���M�b=*	�,���r��%��E��^dp���v���&v�&��E���,P.V�$����/�AIhg��)�`�fg|���\�G%��u�vV�\��I��E�l��e�(;����,R.V�$��"�K.�����"���d�b`g���9Ihgg�&�������5�����5	;k��z����:h;+R.V�$��"x�Iv���d�+vV�\�bI��Ep�(���!o�,R�2�b�K�����T�p��d������U.��S�T�p��d����)�`�fgl�Y2��,se�������#GV&��H�X�4;�`����`�vV�\�bI��u�v��������"�.V�$��:�M�	�QI�,L�Y�X���"�+.����Y�������`�ng'�&G��;�T/M�X��Y���"�bL������&�^�7v�^�p��%iv��}�M�X�J�gm��(�\�Y�zm��zP�-kvV�2V���H������$k�&��H�X�4;�`�����`����5��u.�#kvV�\��I��Ep\_6y�����H����U.I��������${'���e�rhg���	�AI��N�Y�r�
&iv�q}��1���u�9U'\�&�v�^�%c=)��	;k�������"��.V�$��"���d�'8�:agE��*���Y{u��zT�=�vV�2V���H������$[V'��H�X�4;�����p���v���p�
&iv���%c=)��	;k�������*Xv��.V��d{����zY��#�vV�\�rI��E��'\�G%��=agE~�?shg�z�#9\�%��E��'\��I��E��g�XO
�F{��;rI��up��.��������&��+��#�v��.V��d{����{Y��#�vV�\�rI��E��'\�G%��=agE,c�@;����&���w�v��5�;P2����g��
]���m�!��Vf�#�d�����������vV!���]2vd��V�����vV!�Y�X���V�:i�p�#�v���&v����v�agE,c�@;��8g6����$�������U0I��>��m�A��:d;+R.V�$��"8:��.�����"x�(�\�Y�^�E�AIhg�ob�`�Y�K��b�K;����r���v�A�Y�r�
&iv�g���zT�YmgE��*���Y���[�zP����K�"�bK��,���b=*	�,��K�*v���p����v�q�l��
�����`�Y����\���&��������"�bL��,�g�d�II��2agE��*���Y��������"�+.V�$��:�M�	�QI��L�Y�X���:�N�	�AI�,M�Y�r�
&iv�V�%c=(�2W&���{*�;rde�����U0I����,�A��0agE��*���YmgM����,��0�bL�����T�p��d������U.�,R�2�b=(���	;;\�&�v�q�lr�����H����u.���	;+R.V�$��:h;k��%y`g���	�\�fg�������${�f�Y�r��%��E��&\�%��6agE,c�@;�T�M�XJ�fm�����U0I����,�I��M�Y�X��<�6agE��*���Y��e�W��;�T/N�X��4;�`/N�X�J�gq���X�*�v�^�p��d����)�`�fg��MSQ��Y�Su���`�ng��Y2��,Q���&��(��,��:�bL��,��:K�zR�#�vV�\�rI��E�W'\�G%��:agE,c�@;�T�N�XJ�eu�����U0I����H�QIhg��	�`�fg��Y2��,����&��+����egM�b�!K�'������
8�=agE��*���Y{{��zT�=�vV�2V���H���.�����"��.V�$��"���d�'X�=ag���$��:�N�	�QI�lO�Y����w���	;kb��
Y�=agM��,o��	;+R.V�$��"��.��������"�����Ej�Y�s��;B;��������\]��W���V�n�"C�~��Ze+���\����=W������E���"w�kd+a+;�%n���s�%�
������_���b}l%�������=�Z�Z��^@�����7�"0�R��t���L�D��_T��q��-�)�e�fbO(V��9C�~��"<���"�~���=aW1D��%�M���^�EF�m:��2yq�-�9�
�fLE~��/�.�9�
��KE���2�b;����T�����m�9��fJO(P��9C�~��"��(��!-��M���t������4���2|�7�)�*��<���R�5@
9�e*��L�V0�1UA7�"��{����:���\�td�E~
��)�*�@MR��xa-P�\B��ti�P�����9�\�h��M������@��5e��o���G�9���z��E���!��
tY�/���B�)�r)�o�Q���_��si�/bP�5CLyO��5�Hk�5g�oVP��@�'����A��j��}���	�Ws0��z��?��j�ps������V�n]f���h�PS.-�M���f(�9rr����u���Oz���
M�	�n(�)����i�P������+�<:�6�h�$�	{Vs0�����4���9�~�9�f�DZ?����]���~hJl�K�}k�
`����y��s�s?H�}�j"��
�,�7&���j�P\�4��u�
!_~z��h�K�5D��2��7&����SCt�%r]eC�������%�:��\V���:��0��;BJ�������B�I�o�J�uD�KB���I	�^��`�{��n�DZG�9C>}T"�#�Vz��h.J�uD��2��77%�:���SGt
u�\���k�����H)�vQ��I�M$��<z��������(Ps����DZG4�#���l������`�{����Nh��cPs����DZG4�3�p9�|�H�����:����M�ti�/�G���WL9���VG�uD������_5��6�:���g���s~-�_�\�S�n�����s
`�e[��������Z���s��6�������9aY0�=w�,�7sB�����7
s�\����C�|Q0'�JM1��s���[DZG�����������#X��sS���s�=j�_��_��_��������w��_�����������m=�c�������_�Y����������g����.�7���--����sn?[9��kl�#�.�|m�g������F\��i[���v�"x�^�-^b� ���
�q|�^�O�����m��`�?��p����� ��>��m}�M��:��U0�2?�����>��_��G�L��>��UO���=1��S����\&~n�����zz�M�����!�}���S���s$�O�\O�����������OT�����3�A�\���*�l���������gY7<]��%��k<[���K�TO����A�li�.}S=] �?K���A�����.��t��EU����������9��za�}�8��.?]��c:�'��H����>S!���^p}�_7�&���M"��������)���<�����-�������x.&�?�����s����>����|� ��?�����@�x�g��?H�_�z,?�y�XY��O���0��M?I��������������<?K�������s���2�3��.����/�1�����������o���|����=���H����k?��Q�#P��cy}~	���ox���h������W�}�����������?��k�^����[���y���������������_����������{��������?������g����q���������������~=-�����?~������6�-�~�����|�������O����^_�g����+�#�?���_���~,������{������w}�������������k��~�y��l����?���}[�����������m}���\��C_���y�~u����z�?����������|���,��W��:��m���m{����}��l��s���r�?�����?����nw���{<����x.��������{��}g��R��i��?L�S����������uw�_���7��3����~��x�}�o��?���}�19���7"o�&��?��?���|�~��k�y���]��2[��=����o��������q��]?����k��{���m���{��G����?����6���?sX��'o���������|6�pX>�_�d���^G����uO~���v`|���e��|������Z)�Eq~����d�W<���=����e������w��_=�i���kO���k�v�������2��\������a������|~?,�������=�o�/�m���}�����������;�������sX������W__����g���&������������G���w��4{i�;^�������?,o7;���������k;u��e9���.|�����w�e��z�}���s�#��uw�6�vQ�v{[���|���Z������|��������j~!~=0�W�?�~�/����O��>������k������}y�}����m_~����~�:�����F?������C��Sf������C����>�x��m���~������<�^���N����%�������i_xxD������������W�j�wh�5�/��?�C��_B��?���C���8�s�\>�n{�t�t������g��>����o8j~xo=������z��9�_{��Y'�8��^������[�k��cy/��m~������_�Qr��n�W�R�v��z�=�^�������9~�������>��=�����������k��������vH�����^��y������g�b�V_?�x<_^���\����o�����Q?���'��������a�l�?_	:��7������x��Lo:>�������$��/�]���3���&��&{��^�~��l<�����q �+y��GU�����r�n��}�Y�?�������,�������| ?~�>x?~��4};?�Z{���=a�~�x�;/��V���R_��_�������}9�,|���������x��?�uy}����?�~�O�{��{��"oz;����~�=}Eo�t$������w����#��v�����Q������R��g�?�������a���?{������'����������xR_��w7�g��reo�y�@��������,��v5����w��8J���o�C>K�j/<�:4�}i��gC3,/�t�_�/��|�����{�y|^B��������m�{��v�m�5���(�u�����:�O_���H���m����?���w��n�����9L��|����^��,8�<�}�Y^����g{����o?����������'��EP�W8�������y�u����^��,��������/~V$}��������7q�{��������~��Ge�|}�������>}8=�O=����(���o������������Qz����f���������������yO��!x��_���o�
~�����������Y��sz���z�Z��]��c?k��O���v�~��u����������O�po����c�M�}������7y�������c���tt��_��ys������u�;�V�%w����9�\��=v�;W�y�X�����y�]�����:>�����V��nu�S����/�}�
����ux���v������iyzO^q���S+��v������<�ey~y��y���}���)��Qn�������-��.s~�S������S���#��g����$~*��O?���w"�U�$/���bS:��}o�s�g{71�����\m����/\����>�������Nr�����
E���.w���~����zUz�����g����9���G�*�w�����fd�v����}�p�7������{�:�����������?C�[]~=�o����=������S��	��]�{%.Z�aYr���?�t����u���n���1_����ka�jK�~����GW�6��]��>'���
����*������������p_������
��~����O���|���8�x=L����7����+m��_�������m��������v�k���N"��J�|�����v�W�����������?\��]����Q�]����������z}.7�<���y������������g���{ll<ml?��Kal<tV��3���q�6N�~�q�|`�6j�|�^]O�{[v<>���������3IO������o�~��>���u�~��:y�^��z<V���������K?���q��0����������~��}�����y��|._~���5�'��p��x����W���~.���|������Q��������W�������^���e9����n��{��|��;��$���W����#����U�����s��>������w3�������lztj�;K�����v�����l�]���Wm<�i���o����s.�?�C����W�����v�<������yh���.&x�z��{�����;���|���?��]��cy������/����m��Eu�v�y/8muh:~��/���_^P���������������=t4������m�|���l���.3�=�����5u���5�����e�?����C��.�,���y�S����:j��8*����F�~n+��O9*�sT����?������~�t,�^c�?����2C=C�U~/�����<���w]g���X~���q}|H{�������y9�_v��g��	����]�<�`���L��y��=,��q����r^�~�x�=wy�7�k������z�~��X�����t�{���x���]�;�k�����i���x��
��r�y����E����x?�G\j���w���;�����������������7������wx�����
���1ITf�����HZ���%j�����I����`����,�r��}��[�*gr�����5���z��9%�;i�C�r]���9j�i/������/��@��[X��}��W~d_��D��7V����=����c��/%(��V�5���]\�y��
:2��|+���Y��,I�5�OV��V���(�_�^E��+b����<����O���DIo����m]������w����n��t��t���-������o1��w�]�����k/�z�w���{�G�o�os#���~>l��h�7i�2�*=���a�X����3�#=MlWX�f����;f��<N�?�^�}[{R�&~4R��l_/�g6i���%/u����������k����~20�'9)���ah�}�c�����L|-�s��.~��<��r����>�kN�l��~�M#6�a���):x"�p������/�|;9��{��4c@��?e�
F)���!x��L!"'
��N������Z�r���2��[��,V�v����{�������/wz��#�������z:�����e���r*-�v~m�
�=�h�8�8
����Y�����.�/�p"��)��B�A�s��]�!:��H�I��U��}�9��}iN���������%��"��IW��]��)�' x���	��b&*���N')���R�a��&/�=��3�t�Fq��4/7yF�KK���k���3����{��:1������a�����BP�#D��I�������=����~�|����4V��b�NG�&�'��A��������F��|�+�� �J0��������;K�KX��������� ����:w��K3']����:������eF��-?���K�o���%��vBi�b
�����;�s��GO���N�9yoj��"�q2�*������I>�o�����iL��Z2vu��f���[�V���AB����dfoN\R����o�����n'C�N��Ck��6�w�����%MM������Z���>�td#�r�'���&�d����~	��u�	T��:���2?�V>K��^�'V3�|Rx1�.s��5@���R�z��w��j�_�B��f_Sy�b)�wz=P@!_<�ov{��m�����'�/#�������������KW�,>��qG�����#�N�tj������m)�G��|�K.ay�2��i/B|h�~�%����zqG1�RW*���;��-�f�u��Q"L���=��v�+m|k�"W��t��iM������'k��rYS���$���[(&��w��� ���="�n�-N2���<�+)Y���A.)V����{�����-T���E?R����mk(��` ��%��hz�����KD�pJ�������M#�-o�S�6^�1u��!��R�#���17�U�_��x��/���j0�]���2j��c���2�E�F�Q����~�-;o�f������mu�0����/S{.��=i�i)B��u�.����������3�������f���Km�>i�Q��
^�����4�|����7���f�}������:�6���W)`�_�W]���c����q�j��\����-����*8��l��}��>�u���
�����O>��JB����?�"he�8N����*�����V��l��j�z�h'�/S�R��jO�z�mK�0�wBc~�Y����� �RH4a��M;����o�*�}�k�,C�����$+�����a+���i@��+�m�\�Y����Z���z�/=��ML�)�J.�d���?��b�"}���h���0��K�-W��}�m	yu��J�g����r�0�����?|�v�R�3�L{XYX�z_����)�Z�
��0����w��Ef%V<bP>mJ�)8�|���5��$�x�]���;~���^|>�g)�r>8��v�E%�|��y.E�r������z������Gd=�!���g�5(]7g�����d���=�B���`u\���d��X��2��;N2T��2X�%�v��/���
^#�~r��G^>�������"��T�<�O|5�;��hx������V��:��t{5r8�1�-B���������������"��{'Kiz�Uh�	}},`���?-����A���0N]��K��}rX��`�������-��S[{<*�Cq�3l7�i
�	�a� ��"�R�^F)�L��{6��`����V�w�'���v�#K]r?^���}�[f=e��K�?� 0��{1�������0G4y�x6v����->H���o������Uz���E�$?)��A��b5Y'��i+4h�v��N��{���K~���L��x+aE�����Y�a��l��W|�Ik�1�g+�Lw'�)��|�N�zT��t���i��>�6ECo���&3��N����*���{����
l����~.����?zv������s�_3y�m���=�����_��H�����\���p�\VT���M��&�l���	,%��(��#<���DEf�����3��h�W����P����'���o�.���<f��#1��OR�s��}A���x��#���%���!�Fm_�N�l[����^up�lak�w���O��0��D3�o��}o�_1���p��|�������w07�U��&�=��e�nY�����o[mXE���-K�L�%���;��An�,�P�	Q�������"xEk2]@��I?r2~�@�N���������[�%�l4`fi@�A�u�IG�=���3/�2��}��t���=����%&���7I���Hs���&H$����>�����'���'��C�'>�E=~��^�* �^�>s%}@T����� ��d	X���k�Z��-�����a)|�t�"�n}���������=kik
�E��\|���5A�m��	�uI���.�"vf�	/�7���2��2��R�6��/���ML��@
�W����u����M���
����	a*;EN�������U�����e��\>���Lm�3a�,A=[V�U�%�S�W\��Z��)�>�k����p(�'����|V����������/1�5=*�o1����NY{�C2r�.`V[�k�M.}/H��@�$�M�
�&�{}sOa@�@�H�1��LEF�}�J_;�7KQ������}\����$�������?]�_V^����\�%�sbms���$�d.���8	
n��zt� c�����ns!��z[4>m�g.��]<s��O�	���>�MA�Ud,}c��X�/�.�������w��/-����sAy/~�]��.��,vt.e�&��B{Q)������4a��o�80|S�s��L���F���\�2}��"_.��/�E_b�a+(=<};�<(������W[;��k�Qd��A&�
�@�M�G�u~��'�c��&�o/'�/]hM��[/���D�k��Z��6�G�L�� �u��}$� NA����F�����[����F��Z�����doV��',��>{&/O����$F�����m_�*�./�B��#T=l�,��P���Z�f*���0�o�D�D���[�U�r�j����=WW�)9����.�U{�����W���
�j���FTv��8v$���7c���&Y������s�|���i�,��Fz��j� ��H�Wo��%��m��
�m�
tEj�*���]��J�F6��LX��Y�R��T����'�R�\�	b+�_;�@�:>��O-���d���6��r�`��%�@[t�� s"�	���<@��`L�V<@5k�a�`�O
���tE^�_��M�q�6�X_^�H#���P@�F=_N!��/X
/8��$��Rw�@�oa?T���k�W��&��}K�nQ��3�v��]A��
��*����u�8�E�&k(�x��!�����Y���A2~{��z4E
�LA:;�9�F�]D5�9/�ij�tR�"�05�$���Oj��/��p*�����v�"Sj���z_E��U3[�dW��B�bP�����������0���#�a\��!��g�*<l�1���)��#=Y�����8M��v/���41gsw���K���t���(l����������?S�^��v��p���3H[��������'T#�e�Z��B��H(�{������
�}�;����
��go�� �+-3B�
K]J���@o+����,EQ-@�e7�t�z���������*����mI#o�F4Y�T����RkZl���������g;L�Y����M3hA-����*aE�]�x�MC����~�D���Jx����\kk���oZ�Vd� o'%��4M�R��\�����It�Q�!�'�����r�x~I"��_iC���7��7m@.���8x�e?O^�UA�#~v�a����������,!���=���A����2�~���&7����V/��� ����@B�i���������tQr
���Rk��Pv��L3tB`�g�D�
w�����B�{8 t���U��C1�}�"-�AR�0�.�C��GE��<���9T:x�������'��2�O�C����O34���Y�0<2��2��]:T�t����J�����
)�!hc��I�{�����mR��i����n��rVF�w=�����A�$=�#��
�`�����[ i(q�������o]+U�t������,s$�!$�����~B@���\��)�����Wjp� ���.0nZ_[E5K�>�����}�=��_���>J~FH�������������_�������������G��*C���Gj��H������\��p�fO�q����������>���g����P�����<k({f
���4�	�'P�eA<��Ry�Gf,6<tt�9��L�3@$S@<L�<n|��&�$2��5���_�b�q�DEF�@\�s�o�m�&
���B���Q���C'����dsQG���P��H��95�����#`�:� (!�z[{��"��'��3����= �F��rF���=��(!�4��M�7�^
����q[����N�8 T�Nb��U��P��D!��+VC�+�����*�j�
!���h:�R����t����k��O�A��;q
�#p�q7�V�(��m�|%9�����Z}]�3�D0M5���@F��@�y��M���r0S'.�|a�k9�v4�g&�q�"��`�O��xk���u9�h�y6}�2�0����Yd�����t�-�G�����7c���o���� ��B���0�n?�e�0"T�o�m�������@�<<��@\�G�����I�"Y0^#�2~&��W���� j$flh�c�.�-��Q=
����.-�����:P�������>>�s��p�I��'���3F���OW�4n!���{�$	0[���B��V��C���f����=n�����67���8K��-0���������#�U�G���P	1b�#l�Y�C [�y r��8������[��,�)DE��@��)<�����������$%��<b�t���n�2&���x��j'����������o'�m?0��y��c\sQ�SL��E')���ZY��YdBA�G`_3��gd2}rd#����N��{Db�H���Z����V�Uw��@���O{��-�
's����9�W =n|$����.��H�r&L)���O�~�� b�|u�
�*��D|���p��K����N��~o�����~��N
Q%���$I����@�hQ%I��u���!��p�N
W?�w�~���T�W8�>�7�$gW��F�I;R 
\1�~v����F8TR�N���c	a���z<%��1�I��Q��� P4�9?Iw3��:_��g���FKt_������J��nK����
������u��E���cs<�B�8�U���������Di��#�<�q���gh�`�����'��h�M������9"FH�z����������������
DKOG(�c:�i�#�/(�
���$f�@rb���h��!���=Z�Z
��pc�h�	tU�u������+�s��������o�0������Q�g�;S���s��GIj��}��:��V�1"�rE�+���=K@>���)q���-^��V�#�:�"=Lp��D�75Ai<~��X/��A7��� ��<�El�z����e�5�-��5;�!��-d�U�*�J��4sD���o#�?������kt�����Bw~�*[��{Ip�{@5�%8O���x�0��0�o(���H�,�I�/
�u#��#�6S��Ma�v��IA�f��M��/���=�����>g��v%A_�@�P�u~��������5�-Ym�rO90�<����HA��?I�����c8�+<��?��d�f�oE��5�Hh��.����=�{�	�nv^��[y��B�-
1#hl����}�H�9"[wG�g�������
<�!Z3�s\�F"�
~�"���=���D6�7�����3��7E"����5�<��N��5$�m�IY8���=}G
J��XsP���M��N�V)��� 
�1��M��_�[���H�.��Z��$����?��?
��[���-�]���c|
y�q5������b� ����Y�k��(3��-`�L)�p?>�OU{��'��.�m1d�Rp����h������� W�T|���`���S��;\:84j�+^�
���Cc���c-�'lx�8��}�/����D��������;�S�C2�����o��7����U�����<���7���hd�oe��^����6����S,�Q}����������S4�|��mQ��q/u�("�M[{���3�[�
�jC��E�l_~���n9�����v��`����[�m\h����w���/����@� ���zM����cLP�0���O�7C~�aA����Ct��V�4�������=���������4�3�eL�a���
�FU��U�i1nN%���I��:\���>��@<T-�������n��z�#����I����a���S�W�<C�� ��y�a�V�Sh����d�c
�.����0��I�x�q�4=
@����w�"q�n�n�<|k&�)�!���+���g�{{�������?.����E����N�������1�gfq����a����o���:X^g�!�?w�8��_/���U0IQ����&u�_����	�%L~>�|3=/��>���\�y>�!���)y�|�1p����Q�[��K:�8�I����~��ln
M��1�F�+��_��t�p3�����e����6��>����&,P%v��_����,��I�}��$���v�!�y�u�{Zo�+3��30��Kb0#��M��H0�us]�k��h�y�>g�tTML[�>����i����A���D��R�W<��p��t#���5.k�3�+N��8Xq�>���H��MD�r����kt���`Q=DG������:MsNA1���lT{���7v�5��lo7M���K���4{z�Z�~j���Kbv�x
���@��Xbl�M����r�}��4r��
r�^o\��z���/���A�0w2
3�":��c���F���T�P���y���r���bPb����f3 ���'=����G[�����`YX�l�F>&��|��9���,c{`������}��wp����[�l|�F��0�[G�#��z�?T���w�=�c����"���uHm���I�:������P�@w�0�d��0F�o<��c��7F��ES��T9E@��N�5��w<���>�)����+�5�X����T����g�J�[0��r_�4�\k��	�,�A����5FVHt]��H9�f�{�Np����f�0�;����qz���	��0�z$z��`Op�P��Vk�d=�D96���F�B����������.pV��8H8�$�F
Qp�*[b�xo������_bS����>p��2�����Q���Q�����5i����W�Ms?���(�.+q}E>��%3�~�s���H	g��"�+���y*1�l�t�,2���,J1GL��B�%��+p�cf�����R,�TN(�
�����drn��`dz�+n�/6����[CB���E8�YOODyWB55@��	GV{D��	�H[z�R���p��KCB���L�K��a�+z1;��������%�8�<b��vQ������M��	�
��m{e�Y��t-������o
�A,.�Cs:���B�����X' Og_Z���\��`v��&��nu�v�p�R��W�5��zh��b��I�!����c���2nzx�F6��}�
yG��t{/�(
�V�����L���q$A���6�-������@ou�u���D�g��5���	c�L��Q�g�@p$4;&�Z#P�'?r��#�����8�f[3]�Y`�J�r��9u"���T�����S%��j�D�V����8(��}%p��R��u���b$�N\����I9T�/7!���n��K��[K���V���vy�>�����D�M�W��5u��!�*_�����O������FJp��6B���h=[�c0��2F�3��������b Q�k�y<����H�xQu2�$�r_ynu����&3�%�2��,\Z��|fs�%�e��EKQ��e)��jH���R��(�����X���x����S)����J7��K�Io-���Z�wz6�'��x���h����v�'T�{RO�7���u���]���i�S��|K�;��U'D)H�Z��u}��gM��CV��7[�%��)F!B�y�5��Y�J+LM��a��qW�>���a�Hg��#�*BW�X�Q%.}���W�P���}����MH�{������#�����n�D�����n�,?zK�-�BP��S��]8�bo^������,�7��)��%�Fphn7�6:b.g|�,Y�^3�^,���cn�/&Z}��R��W�*�����SE��1M�H��z<x���D����X�A%�����Q�Z����>���h�a�G^yuyw�m�j��f�� 9�,e�<Jx���C��d�FD7d�[u�4�
�ma�PA�`%�����"<&���j�E�s)}{�����	px��2�m��M�Ix���#�s���$a�)����]4\4�(���$���E��B�� �ydt-��*�E)Y�|tu��@��/������D�8*������o�Q�2�,O/������MQXfF�lr���$�6�-X�%����zA����J\��P��M�UQs�.M]�N��B���?�M�n��s5�����j��t�O�^�H�,����,�B9�Z���+^��K2��#��K�/pT��la{���}���@�lb�������b��J�}z��b�]x�q����83=����|�?�Z)�p��|�kg��Dlj�������kg��U=S�#��0C���!�=m��N��3��~'X����(]��(!L�i�@/e��&��������}.��%i�Kwo��������mf��BJ'�S��D���������	�Z]h]�K����Rr�;Gz!�r���z�������}M@��(}@l7L���������>8����:��l��C���J���F��<��	��3|v��]C�(�U��_(�x����Z�_(�G^Dh���t����Q4��gT�+��~��h���#A��5��T
�E+-�;WwT8"O����sg�h�����<P�v	��V�D5���������#�3v6_���3��2��ma�������|�s&�=�_���R�kf�����n���@��	��MI3��8��L����)(���!�w�6�5�;@j�7��?����;�?"�O��J�l	��H��&_o,Yr���LT�'P���$���Z���C��N4����m
0�E���Y'x���a\����?p#����y)�GZ`M'����p���=p���������*���m����d�p�@���`;+X<diQ���\.����������cS�.gV��u����Vc����EI��R{������.�W�Q��_
�.��[����!���i
[���=�����"�F&�h�����rm��w/	��N1k*��|H���U�v���$����1q��M���������x��4���r�����8��~��%w��o����fc ��7��F���)@�h����Pi�����C�dA�����F�Q�-�������]���I���7^c�+P����]/�x�[��3{�^@�M��g�TXp0�?pL���n�;���A��ABF0��:�4�=C\:��X���')��DPk���W����r�M^��VQO��x�f����'��|`�K�d�'Rhe,�,Y���[ Z�������5�������4E�]+��s^"t����	��U����}��!��!�6��U�����@��!q��~ ��"�O��B����,��^�_<�o���V?��qf���/3�!�jz��I�I����`�F�Q+����L0m�(Z�
u�+;�r�*Zz�*�X@�&���@�m��E���L`
��t
m�@���VX+e���`3�<"@	������k��L�/����Q��T������
m=�)�S��1D���d8=;M$A��jB�mF1n��W� � �s���}��R[4�$��[p��yH;�P�b�C���{�����4�	nH���h��4����_���fw�|��� ��k�w��]
���
i�Z���p�6�q����h���;�����
��)
t�Y���B���]��f-�T�����/�.�`8��l"8^�D���a+���z�H{ZA��^��s�>�'�Y�G�r�������q��7+b�r8�&3�`��u��sWk�a�|��m���.��J�px\�n�� ���,XQIcv'$hb���V
�Kw�;��P���������F����~������j^Z(�O�=�����R�����Fi�h�����v���$~u��K�G�(�5��)n\����l�i}d��[��t0��;�P�}�m�H�����[[�k�e����z�e�e��H���2��@M�{U}�T����:N�g��i�X������$�Z L����i�Z�DTSM?Jx�y=g�l5Zg��P�R��g��N�9�A������S�/X������~�:L����]!{_{?g8�`�C��G��R�|�sh�~��
�];�/��o�������Oh2�i�+��7i�G��@�F���x��&�t�[cMJ�5�@K��G�`*��~��������!����Y�i������MT�����L�L"�R�Y�M��]
s?����T*��"+���V����������PGp_��������u����c�>#!�����7����uc��+O���ws�k�M�L#�o_3GO�-����oCb�HeR�������,�������C��K��%'D+�Q�5�������G#�k��
��zf��?���Zw�T��"sk�A�-�V�6e�+g�*��X��`���Y&t\���R��h��N����<?s�\I��y�w���Q��C\Kq��Y��A�����'h"�-0�j��L�n[�$q����-����L�e>�1�r44��-���R���7���7�����b�D_B�-.G�v�:��u|I~ u�	����J=tit�_$��Pu;n��7�����O53T0:5�T��/%��
v�*����U���\P��������'��y������%[_������0�nF'��Jl��EUA����h��33�M����{�g��Q�]�fl�^9kEs�\Gb�O~�tk���PK��]�XL-��{��avL����5�
`>�/��L�>[������0xf��S���u���G!�{�H�gG�H��k����s��~'�2�3�x~�4�.;�����	=���	="Li�����N�����/f�n���%�TK�w�>�����h�oG�����*����g�������]xp�	���aLH>O�R(-��hGIe��&T��6W\�-.�p$��\8��Z]|S�����������U[�T�F�����gL��b�d;��;�3w�W`�H.wT�qr��P$E��%H0�dK0f����-q�@�{��n�a`.5����1����4Dm������������'�^]b-�we�H��3�����������[|f~��d��=�	p�o-�o(Y���G���<WC����Y������>�y�b��I�o+"����]��� &{1;�R��R��U9CT���x�..=%�������T�O�AQ�Hu���N���jw'�p�f
%e*�e�R�u�4��@�wG��6����6��K���~3h)�#�#����!�~v�%��zY
���^�RS��n�����1��0L���b���������A�����r/�X#�1O��S�����6��)Ja���+����d��d}G���x��d.��&�Zvp���Z�r����}��1������w?Gg���I����������cnAlU��������\=%����p���:��f����#��:����S��U����f���J ����1[���a<K����v����k?M�+��1"�y��%O,�����wsk�����k����f����2`�$��^
Bu��4�[�$��6��y�Hu�UiUP0�K������d�h��C������h?����Iw�{���-�s���%/�B����Z�5�Q�%l����<�-xB�}j;H��T����8+.�L�jA�,�OO�cX�r�@i�;�%�'�(�6N�A��a�a��y%�I��`
�]��Q.}Y�����[S#��+@7�-��w\�y�1�8�8+t����+���5c�I���`���L�Ax�l{:	���C��7�b���e.;Q��{5�t��@���P�)Lg���'-�G�	�Z&����������s��>���+1	D���V��������^E���[ZG^�6�E�����/�(�YL5���������k�	����g>������F�k��h����k@7��IfG�d�i�����v�9���Ty�3��mQ�s���?ZltA~'��jx.T��mB���ns�p��D�I9������I�j�����b�:$���p�}��5���B����� ��������9| ��m�������L��������\�/�gph:�1�X�2�S���_���_����57����~2gp��(&��{V�B���a�Qy����.��q�����_;�}E��x5�Ukd�	��-�s�(�@�\)��9�7���_�!��E[y�����q ��T����M���_��t��Sm<N���Kv��2@{���6�[}< ����4��u�o�S����pX�4�I�Ss������ �Gv����-�dD*K�5���o���D<���������]�g�[{��-�~`���}by�t�l��uH���w��A����7iN�4#�vx���2"<�s��!�%�p�a2,0��|f51�������'\����-�B�]�/-?N�"�j`xkg�� ��g4�d�����p��,��z;Jv �U�}��V�����������<$;i�v,��G��g�����%�l�T���yw2T4�������(p��x�����q�t�5���;����+���k[�7����H6��gs��y�?E:�`�BHSym=�1��5���2�Q������r�~��H�Ha�#S	����6��
��Kt���,�W�z����LF����S�l��P��+?Kl��B�t���>�����������(n�M�K!=����G����{�"�x:�w$����������`wJ�?C0�����\d:������Hrgd�.G�����WJ�k���R�#�<A:�w���6�J����[��X����Q
�1y��1j�!>_�i��t:J��A/��S�ZKK����g�PZDV�hZ����1&��q<2B�#���R��b��y(r��7��P��{������5��r�(������+SX����?���0��Z���YK���a���e�f�Kh�5����j�_x��_?e����V��?`���.p�_+�`�q�����E|����O7jc�QG�2`���.XTC��2�B�=J�GnfY�<J5�'><?�@�����p�3�yg�F1K�A��A�r�}�!�~�� V>���+7�I�p��U{������E����e?5�7�#��X�O��w�����$��_*�x�G:�����&�~��,�����
����E;)�������hC�o��<�l���������<"�o�
�N�<t�����&���n����#	oI��{���Z����K-p��Y�W����/�o�C	�/��p(�6���ot�~�g��������z(7��#�����(_�km/aYmO�����o:"G�B��N|�(����{7�xNm�4)���{,��vltqB,�gTp'-k�����o�H><�,��=���s
��G\�^�����;@�")����R���s�N����F
J!�<���K��x"^��r����
9��V��j����RA��z��c!FpK�t�U���W���jf�������,��SBl�m���������F�i��b��d��7���uy��8��\,B����.����Q-Gz����}�Uh���6��o�c�+�ymF�`�[�����GQ��`T�k�??���3�	��v��Di#J��#��?��
�]?�\�[�w����e=��Z#eb�R�5�e����M����� ��)�n��x�]���������'���E��d&��d5;(�C��L-����@G����>�XM��8S����<�X�"�Jpe
,�N7
�����D�qG�����������7�������n|������m���~?���]��&��u���^�r=X��:�����+�kal~��]��M&GuT]�Ax�����W��p�
"�,K����%�+�)F
A�P����c��p�	YI�E�R�j�yG�K���iO7�P-�6�]'e=i�fE�S��/���5'���Q�7�e��$���JB���(�)'�E�8�4���C���P�E.=8�����h�R����� ��8��!ri�w���9�D+��X�@����y��c����yY)}���1rA�vP�5&�����<�%X&\1,��h��E�S	�
�����Z�S@���)wf��\-z<����$1L�y��U�Eu��{,yZ3���	�?b���.�_�����E����%��o4�dU��l|�
=�Lh%�Y����_	�>��xV�s�Of����~�G��)��1�S�R��j���h��~�:���}�3�������y�G�������*�����;�K��"�'���3��zX���b>(��������dy����0{�1�0����?��k.D��;x��1E�0��u�p��Qe�1��h�b��.	�,�p	��mAQ���v�d�`��[C��(h?X��c�������B��(���6
���WvJ��ZYM�:<�H��@X?�b�%���&����)�I��4��{uC���i��y6GB�����V
����;�����d��QO�W����R�����>����1�0���Fo~�gz����=�d����N1��	**���E)�7�-Y���7:GV\@e8oT�$��� �$\�m�f_�N.����%���|��ZA�7��g9��S�b�ix9��Y���0�9�k�Y&�5��9�sF��5x��uw�;���T���=��e�_��?��O��sG������^���\�k�,���;%�c��S~��]����)L�����q�R�H3^���N|m��X�g���L��I:�c���.����!#
�(�f[�w��E�W���
�XjR16@hF19��FuY�YF?��B����;�-�Y;i����~2��o��G=����+
k~G�ju�F+^6*Ta�S���s$�r���t*}��a���jY�y������d�m?��x���#����"V	�5gh�@��"]�{��C?�������K=qEs��)Nu�O&M
u��������i��a`.AMI�t��4�Z����#��d�'7/k��q0f����=mF:Z|�9�+;r�SX��=��!�
������'m�W<�g�����
�����tI�_��?����6�]2���������L��g����wO�����,�Y��x���f�cR�5.�uRd�"�p�qDvd�l,�]��.��z'�j0�t��������N[��[rtg�9n��0��]�'���'���I����c�5"�m*�^<|D���Q�{�("�������x���+�{��a7�������L��V�z�u��Z�N���hl��#U4��EZ�;��y�H�3&�tE�=H=�-��������;A=�,S]����9����%$�c3h��MZ!=P�_vbs�������$�Ea;\��E�g�����j����Q����S�>�B����f��*�<�=�"���lA�<��$
T�o#M�s�)��#�*����=wp
���T������:=��Z6��Jy���������*��t�>�����N�*D]#�[!���)���$��#!�&���B�������N�t�R�3����Z����_���P3���	"���P��]Q�������_��}��7�����VBJ��w�����E�����swW��J����>����L.���:���}:8�z��M�r8L,m���;0�?rO0�?C�h���wO�����x^��s�ke�$62u�y�$]p���i�[��u`;�:��6�r1���:����o�0Y�r�x��r$O��Y{�t�+o^�!� ,4j{A����fi���p�Q4���Ol��|V36`B��<,HU���(����4�����������iA:iM��T7z���}��vQ���c��
��?�b���;��	��]>��K	������>�i�
endstream
endobj
14 0 obj
   99825
endobj
12 0 obj
<<
   /ExtGState <<
      /a0 << /CA 1 /ca 1 >>
      /a1 << /CA 0.14902 /ca 0.14902 >>
   >>
   /Font <<
      /f-0-0 7 0 R
      /f-1-0 8 0 R
   >>
>>
endobj
16 0 obj
<< /Type /ObjStm
   /Length 17 0 R
   /N 1
   /First 5
   /Filter /FlateDecode
>>
stream
x�34U0�������
endstream
endobj
17 0 obj
   17
endobj
19 0 obj
<< /Length 20 0 R
   /Filter /FlateDecode
   /Length1 25452
>>
stream
x���y|E�8�T�5�s��gfz2�I�$r��4���	&�HPBA9�� ����x���2��%� �(� ��HtQ�5����z%�~�z&�]������������������~�H�(3�X�Q�3�yO��l���������o\t���V4l�0�M7���������F�3{��N�����0h���3�%�o�T@��K�?����S�����\�0�<�`��E\����M�,Z<{Q��������`�X8��%d��,!,��0?��
��`=e�B���wmz��:9������������u����@��1�eo0$S=0fS0g��r�fK�%S��I�`��`L�.��|�.�����1zci�=��?q}�p�s��)a��SD�	��������^�,����mq����f�����,n����V�&���
z}Nu������z��b�e�����<����`�3+$�������D������#�}�G'��N�����oT����	|���L��z�H��L��x����$L����
d�p��B��S��\ ��$3���N2���	!�����T �y�D&ON$��������^�e~����c�c��Y�OcW,�t�w���]�y�b�������#w�]W�X+��X��v[�n������!��Xl-�C<#�/�J,%�A�J��.R8,aKi4��5(�����g�_����������3<m�����g]�v}d����n������y��G^�~�._>~���PZ��|��=>����,f�F��d�;u ���<-�4L�N���SE�l��"���c<A\rR3CM&7�1�J��+�P�.�:���[*&S�����V�6�h�z�5��gY����Q,��z�Vf�t��;/���0"�
~�Xegl`�z�A\�����aO�g�/��?�VI���</��N�8Q���]x�e#�d�8Iy��!,�:�^�DfU�X�3\��\�(B���4	��Fd��M�S\��-�G�FL1m��6"��)����(�Z)�Z)��4<5?	���/�����b1Z���.��c�.����R��+�*�*-���\��J>������V�YW��dh���#&���Z��,���R���@#����X�U!���*�d����
>�:���jG��.�!Mq����i����:ZCL2���$���r�U��@�z��X�2��4�tvg����+i�'S[����w���(�@I�@M����� F�eCz���yh��=������%z�u����z�U�s�v(`v�6���h��e�����r3@4D2@Ng���
a7k�f����W��9S�gg����y����?0F��u���tq>\��\�$�4�4q�4M?�0�8O7O�'���3�3�E�r������~�r�J
�Y�YyK�KrZr~%=ex(���G� �bx>������������������g^����yM����Uk�b�.7b�X�u���Y�$~U���
z�<�<�=�=G=���,����A��y���
(����p�����d�Q2����,%�*�,����us��;�,���&S�SVN�FX9���z�7��������2"�<n�$��q��(�N�B��P��8�����u���:R����.'?�����8��������d�\y(�����Wt�����S��t�������b\U�R��e�P�5���h��)��7��$kS(*9f����������d�G5J�`6����	�y��d������rwl�|!�4&�)���X���r�)v�66��t�f]�����j�VQ	;+w��b��X�TsiBM�-�9{A�"[e����F�b��C\��C����)�������'�P^�(�1�A9���aQZA�`~l����G�������2�V�t�*+�����e�����t�M:�]N���;����U��{n_��,��CO�68��I���jI�����t��������V�]���xv��aw�x����V�cW�~�{����a�M�)�r���Dq�P�:���C����M�F����s�)n�� y���tS��M�\��Tb�,s����Zg�{7M�(��q�D�n�8K^`]����9D�3^�L�&K�nffs���
���
�^o��j{������U8��J���<���T��"� �[��IC*�	@��T��6�DJYPF �*�I��g|�GF,�K���D���J;EeSLf"�(���S)g�RN����	N*���H)Q�0���>�(u��`����LP8~	u	z7��c�x��V����85����q7I���Ad� !�lr���b�Plv
�:U�p�["�����LOW{���[w�[��m(��e=�t��(��������z�0����C�P�����"�YfK��$�Fv	�����6�hF��~���$�m�!]�bC6�m��f��h�on� ���dP�y@
�N`�(�D�"�j��K����{�����/�G�ku0��i��Uj
o����PUU�e���TPe��w��V8��Q<����Q���{n�����:|���l�������;��qq�	�79��p>��P�i�I���t@�|��pG�F��N
GK[<��(!��3K�����r6d#��n��n�U��X��R��F�i	-�f�A�		�C8&�B2�
�F�������v
�����
�""�JD�SR��t�����7������7]8+w�
���^�$�Y�]i��VXJJ�w@������Y�e%�rK�#l���e�5�7�\p�];w�����n���~�����{�����1^���S��.v(x����+u^��I�6��fZ����r��`�i���H���`�~����zdr:�)�#���|��,���C�����b�7p�O%P�?�6�5�6��hkt=��d�_�_�tF�4�e�qK
��-�
����.��i���w��������3"�Ft�E5�" &`'��f=\Z����Vh&�����9�D1���BIZ?�t-�)9�X!@����:�B*��H%�
����U����<��;(�9(�:(�9r�
(H-��j�
T�(C�*a�����(�D��k'�k'L�o'Z����Xl����M�VY*���Y9~��d�75��I����Rj�dF6��T������z����{^�(���f��W_�k��h�k�Q����^��Y���?��;����l�
V�G�%�P�R���*%������*vg
�Z�lVtC\C|W���5��3LsM����7�����:�������������N%�8�lL�9��!�H�jy����Y=��bb�~��~�L����d��C^�l�'���dI���U��S������'�,���?f,�o���4z��9������Jp�eL��1��6�m(��#6���8��d�g�������(U E�d�"et(���@1�J1�U�FD_��� Nq�,�� TUuQ�L�G����d�x�;&�#����c�_���
��-=s���[^\��7/-i��3�{s��	�R�?��������y���w���_��su��g�B�����(�{���~���
� G�oH����1�?�87�q�F����8���bt�VH�=����@/z^���y#���h�_��t�N b�B��Ji���R�Lu��t�����
��`4{U�]C�F���2�z�;��?�,��&��E9�����=���~����Tez����2q[M�M�����T1�0��p|YB����*�&��2E���t?�"���.���#v�_���CM�T�g��f�3�5e(��������0<�~"��d�k:wu���*�s��������j}����r��r2.��$�N5����Jt@t{��H�:���&S'3���d��@����V�Q��U�
y����.��]wvE�"��E��r��Gy��x��Io'cxzo"{�Si���+����S���Z�SK�8��$Lc���X,c���z��J��g�.w6V��M���>�6i�H����)�AS���e�������@�����	N���t��/�����1����m�7������nB%5���J�o9v��W���+{��u��������\4�����Y7v�X�����H�^���n��V�qu���Zw�`t:�/��)����qW���P%�����K�Kd?Z��hI�D�4��,e6���'�����@K�LugO��&S�juBQ�-CK��-Q�+��t�!�#'n��	�C<#���"�E��n�S� ����W�ZHg]���xV���Vv�`;�N��`��X�=�v�,�QmYB�.��,����X;A1V#c
�P����zw��:�����.����������h���7����VV�`,%�
mmm�?����F:��`>g���Pm����rR�;���<s���D?��KW��q�����r���n�;�~��6�d4L��)#�(O
R���V���T��Xo'{�'��BOOwE�MG�;�dg�v�-�d�������'SU�j�j������H?�MN��tPi�}������7�fp��Iy��r'�Nj�\l�X���c�I�R����l��#1=�:��c��� �Z�����/����._��|"~�9uQM��*�<Diw�����N$�����Cf��v���E�&����K�!����n���g��R[����_b��m�Y4�xUw3����z�{�v�u�/��"�����)���

��=�D�i"<��h������i�����8D�Z��iB<G����IM[���j����$�5��P�f�
��&��	a�n�]���E����#E=���|0F��~V��D�Z����Y��^�0�4|���6�)�������N`R��l�V����l7��&�1�_Pmd!�i�	�Lf�����cf�q�(���Z��,�������[��j'd���\*7B�[
�K��e7v'S��%�7+�}���0����]h/��P��p�*`�L��v�KXA<�u��B�|���T���?K�!n�(��]�����1n�|,�����S����!���d�-�1�/A b��M�7�����k7��r?��{����@�%�]�c7j�7�{��-��������,��s��o?��I�R_�
�800������
L|����1af+����2@v�0 1��CI��.v�mn�Y��%�X@U%	�W�5#�A<��l���p_�H�^�	j]�;MY����
5��"�dxv������~^��6��X�.N��G�����rrE��G��G,%%��@�����C�m���K-���/��'S_����g������P)�B�Tq�#������S��������/5�A��*�L�
FIS����oD7�����r���W����J��z|7s��A�Q|�^���7a��Cz��N�����w�'� H�InpJy���q�J"�Z����7���)"���:C5l�����F��+�s�AO����#2�;;���*��>�\t��(�EQ�;B�IH�N�1�IdqEd����*��XL"�.�k�0�D>UT����_��PC�����{�]g�DO&nd�A������&U�����.� ������5��V��o{n���H�����6�}�M'/�4��	��2�$0�=����
��}�����R���y-VIK��UEu�t4����)����iJ+e���4���0������w
s��a��"����g0���kg�16Y�y�:�h�*����%6�o��q��$X�I�"1@����7T=���\5
jv�Th	�	����A��^
X�
f0��������)f�2%�&���&.O��Pe�R�\�
�|��be�M�^����f-~������
c2uB��T0����U��vq��T}��e|�A�V���0���5�B�P*��)�����w��u�P[O��kf��W���Y��G��k�r/ZG������ ������v`��m���S���-)+q�=r�HF�:��;z�����W)C�Dj`D�2���kdi���)u�,������`�K���e��!�|���*$�9����:�H�-�j[b��%R�F��q"����%RmK$��j["��H�n���c����
�y��9�9���ub;�C;�p;�g����UK�o������5G���U��bE��}O6-�����T\��L�I��x�u�L��1DC����onB���e��m[:�}��������T�t����'����
@��gn���W�R��lt��D%;a�I�D%�E�j��Q�y���&�#�����\#�E���|Py	/G����G�4f���+r��P�g�>�=2}s���c
}���`�l��;.�O
�Ed�yxT�"������u^c�1����r �7X��32�_B��X�i��S6zt6����.z�.�fX����=����������yT�T��4��t�Hd��-�.�����������W�s��"�����n�>�BpI���.�=Zr�*��t�@W��B����U]�����^�c^�>��f��X���(���%�%]VZ~���p&��u��{��k��O�>z����i~��k�OK#o�������l����d,J2�HN\���$*i����&zJ{g�oK�k�R�h"�h�$
���5k'��^��`!q�hn	Q�e���b�W���v��1G{&�N����G7N��O������GGy����R��t��
"M��=b�%�-���5����}�E"�fD0:,����
H����!�Cr���kJ�y�C���N�c��S��R����8�p*�9�~/�D��D|����	�����#QK����P���B��A�%���a�'?�i����+��ux:��JV�: ��� ��7U���h��2�Bw%�f��U �lt�l���f�P�U$�k���w��@���&��Pu(@��\.%([0V�D7:q��G����i�� �2��M2��j�tBU4[pf�NUo�������g�b����s��{�K��b�|d6:�:�
�
�
n?������0��`�l�o�e��z���>�g��|�����=6���r���������]�<����7 �:�?����~���e�y��1 '�;�Y�%����Y�N�
RF��2�L"BG�*M��8"�z�d4X5XvU��x!^�Y��@=�6��!Dl��>�?K|j������&!��5�HA|��4S(�E������^hJx^`����+���o_~��;�B����|��U/���i��_V9�c���n����6��~������{a������>g�\�P��K&t����V�7u���@�Gbb�KF����~���
��s��a���A`U��^���F\-�)�6��"�VTU�,>kEQ�!���B>+&���8��Xc����X��,�1�7�����K�+�w7�����(q
�F�z���
(l4 r���}��QY���`�{���sT��sl����9bk����lm��,T�B�
���9JM�(">b�������$��9�K�r���rCY�|��n����)�d��h�`�^���k�}M�L��H	������#��lnU�5v��9y�����m��~����g�����������'�'�X����|��;��K���M���?�?���=���f[�I}�:���|�����0�����IY>������p��%D�2�[��-�����2d�%�QMG��T#2�\��e�F)� "��<I��K&�E1�E]�.��&�w���V�''QT�p��=�ZP��je�u�2��%�����R�����������pZ��n�f=��NM
)�����\%��H���r��v:>��$���5#� ��7�-�A�H/zE�e�^2Y���L�����@J���-@%f������$�P��\�]]k\�K���E��p�t,W����}oF_�x��4$��W�J�gh ��"�i�H��?aF���$M�p"{�
��Nw���S�����������cw}�s��{��~��|��c�������N2�0f��o>������l�Z�j�J���_������6$�y|�v*?g�N�%0�"�t��X#b��#�6#V4���G�4��f]��������B��B���cJ�&M��s�k�|�Hx��!��(����)n+9x`x!<�j-������U;�����?��?����k�O�_G_���"���R_��\���v�������:/���;M=��lz]D��g�����^NI������^z�/Cz���4�D;���n'���N�������_�'Z��+&��x���xh3�/�����������<���!�����v1���D�4�����f2J)���R5����/}��R�#M�}�#��u���O����������B�R���q��'m	qd�F2-��AU&}�K�����������2'�������N�V�*��]�k�/6,5���6�M������w�s�g9d�f'S��~�K*E)"U�%\��O1@�
_������NgI �H��P6h�/"q/�	b�A���X/��5�U%\:���,��9�T=�nC�o(�q7'�$�M�#�v��������(��Cu`���ogDuS��B�g�W�Y+�t������AS�/:H���@��e�����b��eAh���?�����������=����s��s�M���<i������%����y�N��v�c�
n���:�=�<��;��\���1����r��/k2��*,\������Lw�L#|/As���L��K��(M�$�������}�0�`9{�ht��4Y��!Z�F���}�A������.���!j~4
|�����]����a|��2�l���O���bE��T'8S�`Mu�9���e��x�~��'
z7��]���3�7�6�1�3�7_���i��1���[�o���o3����q��'�������f����_W/���E*-E�L��4����Wrs0���7�����J�~������Y��9�+)54&`�G�|�7Ag�������&�!��!�����M&�h�����|���%������_Y���WV��:��#]����=�S===^|z���o��9h�7s���wD� oks����Rn���-���1����B�)���iakE�\Z�5t���EW��A��K4~)���s}�=��A��&�L�g�LJ����y���[-�,nKAL� ��q&�d��x���a�-|"1�lb���?�S
��r��X���L���o��}q)J��S��B�������m�����C��8��u�k�727;xo
��]���7����w��+���E�v����NfH�Y<�%�p�Su�^��3M'�<?����md��3��x�{Q����Lr�>��'��������}�.������iJ����[T�l�]���h���R�oo`/�%�4�����Z,CqYi.�qY)�;�JQTz)�n����3&�?
zc����p����o��s�����z���WV�zM�o���5[dpO��t;��-=�����������>���S�����:�o���\��\(A�*m�l��9:����[T��cw�=n�����Y��� ��`�[U=��x�s�#y�=�fC��5����[ ��-;�_+�����U$�����N�H��.�[wwi! �R����M�X�?F���+����_ �e`FcA(�DFl��u�E2��sQ~V^���� �����A��A$��A�� �������(n�HOr��/+�2$~�_�n}�������n��0uxu���L�C?�>{������_�
!�5
S��O�����Bo-y���%[�o�������I�����'7-!��6u�
�C�Y����1q..��g3����l���y*��g����S��������h��f���`�����a]���_�/w\��28���r�w6:9���Y�&cYf}~I���W4T���D�i��Gl~V��X���!��R���G��j������f$�s�KFd���:-%��6DA'�;�QnZ��
��6eJ�r��������q��o���f��0�O3���X?���F��	��Y������$_��bi���^�����]��T���e��I���3	�2���.����B�����|��e��������~>'�������`<�����)���P1���z>��AV�����{���x�����r�1��o&�e�[�M�7W���,M����uY�&�8�oE��;�S����2�0P��#�_8�p��� ��;����3�y�	r��.��@��\"O������t+�8��*�1�mm��-
�K� ��+H�R��7���G���D1-��%���
�X��m��Gj:���9��,����+%�#�Rc��+���������I���,n�8����������|f��������:���3�y+���Z��������;��2c-�1����_+M1�d�����#�`���1�e3��z�������E�=��(!���z��93���\�GGd�����hi��AhR���J?��	���l�~�MS�-I-���P2������S���Ts\�NZ�������*Q���_8*�R���@�h9��2��{��>=���}(�D(v�9�xS��jw����x��a�����R&_������Y������������t��;-�����t��_�}��w?�����"f��a���Hl}����z���W��e:�� �-Wo���w_�k�
_����z6��R[��*Pm��M�M�����5Z'�����_�r��p�!���e��x�a*��?0|����K������F���L������2�@������{5�R/9n3������bf������������m��mv�?���B}f'9ns�KC:<NK.�0H�r��d�~e�CO�KO9���S�nY��Y�%����V��'L���W�g�94!K�s,��s-�����o_�V�s����7��}���{�b�cQ$l�������~����O�|����9������^"��R����f��Kc6���9
���� Y��J�"tf����M���]$����}����d�]���.��\�y�.�w�F�]��*p����lb��:\�5�K�i.	�{�{�"�6o������w@��`H'����=����N�3LW���+�k Q{��H�"
��4�/��\�b���&Kj�u����H�	G�P��l2���t���1��|`�Y|@���k!N��4��F�,%��
�Af�V����d}��r��	�_��T�U��5���w�7p��Il�4����'#����1�{��U��:�Vp���g��c�(�d�����GUU�3[�9�C�/W�{*��e�=a�b��:��]�hV�^�&:�^�
��E��uE�k��A_oz=.=�����?����O1��6~(&Y�i���V��m�y�5��6�$a������}��<�:QD</r,���f�d4"��(����g����Y��!��"�����10v���D�a0/�H���:����-�g��jUJ"�������#T�������h����(M�������]��{s�HI��i^�t�c���^wp�I>��u�\Y��L��m&wV����>�����`�]��5T!�`��e�*D�_����wK���r��+'��3�����y�����";��� ����!=_�<�����K~�1t�	]���40����X< &X)4	�@��j�>�?R��e��Z(;�"�
lCQ#Z�V��m��������#�=��A��m���C�������I����/��?��Np�d��0x�Y������$�A�`+���$f�X!8`��0�D���x�����������{	<D��L\��9R��M�<4��W�L������:��?������m�GpA5<+�aX<L�?�=0&��0�������#��ka5�'r���5��9�a!��xX��kRKa�a��r�n�E�%U��?�P��5�3Lu��0f���7��RA!L�G�	8�w�
�B�3O�b���Y��)�#0�[��0���k`6|��h%3��[��T"u�C������(�������
a9���
�a7$�M8��������
k�
��:����=U��A��0���m8����x!g��9��-u�0��Zx	��s�/���k�C���p0�:x��6�� /*B���/��0�A0�,�����6|�bh76�����o�������	x����4��)���N���x:~�<����E�~��}�����h��A+�z� zA��9<O�������y��g'��������������=��W�8u7L����G�h�v8
�p>E�#2!�P���V���s�e�
jC��1�)�}�������p6��a����O���(>���?0.&��1eL%��,d����ff3������G�W�s�r[����p�������������{�gC��=�=m�O�A9��P	`��y��_�v8����(
E��	h:����rt�mA��k�-������o1`#��5��ex8�����l��7��p>�dF�����b��lf	��y�I0�1��O������L��� ��F�;���.e�a�`���q�r������O��	C���!.< �N�a7�]��/��Nf-S����q	����B���1�
��6�U�
�p��+�h,�g��a|o���T�&�<<P{og_�J�t���l�'f,�
h5��7@+\��b�1�]8��A�,|�J����K�x�Go�C�z1O�o�&�
v��'�&Cc���&�b�=���6(g�w�|�7��[a<�f�7��P�V��"~����������n�6��}W��pv���-���X
GY	>f^c�������<7���
����ZX���A7��@����a%S���aX�a�np�^H�0f,7�4�������
,,��`�k�����qn�Lh6|���3��^�'R7�-���0u��VB^���x���A>���5�H|��*��x~���@����+�-�����_aT�6���A6<7��p�7�\�t@I�X�#5�Y+�30!�R*�$�����>����!]l�v��0OL-af������*�
K��GQ7y�Z5���+�T./+-)8��aA,�_^n4��)�@�����]N��f��f����D��s,���G6*�hc�������\�g(���>
�	e��y����H�)��Tg(��m���T{G"Y�����&�$�T��$�:�>�$��7(�.
���f
��
�Pa�����T+	���$F.���������K#�#fK��C����@�^���"
`W��t�����7\]�����
L�f����	�5��P��� �F�������9F��:M���4�\�6p����c���74����fL�O03��X��N�n;��tYX����_����l�q�U��������	�}{C�lhp$pdd���	u���
j')��x]C}�k(,P������ov���4�Sbxxx��y�3��wc&��z�j{��5�����P��n�Q��a��W����������E��&s0��{�(D��vb��"�������Pf*	�XN��`R�g�����&fM����G4n���vr���ae��B5�����eF�����$x��j	4#'b�D~>AaD�'o0�^�,K�px��$1�>_�@3��B!r��&U��� �h�P�]+p����XC7���L�����dzzoo�
����H�����e��f��r��gk���A���������]i��{��P�6����4�}�MXGL�L.�
	6�`#<E�YIA7�^kA����x�V6H���������.Z]�-�������W\v}�����	6�k'O��Q���vbz���j�����!eD��L$�D��������P'�����	�����
���������#�#7nVFnl�8#�j�!��������4f'��{�/1rSCBn�����0|Gm��CE&M�o��
��[1�#�7��A&��+*m���4��\@-��X��ut��]h��,m��3�h�.��`fkmr�
��$�������1��/�P�l(��*���b���N7W�����R���������W��q{�M2� �w����=ca�?n��6���~���z`P�6C5~��
�!z
0�m��l3\���
i�.��e���+`/i���J\6R��p�m�J����0�m�l3L"��?!ne���
w��0�m���f��j��_��_ %��_���?����_��x�Yn��
��s�9��rVD��>��@��`<@���6��(����@e`(A`�`�����������0�PS����p;Lf�vF��c��~�����k�e��\&�����d�;��b��B���E�T2
lg��(��t&d&k��0��`?�cLx&@{&�le�Iz�,����a���0�`f\�-�������c\0�q���2.��8���q�����=*�j}�DM2��{i�s����r�v9-N/w^���c&hu�hm�m��R���p��-�jk�������c��q�1�	1N@� �� lc�`�>��2��9������ ��`P��R<L�)�-X!���]Z��i�ov5���Oa?��)��?�5���9��*�	[q'���pw���x��;�|�?3>
E�4T��0����4����[||�D��������@�����O����T>�Z^Q�N�XQF�������$�K����L���z����P�d�F����rn0���S��
�O@� �@|d||����O�"|xh�'a>	-�$l�'a>		|�`>	2>	
>
~|�����0�>����I|�5:<8�������GZ����]����oA��0>��0=>���
E�-���w�X��a��~(���
��qx?L����x�g��
Z���pX���%�_��t������U��!W���+��*[�X�>�D���"z�C��B��]��U�����
)�7/+VRDg�+VRD�N/VRD�M.V�q�����=9���q��2��o��VP��0�
,���?�dmO���IH�_~�e/j��Z&���P�l����E-���z�C-~�@-*jy������.+T7j9�Z^G-��%�Z"�%�(�\M�P��Z��j�0Bt8������af�*�58��!8�C��W*�T������w�Wi���/v>k�x�3��P�@#>G�`��@>�����[|R��pg�hi��P���
g�t�
kp6|��������������=�\��u��pH���rL��y���4.�
�rp:�����$�q�������0���,��������I�xk���0z,� ��(�@
�fz]~�K��AT���L"sk� ���]��?����'1�<�#�W%������$����	�=�w��:��M"������������k�I��5��T���������c��q}s�E�981:5x���`����������*���JmT�gwp���`L��S���t�p�>��<�����B�0N$BH
Y�O���:Yg�t�N��u��@g')�1�A�y�H�,)Y
���XS90�a�6��N�j3��%qqR8��	S\x8JXk�v����XmRHML��j����w tCbp,�7$L�O�iZ�#�[; dYw���y��kh�sY���:�R1����t�'v���J<Z;�>�jVC�������������C�k���?I�P��E��L$�������$�B����YS�QR5�����q����-������!UC}�(B����"�"2nGsNM���:��@3��R��9�����1�8L�v��1��t��_S�#��C��t�y��)������;�:�.��kc���1���_L��������yE��i�8n��n�4&�]6��h�AQv�lH[���f�!����������p����i��=�t_���j&������n�B��&<��a�������uO�\���a���J�\�����t�"s�����\��Qt.�8>�~��7����;�^���o���;�EC)�^r���e��XC��0�����a��H��D< �.��+B����t�����Cl�������[�������di���d��2���~�X�&���n^P���T���0�~� �$�F�J�!�6��&����O�M!��;��U�6QL���_���?_�B>}Th	470�@�d���NN��{a+�
[��b�9����i�3��;g��,MC��X���;ck�lI��lV�w���b���Q
endstream
endobj
20 0 obj
   16765
endobj
21 0 obj
<< /Length 22 0 R
   /Filter /FlateDecode
>>
stream
x�]��j�0E��
-�E�[�H��l����l��1L�F�^�������u�z�j��uHk�������^�3_�k�g>�I��k(��z�����^����yH�������K�7}���������#�5���{?B�������nU����j���s:�n��a���Zn�������6�~@Ka�|���yJ'V�m;���S�����2/�c����i��6v��<w�;��H��"��n�������lZ�����AwU?@??�������T��$'��Z�"����Z^#^{D���`#l���5�k�.��~s��E�����j]v��O��0#����$�
f12����1��Y�q��C��x�Z�X����ie�v&\s�T���=�
Y�������?t���
endstream
endobj
22 0 obj
   393
endobj
23 0 obj
<< /Type /FontDescriptor
   /FontName /THHXWE+CairoFont-0-0
   /Flags 32
   /FontBBox [ -664 -324 2000 1005 ]
   /ItalicAngle 0
   /Ascent 905
   /Descent -211
   /CapHeight 1005
   /StemV 80
   /StemH 80
   /FontFile2 19 0 R
>>
endobj
7 0 obj
<< /Type /Font
   /Subtype /TrueType
   /BaseFont /THHXWE+CairoFont-0-0
   /FirstChar 32
   /LastChar 120
   /FontDescriptor 23 0 R
   /Encoding /WinAnsiEncoding
   /Widths [ 277.832031 0 0 0 0 889.160156 0 0 333.007812 333.007812 0 0 0 0 277.832031 277.832031 556.152344 556.152344 556.152344 556.152344 556.152344 556.152344 556.152344 556.152344 556.152344 556.152344 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 556.152344 0 500 556.152344 556.152344 277.832031 0 556.152344 222.167969 0 500 222.167969 833.007812 556.152344 556.152344 556.152344 556.152344 333.007812 500 277.832031 556.152344 500 0 500 ]
    /ToUnicode 21 0 R
>>
endobj
24 0 obj
<< /Length 25 0 R
   /Filter /FlateDecode
   /Length1 9988
>>
stream
x��y{|T����������=��#	�=�d�	�IH��@"oH'���7*�P�Z��Xk�-Q��$���{}`�����C������������=��{���u����~��X�����k��`'8������k~�	��{�-\�e�|�G����04�l]�v���U@���W�n������~
�I�U�zE�����si9����W�8����~���nj��A�.@: z��e-����k��-m���W� �nX�:u����N0m��w1z����Uw���h�1A'MF�&g��������"~��/DgF�F�����k�B��� �
x|-s���p2
 lL��o8�l��Mw�tnq�v>����1��{*���"A'��b���8���+EC��&w
��rz�	�-�r������XX��Sqk~q%���^3gN�{{r��h�L*�/����Gl@�k�U��_�����1�7*2������TsB�3fy}����I��/��u��{�=�{d�vAQ������w�5��p���}��[}r����G��C�y�����}�-��j����"�8��d2���8�(V� 8�� rv����X�F���=NgBt�� �%a�>�}d��/������
���bT��k\55�r��v�Q��/���qg8"��)zM�O��}��U��\����
2�y�qc���+�wu�����-}hYlh�b0�h5?z���o]��n��dh
?��B�*�W/5��v.���i�5V��g8�e
�1N%���:
b*}>>��������@m
|��?B�` bGSi��:`4���b�?s�� /@��!VHF����
�0���^���S,���1����y�Y����f��?�,�O(�U��"�"e��(P�T�T�T���l;�C�|���)Q��t���6�Q������n��� �LrT!TUU����L3�(����|�����p��eQ�cR����������k��\�

�O�j���b����?hi���:��u����In�-c&��%����}��
���$~"����^��g�f�U)�����J��'�J��0oo`o���f�_r��zK��l�.r������|����D�M���<>'����>�fn7/K6�/�Vw�}?���M������p��V�N�91?21���"k��L���A=��5�I-&��kNW�����h1�a}RQ��p^$2�����Le���BU����l52t��-/��)���������-K�g�i���N����}�������1s�|��m�-�����`�)}���'$������U��,�D�gt�#��[�~�!����p�u�o9�^?�R�R��hYi1��rK4��&[�gN��j��m�����a�J�j����!:�8�9:yg��O�D��#�T�@4�|����1P46s�kJw���^���d��
��<�(U�~O����Ct��8���?���sm��h;6������o��}�]�]>��5S����_���'�������v.���W;�������7�i9�J����W���/8��J��__:�r�y��-3'�K��O�O��f#<O�Q` ��	B�'��Y�
	F�Q(�-���f�,�!��y���*w�o���[���f��D�����%e�L���^�@i���q>>cLL�f�Eb||b�N�4D2\��P��{c|���F�epni��U�^Mu�(���b��W�p����z�J3���pp+.w�`�F1����ze=UW����^9����}~��7/z���w�Z63}����(����ENW��J������4w:]������q[�m�Z��V-�������2�i�9\�wr{���v~
�������Cy�q6^�Y�y�<�f������A>��l�#���r\$qk�j��*!��q�D����IF�X�
��4+��GW�'���^��b&��+3.$�����
�������_�w�k���5e��U!z�u'�[9*�?J�i-
����Y^�O����I1��x��������?���>v�_�.~��o�.y�[U�-��N��E7Zg�<�yRiQ����uSw�}����X^��ZX9��Y-Z��:�[�O�����]��M��y��g�4������3FX!?���T�J�#��d��9����0��%�Y������f��E�(�`�h�[8��a84�f����KQ\�l�s�r$�o�]����z��t�t�c��}:�S��q�Hw��T���7e(���
t�;q��$��+<���������d�#s�fM/��t�B>���[F�{{h`�Q�u���TC<����l�	Gj��*����z���2�v��
��j�Z�e�"������,���������0�K-p��d<��$��,�c�����X���s����������1�����*4�i>��q	x�1_�d��+�f�
���"f�s�:�&�]��i��j%�D��tL�^Q����k&�.-��N[A�p�vp���(�#��'S��������m�w���������9XR��������]K��m��0k��o���KJ�mx������N����<��fv���[���o����'����>f���]�]��,G����Q���a�e�ms�XL�b�k���
���/������}���6[�`�����
&2L0}f��X���5��}A�A�'|)��;�X�������� L��zr�	S������2�e8c8o�J
13��!�2���6i���3.���.(Zs�����9����h��T�6s����,�/;9\��=��tUUu�F��������y������oM�7���������+k��MJ���w4V�������Y~.?�h��+}&>�����\x����Z�������5yf5�l��e5K�Zm�x�goy�3�J����V��xIi%"��AZN��Y�I�z?����@�^�k!�T�X=���-L�vK������8����=x��7�X7~���,����[?����7�����������~������-[�q`��_#���~"�8��/�X�����S#��m%�jjs=�:iz����5����o�����3��5�DZV���v�+���
"���G2
\���.��Xh����D�hN�3������=f'�>��������e�!F}��i���&\�����L����h�?��{�SHT�E�z�J*�Cs��@R����S��R��
ra�p(pu�uc_����w�;kZ��?T��#�k���<�����O����|������w0V�dSV<�a����������x��vZ��}�sg`g�������|�r������P����V�Rt:.��jud��~o ���7���b�9���l8<~����Z��,���2�7��,O[/[�Lv����N���V�����,)��a	�N��=N�>��q����9w������/�$~�
~��_Q�Q`���g�E��8��w�!��S�~�s���K/��������$�#�*<��J?
Wk=&e���*�0~d�����&V�����?�+���qC��������V�]����������a������f��gy?9���pk��r4���Y �*k,{]lq��V�V�G&�����6�R�%LFg�c���������n�*%���Le����'�\g��
#�5\�3��t�WIj��^ �
��7���A#���L�B��.�����=z��6~{��Em��O���/�y�)����l+6�}��={��� p��6�l&��������y�Bm����^a���L/?�K���.8Hs�����a�G.�k.���a$���i?��~B��^�W�4��g��]f8`�
�~/�m�j& X����m����2�!�:��G���Z��L�
�K������O�z'�=�D'� ��������W��g���a#��0~�g�y����	?Z�?PF\�-<���#�x
o����%������~������`�A���~O<��0lL��3^������p!����X�N<��x�R��6�B�cZ���~I��l3;��6,H?��]�3`BS����[�0^�9���^��������JLB=6c'^���>�?h�d
����~U��>�(�tL�,@3n����G��q��^���JP�j�G#�0��;�� �(�J�h
��L`5l;����B����q��?rp���m�GFk�0��^�nKH�L�	nH(��X���6Z��[�w`�����Q�@
������	�����r�@QZI7Q=E���5��%Y�{�s�C�a|?����fC���
u
�*�Hw�_M��B(@%H`v�N���Q��P����=�� 3�I$/�)�FS	�R��9��V�&�J��{��~D�I�z�^���:K��s�������$���Y	�f�Ul7�`O���9�{���~��e���r.��y�<.�M��s��u\������s�sgx�7�N��/���?�?��������j��p��!����0��k�����S�?�0GX)�n�#�0�6=�n�D��2��b��J/�}����(�f����p#�c�C���L����m�-������g�'���9z��c7�6�I���i��c��d�i���c��Z��UX��0/^g�q'������:��{aS1��i����'�.��l� 2lb?�m�YfC#
�h�aVE��6�{���&{����V�Z�A|D?����`w����6���iX�V�?�M�V2{��HDO�8�����XL����Lc���<���V~�?bm��;�.�$z����q�;���[R�4�F7�����$�0v�Oq]�*>7�����Qz+������/�C�h?����ka�QR��R'L�����""��:�����m�)�UQ!f�<��&	����DZ���1����`3�����c��N_3�zleE�5e�cJ������"����,����h�+<n��t�mV���i���><�YV#�*	O�Z���Y��\�hV�Y�r��*7�j����Y]��4���eM�(�%�r}XV���)Z8'�����M�:��g�c>���pS(TR,����u�J�r�:e�������b��Zj��+,%���Xk����b��pk�'�>`���]&{Iq����W��:m*WP��\�='Q_�
5��T�,�TEx��Tt�����ZU����h���rW���})K�������	�ki��p)�?\�����!K�Uwmb�����>�F������zpN�JiH�MM����b�Lin���[�iV������[����p��i�AV������74��jv���[C������d�������	7���vy�>wkO0.���w���5�����~�`�e�>���Q����$mG�ij�Y���*�&�*+�����l\NH�����A]>'Q�F5�6���5�6_5�a������jN�0�X ~m�9�eS���XU��H��V5jw0Q���oI��p�(�XU��bvB��������PH{�{Sq,-)�;�$2���9���*M*k�$�.I�h���$��7�C%���F;K5E.�;E��~�x�|��xEF�0/�0gaB�oo�m�����|�e��H��&�6<b9�.U���/+kD���*_`�=yyJ0�Id8$OQ����d	����R���,}3mx��x�jz�U�U���s
�U>��/lo�\%�����>%,OionoI�w.
�b���=�mo�o��DS���9��}M������w1L�
��9]q�3oa�W�=���Xm����|�3'�+q��.s5J�(4P��D73����8�S��:C���:�t�GX�b������J�s`�nw}�~�g�S����C~qR�������?��| \�����	�f�����?~5��u��U�i��`�v��7�?�7�~#n�Wq-�dO����v��\^��8����(���C? �S
0������p��:��~}�N|�(��X�]lx�]���eO�gG�k*�)v�G�*�p���������Or�N<�:q�u���2��Y�KX'8�Yg���~g����S�S���(������3�<>��*4��=�m}��||�.�&C���>Id�A8�C';�Rv1v����g�{�Fd�����C=�9����ng�p���x(n1{�����vnR5}������:\���Ku���U[]�'u��Ku��,����O4@����	}B����(&H$�(Q����������})���+�1r�T.�J�uR�\+Ir���h�TR4U
�I���71���p�L�=���n��n;�)�u]/M2�x���rU��aH�wm�^ ������vKKR��-}%�L�-�SJ1�{�H����g�/���kEG���=�-��O����;wJ{�����~�M�I�Y�E7�R<����������RBNi����U��R��	����TW�":!���I%��rm�	�i�4F��+�,7:��B
��FI7Iy�*����n��xO�8"tt
;��IB���J�+t�	�B�"t#��mM��d1�LFob&���%E�b�Q���� ��E�A�	rF&��P=\k�7��S���TV��N�e�B��L��
�'�qJCJH�U��U��(�EtO�:NQ��a~"EA�ug�����(x��9N�ywS|[b��{��fJ��4�+~�\����0{k/$J�����4�����������
��#��rm�����?O^��������^�����\1=U?W�s�uMM
)zX�C������PS���[�4=�L���x���u=��z>a]/����I?���4������H]o���z]}����p��w��:}��R���$��u�$]�$]E"��L�F�dXe�e�1�J}�#et��%������VLV��5���Nt�0��vq�����s�'>����p���4���d���X,��De����dUO��'�;r�x�]�����E%�J&i"���������	��>:2,��UWx2����M�7o��@���������x��y
jl��D� �����&4�e�xVk}*}*�3�A�jL���x�g6+�.N��UL�$�V6mjR6*��q��+-�i��a���
endstream
endobj
25 0 obj
   7179
endobj
26 0 obj
<< /Length 27 0 R
   /Filter /FlateDecode
>>
stream
x�]���� ��>����M���Kz�a�l �16F�=�����=(���:��;w�%��q�=&�����|�����lW�q:=���i����_��S�������[R\a�j�+�0��`t~����)��C��	}������m����K��3��K���������Jz6��Ac��L
�@Z�z��lw�����!2�H!��b�9�2b������cf�����������\W�!�37��)},�mf$��4�49O=�}y����Iy��Y�{��S��2�<����a���_Vd�"
endstream
endobj
27 0 obj
   290
endobj
28 0 obj
<< /Type /FontDescriptor
   /FontName /QLGDOD+CairoFont-1-0
   /Flags 32
   /FontBBox [ -517 -324 1358 997 ]
   /ItalicAngle 0
   /Ascent 905
   /Descent -211
   /CapHeight 997
   /StemV 80
   /StemH 80
   /FontFile2 24 0 R
>>
endobj
8 0 obj
<< /Type /Font
   /Subtype /TrueType
   /BaseFont /QLGDOD+CairoFont-1-0
   /FirstChar 32
   /LastChar 121
   /FontDescriptor 28 0 R
   /Encoding /WinAnsiEncoding
   /Widths [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 556.152344 500 556.152344 556.152344 0 0 0 222.167969 0 0 0 0 556.152344 556.152344 556.152344 0 333.007812 500 277.832031 556.152344 0 722.167969 0 500 ]
    /ToUnicode 26 0 R
>>
endobj
18 0 obj
<< /Type /ObjStm
   /Length 31 0 R
   /N 5
   /First 30
   /Filter /FlateDecode
>>
stream
x���Qk�0���+��X�@s�V-�C[(cJ����`���$�����lo�=���s����I�(��<a�"�YQ <^;�p'ke���9[� ���^��Z���`e��;��}�f�l����s�]��������l�M=���%]���t
��Rp��g��X�.�9��%_����;iT��/��Y����8��#�)��<]�H�$i��4)�N��"��[��E1�|k�tD�G#[�
��u��p�W��> ����R��j�e��������E4u��+w��?������lI��K�u���W]��C���}�l�8
endstream
endobj
31 0 obj
   313
endobj
32 0 obj
<< /Type /XRef
   /Length 133
   /Filter /FlateDecode
   /Size 33
   /W [1 3 2]
   /Root 30 0 R
   /Info 29 0 R
>>
stream
x�c```�����A��L212��10020�3002����9���Ad�G�x���	������9R���/�H�0Y�����^`2DZ�� �D���2Y�U�j#�dad.�
/_����
endstream
endobj
startxref
227244
%%EOF
#15Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Yao Wang (#12)
Re: 回复: An implementation of multi-key sort

On 7/4/24 14:45, Yao Wang wrote:

Hi John,

Thanks for your kind message. I talked to Heikki before getting Tomas's
response, and he said "no promise but I will take a look". That's why I
added his email. I have updated the CF entry and added Tomas as reviewer.

Hi Tomas,

Again, I'd say a big thank to you. The report and script are really, really
helpful. And your ideas are very valuable.

Firstly, the expectation of mksort performance:

1. When mksort works well, it should be faster than qsort because it saves
the cost of comparing duplicated values every time.
2. When all values are distinct at a particular column, the comparison
will finish immediately, and mksort will actually fall back to qsort. For
the case, mksort should be equal or a bit slower than qsort because it need
to maintain more complex state.

Generally, the benefit of mksort is mainly from duplicated values and sort
keys: the more duplicated values and sort keys are, the bigger benefit it
gets.

Analysis on the report in your previous mail
--------------------------------------------

1. It seems the script uses $count to specify the duplicated values:

number of repetitions for each value (ndistinct = nrows/count)

However, it is not always correct. For type text, the script generates
values like this:

expr="md5(((i / $count) + random())::text)"

But md5() generates totally random values regardless of $count. Some cases
of timestamptz have the same problem.

For all distinct values, the sort will finish at first depth and fall to
qsort actually.

You're right, thanks for noticing / fixing this.

2. Even for the types with correct duplicated setting, the duplicated ratio
is very small: e.g. say $nrows = 10000 and $count = 100, only 1% duplicated
rows can go to depth 2, and only 0.01% of them can go to depth 3. So it still
works on nearly all distinct values.

True, but that's why the scripts test with much larger data sets too,
with more comparisons needing to look at other columns. It's be possible
to construct data sets that are likely to benefit more from mksort - I'm
not against doing that, but then there's the question of what data sets
are more representative of what users actually do.

I'd say a random data set like the ones I used are fairly common - it's
fine to not improve them, but we should not regress them.

3. Qsort of PG17 uses kind of specialization for tuple comparator, i.e. it
uses specialized functions for different types, e.g. qsort_tuple_unsigned()
for unsigned int. The specialized comparators avoid all type related checks
and are much faster than regular comparator. That is why we saw 200% or more
regression for the cases.

OK, I'm not familiar with this code enough to have an opinion.

Code optimizations I did for mk qsort
-------------------------------------

1. Adapted specialization for tuple comparator.
2. Use kind of "hybrid" sort: when we actually adapt bubble sort due to
limited sort items, use bubble sort to check datums since specified depth.
3. Other other optimizations such as pre-ordered check.

Analysis on the new report
--------------------------

I also did some modifications to your script about the issues of data types,
plus an output about distinct value count/distinct ratio, and an indicator
for improvement/regression. I attached the new script and a report on a
data set with 100,000 rows and 2, 5, 8 columns.

OK, but I think a report for a single data set size is not sufficient to
evaluate a patch like this, it can easily miss various caching effects
etc. The results I shared a couple minutes ago are from 1000 to 10M
rows, and it's much more complete view.

1. Generally, the result match the expectation: "When mksort works well, it
should be faster than qsort; when mksort falls to qsort, it should be equal
or a bit slower than qsort."

The challenge is how to know in advance if mksort is likely to work well.

2. For all values of "sequential" (except text type), mksort is a bit slower
than qsort because no actual sort is performed due to the "pre-ordered"
check.

OK

3. For int and bigint type, mksort became faster and faster when
there were more and more duplicated values and sort keys. Improvement of
the best cases is about 58% (line 333) and 57% (line 711).

I find it hard to interpret the text-only report, but I suppose these
are essentially the "green" patches in the PDF report I attached to my
earlier message. And indeed, there are nice improvements, but only with
cases with very many duplicates, and the price for that is 10-20%
regressions in the other cases. That does not seem like a great trade
off to me.

4. For timestamptz type, mksort is a bit slower than qsort because the
distinct ratio is always 1 for almost all cases. I think more benefit is
available by increasing the duplicated values.

Yeah, this was a bug in my script, generating too many distinct values.
After fixing that, it behaves pretty much exactly like int/bigint, which
is not really surprising.

5. For text type, mksort is faster than qsort for all cases, and
improvement of the best case is about 160% (line 1510). It is the only
tested type in which specialization comparators are disabled.

Correct.

Obviously, text has much better improvement than others. I suppose the cause
is about the specialisation comparators: for the types with them, the
comparing is too faster so the cost saved by mksort is not significant. Only
when saved cost became big enough, mksort can defeat qsort.

For other types without specialisation comparators, mksort can defeat
qsort completely. It is the "real" performance of mksort.

No opinion, but if this is the case, then maybe the best solution is to
only use mksort for types without specialized comparators.

Answers for some other questions you mentioned
----------------------------------------------

Q1: Why are almost all the cases that got better for a single-column sort?

A: mksort is enabled only for multi column sort. When there is only one
column, qsort works. So we can simply ignore the cases.

Q2: Why did the perf become worse by just reversing the sort keys?

A: In the example we used, the sort keys are ordered from more duplicated
to less. Please see the SQL:

update t1 set c2 = c1 % 100, c3 = c1 % 50, c4 = c1 % 10, c5 = c1 % 3;
update t1 set c6 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb'
|| (c1 % 5)::text;

So c6 has most duplicated values, c5 has less, and so on. By the order
"C6, c5, c4, ...", mksort can take effect on every sort key.

By the reverse order "c2, c3, c4...", mksort almost finished on first
sort key (c2) because it has only 1% duplicated values, and fell back
to qsort actually.

Based on the new code, I reran the example, and got about 141% improvement
for order "c6, c5, c4...", and about -4% regression for order
"c2, c3, c4...".

OK

Q3: Does mksort work effectively only for particular type, e.g. string?

A: No, the implementation of mksort does not distinguish data type for
special handling. It just calls existing comparators which are also
used by qsort. I used long prefix for string just to enlarge the time
cost of comparing to amplify the result. The new report shows mksort
can work effectively on non-string types and string without long prefix.

Maybe I misunderstood, but I think it seems to help much less for types
with specialized comparators, so maybe I'd rephrase this if it only
works effectively for types without them.

Q4: Was the algorithm good for a hardware at that time, but the changes
(e.g. the growing important of on-CPU caches) made it less relevant?

A: As my understanding, the answer is no because the benefit of mksort
is from saving cost for duplicated comparison, which is not related to
hardware. I suppose the new report can prove it.

However, the hardware varying definitely affects the perf, especially
considering that the perf different between mksort and qsort is not so
big when mksort falls back to qsort. I am not able to test on a wide
range of hardwares, so any finding is appreciated.

OK. FWIW I think it's important to test with a range of data set sizes
exactly to evaluate these hardware-related effects (some of which are
related to size of various caches).

Potential improvement spaces
----------------------------

I tried some other optimizations but didn't add the code finally because
the benefit is not very sure and/or the implementation is complex. Just
raise them for more discussion if necessary:

1. Use distinct stats info of table to enable mksort

It's kind of heuristics: in optimizer, check Form_pg_statistic->stadistinct
of a table via pg_statistics. Enable mksort only when it is less than a
threshold.

The hacked code works, which need to modify a couple of interfaces of
optimizer. In addition, a complete solution should consider types and
distinct values of all columns, which might be too complex, and the benefit
seems not so big.

I assume that's not in v5, or did I miss a part of the patch doing it?

I any case, I suspect relying on stadistinct is going to be unreliable.
It's known to be pretty likely to be off, and especially if this is
about multiple columns, which can be correlated in some way.

It would be much better if we could make this decision at runtime, based
on some cheap heuristics. Not sure if that's possible, though.

2. Cache of datum positions

e.g. for heap tuple, we need to extract datum position from SortTuple by
extract_heaptuple_from_sorttuple() for comparing, which is executed
for each datum. By comparison, qsort does it once for each tuple.
Theoretically we can create a cache to remember the datum positions to
avoid duplicated extracting.

The hacked code works, but the improvement seems limited. Not sure if more
improvement space is available.

No idea. But it seems more like an independent optimization than a fix
for the cases where mksort v5 regresses, right?

3. Template mechanism

Qsort uses kind of template mechanism by macro (see sort_template.h), which
avoids cost of runtime type check. Theoretically template mechanism can be
applied to mksort, but I am hesitating because it will impose more complexity
and the code will become difficult to maintain.

No idea, but same as above - I don't see how templating could address
the regressions.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#16Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Konstantin Knizhnik (#13)
Re: 回复: An implementation of multi-key sort

On 7/7/24 08:32, Konstantin Knizhnik wrote:

On 04/07/2024 3:45 pm, Yao Wang wrote:

Generally, the benefit of mksort is mainly from duplicated values and
sort
keys: the more duplicated values and sort keys are, the bigger benefit it
gets.

...

1. Use distinct stats info of table to enable mksort

It's kind of heuristics: in optimizer, check
Form_pg_statistic->stadistinct
of a table via pg_statistics. Enable mksort only when it is less than a
threshold.

The hacked code works, which need to modify a couple of interfaces of
optimizer. In addition, a complete solution should consider types and
distinct values of all columns, which might be too complex, and the
benefit
seems not so big.

If mksort really provides advantage only when there are a lot of
duplicates (for prefix keys?) and of small fraction of duplicates there
is even some (small) regression
then IMHO taking in account in planner information about estimated
number of distinct values seems to be really important. What was a
problem with accessing this statistics and why it requires modification
of optimizer interfaces? There is `get_variable_numdistinct` function
which is defined and used only in selfuncs.c

Yeah, I've been wondering about that too. But I'm also a bit unsure if
using this known-unreliable statistics (especially with filters and
multiple columns) would actually fix the regressions.

Information about values distribution seems to be quite useful for 
choosing optimal sort algorithm. Not only for multi-key sort
optimization. For example if we know min.max value of sort key and it is
small, we can use O(N) algorithm for sorting. Also it can help to
estimate when TOP-N search is preferable.

This assumes the information is accurate / reliable, and I'm far from
sure about that.

Right now Posgres creates special path for incremental sort. I am not
sure if we also need to be separate path for mk-sort.
But IMHO if we need to change some optimizer interfaces to be able to
take in account statistic and choose preferred sort algorithm at
planning time, then it should be done.
If mksort can increase sort more than two times (for large number of
duplicates), it will be nice to take it in account when choosing optimal
plan.

I did commit the incremental sort patch, and TBH I'm not convinced I'd
do that again. It's a great optimization when it works (and it seems to
work in plenty of cases), but we've also had a number of reports about
significant regressions, where the incremental sort costing is quite
off. Granted, it's often about cases where we already had issues and
incremental sort just "exacerbates" that (say, with LIMIT queries), but
that's kinda the point I'm trying to make - stats are inherently
incomplete / simplified, and some plans are more sensitive to that.

Which is why I'm wondering if we might do the decision based on some
information collected at runtime.

Also in this case we do not need extra GUC for explicit enabling of
mksort. There are too many parameters for optimizer and adding one more
will make tuning more complex. So I prefer that decision is take buy
optimizer itself based on the available information, especially if
criteria seems to be obvious.

The GUC is very useful for testing, so let's keep it for now.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#17Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Tomas Vondra (#14)
Re: 回复: An implementation of multi-key sort

BTW I forgot to report that I intended to test this on 32-bit ARM too,
because that sometimes triggers "funny" behavior, but the build fails
like this:

In file included from tuplesort.c:630:
mk_qsort_tuple.c: In function ‘mkqs_compare_datum_by_shortcut’:
mk_qsort_tuple.c:167:23: warning: implicit declaration of function
‘ApplySignedSortComparator’; did you mean ‘ApplyUnsignedSortComparator’?
[-Wimplicit-function-declaration]
167 | ret = ApplySignedSortComparator(tuple1->datum1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~
| ApplyUnsignedSortComparator
mk_qsort_tuple.c: In function ‘mkqs_compare_tuple’:
mk_qsort_tuple.c:376:23: warning: implicit declaration of function
‘qsort_tuple_signed_compare’; did you mean
‘qsort_tuple_unsigned_compare’? [-Wimplicit-function-declaration]
376 | ret = qsort_tuple_signed_compare(a, b, state);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
| qsort_tuple_unsigned_compare
/usr/bin/ld: utils/sort/tuplesort.o: in function
`mkqs_compare_datum_by_shortcut':
/home/debian/postgres/src/backend/utils/sort/mk_qsort_tuple.c:167:
undefined reference to `ApplySignedSortComparator'
/usr/bin/ld:
/home/debian/postgres/src/backend/utils/sort/mk_qsort_tuple.c:167:
undefined reference to `ApplySignedSortComparator'
/usr/bin/ld:
/home/debian/postgres/src/backend/utils/sort/mk_qsort_tuple.c:167:
undefined reference to `ApplySignedSortComparator'
/usr/bin/ld: utils/sort/tuplesort.o: in function `mkqs_compare_tuple':
/home/debian/postgres/src/backend/utils/sort/mk_qsort_tuple.c:376:
undefined reference to `qsort_tuple_signed_compare'
/usr/bin/ld: utils/sort/tuplesort.o: in function
`mkqs_compare_datum_by_shortcut':
/home/debian/postgres/src/backend/utils/sort/mk_qsort_tuple.c:167:
undefined reference to `ApplySignedSortComparator'
collect2: error: ld returned 1 exit status
make[2]: *** [Makefile:67: postgres] Error 1
make[1]: *** [Makefile:42: all-backend-recurse] Error 2
make: *** [GNUmakefile:11: all-src-recurse] Error 2

I haven't investigated why it fails.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#18Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#13)
Re: 回复: An implementation of multi-key sort

On Sun, Jul 7, 2024 at 2:32 AM Konstantin Knizhnik <knizhnik@garret.ru> wrote:

If mksort really provides advantage only when there are a lot of
duplicates (for prefix keys?) and of small fraction of duplicates there
is even some (small) regression
then IMHO taking in account in planner information about estimated
number of distinct values seems to be really important.

I don't think we can rely on the planner's n_distinct estimates for
this at all. That information tends to be massively unreliable when we
have it at all. If we rely on it for good performance, it will be easy
to find cases where it's wrong and performance is bad.

--
Robert Haas
EDB: http://www.enterprisedb.com

#19Yao Wang
yao-yw.wang@broadcom.com
In reply to: Robert Haas (#18)
5 attachment(s)
Re: 回复: An implementation of multi-key sort

Thanks for all of your comments.

Progress
--------

Because there are too many details for discussion, let me summarize to
two major issues:

1. Since it seems the perf is ideal for data types without specialized
comparator, can we improve the perf for data types with specialized
comparator to a satisfying level?

I refactored some code on mksort code path and eliminated kinda perf
bottlenecks. With latest code, most of results shows mksort got better
perf than qsort. For other cases, the regressions are usually less than
5% except seldom exceptions. Since most of the exceptions are transient
(happening occasionally in a series of "normal" results), I prefer they
are due to external interruption because the test machine is not
dedicated. Let's discuss on the latest code.

2. Should we update optimizer to take in account statistic
(pg_statistic->stadistinct) and choose mksort/qsort accordingly?

The trick here is not just about the reliability of stadistinct. The
sort perf is affected by a couple of conditions: sort key count, distinct
ratio, data type, and data layout. e.g. with int type, 5 sort keys, and
distinct ratio is about 0.05, we can see about 1% perf improvement for
"random" data set, about 7% for "correlated" data set, and almost the
same for "sequential" data set because of pre-ordered check. So it is
pretty difficult to create a formula to calculate the benefit.

Anyway, I tried to make an implementation by adding "mkqsApplicable"
determined by optimizer, so we can discuss based on code and perf result.
I also updated the test script to add an extra column "mk_enabled"
indicating whether mksort is enabled or not by optimizer.

According to the result, the choosing mechanism in optimizer
almost eliminated all regressions. Please note that even when mksort is
disabled (i.e. qsort was performed twice actually), there are still
"regressions" which are usually less than 5% and should be accounted
to kinda error range.

I am still hesitating about putting the code to final version because
there are a number of concerns:

a. for sort keys without a real table, stadistinct is unavailable.
b. stadistinct may not be accurate as mentioned.
c. the formula I used may not be able to cover all possible cases.

On the other hand, the worst result may be just 5% regression. So the
side effects may not be so serious?

I can refine the code (e.g. for now mkqsApplicable is valid only for
some particular code paths), but I prefer to do more after we have a
clear decision about whether the code in optimizer is needed.

Please let me know your comments, thanks.

Attachements
------------

- v6-Implement-multi-key-quick-sort.patch
(v6) The latest code without optimizer change

- v6-1-add-Sort-ndistInFirstRow.patch .
(v6.1) The latest code with optimizer change, can be applied to v6

- mksort-test-v2.sh
The script made by Tomas and modified by me to produce test result

- new_report.txt
Test result in a small data set (100,000 rows) based on v6

- new_report_opti.txt
Test result in a small data set (100,000 rows) based on v6.1

I tried to produce a "full" report with all data ranges, but it seems
kept working for more than 15 hours on my machine and was always
disturbed by other heavy loads. However, I did run tests on some
datasets with other sizes and got similar results.

Answers for other questions
---------------------------

1. Can we enable mksort for just particular data types?

As I mentioned, it is not easy to make the decision considering all the
factors impacting the result and all possible combinations. Code in
optimizer I showed may be a grip.

2. Does v5 include the code about "distinct stats info" and others?

No. As I mentioned, all code in "Potential improvement spaces" was not
included in v5 (or not implemented at all). v6.1 includes some code in
optimizer.

3. Should we remove the GUC enable_mk_sort?

I kept it at least for coding phase. And I prefer keeping it permanently
in case some scenarios we are not aware of.

4. Build failure on 32-bit ARM

It is a code fault by myself. ApplySignedSortComparator() is built only
when SIZEOF_DATUM >= 8. I was aware of that, but missed encapsulating
all relevant code in the condition. It is supposed to have been fixed on
v6, but I don't have a 32-bit ARM platform. @Tomas please take a try if
you still have interest, thanks.

5. How templating could address the regressions?

Please refer the implementation of qsort in sort_template.h, which adapted
kinda template mechanism by using macros since C language does not have
built-in template. .e.g. for comparator, it uses a macro ST_COMPARE which
is specialized for different functions (such as
qsort_tuple_unsigned_compare()) for different data types. As a contrast,
mksort needs to determine the comparator on runtime for each comparison
(see mkqs_compare_tuple()), which needs more costs. Although the cost is
not much, comparison is very performance sensitive. (About 1~2% regression
if my memory is correct)

Thanks,

Yao Wang

On Wed, Jul 10, 2024 at 2:58 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Sun, Jul 7, 2024 at 2:32 AM Konstantin Knizhnik <knizhnik@garret.ru> wrote:

If mksort really provides advantage only when there are a lot of
duplicates (for prefix keys?) and of small fraction of duplicates there
is even some (small) regression
then IMHO taking in account in planner information about estimated
number of distinct values seems to be really important.

I don't think we can rely on the planner's n_distinct estimates for
this at all. That information tends to be massively unreliable when we
have it at all. If we rely on it for good performance, it will be easy
to find cases where it's wrong and performance is bad.

--
Robert Haas
EDB: http://www.enterprisedb.com

--
This electronic communication and the information and any files transmitted
with it, or attached to it, are confidential and are intended solely for
the use of the individual or entity to whom it is addressed and may contain
information that is confidential, legally privileged, protected by privacy
laws, or otherwise restricted from disclosure to anyone else. If you are
not the intended recipient or the person responsible for delivering the
e-mail to the intended recipient, you are hereby notified that any use,
copying, distributing, dissemination, forwarding, printing, or copying of
this e-mail is strictly prohibited. If you received this e-mail in error,
please return the e-mail to the sender, delete it from your computer, and
destroy any printed copy of it.

Attachments:

new_report.txttext/plain; charset=US-ASCII; name=new_report.txtDownload
new_report_opti.txttext/plain; charset=US-ASCII; name=new_report_opti.txtDownload
mksort-test-v2.shapplication/x-sh; name=mksort-test-v2.shDownload
v6-Implement-multi-key-quick-sort.patchapplication/octet-stream; name=v6-Implement-multi-key-quick-sort.patchDownload
From 5b8bc3ccb09ab9b1616543fa88c164ce27f7238a Mon Sep 17 00:00:00 2001
From: Yao Wang <yaowangm@outlook.com>
Date: Tue, 7 May 2024 08:11:13 +0000
Subject: [PATCH 1/2] Implement multi-key quick sort

MK qsort (multi-key quick sort) is an alternative of standard qsort algorithm,
which has better performance for particular sort scenarios, i.e. the data set
has multiple keys to be sorted. Comparing to classic quick sort, it can get
significant performance improvement once multiple keys are available.

Author: Yao Wang <yao-yw.wang@broadcom.com>
Co-author: Hongxu Ma <hongxu.ma@broadcom.com>
---
 src/backend/utils/misc/guc_tables.c           |  11 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/backend/utils/sort/mk_qsort_tuple.c       | 726 ++++++++++++++++++
 src/backend/utils/sort/tuplesort.c            |  62 ++
 src/backend/utils/sort/tuplesortvariants.c    | 297 ++++++-
 src/include/c.h                               |   4 +
 src/include/utils/tuplesort.h                 |  46 +-
 src/test/regress/expected/geometry.out        |   4 +-
 .../regress/expected/incremental_sort.out     |  12 +-
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/expected/tuplesort.out       | 409 ++++++++++
 src/test/regress/expected/window.out          |  58 +-
 src/test/regress/sql/geometry.sql             |   2 +-
 src/test/regress/sql/tuplesort.sql            |  85 ++
 src/test/regress/sql/window.sql               |  22 +-
 15 files changed, 1656 insertions(+), 86 deletions(-)
 create mode 100644 src/backend/utils/sort/mk_qsort_tuple.c

diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 3fd0b14dd8..5aee20f422 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -103,6 +103,7 @@ extern char *default_tablespace;
 extern char *temp_tablespaces;
 extern bool ignore_checksum_failure;
 extern bool ignore_invalid_pages;
+extern bool enable_mk_sort;
 
 #ifdef TRACE_SYNCSCAN
 extern bool trace_syncscan;
@@ -839,6 +840,16 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_mk_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables multi-key sort"),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_mk_sort,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 2166ea4a87..e1bf50370e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -413,6 +413,7 @@
 #enable_sort = on
 #enable_tidscan = on
 #enable_group_by_reordering = on
+#enable_mk_sort = on
 
 # - Planner Cost Constants -
 
diff --git a/src/backend/utils/sort/mk_qsort_tuple.c b/src/backend/utils/sort/mk_qsort_tuple.c
new file mode 100644
index 0000000000..f8588df54b
--- /dev/null
+++ b/src/backend/utils/sort/mk_qsort_tuple.c
@@ -0,0 +1,726 @@
+/*
+ * MK qsort (multi-key quick sort) is an alternative of standard qsort
+ * algorithm, which has better performance for particular sort scenarios, i.e.
+ * the data set has multiple keys to be sorted.
+ *
+ * The sorting algorithm blends Quicksort and radix sort; Like regular
+ * Quicksort, it partitions its input into sets less than and greater than a
+ * given value; like radix sort, it moves on to the next field once the current
+ * input is known to be equal in the given field.
+ *
+ * The implementation is based on the paper:
+ *   Jon L. Bentley and Robert Sedgewick, "Fast Algorithms for Sorting and
+ *   Searching Strings", Jan 1997
+ *
+ * Some improvements which is related to additional handling for equal tuples
+ * have been adapted to keep consistency with the implementations of postgres
+ * qsort.
+ *
+ * For now, mk_qsort_tuple() is called in tuplesort_sort_memtuples() as a
+ * replacement of qsort_tuple() when specific conditions are satisfied.
+ */
+
+/* Swap two tuples in sort tuple array */
+static inline void
+mkqs_swap(int a,
+		  int b,
+		  SortTuple *x)
+{
+	SortTuple	t;
+
+	if (a == b)
+		return;
+	t = x[a];
+	x[a] = x[b];
+	x[b] = t;
+}
+
+/* Swap tuples by batch in sort tuple array */
+static inline void
+mkqs_vec_swap(int a,
+			  int b,
+			  int size,
+			  SortTuple *x)
+{
+	while (size-- > 0)
+	{
+		mkqs_swap(a, b, x);
+		a++;
+		b++;
+	}
+}
+
+/*
+ * Check whether current datum (at specified tuple and depth) is null
+ * Note that the input x means a specified tuple provided by caller but not
+ * a tuple array, so tupleIndex is unnecessary.
+ */
+static inline bool
+check_datum_null(SortTuple *x,
+				 int depth,
+				 Tuplesortstate *state)
+{
+	Datum		datum;
+	bool		isNull;
+
+	Assert(depth < state->base.nKeys);
+
+	if (depth == 0)
+		return x->isnull1;
+
+	state->base.mkqsGetDatumFunc(x, NULL, depth, state,
+								 &datum, &isNull, NULL, NULL);
+
+	return isNull;
+}
+
+/*
+ * Compare two tuples at specified depth
+ *
+ * If "abbreviated key" is disabled:
+ *   get specified datums and compare them by ApplySortComparator().
+ * If "abbreviated key" is enabled:
+ *   Only first datum may be abbr key according to the design (see the comments
+ *   of struct SortTuple), so different operations are needed for different
+ *   datum.
+ *   For first datum (depth == 0): get first datums ("abbr key" version) and
+ *   compare them by ApplySortComparator(). If they are equal, get "full"
+ *   version and compare again by ApplySortAbbrevFullComparator().
+ *   For other datums: get specified datums and compare them by
+ *   ApplySortComparator() as regular routine does.
+ *
+ * See comparetup_heap() for details.
+ */
+static inline int
+mkqs_compare_datum_tiebreak(SortTuple *tuple1,
+							SortTuple *tuple2,
+							int depth,
+							Tuplesortstate *state)
+{
+	Datum		datum1,
+				datum2;
+	bool		isNull1,
+				isNull2;
+	SortSupport sortKey;
+	int			ret = 0;
+
+	Assert(state->base.mkqsGetDatumFunc);
+	Assert(depth < state->base.nKeys);
+
+	sortKey = state->base.sortKeys + depth;
+	state->base.mkqsGetDatumFunc(tuple1,
+								 tuple2,
+								 depth,
+								 state,
+								 &datum1,
+								 &isNull1,
+								 &datum2,
+								 &isNull2);
+
+	/*
+	 * If "abbreviated key" is enabled, and we are in the first depth, it
+	 * means only "abbreviated keys" was compared. If the two datums were
+	 * determined to be equal by ApplySortComparator() in
+	 * mkqs_compare_datum(), we need to perform an extra "full" comparing
+	 * by ApplySortAbbrevFullComparator().
+	 */
+	if (sortKey->abbrev_converter &&
+		depth == 0)
+		ret = ApplySortAbbrevFullComparator(datum1,
+											isNull1,
+											datum2,
+											isNull2,
+											sortKey);
+	else
+		ret = ApplySortComparator(datum1,
+								  isNull1,
+								  datum2,
+								  isNull2,
+								  sortKey);
+
+
+	return ret;
+}
+
+/*
+ * Compare two tuples at first depth by some shortcuts
+ *
+ * The reason to use MkqsCompFuncType but not compare function pointers
+ * directly is just for performance.
+ */
+static inline int
+mkqs_compare_datum_by_shortcut(SortTuple      *tuple1,
+							   SortTuple      *tuple2,
+							   Tuplesortstate *state)
+{
+	int ret = 0;
+	MkqsCompFuncType compFuncType = state->base.mkqsCompFuncType;
+	SortSupport sortKey = &state->base.sortKeys[0];
+
+	if (compFuncType == MKQS_COMP_FUNC_UNSIGNED)
+		ret = ApplyUnsignedSortComparator(tuple1->datum1,
+										  tuple1->isnull1,
+										  tuple2->datum1,
+										  tuple2->isnull1,
+										  sortKey);
+	else if (compFuncType == MKQS_COMP_FUNC_SIGNED)
+		ret = ApplySignedSortComparator(tuple1->datum1,
+										tuple1->isnull1,
+										tuple2->datum1,
+										tuple2->isnull1,
+										sortKey);
+	else if (compFuncType == MKQS_COMP_FUNC_INT32)
+		ret = ApplyInt32SortComparator(tuple1->datum1,
+									   tuple1->isnull1,
+									   tuple2->datum1,
+									   tuple2->isnull1,
+									   sortKey);
+	else
+	{
+		Assert(compFuncType == MKQS_COMP_FUNC_GENERIC);
+		ret = ApplySortComparator(tuple1->datum1,
+								  tuple1->isnull1,
+								  tuple2->datum1,
+								  tuple2->isnull1,
+								  sortKey);
+	}
+
+	return ret;
+}
+
+/*
+ * Compare two tuples at specified depth
+ *
+ * Firstly try to call some shortcuts by mkqs_compare_datum_by_shortcut(),
+ * which are much faster because they just compare leading sort keys; if they
+ * are equal, call mkqs_compare_datum_tiebreak().
+ *
+ * The reason to use MkqsCompFuncType but not compare function pointers
+ * directly is just for performance.
+ *
+ * See comparetup_heap() for details.
+ */
+static inline int
+mkqs_compare_datum(SortTuple *tuple1,
+				   SortTuple *tuple2,
+				   int depth,
+				   Tuplesortstate *state)
+{
+	int			ret = 0;
+
+	if (depth == 0)
+	{
+		ret = mkqs_compare_datum_by_shortcut(tuple1, tuple2, state);
+
+		if (ret != 0)
+			return ret;
+
+		/*
+		 * If they are equal and it is not an abbr key, no need to
+		 * continue.
+		 */
+		if (!state->base.sortKeys->abbrev_converter)
+			return ret;
+	}
+
+	ret = mkqs_compare_datum_tiebreak(tuple1,
+									  tuple2,
+									  depth,
+									  state);
+
+	return ret;
+}
+
+/* Find the median of three values */
+static inline int
+get_median_from_three(int a,
+					  int b,
+					  int c,
+					  SortTuple *x,
+					  int depth,
+					  Tuplesortstate *state)
+{
+	return mkqs_compare_datum(x + a, x + b, depth, state) < 0 ?
+			 (mkqs_compare_datum(x + b, x + c, depth, state) < 0 ?
+				b : (mkqs_compare_datum(x + a, x + c, depth, state) < 0 ? c : a))
+			 : (mkqs_compare_datum(x + b, x + c, depth, state) > 0 ?
+				b : (mkqs_compare_datum(x + a, x + c, depth, state) < 0 ? a : c));
+}
+
+/*
+ * Compare two tuples by starting specified depth till latest depth
+ */
+static inline int
+mkqs_compare_tuple_by_range_tiebreak(SortTuple *tuple1,
+									 SortTuple *tuple2,
+									 int depth,
+									 Tuplesortstate *state)
+{
+	int			ret = 0;
+	Datum		datum1,
+				datum2;
+	bool		isNull1,
+				isNull2;
+	const MkqsGetDatumFunc getDatumFunc = state->base.mkqsGetDatumFunc;
+	SortSupport sortKey = state->base.sortKeys + depth;
+
+	Assert(getDatumFunc);
+	Assert(depth < state->base.nKeys);
+
+	if (depth == 0)
+	{
+		/*
+		 * If "abbreviated key" is enabled, and we are in the first depth, it
+		 * means only "abbreviated keys" was compared. If the two datums were
+		 * determined to be equal by ApplySortComparator() in
+		 * mkqs_compare_datum(), we need to perform an extra "full" comparing
+		 * by ApplySortAbbrevFullComparator().
+		 */
+		if (sortKey->abbrev_converter)
+		{
+			getDatumFunc(tuple1,
+						 tuple2,
+						 depth,
+						 state,
+						 &datum1,
+						 &isNull1,
+						 &datum2,
+						 &isNull2);
+			ret = ApplySortAbbrevFullComparator(datum1,
+												isNull1,
+												datum2,
+												isNull2,
+												sortKey);
+			if (ret != 0)
+				return ret;
+		}
+
+		/*
+		 * By now, all works about first depth have been down. Move the
+		 * depth and sortKey to next level.
+		 */
+		depth++;
+		sortKey++;
+	}
+
+	while (depth < state->base.nKeys)
+	{
+		getDatumFunc(tuple1,
+					 tuple2,
+					 depth,
+					 state,
+					 &datum1,
+					 &isNull1,
+					 &datum2,
+					 &isNull2);
+
+		ret = ApplySortComparator(datum1,
+								  isNull1,
+								  datum2,
+								  isNull2,
+								  sortKey);
+
+		if (ret != 0)
+			return ret;
+
+		depth++;
+		sortKey++;
+	}
+
+	Assert(ret == 0);
+	return 0;
+}
+
+/*
+ * Compare two tuples by starting specified depth till latest depth
+ *
+ * Caller should guarantee that all datums before specified depth
+ * are equal.
+ *
+ * If depth == 0, call mkqs_compare_datum_by_shortcut() to compare
+ * compare leading sort keys. If they are equal, or depth != 0, call
+ * mkqs_compare_tuple_by_range_tiebreak().
+ */
+static inline int
+mkqs_compare_tuple_by_range(SortTuple *tuple1,
+							SortTuple *tuple2,
+							int depth,
+							Tuplesortstate *state)
+{
+	if (depth == 0)
+	{
+		int ret = 0;
+
+		ret = mkqs_compare_datum_by_shortcut(tuple1, tuple2, state);
+
+		if (ret != 0)
+			return ret;
+
+		/*
+		 * No need to check state->base.onlyKey to decide to call the
+		 * tiebreak function like qsort_tuple_unsigned_compare(), 
+		 * because mk qsort has at least two sort keys, i.e. we have
+		 * to call tiebreak function anyway at the time.
+		 */
+	}
+
+	return mkqs_compare_tuple_by_range_tiebreak(tuple1,
+												tuple2,
+												depth,
+												state);
+}
+
+/*
+ * Compare two tuples by using interfaces of qsort()
+ */
+static inline int
+mkqs_compare_tuple(SortTuple *a, SortTuple *b, Tuplesortstate *state)
+{
+	int ret = 0;
+	MkqsCompFuncType compFuncType = state->base.mkqsCompFuncType;
+
+	/*
+	 * The function should never be called with
+	 * MKQS_COMP_FUNC_GENERIC
+	 */
+	Assert(compFuncType != MKQS_COMP_FUNC_GENERIC);
+
+	if (compFuncType == MKQS_COMP_FUNC_UNSIGNED)
+		ret = qsort_tuple_unsigned_compare(a, b, state);
+	else if (compFuncType == MKQS_COMP_FUNC_SIGNED)
+		ret = qsort_tuple_signed_compare(a, b, state);
+	else if (compFuncType == MKQS_COMP_FUNC_INT32)
+		ret = qsort_tuple_int32_compare(a, b, state);
+	else
+		Assert(false);
+
+	return ret;
+}
+
+#ifdef USE_ASSERT_CHECKING
+/*
+ * Verify whether the SortTuple list is ordered or not at specified depth
+ */
+static void
+mkqs_verify(SortTuple *x,
+			int n,
+			int depth,
+			Tuplesortstate *state)
+{
+	int			ret;
+
+	for (int i = 0; i < n - 1; i++)
+	{
+		ret = mkqs_compare_datum(x + i,
+								 x + i + 1,
+								 depth,
+								 state);
+		Assert(ret <= 0);
+	}
+}
+#endif
+
+/*
+ * Major of multi-key quick sort
+ *
+ * seenNull indicates whether we have seen NULL in any datum we checked
+ */
+static void
+mk_qsort_tuple(SortTuple *x,
+			   size_t n,
+			   int depth,
+			   Tuplesortstate *state,
+			   bool seenNull)
+{
+	/*
+	 * In the process, the tuple array consists of five parts: left equal,
+	 * less, not-processed, greater, right equal
+	 *
+	 * lessStart indicates the first position of less part lessEnd indicates
+	 * the next position after less part greaterStart indicates the prior
+	 * position before greater part greaterEnd indicates the latest position
+	 * of greater part the range between lessEnd and greaterStart (inclusive)
+	 * is not-processed
+	 */
+	int			lessStart,
+				lessEnd,
+				greaterStart,
+				greaterEnd,
+				tupCount;
+	int32		dist;
+	SortTuple  *pivot;
+	bool		isDatumNull;
+
+	Assert(depth <= state->base.nKeys);
+	Assert(state->base.sortKeys);
+	Assert(state->base.mkqsGetDatumFunc);
+
+	if (n <= 1)
+		return;
+
+	/* If we have exceeded the max depth, return immediately */
+	if (depth == state->base.nKeys)
+		return;
+
+	CHECK_FOR_INTERRUPTS();
+
+	/* Pre-ordered check */
+	if (state->base.mkqsCompFuncType != MKQS_COMP_FUNC_GENERIC)
+	{
+		/*
+		 * If there is specialized comparator for the type, use classic
+		 * pre-ordered check by comparing the entire tuples.
+		 * The check is performed only for first depth since we compare
+		 * entire tuples but not datums.
+		 */
+		if (depth == 0)
+		{
+			int ret;
+			bool preOrdered = true;
+
+			for (int i = 0; i < n - 1; i++)
+			{
+
+				CHECK_FOR_INTERRUPTS();
+				ret = mkqs_compare_tuple(x + i, x + i + 1, state);
+				if (ret > 0)
+				{
+					preOrdered = false;
+					break;
+				}
+			}
+
+			if (preOrdered)
+				return;
+		}
+	}
+	else
+	{
+		/*
+		 * If there is no specialized comparator for the type, perform
+		 * pre-ordered check by comparing datums at each depth.
+		 *
+		 * Different from qsort_tuple(), the array must be strict ordered (no
+		 * equal datums). If there are equal datums, we must continue the mk
+		 * qsort process to check datums on lower depth.
+		 *
+		 * Note uniqueness check is unnecessary here because strict ordered
+		 * array guarantees no duplicate.
+		 */
+		int ret;
+		bool strictOrdered = true;
+
+		for (int i = 0; i < n - 1; i++)
+		{
+			CHECK_FOR_INTERRUPTS();
+			ret = mkqs_compare_datum(x + i,
+									 x + i + 1,
+									 depth,
+									 state);
+			if (ret >= 0)
+			{
+				strictOrdered = false;
+				break;
+			}
+		}
+
+		if (strictOrdered)
+			return;
+	}
+
+	/*
+	 * When the count < 16 and no need to handle duplicated tuples, use
+	 * bubble sort.
+	 *
+	 * Use 16 instead of 7 which is used in standard qsort, because mk qsort
+	 * need more cost to maintain more complex state.
+	 *
+	 * Bubble sort is not applicable for scenario of handle duplicated tuples
+	 * because it is difficult to check NULL effectively.
+	 *
+	 * No need to check for interrupts since the data size is pretty small.
+	 *
+	 * TODO: Can we check NULL for bubble sort with minimal cost?
+	 */
+	if (n < 16 && !state->base.mkqsHandleDupFunc)
+	{
+		for (int m = 0;m < n;m++)
+			for (int l = m; l > 0; l--)
+			{
+				if (mkqs_compare_tuple_by_range(x + l - 1, x + l, depth, state)
+					<= 0)
+					break;
+				mkqs_swap(l, l - 1, x);
+			}
+		return;
+	}
+
+	/* Select pivot by random and move it to the first position */
+	if (n > 7)
+	{
+		int m, l, r, d;
+		m = n / 2;
+		l = 0;
+		r = n - 1;
+		if (n > 40)
+		{
+			d = n / 8;
+			l = get_median_from_three(l, l + d, l + 2 * d, x, depth, state);
+			m = get_median_from_three(m - d, m, m + d, x, depth, state);
+			r = get_median_from_three(r - 2 * d, r - d, r, x, depth, state);
+		}
+		lessStart = get_median_from_three(l, m, r, x, depth, state);
+	}
+	else
+		lessStart = n / 2;
+
+	mkqs_swap(0, lessStart, x);
+	pivot = x;
+
+	lessStart = 1;
+	lessEnd = 1;
+	greaterStart = n - 1;
+	greaterEnd = n - 1;
+
+	/* Sort the array to three parts: lesser, equal, greater */
+	while (true)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		/* Compare the left end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare lessEnd and pivot at current depth */
+			dist = mkqs_compare_datum(x + lessEnd,
+									  pivot,
+									  depth,
+									  state);
+
+			if (dist > 0)
+				break;
+
+			/* If lessEnd is equal to pivot, move it to lessStart */
+			if (dist == 0)
+			{
+				mkqs_swap(lessEnd, lessStart, x);
+				lessStart++;
+			}
+			lessEnd++;
+		}
+
+		/* Compare the right end of the array */
+		while (lessEnd <= greaterStart)
+		{
+			/* Compare greaterStart and pivot at current depth */
+			dist = mkqs_compare_datum(x + greaterStart,
+									  pivot,
+									  depth,
+									  state);
+
+			if (dist < 0)
+				break;
+
+			/* If greaterStart is equal to pivot, move it to greaterEnd */
+			if (dist == 0)
+			{
+				mkqs_swap(greaterStart, greaterEnd, x);
+				greaterEnd--;
+			}
+			greaterStart--;
+		}
+
+		if (lessEnd > greaterStart)
+			break;
+		mkqs_swap(lessEnd, greaterStart, x);
+		lessEnd++;
+		greaterStart--;
+	}
+
+	/*
+	 * Now the array has four parts: left equal, lesser, greater, right equal
+	 * Note greaterStart is less than lessEnd now
+	 */
+
+	/* Move the left equal part to middle */
+	dist = Min(lessStart, lessEnd - lessStart);
+	mkqs_vec_swap(0, lessEnd - dist, dist, x);
+
+	/* Move the right equal part to middle */
+	dist = Min(greaterEnd - greaterStart, n - greaterEnd - 1);
+	mkqs_vec_swap(lessEnd, n - dist, dist, x);
+
+	/*
+	 * Now the array has three parts: lesser, equal, greater Note that one or
+	 * two parts may have no element at all.
+	 */
+
+	/* Recursively sort the lesser part */
+
+	/* dist means the size of less part */
+	dist = lessEnd - lessStart;
+	mk_qsort_tuple(x,
+				   dist,
+				   depth,
+				   state,
+				   seenNull);
+
+	/* Recursively sort the equal part */
+
+	/*
+	 * (x + dist) means the first tuple in the equal part Since all tuples
+	 * have equal datums at current depth, we just check any one of them to
+	 * determine whether we have seen null datum.
+	 */
+	isDatumNull = check_datum_null(x + dist, depth, state);
+
+	/* (lessStart + n - greaterEnd - 1) means the size of equal part */
+	tupCount = lessStart + n - greaterEnd - 1;
+
+	if (depth < state->base.nKeys - 1)
+	{
+		mk_qsort_tuple(x + dist,
+					   tupCount,
+					   depth + 1,
+					   state,
+					   seenNull || isDatumNull);
+	}
+	else
+	{
+		/*
+		 * We have reach the max depth: Call mkqsHandleDupFunc to handle
+		 * duplicated tuples if necessary, e.g. checking uniqueness or extra
+		 * comparing
+		 */
+
+		/*
+		 * Call mkqsHandleDupFunc if:
+		 *  1. mkqsHandleDupFunc is filled
+		 *  2. the size of equal part > 1
+		 */
+		if (state->base.mkqsHandleDupFunc &&
+			(tupCount > 1))
+		{
+			state->base.mkqsHandleDupFunc(x + dist,
+										  tupCount,
+										  seenNull || isDatumNull,
+										  state);
+		}
+	}
+
+	/* Recursively sort the greater part */
+
+	/* dist means the size of greater part */
+	dist = greaterEnd - greaterStart;
+	mk_qsort_tuple(x + n - dist,
+				   dist,
+				   depth,
+				   state,
+				   seenNull);
+
+#ifdef USE_ASSERT_CHECKING
+	mkqs_verify(x,
+				n,
+				depth,
+				state);
+#endif
+}
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 7c4d6dc106..6dd21a2710 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -128,6 +128,7 @@ bool		trace_sort = false;
 bool		optimize_bounded_sort = true;
 #endif
 
+bool		enable_mk_sort = true;
 
 /*
  * During merge, we use a pre-allocated set of fixed-size slots to hold
@@ -337,6 +338,9 @@ struct Tuplesortstate
 #ifdef TRACE_SORT
 	PGRUsage	ru_start;
 #endif
+
+	/* Whether multi-key quick sort is used */
+	bool		mkqsUsed;
 };
 
 /*
@@ -622,6 +626,8 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
 #define ST_DEFINE
 #include "lib/sort_template.h"
 
+#include "mk_qsort_tuple.c"
+
 /*
  *		tuplesort_begin_xxx
  *
@@ -690,6 +696,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate, int sortopt)
 	state->base.sortopt = sortopt;
 	state->base.tuples = true;
 	state->abbrevNext = 10;
+	state->mkqsUsed = false;
 
 	/*
 	 * workMem is forced to be at least 64KB, the current minimum valid value
@@ -2559,6 +2566,8 @@ tuplesort_get_stats(Tuplesortstate *state,
 		case TSS_SORTEDINMEM:
 			if (state->boundUsed)
 				stats->sortMethod = SORT_TYPE_TOP_N_HEAPSORT;
+			else if (state->mkqsUsed)
+				stats->sortMethod = SORT_TYPE_MK_QSORT;
 			else
 				stats->sortMethod = SORT_TYPE_QUICKSORT;
 			break;
@@ -2592,6 +2601,8 @@ tuplesort_method_name(TuplesortMethod m)
 			return "external sort";
 		case SORT_TYPE_EXTERNAL_MERGE:
 			return "external merge";
+		case SORT_TYPE_MK_QSORT:
+			return "multi-key quick sort";
 	}
 
 	return "unknown";
@@ -2717,6 +2728,57 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 
 	if (state->memtupcount > 1)
 	{
+		/*
+		 * Apply multi-key quick sort when:
+		 *  1. enable_mk_sort is set
+		 *  2. There are multiple keys available
+		 *  3. mkqsGetDatumFunc is filled, which implies that current tuple
+		 *     type is supported by mk qsort. (By now only Heap tuple and Btree
+		 *     Index tuple are supported, and more types may be supported in
+		 *     future.)
+		 *
+		 * A summary of tuple types supported by mk qsort:
+		 *
+		 *  HeapTuple: supported
+		 *  IndexTuple(btree): supportedi
+		 *  IndexTuple(hash): not supported because there is only one key
+		 *  DatumTuple: not supported because there is only one key
+		 *  HeapTuple(for cluster): not supported yet
+		 *  IndexTuple(gist): not supported yet
+		 *  IndexTuple(brin): not supported yet
+		 */
+		if (enable_mk_sort &&
+			state->base.nKeys > 1 &&
+			state->base.mkqsGetDatumFunc != NULL)
+		{
+			/*
+			 * Set relevant Datum Sort Comparator according to concrete data type
+			 * of the first sort key
+			 */
+			state->base.mkqsCompFuncType = MKQS_COMP_FUNC_GENERIC;
+			if (state->base.haveDatum1)
+			{
+				if (state->base.sortKeys[0].comparator == ssup_datum_unsigned_cmp)
+					state->base.mkqsCompFuncType = MKQS_COMP_FUNC_UNSIGNED;
+#if SIZEOF_DATUM >= 8
+				else if (state->base.sortKeys[0].comparator == ssup_datum_signed_cmp)
+					state->base.mkqsCompFuncType = MKQS_COMP_FUNC_SIGNED;
+#endif
+				else if (state->base.sortKeys[0].comparator == ssup_datum_int32_cmp)
+					state->base.mkqsCompFuncType = MKQS_COMP_FUNC_INT32;
+			}
+
+			state->mkqsUsed = true;
+
+			mk_qsort_tuple(state->memtuples,
+						   state->memtupcount,
+						   0,
+						   state,
+						   false);
+
+			return;
+		}
+
 		/*
 		 * Do we have the leading column's value or abbreviation in datum1,
 		 * and is there a specialization for its comparator?
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 05a853caa3..d1c5efe575 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -30,6 +30,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
+#include "miscadmin.h"
 
 
 /* sort-type codes for sort__start probes */
@@ -92,6 +93,48 @@ static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
 						  LogicalTape *tape, unsigned int len);
 static void freestate_cluster(Tuplesortstate *state);
 
+static void mkqs_get_datum_heap(const SortTuple *x1,
+								const SortTuple *x2,
+								const int depth,
+								Tuplesortstate *state,
+								Datum *datum1,
+								bool *isNull1,
+								Datum *datum2,
+								bool *isNull2);
+
+static void
+mkqs_get_datum_index_btree(const SortTuple *x1,
+						   const SortTuple *x2,
+						   const int depth,
+						   Tuplesortstate *state,
+						   Datum *datum1,
+						   bool *isNull1,
+						   Datum *datum2,
+						   bool *isNull2);
+
+static void
+			mkqs_handle_dup_index_btree(SortTuple *x,
+										const int tupleCount,
+										const bool seenNull,
+										Tuplesortstate *state);
+
+static int
+			mkqs_compare_equal_index_btree(const SortTuple *a,
+										   const SortTuple *b,
+										   Tuplesortstate *state);
+
+static pg_attribute_always_inline void
+extract_heaptuple_from_sorttuple(const SortTuple *sortTuple,
+								 HeapTupleData *heapTuple);
+
+static inline int
+			tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+											  const IndexTuple tuple2);
+
+static inline void
+			raise_error_of_dup_index(IndexTuple x,
+									 Tuplesortstate *state);
+
 /*
  * Data structure pointed by "TuplesortPublic.arg" for the CLUSTER case.  Set by
  * the tuplesort_begin_cluster.
@@ -163,6 +206,14 @@ typedef struct BrinSortTuple
 /* Size of the BrinSortTuple, given length of the BrinTuple. */
 #define BRINSORTTUPLE_SIZE(len)		(offsetof(BrinSortTuple, tuple) + (len))
 
+#define ST_SORT qsort_tuple_by_itempointer
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) mkqs_compare_equal_index_btree(a, b, state)
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
 
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
@@ -200,6 +251,7 @@ tuplesort_begin_heap(TupleDesc tupDesc,
 	base->removeabbrev = removeabbrev_heap;
 	base->comparetup = comparetup_heap;
 	base->comparetup_tiebreak = comparetup_heap_tiebreak;
+	base->mkqsGetDatumFunc = mkqs_get_datum_heap;
 	base->writetup = writetup_heap;
 	base->readtup = readtup_heap;
 	base->haveDatum1 = true;
@@ -388,6 +440,8 @@ tuplesort_begin_index_btree(Relation heapRel,
 	base->removeabbrev = removeabbrev_index;
 	base->comparetup = comparetup_index_btree;
 	base->comparetup_tiebreak = comparetup_index_btree_tiebreak;
+	base->mkqsGetDatumFunc = mkqs_get_datum_index_btree;
+	base->mkqsHandleDupFunc = mkqs_handle_dup_index_btree;
 	base->writetup = writetup_index;
 	base->readtup = readtup_index;
 	base->haveDatum1 = true;
@@ -1531,10 +1585,6 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 */
 	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && equal_hasnull))
 	{
-		Datum		values[INDEX_MAX_KEYS];
-		bool		isnull[INDEX_MAX_KEYS];
-		char	   *key_desc;
-
 		/*
 		 * Some rather brain-dead implementations of qsort (such as the one in
 		 * QNX 4) will sometimes call the comparison routine to compare a
@@ -1543,18 +1593,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 		 */
 		Assert(tuple1 != tuple2);
 
-		index_deform_tuple(tuple1, tupDes, values, isnull);
-
-		key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
-
-		ereport(ERROR,
-				(errcode(ERRCODE_UNIQUE_VIOLATION),
-				 errmsg("could not create unique index \"%s\"",
-						RelationGetRelationName(arg->index.indexRel)),
-				 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
-				 errdetail("Duplicate keys exist."),
-				 errtableconstraint(arg->index.heapRel,
-									RelationGetRelationName(arg->index.indexRel))));
+		raise_error_of_dup_index(tuple1, state);
 	}
 
 	/*
@@ -1563,25 +1602,7 @@ comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
 	 * attribute in order to ensure that all keys in the index are physically
 	 * unique.
 	 */
-	{
-		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
-		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
-
-		if (blk1 != blk2)
-			return (blk1 < blk2) ? -1 : 1;
-	}
-	{
-		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
-		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
-
-		if (pos1 != pos2)
-			return (pos1 < pos2) ? -1 : 1;
-	}
-
-	/* ItemPointer values should never be equal */
-	Assert(false);
-
-	return 0;
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
 }
 
 static int
@@ -1888,3 +1909,209 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
 	if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */
 		LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen));
 }
+
+/*
+ * Get specified datums from SortTuple (HeapTuple) list
+ *
+ * When x1 and x2 are provided by caller, two datums will be returned.
+ * When x2 is NULL, only one datum will be returned.
+ *
+ * Note the function does not check leading sort key (tuple->datum1 and
+ * tuple->isnull), which should be checked in other functions (e.g.
+ * mkqs_compare_datum()).
+ *
+ * See comparetup_heap() for details.
+ */
+static void mkqs_get_datum_heap(const SortTuple *x1,
+								const SortTuple *x2,
+								const int depth,
+								Tuplesortstate *state,
+								Datum *datum1,
+								bool *isNull1,
+								Datum *datum2,
+								bool *isNull2)
+{
+	TupleDesc	tupDesc = NULL;
+	HeapTupleData heapTuple1, heapTuple2;
+	AttrNumber	attno;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	SortSupport sortKey = base->sortKeys + depth;;
+
+	Assert(state);
+	Assert(x1 != NULL);
+
+	tupDesc = (TupleDesc) base->arg;
+	attno = sortKey->ssup_attno;
+
+	/* Extract datum from sortTuple->tuple */
+	extract_heaptuple_from_sorttuple(x1, &heapTuple1);
+	*datum1 = heap_getattr(&heapTuple1, attno, tupDesc, isNull1);
+
+	if (x2 != NULL)
+	{
+		extract_heaptuple_from_sorttuple(x2, &heapTuple2);
+		*datum2 = heap_getattr(&heapTuple2, attno, tupDesc, isNull2);
+	}
+}
+
+static pg_attribute_always_inline void
+extract_heaptuple_from_sorttuple(const SortTuple *sortTuple,
+								 HeapTupleData *heapTuple)
+{
+	heapTuple->t_len = ((MinimalTuple) sortTuple->tuple)->t_len
+						+ MINIMAL_TUPLE_OFFSET;
+	heapTuple->t_data = (HeapTupleHeader) ((char *) sortTuple->tuple
+						- MINIMAL_TUPLE_OFFSET);
+}
+
+/*
+ * Get specified datums from SortTuple (IndexTuple for btree index) list
+ *
+ * When x1 and x2 are provided by caller, two datums will be returned.
+ * When x2 is NULL, only one datum will be returned.
+ *
+ * Note the function does not check leading sort key (tuple->datum1 and
+ * tuple->isnull), which should be checked in other functions (e.g.
+ * mkqs_compare_datum()).
+ *
+ * See comparetup_index_btree() for details.
+ */
+static void
+mkqs_get_datum_index_btree(const SortTuple *x1,
+						   const SortTuple *x2,
+						   const int depth,
+						   Tuplesortstate *state,
+						   Datum *datum1,
+						   bool *isNull1,
+						   Datum *datum2,
+						   bool *isNull2)
+{
+	TupleDesc	tupDesc;
+	IndexTuple	indexTuple1, indexTuple2;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	Assert(state);
+	Assert(x1);
+
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+	indexTuple1 = (IndexTuple) x1->tuple;
+
+	/*
+	 * Set parameter attnum = depth + 1 because attnum starts from 1 but depth
+	 * starts from 0
+	 */
+	*datum1 = index_getattr(indexTuple1, depth + 1, tupDesc, isNull1);
+
+	if (x2 != NULL)
+	{
+		indexTuple2 = (IndexTuple) x2->tuple;
+		*datum2 = index_getattr(indexTuple2, depth + 1, tupDesc, isNull2);
+	}
+}
+
+/*
+ * Handle duplicated SortTuples (IndexTuple for btree index during mk qsort)
+ *  x: the duplicated tuple list
+ *  tupleCount: count of the tuples
+ */
+static void
+mkqs_handle_dup_index_btree(SortTuple *x,
+							const int tupleCount,
+							const bool seenNull,
+							Tuplesortstate *state)
+{
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	/* If enforceUnique is enabled and we never saw NULL, raise error */
+	if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && seenNull))
+	{
+		/*
+		 * x means the first tuple of duplicated tuple list Since they are
+		 * duplicated, simply pick up the first one to raise error
+		 */
+		raise_error_of_dup_index((IndexTuple) (x->tuple), state);
+	}
+
+	/*
+	 * If key values are equal, we sort on ItemPointer.  This is required for
+	 * btree indexes, since heap TID is treated as an implicit last key
+	 * attribute in order to ensure that all keys in the index are physically
+	 * unique.
+	 */
+	qsort_tuple_by_itempointer(x,
+							   tupleCount,
+							   state);
+}
+
+/*
+ * Compare two btree index tuples by ItemPointer
+ * It is a callback function for qsort_tuple() called by
+ * mkqs_handle_dup_index_btree()
+ */
+static int
+mkqs_compare_equal_index_btree(const SortTuple *a,
+							   const SortTuple *b,
+							   Tuplesortstate *state)
+{
+	IndexTuple	tuple1;
+	IndexTuple	tuple2;
+
+	tuple1 = (IndexTuple) a->tuple;
+	tuple2 = (IndexTuple) b->tuple;
+
+	return tuplesort_compare_by_item_pointer(tuple1, tuple2);
+}
+
+/* Compare two index tuples by ItemPointer */
+static inline int
+tuplesort_compare_by_item_pointer(const IndexTuple tuple1,
+								  const IndexTuple tuple2)
+{
+	{
+		BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid);
+		BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid);
+
+		if (blk1 != blk2)
+			return (blk1 < blk2) ? -1 : 1;
+	}
+	{
+		OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid);
+		OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid);
+
+		if (pos1 != pos2)
+			return (pos1 < pos2) ? -1 : 1;
+	}
+
+	/* ItemPointer values should never be equal */
+	Assert(false);
+
+	return 0;
+}
+
+/* Raise error for duplicated tuple when creating unique index */
+static inline void
+raise_error_of_dup_index(IndexTuple x,
+						 Tuplesortstate *state)
+{
+	Datum		values[INDEX_MAX_KEYS];
+	bool		isnull[INDEX_MAX_KEYS];
+	TupleDesc	tupDesc;
+	char	   *key_desc;
+	TuplesortPublic *base = TuplesortstateGetPublic(state);
+	TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg;
+
+	tupDesc = RelationGetDescr(arg->index.indexRel);
+	index_deform_tuple((IndexTuple) x, tupDesc, values, isnull);
+	key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull);
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNIQUE_VIOLATION),
+			 errmsg("could not create unique index \"%s\"",
+					RelationGetRelationName(arg->index.indexRel)),
+			 key_desc ? errdetail("Key %s is duplicated.", key_desc) :
+			 errdetail("Duplicate keys exist."),
+			 errtableconstraint(arg->index.heapRel,
+								RelationGetRelationName(arg->index.indexRel))));
+}
diff --git a/src/include/c.h b/src/include/c.h
index dc1841346c..f7c368cd16 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -857,12 +857,14 @@ typedef NameData *Name;
 
 #define Assert(condition)	((void)true)
 #define AssertMacro(condition)	((void)true)
+#define AssertImply(condition1, condition2) ((void)true)
 
 #elif defined(FRONTEND)
 
 #include <assert.h>
 #define Assert(p) assert(p)
 #define AssertMacro(p)	((void) assert(p))
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
 
 #else							/* USE_ASSERT_CHECKING && !FRONTEND */
 
@@ -886,6 +888,8 @@ typedef NameData *Name;
 	((void) ((condition) || \
 			 (ExceptionalCondition(#condition, __FILE__, __LINE__), 0)))
 
+#define AssertImply(cond1, cond2) Assert(!(cond1) || (cond2))
+
 #endif							/* USE_ASSERT_CHECKING && !FRONTEND */
 
 /*
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index e7941a1f09..60eb77ee01 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -29,7 +29,6 @@
 #include "utils/relcache.h"
 #include "utils/sortsupport.h"
 
-
 /*
  * Tuplesortstate and Sharedsort are opaque types whose details are not
  * known outside tuplesort.c.
@@ -79,9 +78,10 @@ typedef enum
 	SORT_TYPE_QUICKSORT = 1 << 1,
 	SORT_TYPE_EXTERNAL_SORT = 1 << 2,
 	SORT_TYPE_EXTERNAL_MERGE = 1 << 3,
+	SORT_TYPE_MK_QSORT = 1 << 4,
 } TuplesortMethod;
 
-#define NUM_TUPLESORTMETHODS 4
+#define NUM_TUPLESORTMETHODS 5
 
 typedef enum
 {
@@ -89,6 +89,14 @@ typedef enum
 	SORT_SPACE_TYPE_MEMORY,
 } TuplesortSpaceType;
 
+typedef enum
+{
+	MKQS_COMP_FUNC_GENERIC,
+	MKQS_COMP_FUNC_UNSIGNED,
+	MKQS_COMP_FUNC_SIGNED,
+	MKQS_COMP_FUNC_INT32
+} MkqsCompFuncType;
+
 /* Bitwise option flags for tuple sorts */
 #define TUPLESORT_NONE					0
 
@@ -155,6 +163,24 @@ typedef struct
 typedef int (*SortTupleComparator) (const SortTuple *a, const SortTuple *b,
 									Tuplesortstate *state);
 
+/* Multi-key quick sort */
+
+typedef void
+			(*MkqsGetDatumFunc) (const SortTuple *x1,
+								 const SortTuple *x2,
+								 const int depth,
+								 Tuplesortstate *state,
+								 Datum *datum1,
+								 bool *isNull1,
+								 Datum *datum2,
+								 bool *isNull2);
+
+typedef void
+			(*MkqsHandleDupFunc) (SortTuple *x,
+								  const int tupleCount,
+								  const bool seenNull,
+								  Tuplesortstate *state);
+
 /*
  * The public part of a Tuple sort operation state.  This data structure
  * contains the definition of sort-variant-specific interface methods and
@@ -249,6 +275,21 @@ typedef struct
 	bool		tuples;			/* Can SortTuple.tuple ever be set? */
 
 	void	   *arg;			/* Specific information for the sort variant */
+
+	/*
+	 * Function pointer, referencing a function to get specified datums from
+	 * SortTuple list with multi-key. Used by mk_qsort_tuple().
+	 */
+	MkqsGetDatumFunc mkqsGetDatumFunc;
+
+	/*
+	 * Function pointer, referencing a function to handle duplicated tuple
+	 * from SortTuple list with multi-key. Used by mk_qsort_tuple(). For now,
+	 * the function pointer is filled for only btree index tuple.
+	 */
+	MkqsHandleDupFunc mkqsHandleDupFunc;
+
+	MkqsCompFuncType mkqsCompFuncType;
 } TuplesortPublic;
 
 /* Sort parallel code from state for sort__start probes */
@@ -411,7 +452,6 @@ extern void tuplesort_restorepos(Tuplesortstate *state);
 
 extern void *tuplesort_readtup_alloc(Tuplesortstate *state, Size tuplen);
 
-
 /* tuplesortvariants.c */
 
 extern Tuplesortstate *tuplesort_begin_heap(TupleDesc tupDesc,
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 8be694f46b..094d22861c 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -4273,7 +4273,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
      circle     |       point       |   distance    
 ----------------+-------------------+---------------
  <(1,2),3>      | (-3,4)            |   1.472135955
@@ -4310,8 +4310,8 @@ SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
  <(3,5),0>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (1e+300,Infinity) |      Infinity
  <(5,1),3>      | (1e+300,Infinity) |      Infinity
- <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,2),3>      | (Infinity,1e+300) |      Infinity
+ <(5,1),3>      | (Infinity,1e+300) |      Infinity
  <(1,3),5>      | (1e+300,Infinity) |      Infinity
  <(1,3),5>      | (Infinity,1e+300) |      Infinity
  <(100,200),10> | (1e+300,Infinity) |      Infinity
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 5fd54a10b1..a26f8f100a 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -520,13 +520,13 @@ select * from (select * from t order by a) s order by a, b limit 55;
 
 -- Test EXPLAIN ANALYZE with only a fullsort group.
 select explain_analyze_without_memory('select * from (select * from t order by a) s order by a, b limit 55');
-                                        explain_analyze_without_memory                                         
----------------------------------------------------------------------------------------------------------------
+                                              explain_analyze_without_memory                                              
+--------------------------------------------------------------------------------------------------------------------------
  Limit (actual rows=55 loops=1)
    ->  Incremental Sort (actual rows=55 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 2  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 2  Sort Methods: top-N heapsort, multi-key quick sort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=101 loops=1)
                Sort Key: t.a
                Sort Method: quicksort  Memory: NNkB
@@ -554,7 +554,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
              "Group Count": 2,                  +
              "Sort Methods Used": [             +
                  "top-N heapsort",              +
-                 "quicksort"                    +
+                 "multi-key quick sort"         +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
@@ -728,7 +728,7 @@ select explain_analyze_without_memory('select * from (select * from t order by a
    ->  Incremental Sort (actual rows=70 loops=1)
          Sort Key: t.a, t.b
          Presorted Key: t.a
-         Full-sort Groups: 1  Sort Method: quicksort  Average Memory: NNkB  Peak Memory: NNkB
+         Full-sort Groups: 1  Sort Method: multi-key quick sort  Average Memory: NNkB  Peak Memory: NNkB
          Pre-sorted Groups: 5  Sort Methods: top-N heapsort, quicksort  Average Memory: NNkB  Peak Memory: NNkB
          ->  Sort (actual rows=1000 loops=1)
                Sort Key: t.a
@@ -756,7 +756,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
          "Full-sort Groups": {                  +
              "Group Count": 1,                  +
              "Sort Methods Used": [             +
-                 "quicksort"                    +
+                 "multi-key quick sort"         +
              ],                                 +
              "Sort Space Memory": {             +
                  "Peak Sort Space Used": "NN",  +
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 2f3eb4e7f1..44840e7e5c 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -146,6 +146,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_material                | on
  enable_memoize                 | on
  enable_mergejoin               | on
+ enable_mk_sort                 | on
  enable_nestloop                | on
  enable_parallel_append         | on
  enable_parallel_hash           | on
@@ -157,7 +158,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(23 rows)
+(24 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out
index 6dd97e7427..41d99793d7 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -703,3 +703,412 @@ EXPLAIN (COSTS OFF) :qry;
 (10 rows)
 
 COMMIT;
+-- Test cases for multi-key quick sort
+set work_mem='100MB';
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a | b  |  c   
+---+----+------
+ 0 |  5 | 98f1
+ 0 | 10 | d3d9
+ 1 |  1 | c4ca
+ 1 | 11 | 6512
+ 2 |  2 | c81e
+ 2 | 12 | c20a
+ 3 |  3 | eccb
+ 3 | 13 | c51c
+ 4 |  4 | a87f
+ 4 | 14 | aab3
+ 5 |  0 | 9bf3
+ 5 |  5 | e4da
+ 6 |  1 | c74d
+ 6 |  6 | 1679
+ 7 |  2 | 70ef
+ 7 |  7 | 8f14
+ 8 |  3 | 6f49
+ 8 |  8 | c9f0
+ 9 |  4 | 1f0e
+ 9 |  9 | 45c4
+(20 rows)
+
+-- test sorting on distinct values, in which mk qsort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+ a  | b  | c  
+----+----+----
+  0 | 20 | 20
+  1 | 19 | 19
+  2 | 18 | 18
+  3 | 17 | 17
+  4 | 16 | 16
+  5 | 15 | 15
+  6 | 14 | 14
+  7 | 13 | 13
+  8 | 12 | 12
+  9 | 11 | 11
+ 10 | 10 | 10
+ 11 |  9 | 9
+ 12 |  8 | 8
+ 13 |  7 | 7
+ 14 |  6 | 6
+ 15 |  5 | 5
+ 16 |  4 | 4
+ 17 |  3 | 3
+ 18 |  2 | 2
+ 19 |  1 | 1
+(20 rows)
+
+-- test create index
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 10 - g, g, g::text
+    from generate_series(1, 10) g;
+create unique index idx_mksort_simple on mksort_simple_tbl (a, b ,c);
+drop index idx_mksort_simple;
+-- try to create unique index on duplicated rows
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 1, g, g::text
+    from generate_series(1, 10) g;
+insert into mksort_simple_tbl
+	select * from mksort_simple_tbl order by a, b desc, c limit 1;
+select * from mksort_simple_tbl;
+ a | b  | c  
+---+----+----
+ 1 |  1 | 1
+ 1 |  2 | 2
+ 1 |  3 | 3
+ 1 |  4 | 4
+ 1 |  5 | 5
+ 1 |  6 | 6
+ 1 |  7 | 7
+ 1 |  8 | 8
+ 1 |  9 | 9
+ 1 | 10 | 10
+ 1 | 10 | 10
+(11 rows)
+
+create unique index idx_mksort_simple on mksort_simple_tbl (a, b ,c);
+ERROR:  could not create unique index "idx_mksort_simple"
+DETAIL:  Key (a, b, c)=(1, 10, 10) is duplicated.
+drop table mksort_simple_tbl;
+-- test table with abbr keys
+create table abbr_tbl (a int, b varchar(100), c uuid);
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+-- (see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+select c, b, a from abbr_tbl order by c, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+(50 rows)
+
+select c, b, a from abbr_tbl order by c desc, b, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+(50 rows)
+
+select c, b, a from abbr_tbl order by c, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+(50 rows)
+
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+                  c                   |                            b                            | a  
+--------------------------------------+---------------------------------------------------------+----
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 |  5
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 25
+ 00000000-0000-0000-0000-000000000000 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 45
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 41
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  1
+ 00000000-0000-0000-0000-000000000001 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 21
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 17
+ 00000000-0000-0000-0000-000000000002 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 37
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 13
+ 00000000-0000-0000-0000-000000000003 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 33
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  9
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 29
+ 00000000-0000-0000-0000-000000000004 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 49
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 10
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 30
+ 11111111-1111-1111-1111-111111111110 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 50
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 |  6
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 26
+ 11111111-1111-1111-1111-111111111111 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 46
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 |  2
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 22
+ 11111111-1111-1111-1111-111111111112 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 42
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 18
+ 11111111-1111-1111-1111-111111111113 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 38
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 34
+ 11111111-1111-1111-1111-111111111114 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 14
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 20
+ ffffffff-ffff-ffff-ffff-fffffffffff0 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 40
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 16
+ ffffffff-ffff-ffff-ffff-fffffffffff1 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 36
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 12
+ ffffffff-ffff-ffff-ffff-fffffffffff2 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 32
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 48
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 |  8
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 28
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 |  4
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 24
+ ffffffff-ffff-ffff-ffff-fffffffffff4 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 44
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb6 | 27
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 19
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb5 | 47
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 11
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb4 | 39
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 |  3
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3 | 31
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb2 | 23
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 15
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 43
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 |  7
+                                      | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb0 | 35
+(50 rows)
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+NOTICE:  index "idx_abbr_tbl" does not exist, skipping
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+                  c                   |                            b                            | a 
+--------------------------------------+---------------------------------------------------------+---
+ ffffffff-ffff-ffff-ffff-fffffffffff3 | aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1 | 8
+(1 row)
+
+-- Uniqueness check of CREATE INDEX
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+drop index if exists idx_abbr_tbl;
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+ERROR:  could not create unique index "idx_abbr_tbl"
+DETAIL:  Key (c, b, a)=(00000000-0000-0000-0000-000000000001, aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1, 1) is duplicated.
+drop table abbr_tbl;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ae4e8851f8..2de20ca1d0 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -18,13 +18,13 @@ INSERT INTO empsalary VALUES
 ('sales', 3, 4800, '2007-08-01'),
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary |  sum  
 -----------+-------+--------+-------
  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
@@ -33,13 +33,13 @@ SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM emps
  sales     |     1 |   5000 | 14600
 (10 rows)
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
  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
@@ -90,18 +90,18 @@ SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PA
  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;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
   depname  | empno | salary | rank 
 -----------+-------+--------+------
- develop   |     7 |   4200 |    1
- personnel |     5 |   3500 |    1
  sales     |     3 |   4800 |    1
  sales     |     4 |   4800 |    1
+ personnel |     5 |   3500 |    1
+ develop   |     7 |   4200 |    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)
 
@@ -3749,23 +3749,24 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
  empno |  depname  | rn | rnk | cnt 
 -------+-----------+----+-----+-----
-     8 | develop   |  1 |   1 |   1
-    10 | develop   |  2 |   2 |   1
-    11 | develop   |  3 |   3 |   1
-     9 | develop   |  4 |   4 |   2
-     7 | develop   |  5 |   4 |   2
-     2 | personnel |  1 |   1 |   1
-     5 | personnel |  2 |   2 |   1
      1 | sales     |  1 |   1 |   1
+     2 | personnel |  1 |   1 |   1
      3 | sales     |  2 |   2 |   1
      4 | sales     |  3 |   3 |   1
+     5 | personnel |  2 |   2 |   1
+     7 | develop   |  4 |   4 |   1
+     8 | develop   |  1 |   1 |   1
+     9 | develop   |  5 |   5 |   1
+    10 | develop   |  2 |   2 |   1
+    11 | develop   |  3 |   3 |   1
 (10 rows)
 
 -- Test pushdown of quals into a subquery containing window functions
@@ -4106,17 +4107,17 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
  empno |  depname  | salary | c 
 -------+-----------+--------+---
+     1 | sales     |   5000 | 1
+     2 | personnel |   3900 | 1
+     3 | sales     |   4800 | 3
+     4 | sales     |   4800 | 3
+     5 | personnel |   3500 | 2
      8 | develop   |   6000 | 1
     10 | develop   |   5200 | 3
     11 | develop   |   5200 | 3
-     2 | personnel |   3900 | 1
-     5 | personnel |   3500 | 2
-     1 | sales     |   5000 | 1
-     4 | sales     |   4800 | 3
-     3 | sales     |   4800 | 3
 (8 rows)
 
 -- Ensure we get the correct run condition when the window function is both
@@ -4468,14 +4469,15 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
   depname  | empno | salary | enroll_date | first_emp | last_emp 
 -----------+-------+--------+-------------+-----------+----------
+ develop   |     7 |   4200 | 01-01-2008  |         4 |        1
  develop   |     8 |   6000 | 10-01-2006  |         1 |        5
- develop   |     7 |   4200 | 01-01-2008  |         5 |        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
diff --git a/src/test/regress/sql/geometry.sql b/src/test/regress/sql/geometry.sql
index c3ea368da5..1f47f07f31 100644
--- a/src/test/regress/sql/geometry.sql
+++ b/src/test/regress/sql/geometry.sql
@@ -403,7 +403,7 @@ SELECT circle(f1)
 SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance
    FROM CIRCLE_TBL c1, POINT_TBL p1
    WHERE (p1.f1 <-> c1.f1) > 0
-   ORDER BY distance, area(c1.f1), p1.f1[0];
+   ORDER BY distance, area(c1.f1), p1.f1[0], c1.f1::text;
 
 -- To polygon
 SELECT f1, f1::polygon FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>';
diff --git a/src/test/regress/sql/tuplesort.sql b/src/test/regress/sql/tuplesort.sql
index 8476e594e6..997c6c816a 100644
--- a/src/test/regress/sql/tuplesort.sql
+++ b/src/test/regress/sql/tuplesort.sql
@@ -305,3 +305,88 @@ EXPLAIN (COSTS OFF) :qry;
 :qry;
 
 COMMIT;
+
+-- Test cases for multi-key quick sort
+
+set work_mem='100MB';
+
+-- test simple sorting
+create table mksort_simple_tbl(a int, b int, c varchar);
+
+insert into mksort_simple_tbl
+    select g % 10, g % 15, left(md5(g::text), 4)
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+
+-- test sorting on distinct values, in which mk qsort is supposed to be
+-- not affective, but still can generate correct result
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 20 - g, g, g::text
+    from generate_series(1, 20) g;
+select * from mksort_simple_tbl order by a, b, c;
+
+-- test create index
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 10 - g, g, g::text
+    from generate_series(1, 10) g;
+create unique index idx_mksort_simple on mksort_simple_tbl (a, b ,c);
+drop index idx_mksort_simple;
+
+-- try to create unique index on duplicated rows
+truncate table mksort_simple_tbl;
+insert into mksort_simple_tbl
+    select 1, g, g::text
+    from generate_series(1, 10) g;
+insert into mksort_simple_tbl
+	select * from mksort_simple_tbl order by a, b desc, c limit 1;
+select * from mksort_simple_tbl;
+create unique index idx_mksort_simple on mksort_simple_tbl (a, b ,c);
+
+drop table mksort_simple_tbl;
+
+-- test table with abbr keys
+
+create table abbr_tbl (a int, b varchar(100), c uuid);
+
+-- insert data with abbr keys (uuid)
+-- abbr keys of uuid are generated from the first `sizeof(Datum)` bytes of uuid data
+-- (see uuid_abbrev_convert()), so two uuids with only different tailed values should
+-- have same abbr keys but different "full" datum.
+insert into abbr_tbl values (generate_series(1,50), 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb');
+update abbr_tbl set b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb' || (a % 7)::text;
+update abbr_tbl set c = ('fffffffffffffffffffffffffffffff' || (a % 5)::text)::uuid where a % 4 = 0;
+update abbr_tbl set c = ('0000000000000000000000000000000' || (a % 5)::text)::uuid where a % 4 = 1;
+update abbr_tbl set c = ('1111111111111111111111111111111' || (a % 5)::text)::uuid where a % 4 = 2;
+update abbr_tbl set c = null where a % 4 = 3;
+
+select c, b, a from abbr_tbl order by c, b, a;
+select c, b, a from abbr_tbl order by c desc, b, a;
+select c, b, a from abbr_tbl order by c, b desc, a;
+select c, b, a from abbr_tbl order by c nulls first, b desc, a;
+select c, b, a from abbr_tbl order by c nulls last, b desc, a;
+
+-- CREATE INDEX will cover the scenario of sort IndexTuple
+drop index if exists idx_abbr_tbl;
+create index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+analyze abbr_tbl;
+select c, b, a from abbr_tbl where c = 'ffffffff-ffff-ffff-ffff-fffffffffff3' and b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1' and a = 8;
+
+-- Uniqueness check of CREATE INDEX
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row with null
+insert into abbr_tbl (a, b, c) values (3, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb3', null);
+-- should succeed because uniquess check is not applicable for rows with null
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop index if exists idx_abbr_tbl;
+
+-- insert a duplicated row without null
+insert into abbr_tbl (a, b, c) values (1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb1', '00000000-0000-0000-0000-000000000001');
+-- should fail because of duplicated rows
+create unique index idx_abbr_tbl on abbr_tbl(c desc, b, a);
+
+drop table abbr_tbl;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6de5493b05..46359cb796 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -21,9 +21,9 @@ INSERT INTO empsalary VALUES
 ('develop', 8, 6000, '2006-10-01'),
 ('develop', 11, 5200, '2007-08-15');
 
-SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
+SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary, empno;
 
-SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
+SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER BY depname, salary, empno;
 
 -- with GROUP BY
 SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
@@ -31,7 +31,7 @@ GROUP BY four, ten ORDER BY four, ten;
 
 SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
 
-SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
+SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w, empno;
 
 -- empty window specification
 SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
@@ -1146,11 +1146,12 @@ SELECT
     empno,
     depname,
     row_number() OVER (PARTITION BY depname ORDER BY enroll_date) rn,
-    rank() OVER (PARTITION BY depname ORDER BY enroll_date ROWS BETWEEN
+    rank() OVER (PARTITION BY depname ORDER BY enroll_date, empno ROWS BETWEEN
                  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) rnk,
-    count(*) OVER (PARTITION BY depname ORDER BY enroll_date RANGE BETWEEN
+    count(*) OVER (PARTITION BY depname ORDER BY enroll_date, empno RANGE BETWEEN
                    CURRENT ROW AND CURRENT ROW) cnt
-FROM empsalary;
+FROM empsalary
+ORDER BY empno, depname, rn;
 
 -- Test pushdown of quals into a subquery containing window functions
 
@@ -1332,7 +1333,7 @@ SELECT * FROM
           salary,
           count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
-WHERE c <= 3;
+WHERE c <= 3 ORDER BY empno, depname, salary, c;
 
 -- Ensure we get the correct run condition when the window function is both
 -- monotonically increasing and decreasing.
@@ -1510,10 +1511,11 @@ SELECT * FROM
           empno,
           salary,
           enroll_date,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp,
-          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date, empno) AS first_emp,
+          row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC, empno) AS last_emp
    FROM empsalary) emp
-WHERE first_emp = 1 OR last_emp = 1;
+WHERE first_emp = 1 OR last_emp = 1
+ORDER BY depname, empno, salary, enroll_date, first_emp, last_emp;
 
 -- cleanup
 DROP TABLE empsalary;
-- 
2.25.1

v6-1-add-Sort-ndistInFirstRow.patchapplication/octet-stream; name=v6-1-add-Sort-ndistInFirstRow.patchDownload
From c4eb10fecd7e517746f77154c671bb6fb6ff9500 Mon Sep 17 00:00:00 2001
From: Yao Wang <yaowangm@outlook.com>
Date: Tue, 9 Jul 2024 08:05:43 +0000
Subject: [PATCH 2/2] add Sort->ndistInFirstRow

---
 src/backend/executor/nodeSort.c         |   2 +
 src/backend/optimizer/plan/createplan.c | 232 +++++++++++++++++++++---
 src/backend/utils/sort/mk_qsort_tuple.c |   4 +
 src/backend/utils/sort/tuplesort.c      |  15 +-
 src/include/nodes/plannodes.h           |   3 +
 src/include/utils/tuplesort.h           |   2 +
 6 files changed, 227 insertions(+), 31 deletions(-)

diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c
index 3fc925d7b4..c8e85568eb 100644
--- a/src/backend/executor/nodeSort.c
+++ b/src/backend/executor/nodeSort.c
@@ -122,6 +122,8 @@ ExecSort(PlanState *pstate)
 												  tuplesortopts);
 		if (node->bounded)
 			tuplesort_set_bound(tuplesortstate, node->bound);
+		tuplesort_set_mkqsApplicable(tuplesortstate,
+									 plannode->mkqsApplicable);
 		node->tuplesortstate = (void *) tuplesortstate;
 
 		/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 6b64c4a362..1e17939f71 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -42,6 +42,8 @@
 #include "parser/parsetree.h"
 #include "partitioning/partprune.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
+#include "catalog/pg_statistic.h"
 
 
 /*
@@ -256,12 +258,14 @@ static MergeJoin *make_mergejoin(List *tlist,
 								 bool skip_mark_restore);
 static Sort *make_sort(Plan *lefttree, int numCols,
 					   AttrNumber *sortColIdx, Oid *sortOperators,
-					   Oid *collations, bool *nullsFirst);
+					   Oid *collations, bool *nullsFirst,
+					   bool mkqsApplicable);
 static IncrementalSort *make_incrementalsort(Plan *lefttree,
 											 int numCols, int nPresortedCols,
 											 AttrNumber *sortColIdx, Oid *sortOperators,
 											 Oid *collations, bool *nullsFirst);
-static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+static Plan *prepare_sort_from_pathkeys(PlannerInfo *root,
+										Plan *lefttree, List *pathkeys,
 										Relids relids,
 										const AttrNumber *reqColIdx,
 										bool adjust_tlist_in_place,
@@ -269,8 +273,9 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 										AttrNumber **p_sortColIdx,
 										Oid **p_sortOperators,
 										Oid **p_collations,
-										bool **p_nullsFirst);
-static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+										bool **p_nullsFirst,
+										bool *mkqsApplicable);
+static Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
 									 Relids relids);
 static IncrementalSort *make_incrementalsort_from_pathkeys(Plan *lefttree,
 														   List *pathkeys, Relids relids, int nPresortedCols);
@@ -1282,7 +1287,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 		 * function result; it must be the same plan node.  However, we then
 		 * need to detect whether any tlist entries were added.
 		 */
-		(void) prepare_sort_from_pathkeys((Plan *) plan, pathkeys,
+		(void) prepare_sort_from_pathkeys(NULL,
+										  (Plan *) plan, pathkeys,
 										  best_path->path.parent->relids,
 										  NULL,
 										  true,
@@ -1290,7 +1296,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 										  &nodeSortColIdx,
 										  &nodeSortOperators,
 										  &nodeCollations,
-										  &nodeNullsFirst);
+										  &nodeNullsFirst,
+										  NULL);
 		tlist_was_changed = (orig_tlist_length != list_length(plan->plan.targetlist));
 	}
 
@@ -1326,7 +1333,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 			 * don't need an explicit sort, to make sure they are returning
 			 * the same sort key columns the Append expects.
 			 */
-			subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
+			subplan = prepare_sort_from_pathkeys(NULL,
+												 subplan, pathkeys,
 												 subpath->parent->relids,
 												 nodeSortColIdx,
 												 false,
@@ -1334,7 +1342,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 												 &sortColIdx,
 												 &sortOperators,
 												 &collations,
-												 &nullsFirst);
+												 &nullsFirst,
+												 NULL);
 
 			/*
 			 * Check that we got the same sort key information.  We just
@@ -1358,7 +1367,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 			{
 				Sort	   *sort = make_sort(subplan, numsortkeys,
 											 sortColIdx, sortOperators,
-											 collations, nullsFirst);
+											 collations, nullsFirst,
+											 NULL);
 
 				label_sort_with_costsize(root, sort, best_path->limit_tuples);
 				subplan = (Plan *) sort;
@@ -1467,7 +1477,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 	 * function result; it must be the same plan node.  However, we then need
 	 * to detect whether any tlist entries were added.
 	 */
-	(void) prepare_sort_from_pathkeys(plan, pathkeys,
+	(void) prepare_sort_from_pathkeys(NULL,
+									  plan, pathkeys,
 									  best_path->path.parent->relids,
 									  NULL,
 									  true,
@@ -1475,7 +1486,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 									  &node->sortColIdx,
 									  &node->sortOperators,
 									  &node->collations,
-									  &node->nullsFirst);
+									  &node->nullsFirst,
+									  NULL);
 	tlist_was_changed = (orig_tlist_length != list_length(plan->targetlist));
 
 	/*
@@ -1498,7 +1510,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 		subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
 
 		/* Compute sort column info, and adjust subplan's tlist as needed */
-		subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
+		subplan = prepare_sort_from_pathkeys(NULL,
+											 subplan, pathkeys,
 											 subpath->parent->relids,
 											 node->sortColIdx,
 											 false,
@@ -1506,7 +1519,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 											 &sortColIdx,
 											 &sortOperators,
 											 &collations,
-											 &nullsFirst);
+											 &nullsFirst,
+											 NULL);
 
 		/*
 		 * Check that we got the same sort key information.  We just Assert
@@ -1530,7 +1544,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 		{
 			Sort	   *sort = make_sort(subplan, numsortkeys,
 										 sortColIdx, sortOperators,
-										 collations, nullsFirst);
+										 collations, nullsFirst,
+										 NULL);
 
 			label_sort_with_costsize(root, sort, best_path->limit_tuples);
 			subplan = (Plan *) sort;
@@ -1977,7 +1992,8 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
 	Assert(pathkeys != NIL);
 
 	/* Compute sort column info, and adjust subplan's tlist as needed */
-	subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
+	subplan = prepare_sort_from_pathkeys(NULL,
+										 subplan, pathkeys,
 										 best_path->subpath->parent->relids,
 										 gm_plan->sortColIdx,
 										 false,
@@ -1985,7 +2001,8 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
 										 &gm_plan->sortColIdx,
 										 &gm_plan->sortOperators,
 										 &gm_plan->collations,
-										 &gm_plan->nullsFirst);
+										 &gm_plan->nullsFirst,
+										 NULL);
 
 
 	/*
@@ -2196,7 +2213,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
 	 * relids. Thus, if this sort path is based on a child relation, we must
 	 * pass its relids.
 	 */
-	plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys,
+	plan = make_sort_from_pathkeys(root, subplan, best_path->path.pathkeys,
 								   IS_OTHER_REL(best_path->subpath->parent) ?
 								   best_path->path.parent->relids : NULL);
 
@@ -4526,7 +4543,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	if (best_path->outersortkeys)
 	{
 		Relids		outer_relids = outer_path->parent->relids;
-		Sort	   *sort = make_sort_from_pathkeys(outer_plan,
+		Sort	   *sort = make_sort_from_pathkeys(NULL, outer_plan,
 												   best_path->outersortkeys,
 												   outer_relids);
 
@@ -4540,7 +4557,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	if (best_path->innersortkeys)
 	{
 		Relids		inner_relids = inner_path->parent->relids;
-		Sort	   *sort = make_sort_from_pathkeys(inner_plan,
+		Sort	   *sort = make_sort_from_pathkeys(NULL, inner_plan,
 												   best_path->innersortkeys,
 												   inner_relids);
 
@@ -6067,7 +6084,8 @@ make_mergejoin(List *tlist,
 static Sort *
 make_sort(Plan *lefttree, int numCols,
 		  AttrNumber *sortColIdx, Oid *sortOperators,
-		  Oid *collations, bool *nullsFirst)
+		  Oid *collations, bool *nullsFirst,
+		  bool mkqsApplicable)
 {
 	Sort	   *node;
 	Plan	   *plan;
@@ -6084,6 +6102,7 @@ make_sort(Plan *lefttree, int numCols,
 	node->sortOperators = sortOperators;
 	node->collations = collations;
 	node->nullsFirst = nullsFirst;
+	node->mkqsApplicable = mkqsApplicable;
 
 	return node;
 }
@@ -6161,7 +6180,8 @@ make_incrementalsort(Plan *lefttree, int numCols, int nPresortedCols,
  * or a Result stacked atop lefttree).
  */
 static Plan *
-prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+prepare_sort_from_pathkeys(PlannerInfo *root,
+						   Plan *lefttree, List *pathkeys,
 						   Relids relids,
 						   const AttrNumber *reqColIdx,
 						   bool adjust_tlist_in_place,
@@ -6169,7 +6189,8 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 						   AttrNumber **p_sortColIdx,
 						   Oid **p_sortOperators,
 						   Oid **p_collations,
-						   bool **p_nullsFirst)
+						   bool **p_nullsFirst,
+						   bool *mkqsApplicable)
 {
 	List	   *tlist = lefttree->targetlist;
 	ListCell   *i;
@@ -6179,6 +6200,33 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 	Oid		   *collations;
 	bool	   *nullsFirst;
 
+	/* Calculation of benefit from multi-key qsort
+	 *
+	 * The algorithm is:
+	 *
+	 *   mkqsBene = (sum of benefit of each sort key) - fixed cost
+	 *   (sum of benefit of each sort key) =
+	 *                 weight * (duplicated value ratio of previous sort key)
+	 *
+	 * We get the duplicated value ratio from catalog table (pg_statistic.
+	 * stadistinct), which is not always correct.
+	 * In addition, the value might be unavailable sometimes because the sort
+	 * key is not from a real table or the query to catalog table fails. For
+	 * the case, we simply ignore the sort key for calcaulation.
+	 *
+	 * Enable mk qsort only when estimated benefit >= 0.05, which means
+	 * "mk qsort is supposed to be 5% faster than classical qsort".
+	 */
+
+	/* Set mkqsBene to -0.02 to indicate the fixed cost */
+	double		mkqsBene = -0.02;
+
+	/*
+	 * Duplicated value ratio of previous sort key, init to 1.0 for first sort
+	 * key to indicate "count all value"
+	 */
+	double      dupRio = 1.0;
+
 	/*
 	 * We will need at most list_length(pathkeys) sort columns; possibly less
 	 */
@@ -6188,6 +6236,9 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 	collations = (Oid *) palloc(numsortkeys * sizeof(Oid));
 	nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));
 
+	if (mkqsApplicable)
+		*mkqsApplicable = false;
+
 	numsortkeys = 0;
 
 	foreach(i, pathkeys)
@@ -6200,6 +6251,9 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 		Oid			sortop;
 		ListCell   *j;
 
+		/* Init value of ndist to -1 to indicate "unknown" */
+		double ndist = -1;
+
 		if (ec->ec_has_volatile)
 		{
 			/*
@@ -6303,6 +6357,54 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 			lefttree->targetlist = tlist;	/* just in case NIL before */
 		}
 
+		/* Get the ndist value from pg_stats */
+		if (mkqsApplicable && root != NULL)
+		{
+			/*
+			 * Calculate ndist only when SORT is based on a real table
+			 */
+			if (IsA(tle->expr, Var))
+			{
+				Var *var = (Var *)tle->expr;
+				RangeTblEntry *rte = root->simple_rte_array[var->varno];
+
+				/*
+				 * Try to get distinct info from pg_statistic
+				 */
+
+				if (rte)
+				{
+					HeapTuple tuple = SearchSysCache3(
+												STATRELATTINH,
+												ObjectIdGetDatum(rte->relid),
+												Int16GetDatum(var->varattno),
+												BoolGetDatum(false));
+    	        	if (HeapTupleIsValid(tuple))
+					{
+						/* Use the pg_statistic entry */
+						Form_pg_statistic stats;
+
+						stats = (Form_pg_statistic) GETSTRUCT(tuple);
+						/*
+						 * If stats->stadistinct < 0, it means a fraction of 
+						 * distinct tuple ratio; if it > 0, it means the number
+						 * of distinct tuples; 0 means "unknown".
+						 */
+						if (stats->stadistinct < 0)
+							ndist = -stats->stadistinct;
+						else
+						{
+							RelOptInfo *rel = root->simple_rel_array[var->varno];
+							ndist = stats->stadistinct / rel->tuples;
+						}
+					}
+
+					if (tuple)
+						ReleaseSysCache(tuple);
+				}
+			}
+		}
+
 		/*
 		 * Look up the correct sort operator from the PathKey's slightly
 		 * abstracted representation.
@@ -6321,6 +6423,61 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 		sortOperators[numsortkeys] = sortop;
 		collations[numsortkeys] = ec->ec_collation;
 		nullsFirst[numsortkeys] = pathkey->pk_nulls_first;
+
+		/*
+		 * If ndist is valid, calculate benefit for mk qsort
+		 */
+		if (ndist != -1)
+		{
+			double bene = 0;
+			SortSupportData sortKey;
+
+			sortKey.comparator = NULL;
+			sortKey.ssup_cxt = CurrentMemoryContext;
+			sortKey.ssup_collation = collations[numsortkeys];
+			sortKey.ssup_nulls_first = nullsFirst[numsortkeys];
+			sortKey.ssup_attno = sortColIdx[numsortkeys];
+			PrepareSortSupportFromOrderingOp(sortop, &sortKey);
+
+			/*
+			 * For data type with/without specialized comparator, use different
+			 * weights. The weights are determined by some experiments and may
+			 * be not accurate for all possible cases.
+			 */
+			if (sortKey.comparator == ssup_datum_unsigned_cmp ||
+#if SIZEOF_DATUM >= 8
+				sortKey.comparator == ssup_datum_signed_cmp ||
+#endif
+				sortKey.comparator == ssup_datum_int32_cmp)
+			{
+				/*
+				 * For data type with specialized comparator, ignore the first
+				 * sort key because there is no benefit from duplciated values
+				 * for first sort key.
+				 */
+				if (numsortkeys > 0)
+					bene += dupRio * 0.05;
+			}
+			else
+			{
+				/*
+				 * For data type without specialized comparator, mksort is
+				 * faster than classical qsort even there is no duplicate. So
+				 * add 1 to dupRio for the extra benefit from no-duplicate
+				 * values.
+				 */
+				bene += (dupRio + 1) * 0.05;
+			}
+			mkqsBene += bene;
+		}
+
+		/*
+		 * Remember duplicated value ratio of current sort key for
+		 * calculation by next sort key
+		 */
+		if (ndist != -1)
+			dupRio *= (1 - ndist);
+
 		numsortkeys++;
 	}
 
@@ -6331,6 +6488,13 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 	*p_collations = collations;
 	*p_nullsFirst = nullsFirst;
 
+	/* Enable mk qsort only when estimated benefit >= 0.05 */
+	if (mkqsBene >= 0.05)
+	{
+		Assert(mkqsApplicable);
+		*mkqsApplicable = true;
+	}
+
 	return lefttree;
 }
 
@@ -6343,16 +6507,19 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
  *	  'relids' is the set of relations required by prepare_sort_from_pathkeys()
  */
 static Sort *
-make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
+make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
+						List *pathkeys, Relids relids)
 {
 	int			numsortkeys;
 	AttrNumber *sortColIdx;
 	Oid		   *sortOperators;
 	Oid		   *collations;
 	bool	   *nullsFirst;
+	bool        mkqsApplicable;
 
 	/* Compute sort column info, and adjust lefttree as needed */
-	lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
+	lefttree = prepare_sort_from_pathkeys(root,
+										  lefttree, pathkeys,
 										  relids,
 										  NULL,
 										  false,
@@ -6360,12 +6527,13 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
 										  &sortColIdx,
 										  &sortOperators,
 										  &collations,
-										  &nullsFirst);
+										  &nullsFirst,
+										  &mkqsApplicable);
 
 	/* Now build the Sort node */
 	return make_sort(lefttree, numsortkeys,
 					 sortColIdx, sortOperators,
-					 collations, nullsFirst);
+					 collations, nullsFirst, mkqsApplicable);
 }
 
 /*
@@ -6388,7 +6556,8 @@ make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
 	bool	   *nullsFirst;
 
 	/* Compute sort column info, and adjust lefttree as needed */
-	lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
+	lefttree = prepare_sort_from_pathkeys(NULL,
+										  lefttree, pathkeys,
 										  relids,
 										  NULL,
 										  false,
@@ -6396,7 +6565,8 @@ make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
 										  &sortColIdx,
 										  &sortOperators,
 										  &collations,
-										  &nullsFirst);
+										  &nullsFirst,
+										  NULL);
 
 	/* Now build the Sort node */
 	return make_incrementalsort(lefttree, numsortkeys, nPresortedCols,
@@ -6444,7 +6614,8 @@ make_sort_from_sortclauses(List *sortcls, Plan *lefttree)
 
 	return make_sort(lefttree, numsortkeys,
 					 sortColIdx, sortOperators,
-					 collations, nullsFirst);
+					 collations, nullsFirst,
+					 NULL);
 }
 
 /*
@@ -6498,7 +6669,8 @@ make_sort_from_groupcols(List *groupcls,
 
 	return make_sort(lefttree, numsortkeys,
 					 sortColIdx, sortOperators,
-					 collations, nullsFirst);
+					 collations, nullsFirst,
+					 NULL);
 }
 
 static Material *
diff --git a/src/backend/utils/sort/mk_qsort_tuple.c b/src/backend/utils/sort/mk_qsort_tuple.c
index f8588df54b..cb6f409bdd 100644
--- a/src/backend/utils/sort/mk_qsort_tuple.c
+++ b/src/backend/utils/sort/mk_qsort_tuple.c
@@ -163,12 +163,14 @@ mkqs_compare_datum_by_shortcut(SortTuple      *tuple1,
 										  tuple2->datum1,
 										  tuple2->isnull1,
 										  sortKey);
+#if SIZEOF_DATUM >= 8
 	else if (compFuncType == MKQS_COMP_FUNC_SIGNED)
 		ret = ApplySignedSortComparator(tuple1->datum1,
 										tuple1->isnull1,
 										tuple2->datum1,
 										tuple2->isnull1,
 										sortKey);
+#endif
 	else if (compFuncType == MKQS_COMP_FUNC_INT32)
 		ret = ApplyInt32SortComparator(tuple1->datum1,
 									   tuple1->isnull1,
@@ -387,8 +389,10 @@ mkqs_compare_tuple(SortTuple *a, SortTuple *b, Tuplesortstate *state)
 
 	if (compFuncType == MKQS_COMP_FUNC_UNSIGNED)
 		ret = qsort_tuple_unsigned_compare(a, b, state);
+#if SIZEOF_DATUM >= 8
 	else if (compFuncType == MKQS_COMP_FUNC_SIGNED)
 		ret = qsort_tuple_signed_compare(a, b, state);
+#endif
 	else if (compFuncType == MKQS_COMP_FUNC_INT32)
 		ret = qsort_tuple_int32_compare(a, b, state);
 	else
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 6dd21a2710..ea55113dc3 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -341,6 +341,9 @@ struct Tuplesortstate
 
 	/* Whether multi-key quick sort is used */
 	bool		mkqsUsed;
+
+	/* Should multi-key quick be used or not? Determined by optimizer */
+	bool        mkqsApplicable;
 };
 
 /*
@@ -2736,6 +2739,9 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 		 *     type is supported by mk qsort. (By now only Heap tuple and Btree
 		 *     Index tuple are supported, and more types may be supported in
 		 *     future.)
+		 *  4. state->mkqsApplicable is true, which is determined by optimizer
+		 *     and means "mk qsort is supposed to be faster than qsort" for
+		 *     current case.
 		 *
 		 * A summary of tuple types supported by mk qsort:
 		 *
@@ -2749,7 +2755,8 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
 		 */
 		if (enable_mk_sort &&
 			state->base.nKeys > 1 &&
-			state->base.mkqsGetDatumFunc != NULL)
+			state->base.mkqsGetDatumFunc != NULL &&
+			state->mkqsApplicable)
 		{
 			/*
 			 * Set relevant Datum Sort Comparator according to concrete data type
@@ -3275,3 +3282,9 @@ ssup_datum_int32_cmp(Datum x, Datum y, SortSupport ssup)
 	else
 		return 0;
 }
+
+void tuplesort_set_mkqsApplicable(Tuplesortstate *state,
+								  bool mkqsApplicable)
+{
+	state->mkqsApplicable = mkqsApplicable;
+}
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e025679f89..2e1c22701a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -946,6 +946,9 @@ typedef struct Sort
 
 	/* NULLS FIRST/LAST directions */
 	bool	   *nullsFirst pg_node_attr(array_size(numCols));
+
+	/* Should multi-key quick be used or not? Determined by optimizer */
+	bool        mkqsApplicable;
 } Sort;
 
 /* ----------------
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index 60eb77ee01..cfcfdd6354 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -507,6 +507,8 @@ extern BrinTuple *tuplesort_getbrintuple(Tuplesortstate *state, Size *len,
 										 bool forward);
 extern bool tuplesort_getdatum(Tuplesortstate *state, bool forward, bool copy,
 							   Datum *val, bool *isNull, Datum *abbrev);
+extern void tuplesort_set_mkqsApplicable(Tuplesortstate *state,
+										 bool mkqsApplicable);
 
 
 #endif							/* TUPLESORT_H */
-- 
2.25.1

#20John Naylor
johncnaylorls@gmail.com
In reply to: Yao Wang (#19)
Re: 回复: An implementation of multi-key sort

On Fri, Jul 26, 2024 at 6:18 PM Yao Wang <yao-yw.wang@broadcom.com> wrote:

2. Should we update optimizer to take in account statistic
(pg_statistic->stadistinct) and choose mksort/qsort accordingly?

According to the result, the choosing mechanism in optimizer
almost eliminated all regressions. Please note that even when mksort is
disabled (i.e. qsort was performed twice actually), there are still
"regressions" which are usually less than 5% and should be accounted
to kinda error range.

This is kind of an understatement. To be clear, I see mostly ~1%,
which I take to be in the noise level. If it were commonly 5%
regression, that'd be cause for concern. If actual noise were 5%, the
testing methodology is not strict enough.

I am still hesitating about putting the code to final version because
there are a number of concerns:

a. for sort keys without a real table, stadistinct is unavailable.
b. stadistinct may not be accurate as mentioned.
c. the formula I used may not be able to cover all possible cases.

On the other hand, the worst result may be just 5% regression. So the
side effects may not be so serious?

FWIW, I share these concerns as well. I don't believe this proves a
bound on the worst result, since the test is using ideal well-behaved
data with no filter. The worst result is when the estimates are wrong,
so real world use could easily be back to 10-20% regression, which is
not acceptable. I believe this is what Robert and Tomas were warning
about.

It'd be good to understand what causes the differences, whether better
or worse. Some initial thoughts:

- If the first key is unique, then I would hope multikey would be no
different then a standard sort.

- If the first key commonly ties, and other following keys tie also,
those later comparisons are a waste. In that case, it's not hard to
imagine that partitioning on only one key at a time might be fast.

- If the first key commonly ties, but the second key is closer to
unique, I'm not sure which way is better. Have we tested this case?

- If we actually only have one sort key, a multi-key sort with a
single depth should ideally have no significant performance difference
than standard sort. That seems like a good sanity check. Has this been
tried?

- For the biggest benefit/regression cases, it'd be good to know what
changed at the hardware level. # comparsisons? # swaps? # cache
misses? # branch mispredicts?

Looking at the code a bit, I have some questions and see some
architectural issues. My thoughts below have a bunch of brainstorms so
should be taken with a large grain of salt:

1. The new single-use abstraction for the btree tid tiebreak seems
awkward. In standard sort, all that knowledge was confined to btree's
full comparetup function. Now it's spread out, and general code has to
worry about "duplicated" tuples. The passed-down isNull seems only
needed for this? (Quick idea: It seems we could pass a start-depth and
max-depth to some "comparetup_mk", which would be very similar to
current "comparetup + comparetup_tiebreak". The btree function would
have to know that a depth greater than the last sortkey is a signal to
do the tid comparison. And if start-depth and max-depth are the same,
that means comparing at a single depth. That might simplify the code
elsewhere because there is no need for a separate getDatum function.)

2. I don't understand why the pre-ordered check sometimes tolerates
duplicates and sometimes doesn't.

3. "tiebreak" paths and terminology is already somewhat awkward for
standard sort (my fault), but seems really out of place in multikey
sort. It already has a general concept of "depth", so that should be
used in fullest generality.

3A. Random thought: I wonder if the shortcut (and abbreviated?)
comparisons could be thought of as having their own depth < 0. If it's
worth it to postpone later keys, maybe it's worth it to postpone the
full comparison for the first key as well? I could be wrong, though.

3B. Side note: I've long wanted to try separating all NULL first keys
to a separate array, so we can remove all those branches for NULL
ordering and reduce SortTuple to 16 bytes. That might be easier to
code if we could simply specify "start_depth = 1" at the top level for
that.

4. Trying to stuff all our optimized comparators in the same path was
a heroic effort, but it's quite messy and seems pretty bad for the
instruction cache and branch predictor. I don't think we need yet
another template, but some of these branches should be taken as we
recurse into a partition to keep them out of the hot path.

5.
+ /*
+ * When the count < 16 and no need to handle duplicated tuples, use
+ * bubble sort.
+ *
+ * Use 16 instead of 7 which is used in standard qsort, because mk qsort
+ * need more cost to maintain more complex state.

Note: 7 isn't ideal for standard sort either, and should probably be
at least 10 (at least for single-key sorts). If one implementation's
parameter is more ideal than the other, it obscures what the true
trade-offs are. 16 happens to be a power of two -- how many different
values did you test? (And isn't this the same as our insertion sort,
not bubble sort?).