]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/sort/tuplesort.c
Change the planner-to-executor API so that the planner tells the executor
[postgresql] / src / backend / utils / sort / tuplesort.c
index 7d503f244fb07b597087d0338d637628ebd15373..76c59a423821363adfdb97324814d4c74d4f0191 100644 (file)
@@ -77,7 +77,7 @@
  * grounds that 7 is the "sweet spot" on the tapes-to-passes curve according
  * to Knuth's figure 70 (section 5.4.2).  However, Knuth is assuming that
  * tape drives are expensive beasts, and in particular that there will always
- * be many more runs than tape drives.  In our implementation a "tape drive"
+ * be many more runs than tape drives. In our implementation a "tape drive"
  * doesn't cost much more than a few Kb of memory buffers, so we can afford
  * to have lots of them.  In particular, if we can have as many tape drives
  * as sorted runs, we can eliminate any repeated I/O at all.  In the current
  * above.  Nonetheless, with large workMem we can have many tapes.
  *
  *
- * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/sort/tuplesort.c,v 1.68 2006/07/14 14:52:25 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/sort/tuplesort.c,v 1.74 2007/01/10 18:06:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -119,28 +119,28 @@ bool              trace_sort = false;
 
 
 /*
- * The objects we actually sort are SortTuple structs.  These contain
+ * The objects we actually sort are SortTuple structs. These contain
  * a pointer to the tuple proper (might be a MinimalTuple or IndexTuple),
  * which is a separate palloc chunk --- we assume it is just one chunk and
  * can be freed by a simple pfree().  SortTuples also contain the tuple's
  * first key column in Datum/nullflag format, and an index integer.
  *
  * Storing the first key column lets us save heap_getattr or index_getattr
- * calls during tuple comparisons.  We could extract and save all the key
+ * calls during tuple comparisons.     We could extract and save all the key
  * columns not just the first, but this would increase code complexity and
  * overhead, and wouldn't actually save any comparison cycles in the common
  * case where the first key determines the comparison result.  Note that
  * for a pass-by-reference datatype, datum1 points into the "tuple" storage.
  *
  * When sorting single Datums, the data value is represented directly by
- * datum1/isnull1.  If the datatype is pass-by-reference and isnull1 is false,
+ * datum1/isnull1.     If the datatype is pass-by-reference and isnull1 is false,
  * then datum1 points to a separately palloc'd data value that is also pointed
  * to by the "tuple" pointer; otherwise "tuple" is NULL.
  *
  * While building initial runs, tupindex holds the tuple's run number.  During
  * merge passes, we re-use it to hold the input tape number that each tuple in
  * the heap was read from, or to hold the index of the next tuple pre-read
- * from the same tape in the case of pre-read entries.  tupindex goes unused
+ * from the same tape in the case of pre-read entries. tupindex goes unused
  * if the sort occurs entirely in memory.
  */
 typedef struct
@@ -201,11 +201,11 @@ struct Tuplesortstate
         * They are set up by the tuplesort_begin_xxx routines.
         *
         * Function to compare two tuples; result is per qsort() convention, ie:
-        *
-        * <0, 0, >0 according as a<b, a=b, a>b.
+        * <0, 0, >0 according as a<b, a=b, a>b.  The API must match
+        * qsort_arg_comparator.
         */
-       int                     (*comparetup) (Tuplesortstate *state,
-                                                          const SortTuple *a, const SortTuple *b);
+       int                     (*comparetup) (const SortTuple *a, const SortTuple *b,
+                                                                                  Tuplesortstate *state);
 
        /*
         * Function to copy a supplied input tuple into palloc'd space and set up
@@ -223,19 +223,19 @@ struct Tuplesortstate
         * state->availMem by the amount of memory space thereby released.
         */
        void            (*writetup) (Tuplesortstate *state, int tapenum,
-                                                        SortTuple *stup);
+                                                                                SortTuple *stup);
 
        /*
         * Function to read a stored tuple from tape back into memory. 'len' is
         * the already-read length of the stored tuple.  Create a palloc'd copy,
-        * initialize tuple/datum1/isnull1 in the target SortTuple struct,
-        * and decrease state->availMem by the amount of memory space consumed.
+        * initialize tuple/datum1/isnull1 in the target SortTuple struct, and
+        * decrease state->availMem by the amount of memory space consumed.
         */
        void            (*readtup) (Tuplesortstate *state, SortTuple *stup,
-                                                       int tapenum, unsigned int len);
+                                                                               int tapenum, unsigned int len);
 
        /*
-        * This array holds the tuples now in sort memory.  If we are in state
+        * This array holds the tuples now in sort memory.      If we are in state
         * INITIAL, the tuples are in no particular order; if we are in state
         * SORTEDINMEM, the tuples are in final sorted order; in states BUILDRUNS
         * and FINALMERGE, the tuples are organized in "heap" order per Algorithm
@@ -255,8 +255,8 @@ struct Tuplesortstate
        int                     currentRun;
 
        /*
-        * Unless otherwise noted, all pointer variables below are pointers
-        * to arrays of length maxTapes, holding per-tape data.
+        * Unless otherwise noted, all pointer variables below are pointers to
+        * arrays of length maxTapes, holding per-tape data.
         */
 
        /*
@@ -280,7 +280,7 @@ struct Tuplesortstate
        int                *mergeavailslots;    /* slots left for prereading each tape */
        long       *mergeavailmem;      /* availMem for prereading each tape */
        int                     mergefreelist;  /* head of freelist of recycled slots */
