public inbox for [email protected]
help / color / mirror / Atom feedFrom: Aleksander Alekseev <[email protected]>
To: PostgreSQL Development <[email protected]>
Subject: [PATCH] Add tests for src/backend/nodes/extensible.c
Date: Tue, 31 Mar 2026 18:18:33 +0300
Message-ID: <CAJ7c6TNfn9Fv_Je1etA6rrgq1onVvXbjwBTkbkd4kVQhcu11gg@mail.gmail.com> (raw)
Hi,
Currently extensible.c is not covered by any tests. The proposed patch
fixes this. Also it can serve as an example of using CustomScan and
ExtensibleNode.
For the reviewers
-----------------------
Here is how to check the code coverage:
```
git clean -df
rm -r build
meson setup --buildtype debug -Db_coverage=true -Dcassert=true
-Dinjection_points=true -Dtap_tests=enabled -Dldap=disabled
-Dicu=disabled -DPG_TEST_EXTRA="kerberos ldap libpq_encryption
load_balance oauth regress_dump_restore ssl wal_consistency_checking
xid_wraparound" -Dprefix=/home/eax/pginstall build
ninja -C build
meson test -C build
ninja -C build coverage-html
open build/meson-logs/coveragereport/index.html
```
You are going to need `lcov` 1.16 in your $PATH because there are
certain problems with newer versions [1].
[1]: https://postgr.es/m/CAJ7c6TN%2BMCh99EZ8YGhXZAdnqvNQYir6E34B_mmcB5KsxCB00A%40mail.gmail.com
--
Best regards,
Aleksander Alekseev
Attachments:
[text/x-patch] v1-0001-Add-test-module-for-src-backend-nodes-extensible.patch (23.3K, 2-v1-0001-Add-test-module-for-src-backend-nodes-extensible.patch)
download | inline diff:
From c632e0065a5c6d14ab7ff11c92316b19e0d8757b Mon Sep 17 00:00:00 2001
From: Aleksander Alekseev <[email protected]>
Date: Tue, 31 Mar 2026 15:38:21 +0300
Subject: [PATCH v1] Add test module for src/backend/nodes/extensible.c
The new test implements a CustomScan that uses an ExtensibleNode to carry
private planning data from the planner into the executor. A SQL query against
a test table exercises the full pipeline end-to-end, while thin wrapper
functions cover GetExtensibleNodeMethods() and GetCustomScanMethods().
Author: Aleksander Alekseev <[email protected]>
Reviewed-by: TODO FIXME
Discussion: TODO FIXME
---
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_extensible/Makefile | 27 ++
.../expected/test_extensible.out | 69 ++++
src/test/modules/test_extensible/meson.build | 35 ++
.../test_extensible/sql/test_extensible.sql | 40 ++
.../test_extensible/test_extensible--1.0.sql | 8 +
.../modules/test_extensible/test_extensible.c | 389 ++++++++++++++++++
.../test_extensible/test_extensible.conf | 1 +
.../test_extensible/test_extensible.control | 4 +
10 files changed, 575 insertions(+)
create mode 100644 src/test/modules/test_extensible/Makefile
create mode 100644 src/test/modules/test_extensible/expected/test_extensible.out
create mode 100644 src/test/modules/test_extensible/meson.build
create mode 100644 src/test/modules/test_extensible/sql/test_extensible.sql
create mode 100644 src/test/modules/test_extensible/test_extensible--1.0.sql
create mode 100644 src/test/modules/test_extensible/test_extensible.c
create mode 100644 src/test/modules/test_extensible/test_extensible.conf
create mode 100644 src/test/modules/test_extensible/test_extensible.control
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 28ce3b35eda..4a686b11752 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -28,6 +28,7 @@ SUBDIRS = \
test_dsa \
test_dsm_registry \
test_escape \
+ test_extensible \
test_extensions \
test_ginpostinglist \
test_int128 \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 3ac291656c1..790b0158d6a 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -29,6 +29,7 @@ subdir('test_ddl_deparse')
subdir('test_dsa')
subdir('test_dsm_registry')
subdir('test_escape')
+subdir('test_extensible')
subdir('test_extensions')
subdir('test_ginpostinglist')
subdir('test_int128')
diff --git a/src/test/modules/test_extensible/Makefile b/src/test/modules/test_extensible/Makefile
new file mode 100644
index 00000000000..1369ae8e52b
--- /dev/null
+++ b/src/test/modules/test_extensible/Makefile
@@ -0,0 +1,27 @@
+# src/test/modules/test_extensible/Makefile
+
+MODULE_big = test_extensible
+OBJS = \
+ $(WIN32RES) \
+ test_extensible.o
+PGFILEDESC = "test_extensible - test module for extensible node and custom scan registration"
+
+EXTENSION = test_extensible
+DATA = test_extensible--1.0.sql
+
+REGRESS_OPTS = --temp-config $(top_srcdir)/src/test/modules/test_extensible/test_extensible.conf
+REGRESS = test_extensible
+# Disabled because these tests require "shared_preload_libraries=test_extensible",
+# which typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_extensible
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_extensible/expected/test_extensible.out b/src/test/modules/test_extensible/expected/test_extensible.out
new file mode 100644
index 00000000000..f11769f4597
--- /dev/null
+++ b/src/test/modules/test_extensible/expected/test_extensible.out
@@ -0,0 +1,69 @@
+-- Tests for extensible node and custom scan registration
+-- (src/backend/nodes/extensible.c)
+CREATE EXTENSION test_extensible;
+-- ----------------------------------------------------------------
+-- GetExtensibleNodeMethods() and GetCustomScanMethods() lookup tests
+-- ----------------------------------------------------------------
+-- GetExtensibleNodeMethods: known name returns the registered extnodename.
+SELECT test_get_extensible_node_methods('TestExtNode', false);
+ test_get_extensible_node_methods
+----------------------------------
+ TestExtNode
+(1 row)
+
+-- GetExtensibleNodeMethods: unknown name with missing_ok=true returns NULL.
+SELECT test_get_extensible_node_methods('NoSuchExtNode', true);
+ test_get_extensible_node_methods
+----------------------------------
+
+(1 row)
+
+-- GetExtensibleNodeMethods: unknown name with missing_ok=false raises ERROR.
+SELECT test_get_extensible_node_methods('NoSuchExtNode', false);
+ERROR: ExtensibleNodeMethods "NoSuchExtNode" was not registered
+-- GetCustomScanMethods: known name returns the registered CustomName.
+SELECT test_get_custom_scan_methods('TestCustomScan', false);
+ test_get_custom_scan_methods
+------------------------------
+ TestCustomScan
+(1 row)
+
+-- GetCustomScanMethods: unknown name with missing_ok=true returns NULL.
+SELECT test_get_custom_scan_methods('NoSuchCustomScan', true);
+ test_get_custom_scan_methods
+------------------------------
+
+(1 row)
+
+-- GetCustomScanMethods: unknown name with missing_ok=false raises ERROR.
+SELECT test_get_custom_scan_methods('NoSuchCustomScan', false);
+ERROR: ExtensibleNodeMethods "NoSuchCustomScan" was not registered
+-- ----------------------------------------------------------------
+-- End-to-end CustomScan test
+-- ----------------------------------------------------------------
+CREATE TABLE test_extensible_tbl (id integer, val text);
+INSERT INTO test_extensible_tbl VALUES (1, 'one'), (2, 'two'), (3, 'three');
+-- Verify the planner chose our CustomScan (not a SeqScan).
+EXPLAIN (COSTS OFF) SELECT id, val FROM test_extensible_tbl ORDER BY id;
+ QUERY PLAN
+-----------------------------------------------------------
+ Sort
+ Sort Key: id
+ -> Custom Scan (TestCustomScan) on test_extensible_tbl
+(3 rows)
+
+-- Execute through the CustomScan; each of the 3 inserted rows appears
+-- twice (6 rows total), proving the custom scan logic is in effect.
+SELECT id, val FROM test_extensible_tbl ORDER BY id, val;
+ id | val
+----+-------
+ 1 | one
+ 1 | one
+ 2 | two
+ 2 | two
+ 3 | three
+ 3 | three
+(6 rows)
+
+DROP TABLE test_extensible_tbl;
+DROP EXTENSION test_extensible;
diff --git a/src/test/modules/test_extensible/meson.build b/src/test/modules/test_extensible/meson.build
new file mode 100644
index 00000000000..bcd5312d1a9
--- /dev/null
+++ b/src/test/modules/test_extensible/meson.build
@@ -0,0 +1,35 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+test_extensible_sources = files(
+ 'test_extensible.c',
+)
+
+if host_system == 'windows'
+ test_extensible_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_extensible',
+ '--FILEDESC', 'test_extensible - test module for extensible node and custom scan registration',])
+endif
+
+test_extensible = shared_module('test_extensible',
+ test_extensible_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_extensible
+
+test_install_data += files(
+ 'test_extensible.control',
+ 'test_extensible--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_extensible',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_extensible',
+ ],
+ 'regress_args': ['--temp-config', files('test_extensible.conf')],
+ 'runningcheck': false,
+ },
+}
diff --git a/src/test/modules/test_extensible/sql/test_extensible.sql b/src/test/modules/test_extensible/sql/test_extensible.sql
new file mode 100644
index 00000000000..30c9da3f658
--- /dev/null
+++ b/src/test/modules/test_extensible/sql/test_extensible.sql
@@ -0,0 +1,40 @@
+-- Tests for extensible node and custom scan registration
+-- (src/backend/nodes/extensible.c)
+
+CREATE EXTENSION test_extensible;
+
+-- ----------------------------------------------------------------
+-- GetExtensibleNodeMethods() and GetCustomScanMethods() lookup tests
+-- ----------------------------------------------------------------
+
+-- GetExtensibleNodeMethods: known name returns the registered extnodename.
+SELECT test_get_extensible_node_methods('TestExtNode', false);
+-- GetExtensibleNodeMethods: unknown name with missing_ok=true returns NULL.
+SELECT test_get_extensible_node_methods('NoSuchExtNode', true);
+-- GetExtensibleNodeMethods: unknown name with missing_ok=false raises ERROR.
+SELECT test_get_extensible_node_methods('NoSuchExtNode', false);
+
+-- GetCustomScanMethods: known name returns the registered CustomName.
+SELECT test_get_custom_scan_methods('TestCustomScan', false);
+-- GetCustomScanMethods: unknown name with missing_ok=true returns NULL.
+SELECT test_get_custom_scan_methods('NoSuchCustomScan', true);
+-- GetCustomScanMethods: unknown name with missing_ok=false raises ERROR.
+SELECT test_get_custom_scan_methods('NoSuchCustomScan', false);
+
+-- ----------------------------------------------------------------
+-- End-to-end CustomScan test
+-- ----------------------------------------------------------------
+
+CREATE TABLE test_extensible_tbl (id integer, val text);
+INSERT INTO test_extensible_tbl VALUES (1, 'one'), (2, 'two'), (3, 'three');
+
+-- Verify the planner chose our CustomScan (not a SeqScan).
+EXPLAIN (COSTS OFF) SELECT id, val FROM test_extensible_tbl ORDER BY id;
+
+-- Execute through the CustomScan; each of the 3 inserted rows appears
+-- twice (6 rows total), proving the custom scan logic is in effect.
+SELECT id, val FROM test_extensible_tbl ORDER BY id, val;
+
+DROP TABLE test_extensible_tbl;
+
+DROP EXTENSION test_extensible;
diff --git a/src/test/modules/test_extensible/test_extensible--1.0.sql b/src/test/modules/test_extensible/test_extensible--1.0.sql
new file mode 100644
index 00000000000..ed7a65f496d
--- /dev/null
+++ b/src/test/modules/test_extensible/test_extensible--1.0.sql
@@ -0,0 +1,8 @@
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_extensible" to load this file. \quit
+
+CREATE OR REPLACE FUNCTION test_get_extensible_node_methods(text, bool) RETURNS text
+ AS 'MODULE_PATHNAME', 'test_get_extensible_node_methods' LANGUAGE C;
+
+CREATE OR REPLACE FUNCTION test_get_custom_scan_methods(text, bool) RETURNS text
+ AS 'MODULE_PATHNAME', 'test_get_custom_scan_methods' LANGUAGE C;
\ No newline at end of file
diff --git a/src/test/modules/test_extensible/test_extensible.c b/src/test/modules/test_extensible/test_extensible.c
new file mode 100644
index 00000000000..2cca68dbde8
--- /dev/null
+++ b/src/test/modules/test_extensible/test_extensible.c
@@ -0,0 +1,389 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_extensible.c
+ * Test correctness of extensible node and custom scan registration
+ * functions defined in src/backend/nodes/extensible.c.
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_extensible/test_extensible.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/tableam.h"
+#include "executor/executor.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/extensible.h"
+#include "nodes/nodes.h"
+#include "nodes/plannodes.h"
+#include "nodes/readfuncs.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+
+PG_MODULE_MAGIC;
+
+/* Name of the test table that triggers our CustomScan injection */
+#define TEST_TABLE_NAME "test_extensible_tbl"
+
+/* ----------------------------------------------------------------
+ * TestExtNode – an ExtensibleNode subtype carrying a single int.
+ *
+ * In a real extension this would hold planning metadata passed from
+ * the path stage to the execution stage via CustomScan.custom_private.
+ * ----------------------------------------------------------------
+ */
+typedef struct TestExtNode
+{
+ ExtensibleNode base; /* must be first */
+ int repeat_count; /* how many times to return each scanned row */
+} TestExtNode;
+
+#define TEST_EXT_NODE_NAME "TestExtNode"
+#define TEST_CUSTOM_SCAN_NAME "TestCustomScan"
+
+/* ----------------------------------------------------------------
+ * ExtensibleNodeMethods callbacks
+ * ----------------------------------------------------------------
+ */
+
+static void
+test_ext_node_copy(ExtensibleNode *newnode, const ExtensibleNode *oldnode)
+{
+ ((TestExtNode *) newnode)->repeat_count =
+ ((const TestExtNode *) oldnode)->repeat_count;
+}
+
+static bool
+test_ext_node_equal(const ExtensibleNode *a, const ExtensibleNode *b)
+{
+ return ((const TestExtNode *) a)->repeat_count ==
+ ((const TestExtNode *) b)->repeat_count;
+}
+
+static void
+test_ext_node_out(StringInfo str, const ExtensibleNode *node)
+{
+ appendStringInfo(str, " :repeat_count %d",
+ ((const TestExtNode *) node)->repeat_count);
+}
+
+static void
+test_ext_node_read(ExtensibleNode *node)
+{
+ TestExtNode *tnode = (TestExtNode *) node;
+ const char *token;
+ int length;
+
+ token = pg_strtok(&length); /* skip :repeat_count */
+ token = pg_strtok(&length); /* get value */
+ tnode->repeat_count = atoi(token);
+}
+
+static const ExtensibleNodeMethods test_ext_node_methods =
+{
+ .extnodename = TEST_EXT_NODE_NAME,
+ .node_size = sizeof(TestExtNode),
+ .nodeCopy = test_ext_node_copy,
+ .nodeEqual = test_ext_node_equal,
+ .nodeOut = test_ext_node_out,
+ .nodeRead = test_ext_node_read,
+};
+
+/* ----------------------------------------------------------------
+ * TestCustomScanState – execution state for the custom scan.
+ * ----------------------------------------------------------------
+ */
+typedef struct TestCustomScanState
+{
+ CustomScanState css; /* must be first */
+ TableScanDesc scandesc;
+ int repeat_count; /* repeat_count from TestExtNode */
+ int repeats_left; /* how many more times to return current row */
+} TestCustomScanState;
+
+/* ----------------------------------------------------------------
+ * Executor callbacks
+ * ----------------------------------------------------------------
+ */
+static void
+test_begin_custom_scan(CustomScanState *node, EState *estate, int eflags)
+{
+ TestCustomScanState *tstate = (TestCustomScanState *) node;
+ CustomScan *cscan = (CustomScan *) node->ss.ps.plan;
+ TestExtNode *tnode;
+ Relation rel = node->ss.ss_currentRelation;
+
+ /*
+ * Read repeat_count from the TestExtNode stored in custom_private. This
+ * is the key moment where the ExtensibleNode private data crosses from
+ * the plan tree into the executor state.
+ */
+ Assert(cscan->custom_private != NIL);
+ tnode = (TestExtNode *) linitial(cscan->custom_private);
+ Assert(IsA(tnode, ExtensibleNode));
+ Assert(strcmp(tnode->base.extnodename, TEST_EXT_NODE_NAME) == 0);
+
+ Assert(tnode->repeat_count > 0);
+ tstate->repeat_count = tnode->repeat_count;
+ tstate->repeats_left = 0;
+
+ /* Start a plain sequential heap scan. */
+ tstate->scandesc = table_beginscan(rel, estate->es_snapshot, 0, NULL,
+ SO_NONE);
+}
+
+static TupleTableSlot *
+test_exec_custom_scan(CustomScanState *node)
+{
+ TestCustomScanState *tstate = (TestCustomScanState *) node;
+ TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+
+ /*
+ * If the current tuple still has repeats remaining, return it again
+ * without advancing the heap scan. The repeat count comes from the
+ * TestExtNode that was read in BeginCustomScan.
+ */
+ if (tstate->repeats_left > 0)
+ {
+ tstate->repeats_left--;
+ return slot;
+ }
+
+ /* Fetch the next tuple from the heap. */
+ if (!table_scan_getnextslot(tstate->scandesc, ForwardScanDirection, slot))
+ return NULL;
+
+ /* Schedule (repeat_count - 1) additional returns of this tuple. */
+ tstate->repeats_left = tstate->repeat_count - 1;
+ return slot;
+}
+
+static void
+test_end_custom_scan(CustomScanState *node)
+{
+ TestCustomScanState *tstate = (TestCustomScanState *) node;
+
+ if (tstate->scandesc)
+ table_endscan(tstate->scandesc);
+}
+
+static void
+test_rescan_custom_scan(CustomScanState *node)
+{
+ TestCustomScanState *tstate = (TestCustomScanState *) node;
+
+ tstate->repeats_left = 0;
+ table_rescan(tstate->scandesc, NULL);
+}
+
+static const CustomExecMethods test_custom_exec_methods =
+{
+ .CustomName = TEST_CUSTOM_SCAN_NAME,
+ .BeginCustomScan = test_begin_custom_scan,
+ .ExecCustomScan = test_exec_custom_scan,
+ .EndCustomScan = test_end_custom_scan,
+ .ReScanCustomScan = test_rescan_custom_scan,
+};
+
+static Node *
+test_create_custom_scan_state(CustomScan *cscan)
+{
+ TestCustomScanState *tstate;
+
+ tstate = (TestCustomScanState *)
+ newNode(sizeof(TestCustomScanState), T_CustomScanState);
+ tstate->css.methods = &test_custom_exec_methods;
+
+ /*
+ * Use the heap-tuple slot type so table_scan_getnextslot() can fill it
+ * directly.
+ */
+ tstate->css.slotOps = &TTSOpsBufferHeapTuple;
+
+ return (Node *) tstate;
+}
+
+static const CustomScanMethods test_custom_scan_methods =
+{
+ .CustomName = TEST_CUSTOM_SCAN_NAME,
+ .CreateCustomScanState = test_create_custom_scan_state,
+};
+
+/* ----------------------------------------------------------------
+ * Planner callbacks
+ * ----------------------------------------------------------------
+ */
+static Plan *
+test_plan_custom_path(PlannerInfo *root,
+ RelOptInfo *rel,
+ struct CustomPath *best_path,
+ List *tlist,
+ List *clauses,
+ List *custom_plans)
+{
+ CustomScan *cscan = makeNode(CustomScan);
+
+ /*
+ * Pass the ExtensibleNode from the path to the plan via custom_private.
+ * This is the recommended pattern for conveying private planning data
+ * from a CustomPath to its corresponding CustomScan.
+ */
+ cscan->scan.plan.targetlist = tlist;
+ cscan->scan.plan.qual = NIL;
+ cscan->scan.scanrelid = rel->relid;
+ cscan->flags = best_path->flags;
+ cscan->custom_plans = NIL;
+ cscan->custom_exprs = NIL;
+ cscan->custom_private = best_path->custom_private;
+ cscan->custom_scan_tlist = NIL;
+ cscan->custom_relids = NULL;
+ cscan->methods = &test_custom_scan_methods;
+
+ return (Plan *) cscan;
+}
+
+static const CustomPathMethods test_custom_path_methods =
+{
+ .CustomName = TEST_CUSTOM_SCAN_NAME,
+ .PlanCustomPath = test_plan_custom_path,
+};
+
+/* ----------------------------------------------------------------
+ * set_rel_pathlist_hook – inject our CustomPath for the specific
+ * test table.
+ * ----------------------------------------------------------------
+ */
+static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook = NULL;
+
+static void
+test_set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
+ Index rti, RangeTblEntry *rte)
+{
+ CustomPath *cpath;
+ TestExtNode *tnode;
+ char *relname;
+
+ /* Let previous hooks run first. */
+ if (prev_set_rel_pathlist_hook)
+ prev_set_rel_pathlist_hook(root, rel, rti, rte);
+
+ /* Only handle plain base relations (ordinary tables). */
+ if (rel->reloptkind != RELOPT_BASEREL || rte->rtekind != RTE_RELATION)
+ return;
+
+ /*
+ * Only inject our CustomPath for the specific marker table. This
+ * prevents interference with system-catalog scans.
+ */
+ relname = get_rel_name(rte->relid);
+ if (relname == NULL || strcmp(relname, TEST_TABLE_NAME) != 0)
+ return;
+
+ /*
+ * Build a TestExtNode carrying the planner's row estimate. In a real
+ * extension this might be a device handle or a cache key.
+ */
+ tnode = (TestExtNode *) newNode(sizeof(TestExtNode), T_ExtensibleNode);
+ tnode->base.extnodename = TEST_EXT_NODE_NAME;
+ tnode->repeat_count = 2; /* each row will be returned twice */
+
+ /*
+ * Build the CustomPath. We match the cost of the cheapest existing path
+ * so the planner is free to choose ours when it is equally cheap.
+ */
+ cpath = makeNode(CustomPath);
+ cpath->path.pathtype = T_CustomScan;
+ cpath->path.parent = rel;
+ cpath->path.pathtarget = rel->reltarget;
+ cpath->path.rows = rel->rows;
+ cpath->path.startup_cost = 0;
+ cpath->path.total_cost = 0;
+ cpath->flags = 0;
+ cpath->custom_paths = NIL;
+ cpath->custom_private = list_make1(tnode);
+ cpath->methods = &test_custom_path_methods;
+
+ add_path(rel, (Path *) cpath);
+}
+
+/* ----------------------------------------------------------------
+ * SQL-callable test functions for Get* lookups
+ * ----------------------------------------------------------------
+ */
+PG_FUNCTION_INFO_V1(test_get_extensible_node_methods);
+PG_FUNCTION_INFO_V1(test_get_custom_scan_methods);
+
+/*
+ * test_get_extensible_node_methods(name text, missing_ok bool)
+ *
+ * Thin wrapper around GetExtensibleNodeMethods(). Returns the registered
+ * extnodename, or NULL when missing_ok = true and the name is not found.
+ * Raises ERROR when missing_ok = false and the name is not found.
+ */
+Datum
+test_get_extensible_node_methods(PG_FUNCTION_ARGS)
+{
+ char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ bool missing_ok = PG_GETARG_BOOL(1);
+ const ExtensibleNodeMethods *methods;
+
+ methods = GetExtensibleNodeMethods(name, missing_ok);
+ if (methods == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(methods->extnodename));
+}
+
+/*
+ * test_get_custom_scan_methods(name text, missing_ok bool)
+ *
+ * Thin wrapper around GetCustomScanMethods(). Returns the registered
+ * CustomName, or NULL when missing_ok = true and the name is not found.
+ * Raises ERROR when missing_ok = false and the name is not found.
+ */
+Datum
+test_get_custom_scan_methods(PG_FUNCTION_ARGS)
+{
+ char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ bool missing_ok = PG_GETARG_BOOL(1);
+ const CustomScanMethods *methods;
+
+ methods = GetCustomScanMethods(name, missing_ok);
+ if (methods == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(methods->CustomName));
+}
+
+/* ----------------------------------------------------------------
+ * Module initialisation
+ * ----------------------------------------------------------------
+ */
+void
+_PG_init(void)
+{
+ if (!process_shared_preload_libraries_in_progress)
+ ereport(ERROR,
+ (errmsg("cannot load \"%s\" after startup",
+ "test_extensible"),
+ errdetail("\"%s\" must be loaded with "
+ "\"shared_preload_libraries\".",
+ "test_extensible")));
+
+ /* Register the custom scan methods. */
+ RegisterCustomScanMethods(&test_custom_scan_methods);
+
+ /* Register the extensible node type */
+ RegisterExtensibleNodeMethods(&test_ext_node_methods);
+
+ /* Install the path-list hook to inject CustomPaths for the test table. */
+ prev_set_rel_pathlist_hook = set_rel_pathlist_hook;
+ set_rel_pathlist_hook = test_set_rel_pathlist;
+}
diff --git a/src/test/modules/test_extensible/test_extensible.conf b/src/test/modules/test_extensible/test_extensible.conf
new file mode 100644
index 00000000000..a5b643cb256
--- /dev/null
+++ b/src/test/modules/test_extensible/test_extensible.conf
@@ -0,0 +1 @@
+shared_preload_libraries = 'test_extensible'
diff --git a/src/test/modules/test_extensible/test_extensible.control b/src/test/modules/test_extensible/test_extensible.control
new file mode 100644
index 00000000000..7a19fa6570e
--- /dev/null
+++ b/src/test/modules/test_extensible/test_extensible.control
@@ -0,0 +1,4 @@
+comment = 'Test code for extensible node and custom scan registration'
+default_version = '1.0'
+module_pathname = '$libdir/test_extensible'
+relocatable = false
--
2.43.0
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [PATCH] Add tests for src/backend/nodes/extensible.c
In-Reply-To: <CAJ7c6TNfn9Fv_Je1etA6rrgq1onVvXbjwBTkbkd4kVQhcu11gg@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox