Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wcvfN-003lAi-0L for pgsql-hackers@arkaria.postgresql.org; Fri, 26 Jun 2026 01:47:37 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wcvfL-008HSB-2p for pgsql-hackers@arkaria.postgresql.org; Fri, 26 Jun 2026 01:47:35 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wcvfL-008HS3-1T for pgsql-hackers@lists.postgresql.org; Fri, 26 Jun 2026 01:47:35 +0000 Received: from sender-pp-o92.zoho.in ([103.117.158.92]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wcvfI-00000000I4C-07RH for pgsql-hackers@lists.postgresql.org; Fri, 26 Jun 2026 01:47:34 +0000 ARC-Seal: i=1; a=rsa-sha256; t=1782438447; cv=none; d=zohomail.in; s=zohoarc; b=D0OI/GpWWuAyLeHPm+Ld+hUWCudO4Cxf6K9qOYSLd7qpBx5InSEtRgyCAHdLIwpdLAcgiFr1/EFSdi2L6QQQ8U+elsvsRmZk6JeE2s2F6fUhifTqQrCuBlCf/RqD3GQz/LSVSQGqYHrgKrYgjwAo8IjWPqUsFVxFVmKyu93Z15s= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.in; s=zohoarc; t=1782438447; h=Content-Type:Date:Date:From:From:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To:Cc; bh=izZhDnc/PGUHABdl+vsVTjEvlhZUqDocITNm0fD+EJc=; b=anQAFkA4psHPX2vfGQYOzYMYyRwf7PfCuC6sG9MFlsz186XzjLE4uhXaYA06kYFsXq9Frd6XNGBGxah8eY3MPfe9o0GbgywLtDxNTHo/+HyYa6PHkcTa6IjzCySpa0k/CMcC2wEODYOSPwynqQXXdtUrsqfFCwSXjMrUJ5rcyww= ARC-Authentication-Results: i=1; mx.zohomail.in; dkim=pass header.i=zohomail.in; spf=pass smtp.mailfrom=sambasivareddy.ch@zohomail.in; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1782438447; s=zoho; d=zohomail.in; i=sambasivareddy.ch@zohomail.in; h=Date:Date:From:From:To:To:Message-Id:Message-Id:In-Reply-To:Subject:Subject:MIME-Version:Content-Type:Reply-To:Cc; bh=izZhDnc/PGUHABdl+vsVTjEvlhZUqDocITNm0fD+EJc=; b=kjUk11rLRPdTrE9OzvxEh4gDK4PqPBNtK23I+gna9K6Fq0MUbuJuqXXyCwY+ilqh EAPiq+CZMHWTAzNCvDRK+EGUPQcghkywPGWDqUA6wxDXabD62iyD4tGu6P63NxCvgaT RTeBFXz4akruP2TzDHh+HrUW4kKFaPh5HFIpWDww= Received: from mail.zoho.in by mx.zoho.in with SMTP id 1782438414824336.17929103452946; Fri, 26 Jun 2026 07:16:54 +0530 (IST) Received: from [122.15.156.180] by mail.zoho.in with HTTP;Fri, 26 Jun 2026 07:16:54 +0530 (IST) Date: Fri, 26 Jun 2026 07:16:54 +0530 From: Samba Siva Reddy Chinta To: "pgsql-hackers" Message-Id: <19f019bc9d0.45f9a6aa271083.5147864197130401189@zohomail.in> In-Reply-To: Subject: [PATCH] Add hook for plugins to acquire sample rows during ANALYZE MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_Part_741589_445982825.1782438414800" Importance: Medium Disposition-Notification-To: "Samba Siva Reddy Chinta" User-Agent: Zoho Mail X-Mailer: Zoho Mail X-Zoho-Virus-Status: 1 X-Zoho-AV-Stamp: zmail-av-0.2.10.1.5.2/282.388.57 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk ------=_Part_741589_445982825.1782438414800 Content-Type: multipart/alternative; boundary="----=_Part_741590_980413633.1782438414800" ------=_Part_741590_980413633.1782438414800 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi all, Attached is a patch that adds a hook, AcquireSampleRowsFunc_hook, allowing = extensions to override the row sampling function used during ANALYZE for re= gular heap relations. Motivation Extensions that implement horizontal scaling of tables currently have no cl= ean way to participate in ANALYZE's row sampling. The default acquire_sampl= e_rows() only knows how to sample the local heap, so a distributed-table ex= tension wanting accurate statistics has to either: maintain its own separate stats-collection machinery outside of ANALYZE ent= irely, 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 a= nd supply its own row acquisition function, without having to reinvent stat= s 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). =E2=80=8BRegards,=20 Samba Siva Reddy=20 http://sambasivareddy.in ------=_Part_741590_980413633.1782438414800 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable =

Hi all,

Attached is a patch that adds a hook, AcquireSampleRows= Func_hook, allowing extensions to override the row sampling func= tion used during ANALYZE for regular heap relations.

Motivation
=

Ex= tensions that implement horizontal scaling of tables currently have no clea= n 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 ext= ension wanting accurate statistics has to either:

  • maintain its own separate stats-collection machinery outside of ANALYZE en= tirely, or
  • duplicate/reimplement parts of analyze.c's sampling logi= c to pull rows from remote nodes.

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

What the patch does

  • Adds AcquireSampleRowsFunc_hook (typed identically to AcquireSampleRowsFun= c) in vacuum.h.
  • In analyze.c, both analyze_rel() and acquire_inh= erited_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).
  • Ad= ds a regression test confirming ANALYZE still completes normally with the h= ook unset (the hook itself needs a C extension to exercise meaningfully, so= this just guards against regressions in the unset case).
=E2=80=8B

Regards,
<= /p>

Samba = Siva Reddy



------=_Part_741590_980413633.1782438414800-- ------=_Part_741589_445982825.1782438414800 Content-Type: application/octet-stream; name=v1-acquire-sample-rows-hook.patch Content-Transfer-Encoding: 7bit X-ZM_AttachId: 140017312148000420 Content-Disposition: attachment; filename=v1-acquire-sample-rows-hook.patch From 1c2009a823e1922e3a1a2b280626abbe11a86696 Mon Sep 17 00:00:00 2001 From: Samba Siva Date: Fri, 29 May 2026 18:05:45 +0530 Subject: [PATCH] Add hook for plugins to acquire sample rows during ANALYZE - 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 SupportRequestIndexCondition request type. + + + The hook variable AcquireSampleRowsFunc_hook allows extensions + to override the row sampling function during ANALYZE 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 rows[] buffer with at most + targrows heap tuples and sets *totalrows to the + estimated total live row count of the relation. + 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--