-       int                     mergefirstfree; /* first slot never used in this merge */
+       int                     mergefirstfree; /* first slot never used in this merge */
 
        /*
         * Variables for Algorithm D.  Note that destTape is a "logical" tape
@@ -314,8 +314,7 @@ struct Tuplesortstate
         * tuplesort_begin_heap and used only by the MinimalTuple routines.
         */
        TupleDesc       tupDesc;
-       ScanKey         scanKeys;                       /* array of length nKeys */
-       SortFunctionKind *sortFnKinds;  /* array of length nKeys */
+       ScanKey         scanKeys;               /* array of length nKeys */
 
        /*
         * These variables are specific to the IndexTuple case; they are set by
@@ -330,9 +329,8 @@ struct Tuplesortstate
         * tuplesort_begin_datum and used only by the DatumTuple routines.
         */
        Oid                     datumType;
-       Oid                     sortOperator;
        FmgrInfo        sortOpFn;               /* cached lookup data for sortOperator */
-       SortFunctionKind sortFnKind;
+       int                     sortFnFlags;    /* equivalent to sk_flags */
        /* we need typelen and byval in order to know how to copy the Datums. */
        int                     datumTypeLen;
        bool            datumTypeByVal;
@@ -345,8 +343,8 @@ struct Tuplesortstate
 #endif
 };
 
-#define COMPARETUP(state,a,b)  ((*(state)->comparetup) (state, a, b))
-#define COPYTUP(state,stup,tup)        ((*(state)->copytup) (state, stup, tup))
+#define COMPARETUP(state,a,b)  ((*(state)->comparetup) (a, b, state))
+#define COPYTUP(state,stup,tup) ((*(state)->copytup) (state, stup, tup))
 #define WRITETUP(state,tape,stup)      ((*(state)->writetup) (state, tape, stup))
 #define READTUP(state,stup,tape,len) ((*(state)->readtup) (state, stup, tape, len))
 #define LACKMEM(state)         ((state)->availMem < 0)
@@ -410,36 +408,27 @@ static void tuplesort_heap_insert(Tuplesortstate *state, SortTuple *tuple,
 static void tuplesort_heap_siftup(Tuplesortstate *state, bool checkIndex);
 static unsigned int getlen(Tuplesortstate *state, int tapenum, bool eofOK);
 static void markrunend(Tuplesortstate *state, int tapenum);
-static int     qsort_comparetup(const void *a, const void *b);
-static int comparetup_heap(Tuplesortstate *state,
-                               const SortTuple *a, const SortTuple *b);
+static int comparetup_heap(const SortTuple *a, const SortTuple *b,
+                               Tuplesortstate *state);
 static void copytup_heap(Tuplesortstate *state, SortTuple *stup, void *tup);
 static void writetup_heap(Tuplesortstate *state, int tapenum,
-                                                 SortTuple *stup);
+                         SortTuple *stup);
 static void readtup_heap(Tuplesortstate *state, SortTuple *stup,
-                                                int tapenum, unsigned int len);
-static int comparetup_index(Tuplesortstate *state,
-                                const SortTuple *a, const SortTuple *b);
+                        int tapenum, unsigned int len);
+static int comparetup_index(const SortTuple *a, const SortTuple *b,
+                                Tuplesortstate *state);
 static void copytup_index(Tuplesortstate *state, SortTuple *stup, void *tup);
 static void writetup_index(Tuplesortstate *state, int tapenum,
-                                                  SortTuple *stup);
+                          SortTuple *stup);
 static void readtup_index(Tuplesortstate *state, SortTuple *stup,
-                                                 int tapenum, unsigned int len);
-static int comparetup_datum(Tuplesortstate *state,
-                                const SortTuple *a, const SortTuple *b);
+                         int tapenum, unsigned int len);
+static int comparetup_datum(const SortTuple *a, const SortTuple *b,
+                                Tuplesortstate *state);
 static void copytup_datum(Tuplesortstate *state, SortTuple *stup, void *tup);
 static void writetup_datum(Tuplesortstate *state, int tapenum,
-                                                  SortTuple *stup);
+                          SortTuple *stup);
 static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
