1/*-------------------------------------------------------------------------
2 *
3 * sortsupport.c
4 * Support routines for accelerated sorting.
5 *
6 *
7 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
9 *
10 * IDENTIFICATION
11 * src/backend/utils/sort/sortsupport.c
12 *
13 *-------------------------------------------------------------------------
14 */
15
16#include "postgres.h"
17
18#include "access/nbtree.h"
19#include "catalog/pg_am.h"
20#include "fmgr.h"
21#include "utils/lsyscache.h"
22#include "utils/rel.h"
23#include "utils/sortsupport.h"
24
25
26/* Info needed to use an old-style comparison function as a sort comparator */
27typedef struct
28{
29 FmgrInfo flinfo; /* lookup data for comparison function */
30 FunctionCallInfoBaseData fcinfo; /* reusable callinfo structure */
31} SortShimExtra;
32
33#define SizeForSortShimExtra(nargs) (offsetof(SortShimExtra, fcinfo) + SizeForFunctionCallInfo(nargs))
34
35/*
36 * Shim function for calling an old-style comparator
37 *
38 * This is essentially an inlined version of FunctionCall2Coll(), except
39 * we assume that the FunctionCallInfoBaseData was already mostly set up by
40 * PrepareSortSupportComparisonShim.
41 */
42static int
43comparison_shim(Datum x, Datum y, SortSupport ssup)
44{
45 SortShimExtra *extra = (SortShimExtra *) ssup->ssup_extra;
46 Datum result;
47
48 extra->fcinfo.args[0].value = x;
49 extra->fcinfo.args[1].value = y;
50
51 /* just for paranoia's sake, we reset isnull each time */
52 extra->fcinfo.isnull = false;
53
54 result = FunctionCallInvoke(&extra->fcinfo);
55
56 /* Check for null result, since caller is clearly not expecting one */
57 if (extra->fcinfo.isnull)
58 elog(ERROR, "function %u returned NULL", extra->flinfo.fn_oid);
59
60 return result;
61}
62
63/*
64 * Set up a shim function to allow use of an old-style btree comparison
65 * function as if it were a sort support comparator.
66 */
67void
68PrepareSortSupportComparisonShim(Oid cmpFunc, SortSupport ssup)
69{
70 SortShimExtra *extra;
71
72 extra = (SortShimExtra *) MemoryContextAlloc(ssup->ssup_cxt,
73 SizeForSortShimExtra(2));
74
75 /* Lookup the comparison function */
76 fmgr_info_cxt(cmpFunc, &extra->flinfo, ssup->ssup_cxt);
77
78 /* We can initialize the callinfo just once and re-use it */
79 InitFunctionCallInfoData(extra->fcinfo, &extra->flinfo, 2,
80 ssup->ssup_collation, NULL, NULL);
81 extra->fcinfo.args[0].isnull = false;
82 extra->fcinfo.args[1].isnull = false;
83
84 ssup->ssup_extra = extra;
85 ssup->comparator = comparison_shim;
86}
87
88/*
89 * Look up and call sortsupport function to setup SortSupport comparator;
90 * or if no such function exists or it declines to set up the appropriate
91 * state, prepare a suitable shim.
92 */
93static void
94FinishSortSupportFunction(Oid opfamily, Oid opcintype, SortSupport ssup)
95{
96 Oid sortSupportFunction;
97
98 /* Look for a sort support function */
99 sortSupportFunction = get_opfamily_proc(opfamily, opcintype, opcintype,
100 BTSORTSUPPORT_PROC);
101 if (OidIsValid(sortSupportFunction))
102 {
103 /*
104 * The sort support function can provide a comparator, but it can also
105 * choose not to so (e.g. based on the selected collation).
106 */
107 OidFunctionCall1(sortSupportFunction, PointerGetDatum(ssup));
108 }
109
110 if (ssup->comparator == NULL)
111 {
112 Oid sortFunction;
113
114 sortFunction = get_opfamily_proc(opfamily, opcintype, opcintype,
115 BTORDER_PROC);
116
117 if (!OidIsValid(sortFunction))
118 elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
119 BTORDER_PROC, opcintype, opcintype, opfamily);
120
121 /* We'll use a shim to call the old-style btree comparator */
122 PrepareSortSupportComparisonShim(sortFunction, ssup);
123 }
124}
125
126/*
127 * Fill in SortSupport given an ordering operator (btree "<" or ">" operator).
128 *
129 * Caller must previously have zeroed the SortSupportData structure and then
130 * filled in ssup_cxt, ssup_collation, and ssup_nulls_first. This will fill
131 * in ssup_reverse as well as the comparator function pointer.
132 */
133void
134PrepareSortSupportFromOrderingOp(Oid orderingOp, SortSupport ssup)
135{
136 Oid opfamily;
137 Oid opcintype;
138 int16 strategy;
139
140 Assert(ssup->comparator == NULL);
141
142 /* Find the operator in pg_amop */
143 if (!get_ordering_op_properties(orderingOp, &opfamily, &opcintype,
144 &strategy))
145 elog(ERROR, "operator %u is not a valid ordering operator",
146 orderingOp);
147 ssup->ssup_reverse = (strategy == BTGreaterStrategyNumber);
148
149 FinishSortSupportFunction(opfamily, opcintype, ssup);
150}
151
152/*
153 * Fill in SortSupport given an index relation, attribute, and strategy.
154 *
155 * Caller must previously have zeroed the SortSupportData structure and then
156 * filled in ssup_cxt, ssup_attno, ssup_collation, and ssup_nulls_first. This
157 * will fill in ssup_reverse (based on the supplied strategy), as well as the
158 * comparator function pointer.
159 */
160void
161PrepareSortSupportFromIndexRel(Relation indexRel, int16 strategy,
162 SortSupport ssup)
163{
164 Oid opfamily = indexRel->rd_opfamily[ssup->ssup_attno - 1];
165 Oid opcintype = indexRel->rd_opcintype[ssup->ssup_attno - 1];
166
167 Assert(ssup->comparator == NULL);
168
169 if (indexRel->rd_rel->relam != BTREE_AM_OID)
170 elog(ERROR, "unexpected non-btree AM: %u", indexRel->rd_rel->relam);
171 if (strategy != BTGreaterStrategyNumber &&
172 strategy != BTLessStrategyNumber)
173 elog(ERROR, "unexpected sort support strategy: %d", strategy);
174 ssup->ssup_reverse = (strategy == BTGreaterStrategyNumber);
175
176 FinishSortSupportFunction(opfamily, opcintype, ssup);
177}
178