agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v7 3/3] Add WITH OLD DATA to CREATE OR REPLACE MATERIALIZED VIEW
36+ messages / 4 participants
[nested] [flat]

* [PATCH v7 3/3] Add WITH OLD DATA to CREATE OR REPLACE MATERIALIZED VIEW
@ 2024-07-26 21:33  Erik Wienhold <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Erik Wienhold @ 2024-07-26 21:33 UTC (permalink / raw)

This keeps the matview populated when replacing its definition.
---
 .../sgml/ref/create_materialized_view.sgml    | 15 ++++++++--
 src/backend/commands/createas.c               | 29 +++++++++++++------
 src/backend/parser/gram.y                     | 16 ++++++++++
 src/include/nodes/primnodes.h                 |  1 +
 src/test/regress/expected/matview.out         | 22 ++++++++++++++
 src/test/regress/sql/matview.sql              | 13 +++++++++
 6 files changed, 84 insertions(+), 12 deletions(-)

diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index 5e03320eb73..5b82af2ac70 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -27,7 +27,7 @@ CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_nam
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
     [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
     AS <replaceable>query</replaceable>
-    [ WITH [ NO ] DATA ]
+    [ WITH [ NO | OLD ] DATA ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -37,7 +37,8 @@ CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_nam
   <para>
    <command>CREATE MATERIALIZED VIEW</command> defines a materialized view of
    a query.  The query is executed and used to populate the view at the time
-   the command is issued (unless <command>WITH NO DATA</command> is used) and may be
+   the command is issued (unless <command>WITH NO DATA</command> or
+   <command>WITH OLD DATA</command> is used) and may be
    refreshed later using <command>REFRESH MATERIALIZED VIEW</command>.
   </para>
 
@@ -162,7 +163,7 @@ CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_nam
    </varlistentry>
 
    <varlistentry>
-    <term><literal>WITH [ NO ] DATA</literal></term>
+    <term><literal>WITH [ NO | OLD ] DATA</literal></term>
     <listitem>
      <para>
       This clause specifies whether or not the materialized view should be
@@ -170,6 +171,14 @@ CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_nam
       flagged as unscannable and cannot be queried until <command>REFRESH
       MATERIALIZED VIEW</command> is used.
      </para>
+
+     <para>
+      The form <command>WITH OLD DATA</command> keeps the already stored data
+      when replacing an existing materialized view to keep it populated.  Use
+      this form if you want to use <command>REFRESH MATERIALIZED VIEW CONCURRENTLY</command>
+      as it requires a populated materialized view.  It is an error to use this
+      form when creating a new materialized view.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 30ca0a21903..ac2491fe01c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -329,18 +329,26 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		/* An existing materialized view can be replaced. */
 		if (is_matview && into->replace)
 		{
-			RefreshMatViewStmt *refresh;
-
 			/* Change the relation to match the new query and other options. */
-			(void) create_ctas_nodata(query->targetList, into);
+			address = create_ctas_nodata(query->targetList, into);
+
+			/*
+			 * Refresh the materialized view with a fake statement unless we
+			 * must keep the old data.
+			 */
+			if (!into->keepData)
+			{
+				RefreshMatViewStmt *refresh;
+
+				refresh = makeNode(RefreshMatViewStmt);
+				refresh->relation = into->rel;
+				refresh->skipData = into->skipData;
+				refresh->concurrent = false;
 
-			/* Refresh the materialized view with a fake statement. */
-			refresh = makeNode(RefreshMatViewStmt);
-			refresh->relation = into->rel;
-			refresh->skipData = into->skipData;
-			refresh->concurrent = false;
+				address = ExecRefreshMatView(refresh, pstate->p_sourcetext, qc);
+			}
 
-			return ExecRefreshMatView(refresh, pstate->p_sourcetext, qc);
+			return address;
 		}
 
 		return InvalidObjectAddress;
@@ -383,6 +391,9 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 	 */
 	if (is_matview)
 	{
+		if (into->keepData)
+			elog(ERROR, "must not specify WITH OLD DATA when creating a new materialized view");
+
 		do_refresh = !into->skipData;
 		into->skipData = true;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7aaf0e37ad8..e51ce2701be 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4944,6 +4944,22 @@ CreateMatViewStmt:
 					$7->replace = true;
 					$$ = (Node *) ctas;
 				}
+		| CREATE OR REPLACE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt WITH OLD DATA_P
+				{
+					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
+
+					ctas->query = $9;
+					ctas->into = $7;
+					ctas->objtype = OBJECT_MATVIEW;
+					ctas->is_select_into = false;
+					ctas->if_not_exists = false;
+					/* cram additional flags into the IntoClause */
+					$7->rel->relpersistence = $4;
+					$7->skipData = false;
+					$7->keepData = true;
+					$7->replace = true;
+					$$ = (Node *) ctas;
+				}
 		;
 
 create_mv_target:
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3a0c3b06c81..e4c52ab05b4 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -169,6 +169,7 @@ typedef struct IntoClause
 	/* materialized view's SELECT query */
 	struct Query *viewQuery pg_node_attr(query_jumble_ignore);
 	bool		skipData;		/* true for WITH NO DATA */
+	bool		keepData;		/* true for WITH OLD DATA */
 	bool		replace;		/* replace existing matview? */
 } IntoClause;
 
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index 56104b7ee8b..39b3a7c941c 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -731,6 +731,23 @@ SELECT * FROM mvtest_replace;
  3
 (1 row)
 
+-- replace query but keep old data
+CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
+  SELECT 5 AS a
+  WITH OLD DATA;
+SELECT * FROM mvtest_replace;
+ a 
+---
+ 3
+(1 row)
+
+REFRESH MATERIALIZED VIEW mvtest_replace;
+SELECT * FROM mvtest_replace;
+ a 
+---
+ 5
+(1 row)
+
 -- add column
 CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
   SELECT 4 AS a, 1 b;
@@ -899,3 +916,8 @@ ERROR:  syntax error at or near "NOT"
 LINE 1: CREATE OR REPLACE MATERIALIZED VIEW IF NOT EXISTS mvtest_rep...
                                                ^
 DROP MATERIALIZED VIEW mvtest_replace;
+-- Clause WITH OLD DATA is not allowed when creating a new matview.
+CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
+  SELECT 17 AS a
+  WITH OLD DATA; -- error
+ERROR:  must not specify WITH OLD DATA when creating a new materialized view
diff --git a/src/test/regress/sql/matview.sql b/src/test/regress/sql/matview.sql
index fe0cb4d25bf..fb2e5f4b589 100644
--- a/src/test/regress/sql/matview.sql
+++ b/src/test/regress/sql/matview.sql
@@ -338,6 +338,14 @@ SELECT * FROM mvtest_replace; -- error: not populated
 REFRESH MATERIALIZED VIEW mvtest_replace;
 SELECT * FROM mvtest_replace;
 
+-- replace query but keep old data
+CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
+  SELECT 5 AS a
+  WITH OLD DATA;
+SELECT * FROM mvtest_replace;
+REFRESH MATERIALIZED VIEW mvtest_replace;
+SELECT * FROM mvtest_replace;
+
 -- add column
 CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
   SELECT 4 AS a, 1 b;
@@ -431,3 +439,8 @@ CREATE OR REPLACE MATERIALIZED VIEW IF NOT EXISTS mvtest_replace AS
   SELECT 1 AS a;
 
 DROP MATERIALIZED VIEW mvtest_replace;
+
+-- Clause WITH OLD DATA is not allowed when creating a new matview.
+CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
+  SELECT 17 AS a
+  WITH OLD DATA; -- error
-- 
2.50.1


--r33ycpluvyfavkwy--





^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-05-29 12:35  Samba Siva <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva @ 2026-05-29 12:35 UTC (permalink / raw)

- Introduced AcquireSampleRowsFunc_hook for extensions to override row sampling.
- Updated analyze.c to utilize the hook if registered.
- Added tests to ensure ANALYZE completes without errors with the new hook.
---
 doc/src/sgml/xfunc.sgml               | 10 ++++++++++
 src/backend/commands/analyze.c        | 24 ++++++++++++++++++++----
 src/include/commands/vacuum.h         | 11 +++++++++++
 src/test/regress/expected/analyze.out | 20 ++++++++++++++++++++
 src/test/regress/parallel_schedule    |  1 +
 src/test/regress/sql/analyze.sql      | 24 ++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/analyze.out
 create mode 100644 src/test/regress/sql/analyze.sql

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 1eb5abffd8..f1ef025ae2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4203,4 +4203,14 @@ supportfn(internal) returns internal
     To create such conditions, the support function must implement
     the <literal>SupportRequestIndexCondition</literal> request type.
    </para>
+
+   <para>
+    The hook variable <varname>AcquireSampleRowsFunc_hook</varname> allows extensions
+    to override the row sampling function during <command>ANALYZE</command> for regular
+    heap relations. This is useful for extensions/systems which implement distributed
+    databases and want to sample rows from remote nodes instead of the local heap.
+    The hook function fills the provided <literal>rows[]</literal> buffer with at most
+    <literal>targrows</literal> heap tuples and sets <literal>*totalrows</literal> to the
+    estimated total live row count of the relation.
+   </para>
   </sect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4fffb76e55..3560acdff1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -74,6 +74,8 @@ int			default_statistics_target = 100;
 static MemoryContext anl_context = NULL;
 static BufferAccessStrategy vac_strategy;
 
+/* Hook for plugins to acquire sample rows for ANALYZE */
+AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook = NULL;
 
 static void do_analyze_rel(Relation onerel,
 						   VacuumParams *params, List *va_cols,
@@ -188,8 +190,15 @@ analyze_rel(Oid relid, RangeVar *relation,
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
-		/* Regular table, so we'll use the regular row acquisition function */
-		acquirefunc = acquire_sample_rows;
+		/*
+		 * Regular table, so we'll use the regular row acquisition function.
+		 * If a plugin has registered a hook to acquire sample rows, use it;
+		 * otherwise use the default function.
+		 */
+		if (AcquireSampleRowsFunc_hook)
+			acquirefunc = AcquireSampleRowsFunc_hook;
+		else
+			acquirefunc = acquire_sample_rows;
 		/* Also get regular table's size */
 		relpages = RelationGetNumberOfBlocks(onerel);
 	}
@@ -1467,8 +1476,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
-			/* Regular table, so use the regular row acquisition function */
-			acquirefunc = acquire_sample_rows;
+			/*
+			 * Regular table, so use the regular row acquisition function.
+			 * If a plugin has registered a hook to acquire sample rows, use it;
+			 * otherwise use the default function.
+			 */
+			if (AcquireSampleRowsFunc_hook)
+				acquirefunc = AcquireSampleRowsFunc_hook;
+			else
+				acquirefunc = acquire_sample_rows;
 			relpages = RelationGetNumberOfBlocks(childrel);
 		}
 		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc7..146f936861 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "parser/parse_node.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -113,6 +114,9 @@ typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
 											 int samplerows,
 											 double totalrows);
 