-                                                 int tapenum, unsigned int len);
-
-/*
- * Since qsort(3) will not pass any context info to qsort_comparetup(),
- * we have to use this ugly static variable.  It is set to point to the
- * active Tuplesortstate object just before calling qsort.     It should
- * not be used directly by anything except qsort_comparetup().
- */
-static Tuplesortstate *qsort_tuplesortstate;
+                         int tapenum, unsigned int len);
 
 
 /*
@@ -469,8 +458,8 @@ tuplesort_begin_common(int workMem, bool randomAccess)
        MemoryContext oldcontext;
 
        /*
-        * Create a working memory context for this sort operation.
-        * All data needed by the sort will live inside this context.
+        * Create a working memory context for this sort operation. All data
+        * needed by the sort will live inside this context.
         */
        sortcontext = AllocSetContextCreate(CurrentMemoryContext,
                                                                                "TupleSort",
@@ -479,8 +468,8 @@ tuplesort_begin_common(int workMem, bool randomAccess)
                                                                                ALLOCSET_DEFAULT_MAXSIZE);
 
        /*
-        * Make the Tuplesortstate within the per-sort context.  This way,
-        * we don't need a separate pfree() operation for it at shutdown.
+        * Make the Tuplesortstate within the per-sort context.  This way, we
+        * don't need a separate pfree() operation for it at shutdown.
         */
        oldcontext = MemoryContextSwitchTo(sortcontext);
 
