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