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

