/src/postgres/src/backend/access/gin/ginlogic.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * ginlogic.c |
4 | | * routines for performing binary- and ternary-logic consistent checks. |
5 | | * |
6 | | * A GIN operator class can provide a boolean or ternary consistent |
7 | | * function, or both. This file provides both boolean and ternary |
8 | | * interfaces to the rest of the GIN code, even if only one of them is |
9 | | * implemented by the opclass. |
10 | | * |
11 | | * Providing a boolean interface when the opclass implements only the |
12 | | * ternary function is straightforward - just call the ternary function |
13 | | * with the check-array as is, and map the GIN_TRUE, GIN_FALSE, GIN_MAYBE |
14 | | * return codes to TRUE, FALSE and TRUE+recheck, respectively. Providing |
15 | | * a ternary interface when the opclass only implements a boolean function |
16 | | * is implemented by calling the boolean function many times, with all the |
17 | | * MAYBE arguments set to all combinations of TRUE and FALSE (up to a |
18 | | * certain number of MAYBE arguments). |
19 | | * |
20 | | * (A boolean function is enough to determine if an item matches, but a |
21 | | * GIN scan can apply various optimizations if it can determine that an |
22 | | * item matches or doesn't match, even if it doesn't know if some of the |
23 | | * keys are present or not. That's what the ternary consistent function |
24 | | * is used for.) |
25 | | * |
26 | | * |
27 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
28 | | * Portions Copyright (c) 1994, Regents of the University of California |
29 | | * |
30 | | * IDENTIFICATION |
31 | | * src/backend/access/gin/ginlogic.c |
32 | | *------------------------------------------------------------------------- |
33 | | */ |
34 | | |
35 | | #include "postgres.h" |
36 | | |
37 | | #include "access/gin_private.h" |
38 | | |
39 | | |
40 | | /* |
41 | | * Maximum number of MAYBE inputs that shimTriConsistentFn will try to |
42 | | * resolve by calling all combinations. |
43 | | */ |
44 | 0 | #define MAX_MAYBE_ENTRIES 4 |
45 | | |
46 | | /* |
47 | | * Dummy consistent functions for an EVERYTHING key. Just claim it matches. |
48 | | */ |
49 | | static bool |
50 | | trueConsistentFn(GinScanKey key) |
51 | 0 | { |
52 | 0 | key->recheckCurItem = false; |
53 | 0 | return true; |
54 | 0 | } |
55 | | static GinTernaryValue |
56 | | trueTriConsistentFn(GinScanKey key) |
57 | 0 | { |
58 | 0 | return GIN_TRUE; |
59 | 0 | } |
60 | | |
61 | | /* |
62 | | * A helper function for calling a regular, binary logic, consistent function. |
63 | | */ |
64 | | static bool |
65 | | directBoolConsistentFn(GinScanKey key) |
66 | 0 | { |
67 | | /* |
68 | | * Initialize recheckCurItem in case the consistentFn doesn't know it |
69 | | * should set it. The safe assumption in that case is to force recheck. |
70 | | */ |
71 | 0 | key->recheckCurItem = true; |
72 | |
|
73 | 0 | return DatumGetBool(FunctionCall8Coll(key->consistentFmgrInfo, |
74 | 0 | key->collation, |
75 | 0 | PointerGetDatum(key->entryRes), |
76 | 0 | UInt16GetDatum(key->strategy), |
77 | 0 | key->query, |
78 | 0 | UInt32GetDatum(key->nuserentries), |
79 | 0 | PointerGetDatum(key->extra_data), |
80 | 0 | PointerGetDatum(&key->recheckCurItem), |
81 | 0 | PointerGetDatum(key->queryValues), |
82 | 0 | PointerGetDatum(key->queryCategories))); |
83 | 0 | } |
84 | | |
85 | | /* |
86 | | * A helper function for calling a native ternary logic consistent function. |
87 | | */ |
88 | | static GinTernaryValue |
89 | | directTriConsistentFn(GinScanKey key) |
90 | 0 | { |
91 | 0 | return DatumGetGinTernaryValue(FunctionCall7Coll(key->triConsistentFmgrInfo, |
92 | 0 | key->collation, |
93 | 0 | PointerGetDatum(key->entryRes), |
94 | 0 | UInt16GetDatum(key->strategy), |
95 | 0 | key->query, |
96 | 0 | UInt32GetDatum(key->nuserentries), |
97 | 0 | PointerGetDatum(key->extra_data), |
98 | 0 | PointerGetDatum(key->queryValues), |
99 | 0 | PointerGetDatum(key->queryCategories))); |
100 | 0 | } |
101 | | |
102 | | /* |
103 | | * This function implements a binary logic consistency check, using a ternary |
104 | | * logic consistent function provided by the opclass. GIN_MAYBE return value |
105 | | * is interpreted as true with recheck flag. |
106 | | */ |
107 | | static bool |
108 | | shimBoolConsistentFn(GinScanKey key) |
109 | 0 | { |
110 | 0 | GinTernaryValue result; |
111 | |
|
112 | 0 | result = DatumGetGinTernaryValue(FunctionCall7Coll(key->triConsistentFmgrInfo, |
113 | 0 | key->collation, |
114 | 0 | PointerGetDatum(key->entryRes), |
115 | 0 | UInt16GetDatum(key->strategy), |
116 | 0 | key->query, |
117 | 0 | UInt32GetDatum(key->nuserentries), |
118 | 0 | PointerGetDatum(key->extra_data), |
119 | 0 | PointerGetDatum(key->queryValues), |
120 | 0 | PointerGetDatum(key->queryCategories))); |
121 | 0 | if (result == GIN_MAYBE) |
122 | 0 | { |
123 | 0 | key->recheckCurItem = true; |
124 | 0 | return true; |
125 | 0 | } |
126 | 0 | else |
127 | 0 | { |
128 | 0 | key->recheckCurItem = false; |
129 | 0 | return result; |
130 | 0 | } |
131 | 0 | } |
132 | | |
133 | | /* |
134 | | * This function implements a tri-state consistency check, using a boolean |
135 | | * consistent function provided by the opclass. |
136 | | * |
137 | | * Our strategy is to call consistentFn with MAYBE inputs replaced with every |
138 | | * combination of TRUE/FALSE. If consistentFn returns the same value for every |
139 | | * combination, that's the overall result. Otherwise, return MAYBE. Testing |
140 | | * every combination is O(n^2), so this is only feasible for a small number of |
141 | | * MAYBE inputs. |
142 | | * |
143 | | * NB: This function modifies the key->entryRes array. For now that's okay |
144 | | * so long as we restore the entry-time contents before returning. This may |
145 | | * need revisiting if we ever invent multithreaded GIN scans, though. |
146 | | */ |
147 | | static GinTernaryValue |
148 | | shimTriConsistentFn(GinScanKey key) |
149 | 0 | { |
150 | 0 | int nmaybe; |
151 | 0 | int maybeEntries[MAX_MAYBE_ENTRIES]; |
152 | 0 | int i; |
153 | 0 | bool boolResult; |
154 | 0 | bool recheck; |
155 | 0 | GinTernaryValue curResult; |
156 | | |
157 | | /* |
158 | | * Count how many MAYBE inputs there are, and store their indexes in |
159 | | * maybeEntries. If there are too many MAYBE inputs, it's not feasible to |
160 | | * test all combinations, so give up and return MAYBE. |
161 | | */ |
162 | 0 | nmaybe = 0; |
163 | 0 | for (i = 0; i < key->nentries; i++) |
164 | 0 | { |
165 | 0 | if (key->entryRes[i] == GIN_MAYBE) |
166 | 0 | { |
167 | 0 | if (nmaybe >= MAX_MAYBE_ENTRIES) |
168 | 0 | return GIN_MAYBE; |
169 | 0 | maybeEntries[nmaybe++] = i; |
170 | 0 | } |
171 | 0 | } |
172 | | |
173 | | /* |
174 | | * If none of the inputs were MAYBE, we can just call the consistent |
175 | | * function as-is. |
176 | | */ |
177 | 0 | if (nmaybe == 0) |
178 | 0 | return directBoolConsistentFn(key); |
179 | | |
180 | | /* First call consistent function with all the maybe-inputs set FALSE */ |
181 | 0 | for (i = 0; i < nmaybe; i++) |
182 | 0 | key->entryRes[maybeEntries[i]] = GIN_FALSE; |
183 | 0 | curResult = directBoolConsistentFn(key); |
184 | 0 | recheck = key->recheckCurItem; |
185 | |
|
186 | 0 | for (;;) |
187 | 0 | { |
188 | | /* Twiddle the entries for next combination. */ |
189 | 0 | for (i = 0; i < nmaybe; i++) |
190 | 0 | { |
191 | 0 | if (key->entryRes[maybeEntries[i]] == GIN_FALSE) |
192 | 0 | { |
193 | 0 | key->entryRes[maybeEntries[i]] = GIN_TRUE; |
194 | 0 | break; |
195 | 0 | } |
196 | 0 | else |
197 | 0 | key->entryRes[maybeEntries[i]] = GIN_FALSE; |
198 | 0 | } |
199 | 0 | if (i == nmaybe) |
200 | 0 | break; |
201 | | |
202 | 0 | boolResult = directBoolConsistentFn(key); |
203 | 0 | recheck |= key->recheckCurItem; |
204 | |
|
205 | 0 | if (curResult != boolResult) |
206 | 0 | { |
207 | 0 | curResult = GIN_MAYBE; |
208 | 0 | break; |
209 | 0 | } |
210 | 0 | } |
211 | | |
212 | | /* TRUE with recheck is taken to mean MAYBE */ |
213 | 0 | if (curResult == GIN_TRUE && recheck) |
214 | 0 | curResult = GIN_MAYBE; |
215 | | |
216 | | /* We must restore the original state of the entryRes array */ |
217 | 0 | for (i = 0; i < nmaybe; i++) |
218 | 0 | key->entryRes[maybeEntries[i]] = GIN_MAYBE; |
219 | |
|
220 | 0 | return curResult; |
221 | 0 | } |
222 | | |
223 | | /* |
224 | | * Set up the implementation of the consistent functions for a scan key. |
225 | | */ |
226 | | void |
227 | | ginInitConsistentFunction(GinState *ginstate, GinScanKey key) |
228 | 0 | { |
229 | 0 | if (key->searchMode == GIN_SEARCH_MODE_EVERYTHING) |
230 | 0 | { |
231 | 0 | key->boolConsistentFn = trueConsistentFn; |
232 | 0 | key->triConsistentFn = trueTriConsistentFn; |
233 | 0 | } |
234 | 0 | else |
235 | 0 | { |
236 | 0 | key->consistentFmgrInfo = &ginstate->consistentFn[key->attnum - 1]; |
237 | 0 | key->triConsistentFmgrInfo = &ginstate->triConsistentFn[key->attnum - 1]; |
238 | 0 | key->collation = ginstate->supportCollation[key->attnum - 1]; |
239 | |
|
240 | 0 | if (OidIsValid(ginstate->consistentFn[key->attnum - 1].fn_oid)) |
241 | 0 | key->boolConsistentFn = directBoolConsistentFn; |
242 | 0 | else |
243 | 0 | key->boolConsistentFn = shimBoolConsistentFn; |
244 | |
|
245 | 0 | if (OidIsValid(ginstate->triConsistentFn[key->attnum - 1].fn_oid)) |
246 | 0 | key->triConsistentFn = directTriConsistentFn; |
247 | 0 | else |
248 | 0 | key->triConsistentFn = shimTriConsistentFn; |
249 | 0 | } |
250 | 0 | } |