From 49e93bda7ddc25d23b3b2d66bb65c9b3db256ee0 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
Date: Wed, 7 Jun 2023 16:42:17 -0700
Subject: [PATCH 2/3] WIP: create ScanKeys from derived null tests

...for pushdown to the TableAM layer.

During the scan fixup, we have to also adjust the new splan->keyexprs
list. Otherwise our Vars won't reference the correct RTEs.

TODO:
- quals made redundant with the ScanKeys need to be removed
- costing
---
 src/backend/commands/explain.c          | 12 +++++-
 src/backend/executor/nodeSeqscan.c      | 53 +++++++++++++++++++++++-
 src/backend/optimizer/plan/createplan.c | 55 ++++++++++++++++++++++++-
 src/backend/optimizer/plan/setrefs.c    |  3 ++
 src/include/nodes/execnodes.h           |  2 +
 src/include/nodes/plannodes.h           |  1 +
 6 files changed, 122 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8570b14f62..622d0615bc 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1821,12 +1821,20 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			if (es->analyze)
 				show_tidbitmap_info((BitmapHeapScanState *) planstate, es);
 			break;
+		case T_SeqScan:
+			if (((SeqScan *) plan)->keyexprs)
+				show_scan_qual(((SeqScan *) plan)->keyexprs, "Scan Cond",
+							   planstate, ancestors, es);
+			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+			if (plan->qual)
+				show_instrumentation_count("Rows Removed by Filter", 1,
+										   planstate, es);
+			break;
 		case T_SampleScan:
 			show_tablesample(((SampleScan *) plan)->tablesample,
 							 planstate, ancestors, es);
-			/* fall through to print additional fields the same as SeqScan */
+			/* fall through to print additional fields the same as basic scans */
 			/* FALLTHROUGH */
-		case T_SeqScan:
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_NamedTuplestoreScan:
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 4da0f28f7b..67ff9d1482 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -28,6 +28,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/skey.h"
 #include "access/tableam.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSeqscan.h"
@@ -70,7 +71,7 @@ SeqNext(SeqScanState *node)
 		 */
 		scandesc = table_beginscan(node->ss.ss_currentRelation,
 								   estate->es_snapshot,
-								   0, NULL);
+								   node->nkeys, node->scankeys);
 		node->ss.ss_currentScanDesc = scandesc;
 	}
 
@@ -91,6 +92,8 @@ SeqRecheck(SeqScanState *node, TupleTableSlot *slot)
 	/*
 	 * Note that unlike IndexScan, SeqScan never use keys in heap_beginscan
 	 * (and this is very bad) - so, here we do not check are keys ok or not.
+	 *
+	 * TODO: so I guess this isn't true anymore.
 	 */
 	return true;
 }
@@ -114,6 +117,48 @@ ExecSeqScan(PlanState *pstate)
 					(ExecScanRecheckMtd) SeqRecheck);
 }
 
+static void
+create_scankeys(SeqScanState *scanstate, List *keyexprs)
+{
+	struct ScanKeyData *keys;
+	int			nkeys;
+	ListCell   *l;
+
+	nkeys = list_length(keyexprs);
+	keys = palloc(sizeof(struct ScanKeyData) * nkeys);
+
+	foreach(l, keyexprs)
+	{
+		NullTest   *n;
+		Var		   *var;
+		int			typeflag;
+		int			i = foreach_current_index(l);
+
+		/*
+		 * create_seqscan_plan() only puts NullTests of Vars into the SeqScan's
+		 * key clauses, so that's all we handle here.
+		 */
+		n = lfirst_node(NullTest, l);
+		var = (Var *) n->arg;
+		Assert(IsA(var, Var));
+
+		typeflag = (n->nulltesttype == IS_NULL) ? SK_SEARCHNULL
+												: SK_SEARCHNOTNULL;
+
+		Assert(i < nkeys);
+		ScanKeyEntryInitialize(&keys[i],
+							   SK_ISNULL | typeflag,
+							   var->varattno,
+							   InvalidStrategy,
+							   InvalidOid,
+							   InvalidOid,
+							   InvalidOid,
+							   (Datum) 0);
+	}
+
+	scanstate->nkeys = nkeys;
+	scanstate->scankeys = keys;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitSeqScan
@@ -171,6 +216,12 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
 	scanstate->ss.ps.qual =
 		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
 
+	/*
+	 * Populate scankeys, if necessary.
+	 */
+	if (node->keyexprs)
+		create_scankeys(scanstate, node->keyexprs);
+
 	return scanstate;
 }
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index af48109058..c7cfe59ea9 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -177,7 +177,8 @@ static void copy_generic_path_info(Plan *dest, Path *src);
 static void copy_plan_costsize(Plan *dest, Plan *src);
 static void label_sort_with_costsize(PlannerInfo *root, Sort *plan,
 									 double limit_tuples);