+/* Hook type for plugins to acquire sample rows for ANALYZE */
+typedef AcquireSampleRowsFunc AcquireSampleRowsFunc_hook_type;
+
 typedef struct VacAttrStats
 {
 	/*
@@ -334,6 +338,13 @@ extern PGDLLIMPORT int vacuum_cost_limit;
 
 extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 
+/*
+ * Hook for plugins to override row sampling during ANALYZE.
+ * Also applies to child relations of partitioned/inherited tables.
+ * See acquire_sample_rows() in src/backend/commands/analyze.c.
+ */
+extern PGDLLIMPORT AcquireSampleRowsFunc_hook_type AcquireSampleRowsFunc_hook;
+
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out
new file mode 100644
index 0000000000..5bb6bbc45b
--- /dev/null
+++ b/src/test/regress/expected/analyze.out
@@ -0,0 +1,20 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+-- Should complete without error
+ANALYZE employees;
+DROP TABLE employees;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1e0c54019..71978ef858 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: vacuum_parallel
 # Run this alone, because concurrent DROP TABLE would make non-superuser
 # "ANALYZE;" fail with "relation with OID $n does not exist".
 test: maintain_every
+test: analyze
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql
new file mode 100644
index 0000000000..e0bc84e9f9
--- /dev/null
+++ b/src/test/regress/sql/analyze.sql
@@ -0,0 +1,24 @@
+-- Test AcquireSampleRowsFunc_hook
+-- Usually this would be tested via a C extension.
+-- Here we just confirm this does not break the existing ANALYZE code
+-- by verifying that ANALYZE completes without error.
+
+CREATE TABLE employees (
+    id SERIAL PRIMARY KEY,
+    name TEXT,
+    department TEXT,
+    salary NUMERIC
+);
+
+INSERT INTO employees 
+    SELECT
+        i,
+        'Employee ' || i,
+        'Department ' || (i % 5),
+        (i % 100) * 1000 + 50000
+    FROM generate_series(1, 1000) i;
+
+-- Should complete without error
+ANALYZE employees;
+
+DROP TABLE employees;
-- 
2.50.1 (Apple Git-155)


------=_Part_741589_445982825.1782438414800--






^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-06-26 01:46  Samba Siva Reddy Chinta <[email protected]>
  0 siblings, 1 reply; 36+ messages in thread

From: Samba Siva Reddy Chinta @ 2026-06-26 01:46 UTC (permalink / raw)
  To: pgsql-hackers <[email protected]>

Hi all,

Attached is a patch that adds a hook, AcquireSampleRowsFunc_hook, allowing extensions to override the row sampling function used during ANALYZE for regular heap relations.

Motivation

Extensions that implement horizontal scaling of tables currently have no clean way to participate in ANALYZE's row sampling. The default acquire_sample_rows() only knows how to sample the local heap, so a distributed-table extension wanting accurate statistics has to either:

maintain its own separate stats-collection machinery outside of ANALYZE entirely, or

duplicate/reimplement parts of analyze.c's sampling logic to pull rows from remote nodes.


This hook lets such an extension plug into the existing ANALYZE code path and supply its own row acquisition function, without having to reinvent stats collection or duplicate logic that already exists in core.

What the patch does

Adds AcquireSampleRowsFunc_hook (typed identically to AcquireSampleRowsFunc) in vacuum.h.

In analyze.c, both analyze_rel() and acquire_inherited_sample_rows() check the hook and use it in place of acquire_sample_rows() when set.

Adds doc text in xfunc.sgml describing the hook's contract (fill rows[] up to targrows, set *totalrows).

Adds a regression test confirming ANALYZE still completes normally with the hook unset (the hook itself needs a C extension to exercise meaningfully, so this just guards against regressions in the unset case).


​Regards, 

Samba Siva Reddy 

http://sambasivareddy.in

^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* Re: [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-06-26 12:41  Ashutosh Bapat <[email protected]>
  parent: Samba Siva Reddy Chinta <[email protected]>
  0 siblings, 1 reply; 36+ messages in thread

From: Ashutosh Bapat @ 2026-06-26 12:41 UTC (permalink / raw)
  To: Samba Siva Reddy Chinta <[email protected]>; +Cc: pgsql-hackers <[email protected]>

Hi Samba Siva,

On Fri, Jun 26, 2026 at 7:17 AM Samba Siva Reddy Chinta
<[email protected]> wrote:
>
> Hi all,
>
> Attached is a patch that adds a hook, AcquireSampleRowsFunc_hook, allowing extensions to override the row sampling function used during ANALYZE for regular heap relations.
>
> Motivation
>
> Extensions that implement horizontal scaling of tables currently have no clean way to participate in ANALYZE's row sampling. The default acquire_sample_rows() only knows how to sample the local heap, so a distributed-table extension wanting accurate statistics has to either:
>
> maintain its own separate stats-collection machinery outside of ANALYZE entirely, or
> duplicate/reimplement parts of analyze.c's sampling logic to pull rows from remote nodes.
>
> This hook lets such an extension plug into the existing ANALYZE code path and supply its own row acquisition function, without having to reinvent stats collection or duplicate logic that already exists in core.
>
> What the patch does
>
> Adds AcquireSampleRowsFunc_hook (typed identically to AcquireSampleRowsFunc) in vacuum.h.
> In analyze.c, both analyze_rel() and acquire_inherited_sample_rows() check the hook and use it in place of acquire_sample_rows() when set.
> Adds doc text in xfunc.sgml describing the hook's contract (fill rows[] up to targrows, set *totalrows).
> Adds a regression test confirming ANALYZE still completes normally with the hook unset (the hook itself needs a C extension to exercise meaningfully, so this just guards against regressions in the unset case).
>

If you have implemented sharding using inheritance or partitioning and
FDW, the facility to fetch statistics of foreign tables from foreign
server can be useful here. IIUC, there is a limitation on using it for
inherited foreign tables, but it's worth exploring and improving for
your usecase.

-- 
Best Wishes,
Ashutosh Bapat





^ permalink  raw  reply  [nested|flat] 36+ messages in thread

* Re: [PATCH] Add hook for plugins to acquire sample rows during ANALYZE
@ 2026-07-05 02:22  Samba Siva Reddy Chinta <[email protected]>
  parent: Ashutosh Bapat <[email protected]>
  0 siblings, 0 replies; 36+ messages in thread

From: Samba Siva Reddy Chinta @ 2026-07-05 02:22 UTC (permalink / raw)
  To: Ashutosh Bapat <[email protected]>; +Cc: pgsql-hackers <[email protected]>

Hi Ashutosh, 

If you have implemented sharding using inheritance or partitioning and 

FDW, the facility to fetch statistics of foreign tables from foreign 

server can be useful here. IIUC, there is a limitation on using it for 

inherited foreign tables, but it's worth exploring and improving for 

your usecase.


Thanks for the suggestion. To clarify, this is not FDW-based sharding. The shards here are plain PostgreSQL tables distributed across the nodes like Citus, when a table is distributed, the base table remains on the coordinator and its shards spread across the workers as regular PostgreSQL heap tables.

Since there are no foreign tables involved, the FDW stats-fetching mechanism does not apply here. The base table holds no actual row data, but the shards as the real data - which is precisely the gap AcquireSampleRowsFunc_hook is meant to address.

Happy to clarify further if needed.

Thanks,
Samba Siva Reddy








From: Ashutosh Bapat <[email protected]>
To: "Samba Siva Reddy Chinta"<[email protected]>
Cc: "pgsql-hackers"<[email protected]>
Date: Fri, 26 Jun 2026 18:11:50 +0530
Subject: Re: [PATCH] Add hook for plugins to acquire sample rows during ANALYZE



Hi Samba Siva, 
 
On Fri, Jun 26, 2026 at 7:17 AM Samba Siva Reddy Chinta 
< mailto:[email protected] > wrote: 
> 
> Hi all, 
> 
> Attached is a patch that adds a hook, AcquireSampleRowsFunc_hook, allowing extensions to override the row sampling function used during ANALYZE for regular heap relations. 
> 
> Motivation 
> 
> Extensions that implement horizontal scaling of tables currently have no clean way to participate in ANALYZE's row sampling. The default acquire_sample_rows() only knows how to sample the local heap, so a distributed-table extension wanting accurate statistics has to either: 
> 
> maintain its own separate stats-collection machinery outside of ANALYZE entirely, or 
> duplicate/reimplement parts of analyze.c's sampling logic to pull rows from remote nodes. 
> 
> This hook lets such an extension plug into the existing ANALYZE code path and supply its own row acquisition function, without having to reinvent stats collection or duplicate logic that already exists in core. 
> 
> What the patch does 
> 
> Adds AcquireSampleRowsFunc_hook (typed identically to AcquireSampleRowsFunc) in vacuum.h. 
> In analyze.c, both analyze_rel() and acquire_inherited_sample_rows() check the hook and use it in place of acquire_sample_rows() when set. 
> Adds doc text in xfunc.sgml describing the hook's contract (fill rows[] up to targrows, set *totalrows). 
> Adds a regression test confirming ANALYZE still completes normally with the hook unset (the hook itself needs a C extension to exercise meaningfully, so this just guards against regressions in the unset case). 
> 
 
If you have implemented sharding using inheritance or partitioning and 
FDW, the facility to fetch statistics of foreign tables from foreign 
server can be useful here. IIUC, there is a limitation on using it for 
inherited foreign tables, but it's worth exploring and improving for 
your usecase. 
 
-- 
Best Wishes, 
Ashutosh Bapat 





Regards, 

Samba Siva Reddy 

http://sambasivareddy.in

^ permalink  raw  reply  [nested|flat] 36+ messages in thread


end of thread, other threads:[~2026-07-05 02:22 UTC | newest]

Thread overview: 36+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-07-26 21:33 [PATCH v7 3/3] Add WITH OLD DATA to CREATE OR REPLACE MATERIALIZED VIEW Erik Wienhold <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-05-29 12:35 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva <[email protected]>
2026-06-26 01:46 [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva Reddy Chinta <[email protected]>
2026-06-26 12:41 ` Re: [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Ashutosh Bapat <[email protected]>
2026-07-05 02:22   ` Re: [PATCH] Add hook for plugins to acquire sample rows during ANALYZE Samba Siva Reddy Chinta <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox