From 8ab99c472801eab2bf10dea2c895b7b2716dec06 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 22 Nov 2019 03:24:09 +0300
Subject: [PATCH 3/5] Force GIN recheck for empty ALL keys more accurately

---
 src/backend/access/gin/ginscan.c | 81 +++++++++++++++++++++++++++++++---------
 1 file changed, 64 insertions(+), 17 deletions(-)

diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c
index 903891f..47beef3 100644
--- a/src/backend/access/gin/ginscan.c
+++ b/src/backend/access/gin/ginscan.c
@@ -129,19 +129,21 @@ ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum,
  * Initialize the next GinScanKey using the output from the extractQueryFn
  */
 static void
-ginFillScanKey(GinScanOpaque so, OffsetNumber attnum,
-			   StrategyNumber strategy, int32 searchMode,
+ginFillScanKey(GinScanOpaque so, GinScanKey	key, bool initHiddenEntries,
+			   OffsetNumber attnum, StrategyNumber strategy, int32 searchMode,
 			   Datum query, uint32 nQueryValues,
 			   Datum *queryValues, GinNullCategory *queryCategories,
 			   bool *partial_matches, Pointer *extra_data)
 {
-	GinScanKey	key = &(so->keys[so->nkeys++]);
 	GinState   *ginstate = &so->ginstate;
 	uint32		nUserQueryValues = nQueryValues;
 	uint32		i;
 
+	if (key == NULL)
+		key = &(so->keys[so->nkeys++]);
+
 	/* Non-default search modes add one "hidden" entry to each key */
-	if (searchMode != GIN_SEARCH_MODE_DEFAULT)
+	if (searchMode != GIN_SEARCH_MODE_DEFAULT && initHiddenEntries)
 		nQueryValues++;
 	key->nentries = nQueryValues;
 	key->nuserentries = nUserQueryValues;
@@ -270,8 +272,14 @@ ginNewScanKey(IndexScanDesc scan)
 	GinScanOpaque so = (GinScanOpaque) scan->opaque;
 	int			i;
 	bool		hasNullQuery = false;
-	bool		colNotNull[INDEX_MAX_KEYS] = {0};
-	int			numNotNullKeys = 0;
+	/*
+	 * GIN_FALSE - column has no search keys
+	 * GIN_TRUE  - NOT NULL is implied by some non-empty search key
+	 * GIN_MAYBE - NOT NULL is implied by some empty ALL key
+	 */
+	GinTernaryValue	colNotNull[INDEX_MAX_KEYS] = {0};
+	/* Number of GIN_MAYBE values in colNotNull[]  */
+	int			colNotNullCount = 0;
 	MemoryContext oldCtx;
 
 	/*
@@ -360,13 +368,34 @@ ginNewScanKey(IndexScanDesc scan)
 				 * any non-ALL scankeys; otherwise we end up adding a NOT_NULL
 				 * scankey below.
 				 */
-				so->forcedRecheck = true;
+				GinScanKeyData key;
+				GinTernaryValue res;
+
+				/* Check whether unconditional recheck is needed. */
+				ginFillScanKey(so, &key, false, skey->sk_attno,
+							   skey->sk_strategy, searchMode,
+							   skey->sk_argument, 0,
+							   NULL, NULL, NULL, NULL);
+
+				res = key.triConsistentFn(&key);
+
+				if (res == GIN_FALSE)
+				{
+					so->isVoidRes = true;	/* unsatisfiable query */
+					break;
+				}
+
+				so->forcedRecheck |= res != GIN_TRUE;
 
 				/*
-				 * Increment the number of columns with NOT NULL constraints.
+				 * Increment the number of columns with NOT NULL constraints
+				 * if NOT NULL is not yet implied by some non-empty key.
 				 */
-				colNotNull[colno] = true;
-				numNotNullKeys++;
+				if (colNotNull[colno] == GIN_FALSE)
+				{
+					colNotNull[colno] = GIN_MAYBE;
+					colNotNullCount++;
+				}
 
 				continue;
 			}
@@ -375,6 +404,16 @@ ginNewScanKey(IndexScanDesc scan)
 		}
 
 		/*
+		 * Current non-empty key implies that column is NOT NULL, so decrement
+		 * the number of columns with NOT NULL if NOT NULL already is implied by
+		 * some empty ALL key.
+		 */
+		if (colNotNull[colno] == GIN_MAYBE)
+			colNotNullCount--;
+
+		colNotNull[colno] = GIN_TRUE;
+
+		/*
 		 * Create GinNullCategory representation.  If the extractQueryFn
 		 * didn't create a nullFlags array, we assume everything is non-null.
 		 * While at it, detect whether any null keys are present.
@@ -394,7 +433,7 @@ ginNewScanKey(IndexScanDesc scan)
 			}
 		}
 
-		ginFillScanKey(so, skey->sk_attno,
+		ginFillScanKey(so, NULL, true, skey->sk_attno,
 					   skey->sk_strategy, searchMode,
 					   skey->sk_argument, nQueryValues,
 					   queryValues, categories,
@@ -412,24 +451,32 @@ ginNewScanKey(IndexScanDesc scan)
 			hasNullQuery = true;
 
 			/* Initialize EVERYTHING key if there are no non-NULL columns. */
-			if (!numNotNullKeys)
+			if (!colNotNullCount)
 			{
-				ginFillScanKey(so, FirstOffsetNumber, InvalidStrategy,
-							   GIN_SEARCH_MODE_EVERYTHING, (Datum) 0, 0,
-							   NULL, NULL, NULL, NULL);
+				ginFillScanKey(so, NULL, true, FirstOffsetNumber,
+							   InvalidStrategy, GIN_SEARCH_MODE_EVERYTHING,
+							   (Datum) 0, 0, NULL, NULL, NULL, NULL);
 			}
 			else
 			{
 				/* Initialize NOT_NULL key for each non-NULL column. */
 				for (i = 0; i < scan->indexRelation->rd_att->natts; i++)
 				{
-					if (colNotNull[i])
-						ginFillScanKey(so, i + 1, InvalidStrategy,
+					if (colNotNull[i] == GIN_MAYBE)
+						ginFillScanKey(so, NULL, true, i + 1, InvalidStrategy,
 									   GIN_SEARCH_MODE_NOT_NULL, (Datum) 0, 0,
 									   NULL, NULL, NULL, NULL);
 				}
 			}
 		}
+		else if (colNotNullCount)
+		{
+			/*
+			 * We use recheck instead of adding NOT_NULL entries to eliminate
+			 * rows with NULL columns.
+			 */
+			so->forcedRecheck = true;
+		}
 
 		/*
 		 * If the index is version 0, it may be missing null and placeholder
-- 
2.7.4