@@ -524,8 +513,8 @@ tuplesort_begin_common(int workMem, bool randomAccess)
 
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
-                                        int nkeys,
-                                        Oid *sortOperators, AttrNumber *attNums,
+                                        int nkeys, AttrNumber *attNums,
+                                        Oid *sortOperators, bool *nullsFirstFlags,
                                         int workMem, bool randomAccess)
 {
        Tuplesortstate *state = tuplesort_begin_common(workMem, randomAccess);
@@ -552,19 +541,19 @@ tuplesort_begin_heap(TupleDesc tupDesc,
 
        state->tupDesc = tupDesc;       /* assume we need not copy tupDesc */
        state->scanKeys = (ScanKey) palloc0(nkeys * sizeof(ScanKeyData));
-       state->sortFnKinds = (SortFunctionKind *)
-               palloc0(nkeys * sizeof(SortFunctionKind));
 
        for (i = 0; i < nkeys; i++)
        {
-               RegProcedure sortFunction;
+               Oid             sortFunction;
+               bool    reverse;
 
-               AssertArg(sortOperators[i] != 0);
                AssertArg(attNums[i] != 0);
+               AssertArg(sortOperators[i] != 0);
 
-               /* select a function that implements the sort operator */
-               SelectSortFunction(sortOperators[i], &sortFunction,
-                                                  &state->sortFnKinds[i]);
+               if (!get_compare_function_for_ordering_op(sortOperators[i],
+                                                                                                 &sortFunction, &reverse))
+                       elog(ERROR, "operator %u is not a valid ordering operator",
+                                sortOperators[i]);
 
                /*
                 * We needn't fill in sk_strategy or sk_subtype since these scankeys
@@ -575,6 +564,12 @@ tuplesort_begin_heap(TupleDesc tupDesc,
                                        InvalidStrategy,
                                        sortFunction,
                                        (Datum) 0);
+
+               /* However, we use btree's conventions for encoding directionality */
+               if (reverse)
+                       state->scanKeys[i].sk_flags |= SK_BT_DESC;
+               if (nullsFirstFlags[i])
+                       state->scanKeys[i].sk_flags |= SK_BT_NULLS_FIRST;
        }
 
        MemoryContextSwitchTo(oldcontext);
@@ -619,12 +614,13 @@ tuplesort_begin_index(Relation indexRel,
 
 Tuplesortstate *
 tuplesort_begin_datum(Oid datumType,
-                                         Oid sortOperator,
+                                         Oid sortOperator, bool nullsFirstFlag,
                                          int workMem, bool randomAccess)
 {
        Tuplesortstate *state = tuplesort_begin_common(workMem, randomAccess);
        MemoryContext oldcontext;
-       RegProcedure sortFunction;
+       Oid                     sortFunction;
+       bool            reverse;
        int16           typlen;
        bool            typbyval;
 
@@ -645,13 +641,19 @@ tuplesort_begin_datum(Oid datumType,
        state->readtup = readtup_datum;
 
        state->datumType = datumType;
-       state->sortOperator = sortOperator;
 
-       /* select a function that implements the sort operator */
-       SelectSortFunction(sortOperator, &sortFunction, &state->sortFnKind);
-       /* and look up the function */
+       /* lookup the ordering function */
+       if (!get_compare_function_for_ordering_op(sortOperator,
+                                                                                         &sortFunction, &reverse))
+               elog(ERROR, "operator %u is not a valid ordering operator",
+                        sortOperator);
        fmgr_info(sortFunction, &state->sortOpFn);
 
+       /* set ordering flags */
+       state->sortFnFlags = reverse ? SK_BT_DESC : 0;
+       if (nullsFirstFlag)
+               state->sortFnFlags |= SK_BT_NULLS_FIRST;
+
        /* lookup necessary attributes of the datum type */
        get_typlenbyval(datumType, &typlen, &typbyval);
        state->datumTypeLen = typlen;
@@ -689,8 +691,8 @@ tuplesort_end(Tuplesortstate *state)
        /*
         * Delete temporary "tape" files, if any.
         *
-        * Note: want to include this in reported total cost of sort, hence
-        * need for two #ifdef TRACE_SORT sections.
+        * Note: want to include this in reported total cost of sort, hence need
+        * for two #ifdef TRACE_SORT sections.
         */
        if (state->tapeset)
                LogicalTapeSetClose(state->tapeset);
@@ -710,8 +712,8 @@ tuplesort_end(Tuplesortstate *state)
        MemoryContextSwitchTo(oldcontext);
 
        /*
-        * Free the per-sort memory context, thereby releasing all working
-        * memory, including the Tuplesortstate struct itself.
+        * Free the per-sort memory context, thereby releasing all working memory,
+        * including the Tuplesortstate struct itself.
         */
        MemoryContextDelete(state->sortcontext);
 }
@@ -730,15 +732,16 @@ grow_memtuples(Tuplesortstate *state)
 {
        /*
         * We need to be sure that we do not cause LACKMEM to become true, else
-        * the space management algorithm will go nuts.  We assume here that
-        * the memory chunk overhead associated with the memtuples array is
-        * constant and so there will be no unexpected addition to what we ask
-        * for.  (The minimum array size established in tuplesort_begin_common
-        * is large enough to force palloc to treat it as a separate chunk, so
-        * this assumption should be good.  But let's check it.)
+        * the space management algorithm will go nuts.  We assume here that the
+        * memory chunk overhead associated with the memtuples array is constant
+        * and so there will be no unexpected addition to what we ask for.      (The
+        * minimum array size established in tuplesort_begin_common is large
+        * enough to force palloc to treat it as a separate chunk, so this
+        * assumption should be good.  But let's check it.)
         */
        if (state->availMem <= (long) (state->memtupsize * sizeof(SortTuple)))
                return false;
+
        /*
         * On a 64-bit machine, allowedMem could be high enough to get us into
         * trouble with MaxAllocSize, too.
@@ -813,8 +816,8 @@ tuplesort_putdatum(Tuplesortstate *state, Datum val, bool isNull)
        SortTuple       stup;
 
        /*
-        * If it's a pass-by-reference value, copy it into memory we control,
-        * and decrease availMem.  Then call the common code.
+        * If it's a pass-by-reference value, copy it into memory we control, and
+        * decrease availMem.  Then call the common code.
         */
        if (isNull || state->datumTypeByVal)
        {
@@ -846,10 +849,10 @@ puttuple_common(Tuplesortstate *state, SortTuple *tuple)
                case TSS_INITIAL:
 
                        /*
-                        * Save the tuple into the unsorted array.  First, grow the
-                        * array as needed.  Note that we try to grow the array when there
-                        * is still one free slot remaining --- if we fail, there'll still
-                        * be room to store the incoming tuple, and then we'll switch to
+                        * Save the tuple into the unsorted array.      First, grow the array
+                        * as needed.  Note that we try to grow the array when there is
+                        * still one free slot remaining --- if we fail, there'll still be
+                        * room to store the incoming tuple, and then we'll switch to
                         * tape-based operation.
                         */
                        if (state->memtupcount >= state->memtupsize - 1)
@@ -878,14 +881,14 @@ puttuple_common(Tuplesortstate *state, SortTuple *tuple)
                case TSS_BUILDRUNS:
 
                        /*
-                        * Insert the tuple into the heap, with run number
-                        * currentRun if it can go into the current run, else run number
-                        * currentRun+1.  The tuple can go into the current run if it is
-                        * >= the first not-yet-output tuple.  (Actually, it could go into
-                        * the current run if it is >= the most recently output tuple ...
-                        * but that would require keeping around the tuple we last output,
-                        * and it's simplest to let writetup free each tuple as soon as
-                        * it's written.)
+                        * Insert the tuple into the heap, with run number currentRun if
+                        * it can go into the current run, else run number currentRun+1.
+                        * The tuple can go into the current run if it is >= the first
+                        * not-yet-output tuple.  (Actually, it could go into the current
+                        * run if it is >= the most recently output tuple ... but that
+                        * would require keeping around the tuple we last output, and it's
+                        * simplest to let writetup free each tuple as soon as it's
+                        * written.)
                         *
                         * Note there will always be at least one tuple in the heap at
                         * this point; see dumptuples.
@@ -930,11 +933,11 @@ tuplesort_performsort(Tuplesortstate *state)
                         * amount of memory.  Just qsort 'em and we're done.
                         */
                        if (state->memtupcount > 1)
-                       {
-                               qsort_tuplesortstate = state;
-                               qsort((void *) state->memtuples, state->memtupcount,
-                                         sizeof(SortTuple), qsort_comparetup);
-                       }
+                               qsort_arg((void *) state->memtuples,
+                                                 state->memtupcount,
+                                                 sizeof(SortTuple),
+                                                 (qsort_arg_comparator) state->comparetup,
+                                                 (void *) state);
                        state->current = 0;
                        state->eof_reached = false;
                        state->markpos_offset = 0;
@@ -1271,14 +1274,14 @@ tuplesort_merge_order(long allowedMem)
        int                     mOrder;
 
        /*
-        * We need one tape for each merge input, plus another one for the
-        * output, and each of these tapes needs buffer space.  In addition
-        * we want MERGE_BUFFER_SIZE workspace per input tape (but the output
-        * tape doesn't count).
+        * We need one tape for each merge input, plus another one for the output,
+        * and each of these tapes needs buffer space.  In addition we want
+        * MERGE_BUFFER_SIZE workspace per input tape (but the output tape doesn't
+        * count).
         *
         * Note: you might be thinking we need to account for the memtuples[]
-        * array in this calculation, but we effectively treat that as part of
-        * the MERGE_BUFFER_SIZE workspace.
+        * array in this calculation, but we effectively treat that as part of the
+        * MERGE_BUFFER_SIZE workspace.
         */
        mOrder = (allowedMem - TAPE_BUFFER_OVERHEAD) /
                (MERGE_BUFFER_SIZE + TAPE_BUFFER_OVERHEAD);
@@ -1307,8 +1310,8 @@ inittapes(Tuplesortstate *state)
 
        /*
         * We must have at least 2*maxTapes slots in the memtuples[] array, else
-        * we'd not have room for merge heap plus preread.  It seems unlikely
-        * that this case would ever occur, but be safe.
+        * we'd not have room for merge heap plus preread.  It seems unlikely that
+        * this case would ever occur, but be safe.
         */
        maxTapes = Min(maxTapes, state->memtupsize / 2);
 
@@ -1323,12 +1326,12 @@ inittapes(Tuplesortstate *state)
 
        /*
         * Decrease availMem to reflect the space needed for tape buffers; but
-        * don't decrease it to the point that we have no room for tuples.
-        * (That case is only likely to occur if sorting pass-by-value Datums;
-        * in all other scenarios the memtuples[] array is unlikely to occupy
-        * more than half of allowedMem.  In the pass-by-value case it's not
-        * important to account for tuple space, so we don't care if LACKMEM
-        * becomes inaccurate.)
+        * don't decrease it to the point that we have no room for tuples. (That
+        * case is only likely to occur if sorting pass-by-value Datums; in all
+        * other scenarios the memtuples[] array is unlikely to occupy more than
+        * half of allowedMem.  In the pass-by-value case it's not important to
+        * account for tuple space, so we don't care if LACKMEM becomes
+        * inaccurate.)
         */
        tapeSpace = maxTapes * TAPE_BUFFER_OVERHEAD;
        if (tapeSpace + GetMemoryChunkSpace(state->memtuples) < state->allowedMem)
@@ -1444,7 +1447,7 @@ mergeruns(Tuplesortstate *state)
        /*
         * If we produced only one initial run (quite likely if the total data
         * volume is between 1X and 2X workMem), we can just use that tape as the
-        * finished output, rather than doing a useless merge.  (This obvious
+        * finished output, rather than doing a useless merge.  (This obvious
         * optimization is not in Knuth's algorithm.)
         */
        if (state->currentRun == 1)
@@ -1587,7 +1590,6 @@ mergeonerun(Tuplesortstate *state)
         */
        while (state->memtupcount > 0)
        {
-               CHECK_FOR_INTERRUPTS();
                /* write the tuple to destTape */
                priorAvail = state->availMem;
                srcTape = state->memtuples[0].tupindex;
@@ -1676,7 +1678,7 @@ beginmerge(Tuplesortstate *state)
        memset(state->mergelast, 0,
                   state->maxTapes * sizeof(*state->mergelast));
        state->mergefreelist = 0;       /* nothing in the freelist */
-       state->mergefirstfree = activeTapes;  /* 1st slot avail for preread */
+       state->mergefirstfree = activeTapes;            /* 1st slot avail for preread */
 
        /*
         * Initialize space allocation to let each active input tape have an equal
@@ -1976,7 +1978,7 @@ tuplesort_restorepos(Tuplesortstate *state)
 /*
  * Heap manipulation routines, per Knuth's Algorithm 5.2.3H.
  *
- * Compare two SortTuples.  If checkIndex is true, use the tuple index
+ * Compare two SortTuples.     If checkIndex is true, use the tuple index
  * as the front of the sort key; otherwise, no.
  */
 
@@ -1987,7 +1989,7 @@ tuplesort_restorepos(Tuplesortstate *state)
 
 /*
  * Insert a new tuple into an empty or existing heap, maintaining the
- * heap invariant.  Caller is responsible for ensuring there's room.
+ * heap invariant.     Caller is responsible for ensuring there's room.
  *
  * Note: we assume *tuple is a temporary variable that can be scribbled on.
  * For some callers, tuple actually points to a memtuples[] entry above the
@@ -2003,10 +2005,10 @@ tuplesort_heap_insert(Tuplesortstate *state, SortTuple *tuple,
        int                     j;
 
        /*
-        * Save the tupleindex --- see notes above about writing on *tuple.
-        * It's a historical artifact that tupleindex is passed as a separate
-        * argument and not in *tuple, but it's notationally convenient so
-        * let's leave it that way.
+        * Save the tupleindex --- see notes above about writing on *tuple. It's a
+        * historical artifact that tupleindex is passed as a separate argument
+        * and not in *tuple, but it's notationally convenient so let's leave it
+        * that way.
         */
        tuple->tupindex = tupleindex;
 
@@ -2092,113 +2094,26 @@ markrunend(Tuplesortstate *state, int tapenum)
 
 
 /*
- * qsort interface
- */
-
-static int
-qsort_comparetup(const void *a, const void *b)
-{
-       /* The passed pointers are pointers to SortTuple ... */
-       return COMPARETUP(qsort_tuplesortstate,
-                                         (const SortTuple *) a,
-                                         (const SortTuple *) b);
-}
-
-
-/*
- * This routine selects an appropriate sorting function to implement
- * a sort operator as efficiently as possible. The straightforward
- * method is to use the operator's implementation proc --- ie, "<"
- * comparison. However, that way often requires two calls of the function
- * per comparison.     If we can find a btree three-way comparator function
- * associated with the operator, we can use it to do the comparisons
- * more efficiently.  We also support the possibility that the operator
- * is ">" (descending sort), in which case we have to reverse the output
- * of the btree comparator.
- *
- * Possibly this should live somewhere else (backend/catalog/, maybe?).
+ * Set up for an external caller of ApplySortFunction.  This function
+ * basically just exists to localize knowledge of the encoding of sk_flags
+ * used in this module.
  */
 void
 SelectSortFunction(Oid sortOperator,
-                                  RegProcedure *sortFunction,
-                                  SortFunctionKind *kind)
+                                  bool nulls_first,
+                                  Oid *sortFunction,
+                                  int *sortFlags)
 {
-       CatCList   *catlist;
-       int                     i;
-       HeapTuple       tuple;
-       Form_pg_operator optup;
-       Oid                     opclass = InvalidOid;
-
-       /*
-        * Search pg_amop to see if the target operator is registered as the "<"
-        * or ">" operator of any btree opclass.  It's possible that it might be
-        * registered both ways (eg, if someone were to build a "reverse sort"
-        * opclass for some reason); prefer the "<" case if so. If the operator is
-        * registered the same way in multiple opclasses, assume we can use the
-        * associated comparator function from any one.
-        */
-       catlist = SearchSysCacheList(AMOPOPID, 1,
-                                                                ObjectIdGetDatum(sortOperator),
-                                                                0, 0, 0);
-
-       for (i = 0; i < catlist->n_members; i++)
-       {
-               Form_pg_amop aform;
-
-               tuple = &catlist->members[i]->tuple;
-               aform = (Form_pg_amop) GETSTRUCT(tuple);
-
-               if (!opclass_is_btree(aform->amopclaid))
-                       continue;
-               /* must be of default subtype, too */
-               if (aform->amopsubtype != InvalidOid)
-                       continue;
-
-               if (aform->amopstrategy == BTLessStrategyNumber)
-               {
-                       opclass = aform->amopclaid;
-                       *kind = SORTFUNC_CMP;
-                       break;                          /* done looking */
-               }
-               else if (aform->amopstrategy == BTGreaterStrategyNumber)
-               {
-                       opclass = aform->amopclaid;
-                       *kind = SORTFUNC_REVCMP;
-                       /* keep scanning in hopes of finding a BTLess entry */
-               }
-       }
+       bool    reverse;
 
-       ReleaseSysCacheList(catlist);
+       if (!get_compare_function_for_ordering_op(sortOperator,
+                                                                                         sortFunction, &reverse))
+               elog(ERROR, "operator %u is not a valid ordering operator",
+                        sortOperator);
 
-       if (OidIsValid(opclass))
-       {
-               /* Found a suitable opclass, get its default comparator function */
-               *sortFunction = get_opclass_proc(opclass, InvalidOid, BTORDER_PROC);
-               Assert(RegProcedureIsValid(*sortFunction));
-               return;
-       }
-
-       /*
-        * Can't find a comparator, so use the operator as-is.  Decide whether it
-        * is forward or reverse sort by looking at its name (grotty, but this
-        * only matters for deciding which end NULLs should get sorted to).  XXX
-        * possibly better idea: see whether its selectivity function is
-        * scalargtcmp?
-        */
-       tuple = SearchSysCache(OPEROID,
-                                                  ObjectIdGetDatum(sortOperator),
-                                                  0, 0, 0);
-       if (!HeapTupleIsValid(tuple))
-               elog(ERROR, "cache lookup failed for operator %u", sortOperator);
-       optup = (Form_pg_operator) GETSTRUCT(tuple);
-       if (strcmp(NameStr(optup->oprname), ">") == 0)
-               *kind = SORTFUNC_REVLT;
-       else
-               *kind = SORTFUNC_LT;
-       *sortFunction = optup->oprcode;
-       ReleaseSysCache(tuple);
-
-       Assert(RegProcedureIsValid(*sortFunction));
+       *sortFlags = reverse ? SK_BT_DESC : 0;
+       if (nulls_first)
+               *sortFlags |= SK_BT_NULLS_FIRST;
 }
 
 /*
@@ -2229,74 +2144,42 @@ myFunctionCall2(FmgrInfo *flinfo, Datum arg1, Datum arg2)
 /*
  * Apply a sort function (by now converted to fmgr lookup form)
  * and return a 3-way comparison result.  This takes care of handling
- * NULLs and sort ordering direction properly.
+ * reverse-sort and NULLs-ordering properly.  We assume that DESC and
+ * NULLS_FIRST options are encoded in sk_flags the same way btree does it.
  */
 static inline int32
-inlineApplySortFunction(FmgrInfo *sortFunction, SortFunctionKind kind,
+inlineApplySortFunction(FmgrInfo *sortFunction, int sk_flags,
                                                Datum datum1, bool isNull1,
                                                Datum datum2, bool isNull2)
 {
-       switch (kind)
-       {
-               case SORTFUNC_LT:
-                       if (isNull1)
-                       {
-                               if (isNull2)
-                                       return 0;
-                               return 1;               /* NULL sorts after non-NULL */
-                       }
-                       if (isNull2)
-                               return -1;
-                       if (DatumGetBool(myFunctionCall2(sortFunction, datum1, datum2)))
-                               return -1;              /* a < b */
-                       if (DatumGetBool(myFunctionCall2(sortFunction, datum2, datum1)))
-                               return 1;               /* a > b */
-                       return 0;
-
-               case SORTFUNC_REVLT:
-                       /* We reverse the ordering of NULLs, but not the operator */
-                       if (isNull1)
-                       {
-                               if (isNull2)
-                                       return 0;
-                               return -1;              /* NULL sorts before non-NULL */
-                       }
-                       if (isNull2)
-                               return 1;
-                       if (DatumGetBool(myFunctionCall2(sortFunction, datum1, datum2)))
-                               return -1;              /* a < b */
-                       if (DatumGetBool(myFunctionCall2(sortFunction, datum2, datum1)))
-                               return 1;               /* a > b */
-                       return 0;
-
-               case SORTFUNC_CMP:
-                       if (isNull1)
-                       {
-                               if (isNull2)
-                                       return 0;
-                               return 1;               /* NULL sorts after non-NULL */
-                       }
-                       if (isNull2)
-                               return -1;
-                       return DatumGetInt32(myFunctionCall2(sortFunction,
-                                                                                                datum1, datum2));
+       int32           compare;
 
-               case SORTFUNC_REVCMP:
-                       if (isNull1)
-                       {
-                               if (isNull2)
-                                       return 0;
-                               return -1;              /* NULL sorts before non-NULL */
-                       }
-                       if (isNull2)
-                               return 1;
-                       return -DatumGetInt32(myFunctionCall2(sortFunction,
-                                                                                                 datum1, datum2));
+       if (isNull1)
+       {
+               if (isNull2)
+                       compare = 0;            /* NULL "=" NULL */
+               else if (sk_flags & SK_BT_NULLS_FIRST)
+                       compare = -1;           /* NULL "<" NOT_NULL */
+               else
+                       compare = 1;            /* NULL ">" NOT_NULL */
+       }
+       else if (isNull2)
+       {
+               if (sk_flags & SK_BT_NULLS_FIRST)
+                       compare = 1;            /* NOT_NULL ">" NULL */
+               else
+                       compare = -1;           /* NOT_NULL "<" NULL */
+       }
+       else
+       {
+               compare = DatumGetInt32(myFunctionCall2(sortFunction,
+                                                                                               datum1, datum2));
 
-               default:
-                       elog(ERROR, "unrecognized SortFunctionKind: %d", (int) kind);
-                       return 0;                       /* can't get here, but keep compiler quiet */
+               if (sk_flags & SK_BT_DESC)
+                       compare = -compare;
        }
+
+       return compare;
 }
 
 /*
@@ -2304,11 +2187,11 @@ inlineApplySortFunction(FmgrInfo *sortFunction, SortFunctionKind kind,
  * C99's brain-dead notions about how to implement inline functions...
  */
 int32
-ApplySortFunction(FmgrInfo *sortFunction, SortFunctionKind kind,
+ApplySortFunction(FmgrInfo *sortFunction, int sortFlags,
                                  Datum datum1, bool isNull1,
                                  Datum datum2, bool isNull2)
 {
-       return inlineApplySortFunction(sortFunction, kind,
+       return inlineApplySortFunction(sortFunction, sortFlags,
                                                                   datum1, isNull1,
                                                                   datum2, isNull2);
 }
@@ -2319,7 +2202,7 @@ ApplySortFunction(FmgrInfo *sortFunction, SortFunctionKind kind,
  */
 
 static int
-comparetup_heap(Tuplesortstate *state, const SortTuple *a, const SortTuple *b)
+comparetup_heap(const SortTuple *a, const SortTuple *b, Tuplesortstate *state)
 {
        ScanKey         scanKey = state->scanKeys;
        HeapTupleData ltup;
@@ -2328,9 +2211,11 @@ comparetup_heap(Tuplesortstate *state, const SortTuple *a, const SortTuple *b)
        int                     nkey;
        int32           compare;
 
+       /* Allow interrupting long sorts */
+       CHECK_FOR_INTERRUPTS();
+
        /* Compare the leading sort key */
-       compare = inlineApplySortFunction(&scanKey->sk_func,
-                                                                         state->sortFnKinds[0],
+       compare = inlineApplySortFunction(&scanKey->sk_func, scanKey->sk_flags,
                                                                          a->datum1, a->isnull1,
                                                                          b->datum1, b->isnull1);
        if (compare != 0)
@@ -2354,8 +2239,7 @@ comparetup_heap(Tuplesortstate *state, const SortTuple *a, const SortTuple *b)
                datum1 = heap_getattr(&ltup, attno, tupDesc, &isnull1);
                datum2 = heap_getattr(&rtup, attno, tupDesc, &isnull2);
 
-               compare = inlineApplySortFunction(&scanKey->sk_func,
-                                                                                 state->sortFnKinds[nkey],
+               compare = inlineApplySortFunction(&scanKey->sk_func, scanKey->sk_flags,
                                                                                  datum1, isnull1,
                                                                                  datum2, isnull2);
                if (compare != 0)
@@ -2449,12 +2333,12 @@ readtup_heap(Tuplesortstate *state, SortTuple *stup,
  */
 
 static int
-comparetup_index(Tuplesortstate *state, const SortTuple *a, const SortTuple *b)
+comparetup_index(const SortTuple *a, const SortTuple *b, Tuplesortstate *state)
 {
        /*
         * This is similar to _bt_tuplecompare(), but we have already done the
-        * index_getattr calls for the first column, and we need to keep track
-        * of whether any null fields are present.  Also see the special treatment
+        * index_getattr calls for the first column, and we need to keep track of
+        * whether any null fields are present.  Also see the special treatment
         * for equal keys at the end.
         */
        ScanKey         scanKey = state->indexScanKey;
@@ -2466,9 +2350,11 @@ comparetup_index(Tuplesortstate *state, const SortTuple *a, const SortTuple *b)
        int                     nkey;
        int32           compare;
 
+       /* Allow interrupting long sorts */
+       CHECK_FOR_INTERRUPTS();
+
        /* Compare the leading sort key */
-       compare = inlineApplySortFunction(&scanKey->sk_func,
-                                                                         SORTFUNC_CMP,
+       compare = inlineApplySortFunction(&scanKey->sk_func, scanKey->sk_flags,
                                                                          a->datum1, a->isnull1,
                                                                          b->datum1, b->isnull1);
        if (compare != 0)
@@ -2494,14 +2380,9 @@ comparetup_index(Tuplesortstate *state, const SortTuple *a, const SortTuple *b)
                datum1 = index_getattr(tuple1, nkey, tupDes, &isnull1);
                datum2 = index_getattr(tuple2, nkey, tupDes, &isnull2);
 
-               /* see comments about NULLs handling in btbuild */
-
-               /* the comparison function is always of CMP type */
-               compare = inlineApplySortFunction(&scanKey->sk_func,
-                                                                                 SORTFUNC_CMP,
+               compare = inlineApplySortFunction(&scanKey->sk_func, scanKey->sk_flags,
                                                                                  datum1, isnull1,
                                                                                  datum2, isnull2);
-
                if (compare != 0)
                        return compare;         /* done when we find unequal attributes */
 
@@ -2622,9 +2503,12 @@ readtup_index(Tuplesortstate *state, SortTuple *stup,
  */
 
 static int
-comparetup_datum(Tuplesortstate *state, const SortTuple *a, const SortTuple *b)
+comparetup_datum(const SortTuple *a, const SortTuple *b, Tuplesortstate *state)
 {
-       return inlineApplySortFunction(&state->sortOpFn, state->sortFnKind,
+       /* Allow interrupting long sorts */
+       CHECK_FOR_INTERRUPTS();
+
+       return inlineApplySortFunction(&state->sortOpFn, state->sortFnFlags,
                                                                   a->datum1, a->isnull1,
                                                                   b->datum1, b->isnull1);
 }
@@ -2701,7 +2585,7 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
        }
        else
        {
-               void   *raddr = palloc(tuplen);
+               void       *raddr = palloc(tuplen);
 
                if (LogicalTapeRead(state->tapeset, tapenum, raddr,
                                                        tuplen) != tuplen)