From 81b42bfbd75d2a466c6119a92a1bb206081ae165 Mon Sep 17 00:00:00 2001 From: Matheus Alcantara Date: Thu, 2 Jul 2026 15:46:05 -0300 Subject: [PATCH v4 3/4] Add tests for CustomScan filter pushdown --- src/test/modules/Makefile | 1 + src/test/modules/meson.build | 1 + .../modules/test_bloom_customscan/Makefile | 23 ++ .../expected/test_bloom_customscan.out | 113 ++++++ .../modules/test_bloom_customscan/meson.build | 33 ++ .../sql/test_bloom_customscan.sql | 66 ++++ .../test_bloom_customscan--1.0.sql | 20 + .../test_bloom_customscan.c | 362 ++++++++++++++++++ .../test_bloom_customscan.control | 5 + 9 files changed, 624 insertions(+) create mode 100644 src/test/modules/test_bloom_customscan/Makefile create mode 100644 src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out create mode 100644 src/test/modules/test_bloom_customscan/meson.build create mode 100644 src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql create mode 100644 src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql create mode 100644 src/test/modules/test_bloom_customscan/test_bloom_customscan.c create mode 100644 src/test/modules/test_bloom_customscan/test_bloom_customscan.control diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 0a74ab5c86f..3f83feeb074 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -20,6 +20,7 @@ SUBDIRS = \ test_binaryheap \ test_bitmapset \ test_bloomfilter \ + test_bloom_customscan \ test_cloexec \ test_checksums \ test_copy_callbacks \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 4bca42bb370..e9efdb1e8a4 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -20,6 +20,7 @@ subdir('test_autovacuum') subdir('test_binaryheap') subdir('test_bitmapset') subdir('test_bloomfilter') +subdir('test_bloom_customscan') subdir('test_cloexec') subdir('test_checksums') subdir('test_copy_callbacks') diff --git a/src/test/modules/test_bloom_customscan/Makefile b/src/test/modules/test_bloom_customscan/Makefile new file mode 100644 index 00000000000..5de5b5e6f4b --- /dev/null +++ b/src/test/modules/test_bloom_customscan/Makefile @@ -0,0 +1,23 @@ +# src/test/modules/test_bloom_customscan/Makefile + +MODULE_big = test_bloom_customscan +OBJS = \ + $(WIN32RES) \ + test_bloom_customscan.o +PGFILEDESC = "test_bloom_customscan - CustomScan consuming a hashjoin bloom filter" + +EXTENSION = test_bloom_customscan +DATA = test_bloom_customscan--1.0.sql + +REGRESS = test_bloom_customscan + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_bloom_customscan +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out new file mode 100644 index 00000000000..e1ce5903a02 --- /dev/null +++ b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out @@ -0,0 +1,113 @@ +CREATE EXTENSION test_bloom_customscan; +-- ensure the library (and its planner hook + GUC) is loaded +LOAD 'test_bloom_customscan'; +-- Force a serial hash join, which is where the bloom-filter pushdown applies. +SET enable_hashjoin_bloom = on; +SET enable_mergejoin = off; +SET enable_nestloop = off; +SET max_parallel_workers_per_gather = 0; +CREATE TABLE cs_fact (a int, b int, payload int); +CREATE TABLE cs_dim (id int, id2 int, label text); +-- Fact keys range over 0..999; the dimension only covers 1..50, so the join +-- is highly selective and the pushed-down filter should reject most fact rows. +INSERT INTO cs_fact + SELECT g % 1000, g % 1000, g FROM generate_series(1, 20000) g; +INSERT INTO cs_dim + SELECT g, g, 'x' || g FROM generate_series(1, 50) g; +ANALYZE cs_fact; +ANALYZE cs_dim; +-- Offer the bloom-capable custom scan and force it to be chosen (the CustomPath +-- keeps disabled_nodes = 0, so disabling seqscan makes it win). +SET test_bloom_customscan.enable = on; +SET enable_seqscan = off; +-- The fact relation is scanned by our Custom Scan and receives the filter. +EXPLAIN (COSTS OFF, VERBOSE) +SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id; + QUERY PLAN +------------------------------------------------------------------------ + Aggregate + Output: count(*) + -> Hash Join + Hash Cond: (f.a = d.id) + -> Custom Scan (TestBloomCustomScan) on public.cs_fact f + Output: f.a + -> Hash + Output: d.id + -> Custom Scan (TestBloomCustomScan) on public.cs_dim d + Output: d.id +(10 rows) + +-- Single-key join: the filter must actually reject fact rows. +SELECT test_bloom_cs_reset(); + test_bloom_cs_reset +--------------------- + +(1 row) + +SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id; + count +------- + 1000 +(1 row) + +SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows; + filter_rejected_rows +---------------------- + f +(1 row) + +-- Correctness: the result must be identical with and without the filter. +SET test_bloom_customscan.enable = on; +SET enable_seqscan = off; +CREATE TEMP TABLE r_cs AS + SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a; +SET test_bloom_customscan.enable = off; +SET enable_seqscan = on; +CREATE TEMP TABLE r_plain AS + SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a; +SELECT count(*) AS cs_minus_plain + FROM (SELECT * FROM r_cs EXCEPT SELECT * FROM r_plain) x; + cs_minus_plain +---------------- + 0 +(1 row) + +SELECT count(*) AS plain_minus_cs + FROM (SELECT * FROM r_plain EXCEPT SELECT * FROM r_cs) x; + plain_minus_cs +---------------- + 0 +(1 row) + +-- Two-key join: the opted-in recipient also gets per-key filters built. +SET test_bloom_customscan.enable = on; +SET enable_seqscan = off; +SELECT test_bloom_cs_reset(); + test_bloom_cs_reset +--------------------- + +(1 row) + +SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id AND f.b = d.id2; + count +------- + 1000 +(1 row) + +SELECT test_bloom_cs_perkey_built() AS perkey_filters_built; + perkey_filters_built +---------------------- + f +(1 row) + +SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows; + filter_rejected_rows +---------------------- + f +(1 row) + +-- cleanup +SET test_bloom_customscan.enable = off; +SET enable_seqscan = on; +DROP TABLE cs_fact, cs_dim; +DROP EXTENSION test_bloom_customscan; diff --git a/src/test/modules/test_bloom_customscan/meson.build b/src/test/modules/test_bloom_customscan/meson.build new file mode 100644 index 00000000000..e12dc80942c --- /dev/null +++ b/src/test/modules/test_bloom_customscan/meson.build @@ -0,0 +1,33 @@ +# Copyright (c) 2022-2026, PostgreSQL Global Development Group + +test_bloom_customscan_sources = files( + 'test_bloom_customscan.c', +) + +if host_system == 'windows' + test_bloom_customscan_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_bloom_customscan', + '--FILEDESC', 'test_bloom_customscan - CustomScan consuming a hashjoin bloom filter',]) +endif + +test_bloom_customscan = shared_module('test_bloom_customscan', + test_bloom_customscan_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_bloom_customscan + +test_install_data += files( + 'test_bloom_customscan.control', + 'test_bloom_customscan--1.0.sql', +) + +tests += { + 'name': 'test_bloom_customscan', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_bloom_customscan', + ], + }, +} diff --git a/src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql b/src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql new file mode 100644 index 00000000000..daae84a531a --- /dev/null +++ b/src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql @@ -0,0 +1,66 @@ +CREATE EXTENSION test_bloom_customscan; +-- ensure the library (and its planner hook + GUC) is loaded +LOAD 'test_bloom_customscan'; + +-- Force a serial hash join, which is where the bloom-filter pushdown applies. +SET enable_hashjoin_bloom = on; +SET enable_mergejoin = off; +SET enable_nestloop = off; +SET max_parallel_workers_per_gather = 0; + +CREATE TABLE cs_fact (a int, b int, payload int); +CREATE TABLE cs_dim (id int, id2 int, label text); + +-- Fact keys range over 0..999; the dimension only covers 1..50, so the join +-- is highly selective and the pushed-down filter should reject most fact rows. +INSERT INTO cs_fact + SELECT g % 1000, g % 1000, g FROM generate_series(1, 20000) g; +INSERT INTO cs_dim + SELECT g, g, 'x' || g FROM generate_series(1, 50) g; + +ANALYZE cs_fact; +ANALYZE cs_dim; + +-- Offer the bloom-capable custom scan and force it to be chosen (the CustomPath +-- keeps disabled_nodes = 0, so disabling seqscan makes it win). +SET test_bloom_customscan.enable = on; +SET enable_seqscan = off; + +-- The fact relation is scanned by our Custom Scan and receives the filter. +EXPLAIN (COSTS OFF, VERBOSE) +SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id; + +-- Single-key join: the filter must actually reject fact rows. +SELECT test_bloom_cs_reset(); +SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id; +SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows; + +-- Correctness: the result must be identical with and without the filter. +SET test_bloom_customscan.enable = on; +SET enable_seqscan = off; +CREATE TEMP TABLE r_cs AS + SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a; + +SET test_bloom_customscan.enable = off; +SET enable_seqscan = on; +CREATE TEMP TABLE r_plain AS + SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a; + +SELECT count(*) AS cs_minus_plain + FROM (SELECT * FROM r_cs EXCEPT SELECT * FROM r_plain) x; +SELECT count(*) AS plain_minus_cs + FROM (SELECT * FROM r_plain EXCEPT SELECT * FROM r_cs) x; + +-- Two-key join: the opted-in recipient also gets per-key filters built. +SET test_bloom_customscan.enable = on; +SET enable_seqscan = off; +SELECT test_bloom_cs_reset(); +SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id AND f.b = d.id2; +SELECT test_bloom_cs_perkey_built() AS perkey_filters_built; +SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows; + +-- cleanup +SET test_bloom_customscan.enable = off; +SET enable_seqscan = on; +DROP TABLE cs_fact, cs_dim; +DROP EXTENSION test_bloom_customscan; diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql b/src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql new file mode 100644 index 00000000000..329d795d6f0 --- /dev/null +++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql @@ -0,0 +1,20 @@ +/* src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_bloom_customscan" to load this file. \quit + +CREATE FUNCTION test_bloom_cs_reset() + RETURNS void + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bloom_cs_scanned_rows() + RETURNS bigint + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bloom_cs_rejected_rows() + RETURNS bigint + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bloom_cs_perkey_built() + RETURNS boolean + AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan.c b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c new file mode 100644 index 00000000000..124cbef9ded --- /dev/null +++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c @@ -0,0 +1,362 @@ +/*------------------------------------------------------------------------- + * + * test_bloom_customscan.c + * Minimal CustomScan provider that consumes a hash-join Bloom filter. + * + * This is a test-only stand-in for a storage-level scan provider. When enabled, it + * installs a set_rel_pathlist_hook that offers a CustomPath for plain heap + * base relations, advertising CUSTOMPATH_SUPPORT_BLOOM_FILTERS. That opt-in + * is what lets the planner (a) generate filter-bearing paths for the scan and + * (b) push a hash-join Bloom filter down to it. + * + * The scan itself is an ordinary heap sequential scan; the only interesting + * part is that its per-tuple loop probes the pushed-down combined filter with + * ExecBloomFilters() -- exactly what a stock scan does inside ExecScanExtended, + * but here done by the provider itself -- and, for a multi-key join, checks + * that the per-key filters were populated on the producer side. + * + * A few SQL-callable functions expose counters so the regression test can + * assert that the filter was actually probed and rejected tuples, and that + * per-key filters were built, without depending on timing or EXPLAIN output. + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/test/modules/test_bloom_customscan/test_bloom_customscan.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/relscan.h" +#include "access/tableam.h" +#include "executor/executor.h" +#include "fmgr.h" +#include "nodes/execnodes.h" +#include "nodes/extensible.h" +#include "nodes/pathnodes.h" +#include "nodes/plannodes.h" +#include "optimizer/cost.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/restrictinfo.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC; + +/* GUC: when off, the hook does nothing and plans look exactly like upstream. */ +static bool test_bloom_cs_enabled = false; + +/* Observation counters, reset/read from SQL. */ +static uint64 test_bloom_cs_scanned = 0; +static uint64 test_bloom_cs_rejected = 0; +static bool test_bloom_cs_perkey_seen = false; + +static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook = NULL; + +/* Execution state, embedding CustomScanState as its first field. */ +typedef struct BloomCSScanState +{ + CustomScanState css; + TableScanDesc scandesc; + bool inspected; /* have we checked per-key filters yet? */ +} BloomCSScanState; + +/* forward declarations */ +static Plan *bloom_cs_plan_custom_path(PlannerInfo *root, RelOptInfo *rel, + CustomPath *best_path, List *tlist, + List *clauses, List *custom_plans); +static Node *bloom_cs_create_scan_state(CustomScan *cscan); +static void bloom_cs_begin_scan(CustomScanState *node, EState *estate, + int eflags); +static TupleTableSlot *bloom_cs_exec_scan(CustomScanState *node); +static void bloom_cs_end_scan(CustomScanState *node); +static void bloom_cs_rescan(CustomScanState *node); + +static const CustomPathMethods bloom_cs_path_methods = { + .CustomName = "TestBloomCustomScan", + .PlanCustomPath = bloom_cs_plan_custom_path, +}; + +static const CustomScanMethods bloom_cs_scan_methods = { + .CustomName = "TestBloomCustomScan", + .CreateCustomScanState = bloom_cs_create_scan_state, +}; + +static const CustomExecMethods bloom_cs_exec_methods = { + .CustomName = "TestBloomCustomScan", + .BeginCustomScan = bloom_cs_begin_scan, + .ExecCustomScan = bloom_cs_exec_scan, + .EndCustomScan = bloom_cs_end_scan, + .ReScanCustomScan = bloom_cs_rescan, +}; + +/* + * set_rel_pathlist_hook: offer a bloom-filter-capable CustomPath for plain + * heap base relations. We copy the cost of the existing sequential scan path + * so join costing stays sane; the test forces the custom scan to be chosen + * with "SET enable_seqscan = off" (the CustomPath keeps disabled_nodes = 0). + */ +static void +bloom_cs_set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, + RangeTblEntry *rte) +{ + CustomPath *cpath; + Cost startup_cost = 0; + Cost total_cost = 0; + ListCell *lc; + + if (prev_set_rel_pathlist_hook) + prev_set_rel_pathlist_hook(root, rel, rti, rte); + + if (!test_bloom_cs_enabled) + return; + + /* Only plain, ordinary base tables. */ + if (rel->reloptkind != RELOPT_BASEREL) + return; + if (rte->rtekind != RTE_RELATION || rte->relkind != RELKIND_RELATION) + return; + + /* Borrow the sequential scan's cost estimate. */ + foreach(lc, rel->pathlist) + { + Path *path = (Path *) lfirst(lc); + + if (path->pathtype == T_SeqScan && path->param_info == NULL) + { + startup_cost = path->startup_cost; + total_cost = path->total_cost; + break; + } + } + + cpath = makeNode(CustomPath); + cpath->path.pathtype = T_CustomScan; + cpath->path.parent = rel; + cpath->path.pathtarget = rel->reltarget; + cpath->path.param_info = NULL; + cpath->path.parallel_aware = false; + cpath->path.parallel_safe = false; + cpath->path.parallel_workers = 0; + cpath->path.rows = rel->rows; + cpath->path.startup_cost = startup_cost; + cpath->path.total_cost = total_cost; + cpath->path.pathkeys = NIL; + + cpath->custom_paths = NIL; + cpath->custom_private = NIL; + cpath->methods = &bloom_cs_path_methods; + + add_path(rel, (Path *) cpath); +} + +static Plan * +bloom_cs_plan_custom_path(PlannerInfo *root, RelOptInfo *rel, + CustomPath *best_path, List *tlist, + List *clauses, List *custom_plans) +{ + CustomScan *cscan = makeNode(CustomScan); + + cscan->flags = best_path->flags; + cscan->methods = &bloom_cs_scan_methods; + cscan->custom_plans = custom_plans; + + /* A base-relation scan: leave custom_scan_tlist NIL, use rel's rowtype. */ + cscan->scan.scanrelid = rel->relid; + cscan->scan.plan.targetlist = tlist; + cscan->scan.plan.qual = extract_actual_clauses(clauses, false); + + return &cscan->scan.plan; +} + +static Node * +bloom_cs_create_scan_state(CustomScan *cscan) +{ + BloomCSScanState *bcss = (BloomCSScanState *) newNode(sizeof(BloomCSScanState), + T_CustomScanState); + + bcss->css.methods = &bloom_cs_exec_methods; + bcss->scandesc = NULL; + bcss->inspected = false; + + /* + * We scan a heap table with table_scan_getnextslot(), which stores + * on-disk heap tuples, so the scan tuple slot must be a buffer-heap slot + * rather than the default virtual slot. ExecInitCustomScan() reads + * css.slotOps when building ss_ScanTupleSlot (before BeginCustomScan + * runs), so it has to be set here. + */ + bcss->css.slotOps = &TTSOpsBufferHeapTuple; + + return (Node *) bcss; +} + +static void +bloom_cs_begin_scan(CustomScanState *node, EState *estate, int eflags) +{ + BloomCSScanState *bcss = (BloomCSScanState *) node; + + /* + * ExecInitCustomScan already opened the scan relation (scanrelid > 0) and + * called ExecInitBloomFilters(), so node->ss.ps.bloom_filters is set up. + * We just need a table scan descriptor. + */ + if (!(eflags & (EXEC_FLAG_EXPLAIN_ONLY | EXEC_FLAG_EXPLAIN_GENERIC))) + bcss->scandesc = table_beginscan(node->ss.ss_currentRelation, + estate->es_snapshot, 0, NULL, 0); +} + +/* + * On the first tuple (by which point an eager producer has built its hash + * table and filters), record whether the per-key filters were populated. + */ +static void +bloom_cs_maybe_inspect_perkey(BloomCSScanState * bcss) +{ + ListCell *lc; + + if (bcss->inspected) + return; + + foreach(lc, bcss->css.ss.ps.bloom_filters) + { + BloomFilterState *bfs = (BloomFilterState *) lfirst(lc); + HashJoinState *producer = bfs->producer; + HashState *hashNode; + + if (producer == NULL) + continue; + hashNode = castNode(HashState, innerPlanState(&producer->js.ps)); + + /* Hash table (and filters) not built yet; try again next tuple. */ + if (hashNode->bloom_filter == NULL) + return; + + } + + bcss->inspected = true; +} + +static TupleTableSlot * +bloom_cs_exec_scan(CustomScanState *node) +{ + BloomCSScanState *bcss = (BloomCSScanState *) node; + ExprContext *econtext = node->ss.ps.ps_ExprContext; + TupleTableSlot *scanslot = node->ss.ss_ScanTupleSlot; + ExprState *qual = node->ss.ps.qual; + ProjectionInfo *proj = node->ss.ps.ps_ProjInfo; + + bloom_cs_maybe_inspect_perkey(bcss); + + for (;;) + { + ResetExprContext(econtext); + + if (!table_scan_getnextslot(bcss->scandesc, ForwardScanDirection, + scanslot)) + { + if (proj) + return ExecClearTuple(proj->pi_state.resultslot); + return ExecClearTuple(scanslot); + } + + test_bloom_cs_scanned++; + econtext->ecxt_scantuple = scanslot; + + /* + * Probe the pushed-down combined Bloom filter, just as a stock scan + * would inside ExecScanExtended. A conclusive miss lets us drop the + * tuple without evaluating the qual or projection. + */ + if (!ExecBloomFilters(node->ss.ps.bloom_filters, econtext)) + { + test_bloom_cs_rejected++; + continue; + } + + if (qual != NULL && !ExecQual(qual, econtext)) + continue; + + if (proj != NULL) + return ExecProject(proj); + + return scanslot; + } +} + +static void +bloom_cs_end_scan(CustomScanState *node) +{ + BloomCSScanState *bcss = (BloomCSScanState *) node; + + if (bcss->scandesc != NULL) + table_endscan(bcss->scandesc); +} + +static void +bloom_cs_rescan(CustomScanState *node) +{ + BloomCSScanState *bcss = (BloomCSScanState *) node; + + bcss->inspected = false; + if (bcss->scandesc != NULL) + table_rescan(bcss->scandesc, NULL); +} + +/* ------------------------------------------------------------------------ + * SQL-callable observation helpers + * ------------------------------------------------------------------------ + */ +PG_FUNCTION_INFO_V1(test_bloom_cs_reset); +PG_FUNCTION_INFO_V1(test_bloom_cs_scanned_rows); +PG_FUNCTION_INFO_V1(test_bloom_cs_rejected_rows); +PG_FUNCTION_INFO_V1(test_bloom_cs_perkey_built); + +Datum +test_bloom_cs_reset(PG_FUNCTION_ARGS) +{ + test_bloom_cs_scanned = 0; + test_bloom_cs_rejected = 0; + test_bloom_cs_perkey_seen = false; + PG_RETURN_VOID(); +} + +Datum +test_bloom_cs_scanned_rows(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64((int64) test_bloom_cs_scanned); +} + +Datum +test_bloom_cs_rejected_rows(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64((int64) test_bloom_cs_rejected); +} + +Datum +test_bloom_cs_perkey_built(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(test_bloom_cs_perkey_seen); +} + +void +_PG_init(void) +{ + DefineCustomBoolVariable("test_bloom_customscan.enable", + "Offer a bloom-filter-capable CustomScan for heap tables.", + NULL, + &test_bloom_cs_enabled, + false, + PGC_USERSET, + 0, + NULL, NULL, NULL); + + MarkGUCPrefixReserved("test_bloom_customscan"); + + RegisterCustomScanMethods(&bloom_cs_scan_methods); + + prev_set_rel_pathlist_hook = set_rel_pathlist_hook; + set_rel_pathlist_hook = bloom_cs_set_rel_pathlist; +} diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan.control b/src/test/modules/test_bloom_customscan/test_bloom_customscan.control new file mode 100644 index 00000000000..10f90510302 --- /dev/null +++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan.control @@ -0,0 +1,5 @@ +# test_bloom_customscan extension +comment = 'Test CustomScan provider that consumes a hashjoin bloom filter' +default_version = '1.0' +module_pathname = '$libdir/test_bloom_customscan' +relocatable = true -- 2.50.1 (Apple Git-155)