-static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
+static SeqScan *make_seqscan(List *qptlist, List *qpqual, List *scankeys,
+							 Index scanrelid);
 static SampleScan *make_samplescan(List *qptlist, List *qpqual, Index scanrelid,
 								   TableSampleClause *tsc);
 static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
@@ -2885,6 +2886,47 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
  *****************************************************************************/
 
 
+/*
+ * reconstruct_null_tests
+ *   Creates a list of NullTests, one for each Var in varset, which is a
+ *   multibitmapset of varno/varattno. Whole-row Vars are omitted.
+ */
+static List *
+reconstruct_null_tests(List *tests, NullTestType type, List *varset)
+{
+	ListCell   *lc;
+
+	foreach(lc, varset)
+	{
+		Bitmapset  *varattnos = lfirst_node(Bitmapset, lc);
+		int			i = -1;
+
+		while ((i = bms_next_member(varattnos, i)) >= 0)
+		{
+			AttrNumber	varattno = i + FirstLowInvalidHeapAttributeNumber;
+			Var		   *var;
+			NullTest   *n;
+
+			if (varattno == 0)
+				continue; /* skip whole-row vars */
+
+			var = makeNode(Var);
+			var->varno = foreach_current_index(lc);
+			var->varattno = varattno;
+
+			n = makeNode(NullTest);
+			n->arg = (Expr *) var;
+			n->nulltesttype = type;
+			n->location = -1;
+
+			tests = lappend(tests, n);
+		}
+	}
+
+	return tests;
+}
+
+
 /*
  * create_seqscan_plan
  *	 Returns a seqscan plan for the base relation scanned by 'best_path'
@@ -2896,6 +2938,7 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
 {
 	SeqScan    *scan_plan;
 	Index		scan_relid = best_path->parent->relid;
+	List	   *keyexprs = NIL;
 
 	/* it should be a base rel... */
 	Assert(scan_relid > 0);
@@ -2914,8 +2957,16 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
 			replace_nestloop_params(root, (Node *) scan_clauses);
 	}
 
+	keyexprs =
+		reconstruct_null_tests(keyexprs, IS_NULL,
+							   find_forced_null_vars((Node *) scan_clauses));
+	keyexprs =
+		reconstruct_null_tests(keyexprs, IS_NOT_NULL,
+							   find_nonnullable_vars((Node *) scan_clauses));
+
 	scan_plan = make_seqscan(tlist,
 							 scan_clauses,
+							 keyexprs,
 							 scan_relid);
 
 	copy_generic_path_info(&scan_plan->scan.plan, best_path);
@@ -5460,6 +5511,7 @@ bitmap_subplan_mark_shared(Plan *plan)
 static SeqScan *
 make_seqscan(List *qptlist,
 			 List *qpqual,
+			 List *keyexprs,
 			 Index scanrelid)
 {
 	SeqScan    *node = makeNode(SeqScan);
@@ -5470,6 +5522,7 @@ make_seqscan(List *qptlist,
 	plan->lefttree = NULL;
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
+	node->keyexprs = keyexprs;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 97fa561e4e..2b58b6c2b3 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -632,6 +632,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				splan->scan.plan.qual =
 					fix_scan_list(root, splan->scan.plan.qual,
 								  rtoffset, NUM_EXEC_QUAL(plan));
+				splan->keyexprs =
+					fix_scan_list(root, splan->keyexprs,
+								  rtoffset, NUM_EXEC_QUAL(plan));
 			}
 			break;
 		case T_SampleScan:
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..d015a3a127 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1485,6 +1485,8 @@ typedef struct SeqScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
 	Size		pscan_len;		/* size of parallel heap scan descriptor */
+	struct ScanKeyData *scankeys;
+	int			nkeys;
 } SeqScanState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 1b787fe031..b6dbf64d1e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -394,6 +394,7 @@ typedef struct Scan
 typedef struct SeqScan
 {
 	Scan		scan;
+	List	   *keyexprs;		/* expressions to push down as ScanKeys */
 } SeqScan;
 
 /* ----------------
-- 
2.25.1

