| /*------------------------------------------------------------------------- |
| * |
| * ginlogic.c |
| * routines for performing binary- and ternary-logic consistent checks. |
| * |
| * A GIN operator class can provide a boolean or ternary consistent |
| * function, or both. This file provides both boolean and ternary |
| * interfaces to the rest of the GIN code, even if only one of them is |
| * implemented by the opclass. |
| * |
| * Providing a boolean interface when the opclass implements only the |
| * ternary function is straightforward - just call the ternary function |
| * with the check-array as is, and map the GIN_TRUE, GIN_FALSE, GIN_MAYBE |
| * return codes to TRUE, FALSE and TRUE+recheck, respectively. Providing |
| * a ternary interface when the opclass only implements a boolean function |
| * is implemented by calling the boolean function many times, with all the |
| * MAYBE arguments set to all combinations of TRUE and FALSE (up to a |
| * certain number of MAYBE arguments). |
| * |
| * (A boolean function is enough to determine if an item matches, but a |
| * GIN scan can apply various optimizations if it can determine that an |
| * item matches or doesn't match, even if it doesn't know if some of the |
| * keys are present or not. That's what the ternary consistent function |
| * is used for.) |
| * |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * src/backend/access/gin/ginlogic.c |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres.h" |
| |
| #include "access/gin_private.h" |
| #include "access/reloptions.h" |
| #include "catalog/pg_collation.h" |
| #include "catalog/pg_type.h" |
| #include "miscadmin.h" |
| #include "storage/indexfsm.h" |
| #include "storage/lmgr.h" |
| |
| |
| /* |
| * Maximum number of MAYBE inputs that shimTriConsistentFn will try to |
| * resolve by calling all combinations. |
| */ |
| #define MAX_MAYBE_ENTRIES 4 |
| |
| /* |
| * Dummy consistent functions for an EVERYTHING key. Just claim it matches. |
| */ |
| static bool |
| trueConsistentFn(GinScanKey key) |
| { |
| key->recheckCurItem = false; |
| return true; |
| } |
| static GinTernaryValue |
| trueTriConsistentFn(GinScanKey key) |
| { |
| return GIN_TRUE; |
| } |
| |
| /* |
| * A helper function for calling a regular, binary logic, consistent function. |
| */ |
| static bool |
| directBoolConsistentFn(GinScanKey key) |
| { |
| /* |
| * Initialize recheckCurItem in case the consistentFn doesn't know it |
| * should set it. The safe assumption in that case is to force recheck. |
| */ |
| key->recheckCurItem = true; |
| |
| return DatumGetBool(FunctionCall8Coll(key->consistentFmgrInfo, |
| key->collation, |
| PointerGetDatum(key->entryRes), |
| UInt16GetDatum(key->strategy), |
| key->query, |
| UInt32GetDatum(key->nuserentries), |
| PointerGetDatum(key->extra_data), |
| PointerGetDatum(&key->recheckCurItem), |
| PointerGetDatum(key->queryValues), |
| PointerGetDatum(key->queryCategories))); |
| } |
| |
| /* |
| * A helper function for calling a native ternary logic consistent function. |
| */ |
| static GinTernaryValue |
| directTriConsistentFn(GinScanKey key) |
| { |
| return DatumGetGinTernaryValue(FunctionCall7Coll(key->triConsistentFmgrInfo, |
| key->collation, |
| PointerGetDatum(key->entryRes), |
| UInt16GetDatum(key->strategy), |
| key->query, |
| UInt32GetDatum(key->nuserentries), |
| PointerGetDatum(key->extra_data), |
| PointerGetDatum(key->queryValues), |
| PointerGetDatum(key->queryCategories))); |
| } |
| |
| /* |
| * This function implements a binary logic consistency check, using a ternary |
| * logic consistent function provided by the opclass. GIN_MAYBE return value |
| * is interpreted as true with recheck flag. |
| */ |
| static bool |
| shimBoolConsistentFn(GinScanKey key) |
| { |
| GinTernaryValue result; |
| |
| result = DatumGetGinTernaryValue(FunctionCall7Coll(key->triConsistentFmgrInfo, |
| key->collation, |
| PointerGetDatum(key->entryRes), |
| UInt16GetDatum(key->strategy), |
| key->query, |
| UInt32GetDatum(key->nuserentries), |
| PointerGetDatum(key->extra_data), |
| PointerGetDatum(key->queryValues), |
| PointerGetDatum(key->queryCategories))); |
| if (result == GIN_MAYBE) |
| { |
| key->recheckCurItem = true; |
| return true; |
| } |
| else |
| { |
| key->recheckCurItem = false; |
| return result; |
| } |
| } |
| |
| /* |
| * This function implements a tri-state consistency check, using a boolean |
| * consistent function provided by the opclass. |
| * |
| * Our strategy is to call consistentFn with MAYBE inputs replaced with every |
| * combination of TRUE/FALSE. If consistentFn returns the same value for every |
| * combination, that's the overall result. Otherwise, return MAYBE. Testing |
| * every combination is O(n^2), so this is only feasible for a small number of |
| * MAYBE inputs. |
| * |
| * NB: This function modifies the key->entryRes array. For now that's okay |
| * so long as we restore the entry-time contents before returning. This may |
| * need revisiting if we ever invent multithreaded GIN scans, though. |
| */ |
| static GinTernaryValue |
| shimTriConsistentFn(GinScanKey key) |
| { |
| int nmaybe; |
| int maybeEntries[MAX_MAYBE_ENTRIES]; |
| int i; |
| bool boolResult; |
| bool recheck; |
| GinTernaryValue curResult; |
| |
| /* |
| * Count how many MAYBE inputs there are, and store their indexes in |
| * maybeEntries. If there are too many MAYBE inputs, it's not feasible to |
| * test all combinations, so give up and return MAYBE. |
| */ |
| nmaybe = 0; |
| for (i = 0; i < key->nentries; i++) |
| { |
| if (key->entryRes[i] == GIN_MAYBE) |
| { |
| if (nmaybe >= MAX_MAYBE_ENTRIES) |
| return GIN_MAYBE; |
| maybeEntries[nmaybe++] = i; |
| } |
| } |
| |
| /* |
| * If none of the inputs were MAYBE, we can just call the consistent |
| * function as-is. |
| */ |
| if (nmaybe == 0) |
| return directBoolConsistentFn(key); |
| |
| /* First call consistent function with all the maybe-inputs set FALSE */ |
| for (i = 0; i < nmaybe; i++) |
| key->entryRes[maybeEntries[i]] = GIN_FALSE; |
| curResult = directBoolConsistentFn(key); |
| recheck = key->recheckCurItem; |
| |
| for (;;) |
| { |
| /* Twiddle the entries for next combination. */ |
| for (i = 0; i < nmaybe; i++) |
| { |
| if (key->entryRes[maybeEntries[i]] == GIN_FALSE) |
| { |
| key->entryRes[maybeEntries[i]] = GIN_TRUE; |
| break; |
| } |
| else |
| key->entryRes[maybeEntries[i]] = GIN_FALSE; |
| } |
| if (i == nmaybe) |
| break; |
| |
| boolResult = directBoolConsistentFn(key); |
| recheck |= key->recheckCurItem; |
| |
| if (curResult != boolResult) |
| { |
| curResult = GIN_MAYBE; |
| break; |
| } |
| } |
| |
| /* TRUE with recheck is taken to mean MAYBE */ |
| if (curResult == GIN_TRUE && recheck) |
| curResult = GIN_MAYBE; |
| |
| /* We must restore the original state of the entryRes array */ |
| for (i = 0; i < nmaybe; i++) |
| key->entryRes[maybeEntries[i]] = GIN_MAYBE; |
| |
| return curResult; |
| } |
| |
| /* |
| * Set up the implementation of the consistent functions for a scan key. |
| */ |
| void |
| ginInitConsistentFunction(GinState *ginstate, GinScanKey key) |
| { |
| if (key->searchMode == GIN_SEARCH_MODE_EVERYTHING) |
| { |
| key->boolConsistentFn = trueConsistentFn; |
| key->triConsistentFn = trueTriConsistentFn; |
| } |
| else |
| { |
| key->consistentFmgrInfo = &ginstate->consistentFn[key->attnum - 1]; |
| key->triConsistentFmgrInfo = &ginstate->triConsistentFn[key->attnum - 1]; |
| key->collation = ginstate->supportCollation[key->attnum - 1]; |
| |
| if (OidIsValid(ginstate->consistentFn[key->attnum - 1].fn_oid)) |
| key->boolConsistentFn = directBoolConsistentFn; |
| else |
| key->boolConsistentFn = shimBoolConsistentFn; |
| |
| if (OidIsValid(ginstate->triConsistentFn[key->attnum - 1].fn_oid)) |
| key->triConsistentFn = directTriConsistentFn; |
| else |
| key->triConsistentFn = shimTriConsistentFn; |
| } |
| } |