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 */
27 typedef 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  */
42 static int
43 comparison_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  */
67 void
68 PrepareSortSupportComparisonShim(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  */
93 static void
94 FinishSortSupportFunction(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  */
133 void
134 PrepareSortSupportFromOrderingOp(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  */
160 void
161 PrepareSortSupportFromIndexRel(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