agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v1 4/7] Row pattern recognition patch (executor). 10+ messages / 2 participants [nested] [flat]
* [PATCH v1 4/7] Row pattern recognition patch (executor). @ 2023-06-25 11:48 Tatsuo Ishii <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Tatsuo Ishii @ 2023-06-25 11:48 UTC (permalink / raw) --- src/backend/executor/nodeWindowAgg.c | 223 +++++++++++++++++++++- src/backend/utils/adt/windowfuncs.c | 274 ++++++++++++++++++++++++++- src/include/catalog/pg_proc.dat | 9 + src/include/nodes/execnodes.h | 12 ++ src/include/windowapi.h | 9 + 5 files changed, 517 insertions(+), 10 deletions(-) diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 310ac23e3a..ae997dff86 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -48,6 +48,7 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/fmgroids.h" #include "utils/expandeddatum.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData bool restart; /* need to restart this agg in this cycle? */ } WindowStatePerAggData; +/* + * Map between Var attno in a target list and the parsed attno. + */ +typedef struct AttnoMap { + List *attno; /* att number in target list (list of AttNumber) */ + List *attnosyn; /* parsed att number (list of AttNumber) */ +} AttnoMap; + static void initialize_windowaggregate(WindowAggState *winstate, WindowStatePerFunc perfuncstate, WindowStatePerAgg peraggstate); @@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype); static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1, TupleTableSlot *slot2); -static bool window_gettupleslot(WindowObject winobj, int64 pos, - TupleTableSlot *slot); +static void attno_map(Node *node, AttnoMap *map); +static bool attno_map_walker(Node *node, void *context); /* * initialize_windowaggregate @@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) TupleDesc scanDesc; ListCell *l; + TargetEntry *te; + Expr *expr; + Var *var; + int nargs; + AttnoMap attnomap; + /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); @@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc, &TTSOpsMinimalTuple); + winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc, + &TTSOpsMinimalTuple); + + winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc, + &TTSOpsMinimalTuple); + + winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc, + &TTSOpsMinimalTuple); + winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot); + /* * create frame head and tail slots only if needed (must create slots in * exactly the same cases that update_frameheadpos and update_frametailpos @@ -2667,6 +2692,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) winstate->inRangeAsc = node->inRangeAsc; winstate->inRangeNullsFirst = node->inRangeNullsFirst; + /* Set up row pattern recognition PATTERN clause */ + winstate->patternVariableList = node->patternVariable; + winstate->patternRegexpList = node->patternRegexp; + + /* Set up row pattern recognition DEFINE clause */ + + /* + * Collect mapping between varattno and varattnosyn in the targetlist. + * XXX: For now we only check RPR's argument. Eventually we have to + * recurse the targetlist to find out all mappings in Var nodes. + */ + attnomap.attno = NIL; + attnomap.attnosyn = NIL; + + foreach (l, node->plan.targetlist) + { + te = lfirst(l); + if (IsA(te->expr, WindowFunc)) + { + WindowFunc *func = (WindowFunc *)te->expr; + if (func->winfnoid != F_RPR) + continue; + + /* sanity check */ + nargs = list_length(func->args); + if (list_length(func->args) != 1) + elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs); + + expr = (Expr *) lfirst(list_head(func->args)); + if (!IsA(expr, Var)) + elog(ERROR, "RPR's arg is not Var"); + + var = (Var *)expr; + elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d", + te->resname, var->varattno, var->varattnosyn); + attnomap.attno = lappend_int(attnomap.attno, var->varattno); + attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn); + } + } + + winstate->defineVariableList = NIL; + winstate->defineClauseList = NIL; + if (node->defineClause != NIL) + { + foreach(l, node->defineClause) + { + char *name; + ExprState *exps; + + te = lfirst(l); + name = te->resname; + expr = te->expr; + + elog(DEBUG1, "defineVariable name: %s", name); + winstate->defineVariableList = lappend(winstate->defineVariableList, + makeString(pstrdup(name))); + /* tweak expr so that it referes to outer slot */ + attno_map((Node *)expr, &attnomap); + exps = ExecInitExpr(expr, (PlanState *) winstate); + winstate->defineClauseList = lappend(winstate->defineClauseList, exps); + } + } + winstate->all_first = true; winstate->partition_spooled = false; winstate->more_partitions = false; @@ -2674,6 +2762,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) return winstate; } +/* + * Rewrite Var node's varattno to the varattno which is used in the target + * list using AttnoMap. We also rewrite varno so that it sees outer tuple + * (PREV) or inner tuple (NEXT). + */ +static void +attno_map(Node *node, AttnoMap *map) +{ + (void) expression_tree_walker(node, attno_map_walker, (void *) map); +} + +static bool +attno_map_walker(Node *node, void *context) +{ + FuncExpr *func; + int nargs; + Expr *expr; + Var *var; + AttnoMap *attnomap; + ListCell *lc1, *lc2; + + if (node == NULL) + return false; + + attnomap = (AttnoMap *) context; + + if (IsA(node, FuncExpr)) + { + func = (FuncExpr *)node; + + if (func->funcid == F_PREV || func->funcid == F_NEXT) + { + /* sanity check */ + nargs = list_length(func->args); + if (list_length(func->args) != 1) + elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs); + + expr = (Expr *) lfirst(list_head(func->args)); + if (!IsA(expr, Var)) + elog(ERROR, "PREV/NEXT's arg is not Var"); /* XXX: is it possible that arg type is Const? */ + var = (Var *)expr; + + if (func->funcid == F_PREV) + var->varno = OUTER_VAR; + else + var->varno = INNER_VAR; + } + return expression_tree_walker(node, attno_map_walker, (void *) context); + } + else if (IsA(node, Var)) + { + var = (Var *)node; + + elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno); + + forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn) + { + int attno = lfirst_int(lc1); + int attnosyn = lfirst_int(lc2); + + if (var->varattno == attnosyn) + { + elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno); + var->varattno = attno; + } + } + } + return expression_tree_walker(node, attno_map_walker, (void *) context); +} + /* ----------------- * ExecEndWindowAgg * ----------------- @@ -2691,6 +2849,8 @@ ExecEndWindowAgg(WindowAggState *node) ExecClearTuple(node->agg_row_slot); ExecClearTuple(node->temp_slot_1); ExecClearTuple(node->temp_slot_2); + ExecClearTuple(node->prev_slot); + ExecClearTuple(node->next_slot); if (node->framehead_slot) ExecClearTuple(node->framehead_slot); if (node->frametail_slot) @@ -2740,6 +2900,8 @@ ExecReScanWindowAgg(WindowAggState *node) ExecClearTuple(node->agg_row_slot); ExecClearTuple(node->temp_slot_1); ExecClearTuple(node->temp_slot_2); + ExecClearTuple(node->prev_slot); + ExecClearTuple(node->next_slot); if (node->framehead_slot) ExecClearTuple(node->framehead_slot); if (node->frametail_slot) @@ -3080,7 +3242,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1, * * Returns true if successful, false if no such row */ -static bool +bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot) { WindowAggState *winstate = winobj->winstate; @@ -3420,14 +3582,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, WindowAggState *winstate; ExprContext *econtext; TupleTableSlot *slot; - int64 abs_pos; - int64 mark_pos; Assert(WindowObjectIsValid(winobj)); winstate = winobj->winstate; econtext = winstate->ss.ps.ps_ExprContext; slot = winstate->temp_slot_1; + if (WinGetSlotInFrame(winobj, slot, + relpos, seektype, set_mark, + isnull, isout) == 0) + { + econtext->ecxt_outertuple = slot; + return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno), + econtext, isnull); + } + + if (isout) + *isout = true; + *isnull = true; + return (Datum) 0; +} + +/* + * WinGetSlotInFrame + * slot: TupleTableSlot to store the result + * relpos: signed rowcount offset from the seek position + * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL + * set_mark: If the row is found/in frame and set_mark is true, the mark is + * moved to the row as a side-effect. + * isnull: output argument, receives isnull status of result + * isout: output argument, set to indicate whether target row position + * is out of frame (can pass NULL if caller doesn't care about this) + * + * Returns 0 if we successfullt got the slot. false if out of frame. + * (also isout is set) + */ +int +WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot, + int relpos, int seektype, bool set_mark, + bool *isnull, bool *isout) +{ + WindowAggState *winstate; + int64 abs_pos; + int64 mark_pos; + + Assert(WindowObjectIsValid(winobj)); + winstate = winobj->winstate; + switch (seektype) { case WINDOW_SEEK_CURRENT: @@ -3583,15 +3784,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, *isout = false; if (set_mark) WinSetMarkPosition(winobj, mark_pos); - econtext->ecxt_outertuple = slot; - return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno), - econtext, isnull); + return 0; out_of_frame: if (isout) *isout = true; *isnull = true; - return (Datum) 0; + return -1; } /* @@ -3622,3 +3821,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull) return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno), econtext, isnull); } + +WindowAggState * +WinGetAggState(WindowObject winobj) +{ + return winobj->winstate; +} diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c index b87a624fb2..63b225271c 100644 --- a/src/backend/utils/adt/windowfuncs.c +++ b/src/backend/utils/adt/windowfuncs.c @@ -13,6 +13,9 @@ */ #include "postgres.h" +#include "catalog/pg_collation_d.h" +#include "executor/executor.h" +#include "nodes/execnodes.h" #include "nodes/supportnodes.h" #include "utils/builtins.h" #include "windowapi.h" @@ -39,7 +42,9 @@ typedef struct static bool rank_up(WindowObject winobj); static Datum leadlag_common(FunctionCallInfo fcinfo, bool forward, bool withoffset, bool withdefault); - +static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos); +static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate, + int relpos, char *vname, char *quantifier, bool *result); /* * utility routine for *_rank functions. @@ -713,3 +718,270 @@ window_nth_value(PG_FUNCTION_ARGS) PG_RETURN_DATUM(result); } + +/* + * rpr + * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in + * the target list. + * Usage: SELECT rpr(colname) OVER (..) + * where colname is defined in PATTERN clause. + */ +Datum +window_rpr(PG_FUNCTION_ARGS) +{ +#define MAX_PATTERNS 16 /* max variables in PATTERN clause */ + + WindowObject winobj = PG_WINDOW_OBJECT(); + WindowAggState *winstate = WinGetAggState(winobj); + Datum result; + bool expression_result; + bool isnull; + int relpos; + int64 curr_pos, markpos; + ListCell *lc, *lc1; + + curr_pos = WinGetCurrentPosition(winobj); + elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos); + + /* + * Evaluate PATTERN until one of expressions is not true or out of frame. + */ + relpos = 0; + + forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList) + { + char *vname = strVal(lfirst(lc)); + char *quantifier = strVal(lfirst(lc1)); + + elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier); + + /* evaluate row pattern against current row */ + relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result); + + /* + * If the expression did not match, we are done. + */ + if (!expression_result) + break; + + /* out of frame? */ + if (relpos < 0) + break; + + /* count up relative row position */ + relpos++; + } + + elog(DEBUG1, "relpos: %d", relpos); + + /* + * If current row satified the pattern, return argument expression. + */ + if (expression_result) + { + result = WinGetFuncArgInFrame(winobj, 0, + 0, WINDOW_SEEK_HEAD, false, + &isnull, NULL); + } + + /* + * At this point we can set mark down to current pos -2. + */ + markpos = curr_pos -2; + elog(DEBUG1, "markpos: " INT64_FORMAT, markpos); + if (markpos >= 0) + WinSetMarkPosition(winobj, markpos); + + if (expression_result) + PG_RETURN_DATUM(result); + + PG_RETURN_NULL(); +} + +/* + * Evaluate expression associated with PATTERN variable vname. + * relpos is relative row position in a frame (starting from 0). + * "quantifier" is the quatifier part of the PATTERN regular expression. + * Currently only '+' is allowed. + * result is out paramater representing the expression evaluation result + * is true of false. + * Return values are: + * >=0: the last match relative row position + * -1: current row is out of frame + */ +static +int evaluate_pattern(WindowObject winobj, WindowAggState *winstate, + int relpos, char *vname, char *quantifier, bool *result) +{ + ExprContext *econtext = winstate->ss.ps.ps_ExprContext; + ListCell *lc1, *lc2; + ExprState *pat; + Datum eval_result; + int sts; + bool out_of_frame = false; + bool isnull; + StringInfo encoded_str = makeStringInfo(); + char pattern_str[128]; + + forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList) + { + char *name = strVal(lfirst(lc1)); + bool second_try_match = false; + + if (strcmp(vname, name)) + continue; + + /* set expression to evaluate */ + pat = lfirst(lc2); + + for (;;) + { + if (!get_slots(winobj, winstate, relpos)) + { + out_of_frame = true; + break; /* current row is out of frame */ + } + + /* evaluate the expression */ + eval_result = ExecEvalExpr(pat, econtext, &isnull); + if (isnull) + { + /* expression is NULL */ + elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos); + break; + } + else + { + if (!DatumGetBool(eval_result)) + { + /* expression is false */ + elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos); + break; + } + else + { + /* expression is true */ + elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos); + appendStringInfoChar(encoded_str, vname[0]); + + /* If quantifier is "+", we need to look for more matching row */ + if (quantifier && !strcmp(quantifier, "+")) + { + /* remember that we want to try another row */ + second_try_match = true; + relpos++; + } + else + break; + } + } + } + if (second_try_match) + relpos--; + + if (out_of_frame) + { + *result = false; + return -1; + } + + /* build regular expression */ + snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier); + + /* + * Do regular expression matching against sequence of rows satisfying + * the expression using regexp_instr(). + */ + sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID, + PointerGetDatum(cstring_to_text(encoded_str->data)), + PointerGetDatum(cstring_to_text(pattern_str)))); + elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s", + sts, encoded_str->data, pattern_str); + *result = (sts > 0)? true : false; + } + return relpos; +} + +/* + * Get current, previous and next tuple. + * Returns true if still within frame. + */ +static bool +get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos) +{ + TupleTableSlot *slot; + bool isnull, isout; + int sts; + ExprContext *econtext; + + econtext = winstate->ss.ps.ps_ExprContext; + + /* for current row */ + slot = winstate->temp_slot_1; + sts = WinGetSlotInFrame(winobj, slot, + current_pos, WINDOW_SEEK_HEAD, false, + &isnull, &isout); + if (sts < 0) + { + elog(DEBUG1, "current row is out of frame"); + econtext->ecxt_scantuple = winstate->null_slot; + return false; + } + else + econtext->ecxt_scantuple = slot; + + /* for PREV */ + if (current_pos > 0) + { + slot = winstate->prev_slot; + sts = WinGetSlotInFrame(winobj, slot, + current_pos - 1, WINDOW_SEEK_HEAD, false, + &isnull, &isout); + if (sts < 0) + { + elog(DEBUG1, "previous row out of frame at: %d", current_pos); + econtext->ecxt_outertuple = winstate->null_slot; + } + econtext->ecxt_outertuple = slot; + } + else + econtext->ecxt_outertuple = winstate->null_slot; + + /* for NEXT */ + slot = winstate->next_slot; + sts = WinGetSlotInFrame(winobj, slot, + current_pos + 1, WINDOW_SEEK_HEAD, false, + &isnull, &isout); + if (sts < 0) + { + elog(DEBUG1, "next row out of frame at: %d", current_pos); + econtext->ecxt_innertuple = winstate->null_slot; + } + else + econtext->ecxt_innertuple = slot; + + return true; +} + +/* + * prev + * Dummy function to invoke RPR's navigation operator "PREV". + * This is *not* a window function. + */ +Datum +window_prev(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); +} + +/* + * next + * Dummy function to invoke RPR's navigation operation "NEXT". + * This is *not* a window function. + */ +Datum +window_next(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); +} + diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6996073989..e3a9e0ffeb 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10397,6 +10397,15 @@ { oid => '3114', descr => 'fetch the Nth row value', proname => 'nth_value', prokind => 'w', prorettype => 'anyelement', proargtypes => 'anyelement int4', prosrc => 'window_nth_value' }, +{ oid => '6122', descr => 'row pattern recognition in window', + proname => 'rpr', prokind => 'w', prorettype => 'anyelement', + proargtypes => 'anyelement', prosrc => 'window_rpr' }, +{ oid => '6123', descr => 'previous value', + proname => 'prev', provolatile => 's', prorettype => 'anyelement', + proargtypes => 'anyelement', prosrc => 'window_prev' }, +{ oid => '6124', descr => 'next value', + proname => 'next', provolatile => 's', prorettype => 'anyelement', + proargtypes => 'anyelement', prosrc => 'window_next' }, # functions for range types { oid => '3832', descr => 'I/O', diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index cb714f4a19..bc95c5fe9b 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -2519,6 +2519,13 @@ typedef struct WindowAggState int64 groupheadpos; /* current row's peer group head position */ int64 grouptailpos; /* " " " " tail position (group end+1) */ + /* these fields are used in Row pattern recognition: */ + List *patternVariableList; /* list of row pattern variables names (list of String) */ + List *patternRegexpList; /* list of row pattern regular expressions ('*', '+' or '?'. list of String) */ + List *defineVariableList; /* list of row pattern definition variables (list of String) */ + List *defineClauseList; /* expression for row pattern definition + * search conditions ExprState list */ + MemoryContext partcontext; /* context for partition-lifespan data */ MemoryContext aggcontext; /* shared context for aggregate working data */ MemoryContext curaggcontext; /* current aggregate's working data */ @@ -2555,6 +2562,11 @@ typedef struct WindowAggState TupleTableSlot *agg_row_slot; TupleTableSlot *temp_slot_1; TupleTableSlot *temp_slot_2; + + /* temporary slots for RPR */ + TupleTableSlot *prev_slot; /* PREV row navigation operator */ + TupleTableSlot *next_slot; /* NEXT row navigation operator */ + TupleTableSlot *null_slot; /* all NULL slot */ } WindowAggState; /* ---------------- diff --git a/src/include/windowapi.h b/src/include/windowapi.h index b8c2c565d1..a0facf38fe 100644 --- a/src/include/windowapi.h +++ b/src/include/windowapi.h @@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno, int relpos, int seektype, bool set_mark, bool *isnull, bool *isout); +extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot, + int relpos, int seektype, bool set_mark, + bool *isnull, bool *isout); + extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull); +extern WindowAggState *WinGetAggState(WindowObject winobj); + +extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot); + + #endif /* WINDOWAPI_H */ -- 2.25.1 ----Next_Part(Sun_Jun_25_21_05_09_2023_126)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v1-0005-Row-pattern-recognition-patch-docs.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v7 2/5] Add anytime flush tests for custom stats @ 2026-02-05 05:54 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Bertrand Drouvot @ 2026-02-05 05:54 UTC (permalink / raw) --- .../test_custom_stats/t/001_custom_stats.pl | 41 +++++++++++++ .../test_custom_fixed_stats--1.0.sql | 5 ++ .../test_custom_fixed_stats.c | 57 +++++++++++++++++++ .../test_custom_var_stats--1.0.sql | 5 ++ .../test_custom_stats/test_custom_var_stats.c | 27 +++++++++ 5 files changed, 135 insertions(+) 33.8% src/test/modules/test_custom_stats/t/ 66.1% src/test/modules/test_custom_stats/ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a38577..7be1b281776 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -156,5 +156,46 @@ $result = $node->safe_psql('postgres', ); is($result, "0", "report of fixed-sized after manual reset"); +# Test FLUSH_ANYTIME mechanism with custom fixed stats +# This verifies that custom stats can be flushed during a transaction + +# Reset stats first +$node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +my $anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'anytime:'||numcalls from test_custom_stats_fixed_report(); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^anytime:2/m, + "anytime fixed stats flushed during transaction"); + +# Test FLUSH_ANYTIME mechanism with custom variable stats +# This verifies that custom stats can be flushed during a transaction + +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +$anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_var_anytime_update('entry2'); + select test_custom_stats_var_anytime_update('entry2'); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select * from test_custom_stats_var_report('entry2'); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^entry2|2|/m, + "anytime var stats flushed during transaction"); + # Test completed successfully done_testing(); diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql index 69a93b5241f..da3a798f289 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql @@ -18,3 +18,8 @@ CREATE FUNCTION test_custom_stats_fixed_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_fixed_reset' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_fixed_anytime_update() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c index 908bd18a7c7..30b0fbcbdc7 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c @@ -18,6 +18,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_fixed_stats", @@ -43,11 +44,13 @@ typedef struct PgStatShared_CustomFixedEntry static void test_custom_stats_fixed_init_shmem_cb(void *stats); static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts); static void test_custom_stats_fixed_snapshot_cb(void); +static bool test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only); static const PgStat_KindInfo custom_stats = { .name = "test_custom_fixed_stats", .fixed_amount = true, /* exactly one entry */ .write_to_file = true, /* persist to stats file */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .shared_size = sizeof(PgStat_StatCustomFixedEntry), .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats), @@ -56,8 +59,12 @@ static const PgStat_KindInfo custom_stats = { .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb, .reset_all_cb = test_custom_stats_fixed_reset_all_cb, .snapshot_cb = test_custom_stats_fixed_snapshot_cb, + .flush_static_cb = test_custom_stats_fixed_flush_cb, }; +/* Pending statistics */ +static PgStat_StatCustomFixedEntry PendingCustomStats = {0}; + /* * Kind ID for test_custom_fixed_stats. */ @@ -141,6 +148,38 @@ test_custom_stats_fixed_snapshot_cb(void) #undef FIXED_COMP } +/* + * test_custom_stats_fixed_flush_cb + * Flush pending stats to shared memory + */ +static bool +test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only) +{ + PgStatShared_CustomFixedEntry *stats_shmem; + + /* Nothing to flush if no calls were made */ + if (PendingCustomStats.numcalls == 0) + return false; + + stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS); + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + stats_shmem->stats.numcalls += PendingCustomStats.numcalls; + pgstat_end_changecount_write(&stats_shmem->changecount); + + LWLockRelease(&stats_shmem->lock); + + /* Reset pending stats */ + PendingCustomStats.numcalls = 0; + + return false; /* successfully flushed */ +} + /*-------------------------------------------------------------------------- * SQL-callable functions *-------------------------------------------------------------------------- @@ -222,3 +261,21 @@ test_custom_stats_fixed_report(PG_FUNCTION_ARGS) /* Return as tuple */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* + * test_custom_stats_fixed_anytime_update + * Increment call counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_fixed_anytime_update); +Datum +test_custom_stats_fixed_anytime_update(PG_FUNCTION_ARGS) +{ + /* Accumulate in pending stats */ + PendingCustomStats.numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + pgstat_report_fixed = true; + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 5ed8cfc2dcf..ed66d38981e 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -24,3 +24,8 @@ CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_var_anytime_update(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_anytime_update' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index bc0b5d6e0eb..207e841911b 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -17,6 +17,7 @@ #include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_var_stats", @@ -107,6 +108,7 @@ static const PgStat_KindInfo custom_stats = { .name = "test_custom_var_stats", .fixed_amount = false, /* variable number of entries */ .write_to_file = true, /* persist across restarts */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .track_entry_count = true, /* count active entries */ .accessed_across_databases = true, /* global statistics */ .shared_size = sizeof(PgStatShared_CustomVarEntry), @@ -689,3 +691,28 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * test_custom_stats_var_anytime_update + * Increment custom statistic counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_anytime_update); +Datum +test_custom_stats_var_anytime_update(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + PgStat_EntryRef *entry_ref; + PgStat_StatCustomVarEntry *pending_entry; + + /* Get pending entry in local memory */ + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL); + + pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending; + pending_entry->numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + + PG_RETURN_VOID(); +} -- 2.34.1 --C2xzVbxFmVrP7k6O Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0003-Add-GUC-to-specify-non-transactional-statistics-f.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v8 2/5] Add anytime flush tests for custom stats @ 2026-02-05 05:54 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Bertrand Drouvot @ 2026-02-05 05:54 UTC (permalink / raw) --- .../test_custom_stats/t/001_custom_stats.pl | 41 +++++++++++++ .../test_custom_fixed_stats--1.0.sql | 5 ++ .../test_custom_fixed_stats.c | 57 +++++++++++++++++++ .../test_custom_var_stats--1.0.sql | 5 ++ .../test_custom_stats/test_custom_var_stats.c | 27 +++++++++ 5 files changed, 135 insertions(+) 33.8% src/test/modules/test_custom_stats/t/ 66.1% src/test/modules/test_custom_stats/ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a38577..7be1b281776 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -156,5 +156,46 @@ $result = $node->safe_psql('postgres', ); is($result, "0", "report of fixed-sized after manual reset"); +# Test FLUSH_ANYTIME mechanism with custom fixed stats +# This verifies that custom stats can be flushed during a transaction + +# Reset stats first +$node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +my $anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'anytime:'||numcalls from test_custom_stats_fixed_report(); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^anytime:2/m, + "anytime fixed stats flushed during transaction"); + +# Test FLUSH_ANYTIME mechanism with custom variable stats +# This verifies that custom stats can be flushed during a transaction + +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +$anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_var_anytime_update('entry2'); + select test_custom_stats_var_anytime_update('entry2'); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select * from test_custom_stats_var_report('entry2'); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^entry2|2|/m, + "anytime var stats flushed during transaction"); + # Test completed successfully done_testing(); diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql index 69a93b5241f..da3a798f289 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql @@ -18,3 +18,8 @@ CREATE FUNCTION test_custom_stats_fixed_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_fixed_reset' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_fixed_anytime_update() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c index 908bd18a7c7..30b0fbcbdc7 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c @@ -18,6 +18,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_fixed_stats", @@ -43,11 +44,13 @@ typedef struct PgStatShared_CustomFixedEntry static void test_custom_stats_fixed_init_shmem_cb(void *stats); static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts); static void test_custom_stats_fixed_snapshot_cb(void); +static bool test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only); static const PgStat_KindInfo custom_stats = { .name = "test_custom_fixed_stats", .fixed_amount = true, /* exactly one entry */ .write_to_file = true, /* persist to stats file */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .shared_size = sizeof(PgStat_StatCustomFixedEntry), .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats), @@ -56,8 +59,12 @@ static const PgStat_KindInfo custom_stats = { .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb, .reset_all_cb = test_custom_stats_fixed_reset_all_cb, .snapshot_cb = test_custom_stats_fixed_snapshot_cb, + .flush_static_cb = test_custom_stats_fixed_flush_cb, }; +/* Pending statistics */ +static PgStat_StatCustomFixedEntry PendingCustomStats = {0}; + /* * Kind ID for test_custom_fixed_stats. */ @@ -141,6 +148,38 @@ test_custom_stats_fixed_snapshot_cb(void) #undef FIXED_COMP } +/* + * test_custom_stats_fixed_flush_cb + * Flush pending stats to shared memory + */ +static bool +test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only) +{ + PgStatShared_CustomFixedEntry *stats_shmem; + + /* Nothing to flush if no calls were made */ + if (PendingCustomStats.numcalls == 0) + return false; + + stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS); + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + stats_shmem->stats.numcalls += PendingCustomStats.numcalls; + pgstat_end_changecount_write(&stats_shmem->changecount); + + LWLockRelease(&stats_shmem->lock); + + /* Reset pending stats */ + PendingCustomStats.numcalls = 0; + + return false; /* successfully flushed */ +} + /*-------------------------------------------------------------------------- * SQL-callable functions *-------------------------------------------------------------------------- @@ -222,3 +261,21 @@ test_custom_stats_fixed_report(PG_FUNCTION_ARGS) /* Return as tuple */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* + * test_custom_stats_fixed_anytime_update + * Increment call counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_fixed_anytime_update); +Datum +test_custom_stats_fixed_anytime_update(PG_FUNCTION_ARGS) +{ + /* Accumulate in pending stats */ + PendingCustomStats.numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + pgstat_report_fixed = true; + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 5ed8cfc2dcf..ed66d38981e 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -24,3 +24,8 @@ CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_var_anytime_update(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_anytime_update' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index bc0b5d6e0eb..207e841911b 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -17,6 +17,7 @@ #include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_var_stats", @@ -107,6 +108,7 @@ static const PgStat_KindInfo custom_stats = { .name = "test_custom_var_stats", .fixed_amount = false, /* variable number of entries */ .write_to_file = true, /* persist across restarts */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .track_entry_count = true, /* count active entries */ .accessed_across_databases = true, /* global statistics */ .shared_size = sizeof(PgStatShared_CustomVarEntry), @@ -689,3 +691,28 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * test_custom_stats_var_anytime_update + * Increment custom statistic counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_anytime_update); +Datum +test_custom_stats_var_anytime_update(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + PgStat_EntryRef *entry_ref; + PgStat_StatCustomVarEntry *pending_entry; + + /* Get pending entry in local memory */ + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL); + + pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending; + pending_entry->numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + + PG_RETURN_VOID(); +} -- 2.34.1 --OI5irqZsxWxBW9nT Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-Add-GUC-to-specify-non-transactional-statistics-f.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v9 2/5] Add anytime flush tests for custom stats @ 2026-02-05 05:54 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Bertrand Drouvot @ 2026-02-05 05:54 UTC (permalink / raw) --- .../test_custom_stats/t/001_custom_stats.pl | 41 +++++++++++++ .../test_custom_fixed_stats--1.0.sql | 5 ++ .../test_custom_fixed_stats.c | 57 +++++++++++++++++++ .../test_custom_var_stats--1.0.sql | 5 ++ .../test_custom_stats/test_custom_var_stats.c | 27 +++++++++ 5 files changed, 135 insertions(+) 33.8% src/test/modules/test_custom_stats/t/ 66.1% src/test/modules/test_custom_stats/ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a38577..7be1b281776 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -156,5 +156,46 @@ $result = $node->safe_psql('postgres', ); is($result, "0", "report of fixed-sized after manual reset"); +# Test FLUSH_ANYTIME mechanism with custom fixed stats +# This verifies that custom stats can be flushed during a transaction + +# Reset stats first +$node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +my $anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'anytime:'||numcalls from test_custom_stats_fixed_report(); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^anytime:2/m, + "anytime fixed stats flushed during transaction"); + +# Test FLUSH_ANYTIME mechanism with custom variable stats +# This verifies that custom stats can be flushed during a transaction + +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +$anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_var_anytime_update('entry2'); + select test_custom_stats_var_anytime_update('entry2'); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select * from test_custom_stats_var_report('entry2'); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^entry2|2|/m, + "anytime var stats flushed during transaction"); + # Test completed successfully done_testing(); diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql index 69a93b5241f..da3a798f289 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql @@ -18,3 +18,8 @@ CREATE FUNCTION test_custom_stats_fixed_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_fixed_reset' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_fixed_anytime_update() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c index 908bd18a7c7..30b0fbcbdc7 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c @@ -18,6 +18,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_fixed_stats", @@ -43,11 +44,13 @@ typedef struct PgStatShared_CustomFixedEntry static void test_custom_stats_fixed_init_shmem_cb(void *stats); static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts); static void test_custom_stats_fixed_snapshot_cb(void); +static bool test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only); static const PgStat_KindInfo custom_stats = { .name = "test_custom_fixed_stats", .fixed_amount = true, /* exactly one entry */ .write_to_file = true, /* persist to stats file */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .shared_size = sizeof(PgStat_StatCustomFixedEntry), .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats), @@ -56,8 +59,12 @@ static const PgStat_KindInfo custom_stats = { .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb, .reset_all_cb = test_custom_stats_fixed_reset_all_cb, .snapshot_cb = test_custom_stats_fixed_snapshot_cb, + .flush_static_cb = test_custom_stats_fixed_flush_cb, }; +/* Pending statistics */ +static PgStat_StatCustomFixedEntry PendingCustomStats = {0}; + /* * Kind ID for test_custom_fixed_stats. */ @@ -141,6 +148,38 @@ test_custom_stats_fixed_snapshot_cb(void) #undef FIXED_COMP } +/* + * test_custom_stats_fixed_flush_cb + * Flush pending stats to shared memory + */ +static bool +test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only) +{ + PgStatShared_CustomFixedEntry *stats_shmem; + + /* Nothing to flush if no calls were made */ + if (PendingCustomStats.numcalls == 0) + return false; + + stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS); + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + stats_shmem->stats.numcalls += PendingCustomStats.numcalls; + pgstat_end_changecount_write(&stats_shmem->changecount); + + LWLockRelease(&stats_shmem->lock); + + /* Reset pending stats */ + PendingCustomStats.numcalls = 0; + + return false; /* successfully flushed */ +} + /*-------------------------------------------------------------------------- * SQL-callable functions *-------------------------------------------------------------------------- @@ -222,3 +261,21 @@ test_custom_stats_fixed_report(PG_FUNCTION_ARGS) /* Return as tuple */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* + * test_custom_stats_fixed_anytime_update + * Increment call counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_fixed_anytime_update); +Datum +test_custom_stats_fixed_anytime_update(PG_FUNCTION_ARGS) +{ + /* Accumulate in pending stats */ + PendingCustomStats.numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + pgstat_report_fixed = true; + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 5ed8cfc2dcf..ed66d38981e 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -24,3 +24,8 @@ CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_var_anytime_update(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_anytime_update' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index bc0b5d6e0eb..207e841911b 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -17,6 +17,7 @@ #include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_var_stats", @@ -107,6 +108,7 @@ static const PgStat_KindInfo custom_stats = { .name = "test_custom_var_stats", .fixed_amount = false, /* variable number of entries */ .write_to_file = true, /* persist across restarts */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .track_entry_count = true, /* count active entries */ .accessed_across_databases = true, /* global statistics */ .shared_size = sizeof(PgStatShared_CustomVarEntry), @@ -689,3 +691,28 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * test_custom_stats_var_anytime_update + * Increment custom statistic counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_anytime_update); +Datum +test_custom_stats_var_anytime_update(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + PgStat_EntryRef *entry_ref; + PgStat_StatCustomVarEntry *pending_entry; + + /* Get pending entry in local memory */ + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL); + + pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending; + pending_entry->numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + + PG_RETURN_VOID(); +} -- 2.34.1 --aq/6bi8L6WORnI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v9-0003-Add-GUC-to-specify-non-transactional-statistics-f.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v10 2/5] Add anytime flush tests for custom stats @ 2026-02-05 05:54 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Bertrand Drouvot @ 2026-02-05 05:54 UTC (permalink / raw) --- .../test_custom_stats/t/001_custom_stats.pl | 41 +++++++++++++ .../test_custom_fixed_stats--1.0.sql | 5 ++ .../test_custom_fixed_stats.c | 57 +++++++++++++++++++ .../test_custom_var_stats--1.0.sql | 5 ++ .../test_custom_stats/test_custom_var_stats.c | 27 +++++++++ 5 files changed, 135 insertions(+) 33.8% src/test/modules/test_custom_stats/t/ 66.1% src/test/modules/test_custom_stats/ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a38577..7be1b281776 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -156,5 +156,46 @@ $result = $node->safe_psql('postgres', ); is($result, "0", "report of fixed-sized after manual reset"); +# Test FLUSH_ANYTIME mechanism with custom fixed stats +# This verifies that custom stats can be flushed during a transaction + +# Reset stats first +$node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +my $anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'anytime:'||numcalls from test_custom_stats_fixed_report(); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^anytime:2/m, + "anytime fixed stats flushed during transaction"); + +# Test FLUSH_ANYTIME mechanism with custom variable stats +# This verifies that custom stats can be flushed during a transaction + +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +$anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_var_anytime_update('entry2'); + select test_custom_stats_var_anytime_update('entry2'); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select * from test_custom_stats_var_report('entry2'); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^entry2|2|/m, + "anytime var stats flushed during transaction"); + # Test completed successfully done_testing(); diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql index 69a93b5241f..da3a798f289 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql @@ -18,3 +18,8 @@ CREATE FUNCTION test_custom_stats_fixed_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_fixed_reset' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_fixed_anytime_update() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c index 908bd18a7c7..30b0fbcbdc7 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c @@ -18,6 +18,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_fixed_stats", @@ -43,11 +44,13 @@ typedef struct PgStatShared_CustomFixedEntry static void test_custom_stats_fixed_init_shmem_cb(void *stats); static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts); static void test_custom_stats_fixed_snapshot_cb(void); +static bool test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only); static const PgStat_KindInfo custom_stats = { .name = "test_custom_fixed_stats", .fixed_amount = true, /* exactly one entry */ .write_to_file = true, /* persist to stats file */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .shared_size = sizeof(PgStat_StatCustomFixedEntry), .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats), @@ -56,8 +59,12 @@ static const PgStat_KindInfo custom_stats = { .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb, .reset_all_cb = test_custom_stats_fixed_reset_all_cb, .snapshot_cb = test_custom_stats_fixed_snapshot_cb, + .flush_static_cb = test_custom_stats_fixed_flush_cb, }; +/* Pending statistics */ +static PgStat_StatCustomFixedEntry PendingCustomStats = {0}; + /* * Kind ID for test_custom_fixed_stats. */ @@ -141,6 +148,38 @@ test_custom_stats_fixed_snapshot_cb(void) #undef FIXED_COMP } +/* + * test_custom_stats_fixed_flush_cb + * Flush pending stats to shared memory + */ +static bool +test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only) +{ + PgStatShared_CustomFixedEntry *stats_shmem; + + /* Nothing to flush if no calls were made */ + if (PendingCustomStats.numcalls == 0) + return false; + + stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS); + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + stats_shmem->stats.numcalls += PendingCustomStats.numcalls; + pgstat_end_changecount_write(&stats_shmem->changecount); + + LWLockRelease(&stats_shmem->lock); + + /* Reset pending stats */ + PendingCustomStats.numcalls = 0; + + return false; /* successfully flushed */ +} + /*-------------------------------------------------------------------------- * SQL-callable functions *-------------------------------------------------------------------------- @@ -222,3 +261,21 @@ test_custom_stats_fixed_report(PG_FUNCTION_ARGS) /* Return as tuple */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* + * test_custom_stats_fixed_anytime_update + * Increment call counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_fixed_anytime_update); +Datum +test_custom_stats_fixed_anytime_update(PG_FUNCTION_ARGS) +{ + /* Accumulate in pending stats */ + PendingCustomStats.numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + pgstat_report_fixed = true; + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 5ed8cfc2dcf..ed66d38981e 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -24,3 +24,8 @@ CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_var_anytime_update(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_anytime_update' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index 4c207611236..e9f1bda6b32 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -18,6 +18,7 @@ #include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_var_stats", @@ -108,6 +109,7 @@ static const PgStat_KindInfo custom_stats = { .name = "test_custom_var_stats", .fixed_amount = false, /* variable number of entries */ .write_to_file = true, /* persist across restarts */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .track_entry_count = true, /* count active entries */ .accessed_across_databases = true, /* global statistics */ .shared_size = sizeof(PgStatShared_CustomVarEntry), @@ -690,3 +692,28 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * test_custom_stats_var_anytime_update + * Increment custom statistic counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_anytime_update); +Datum +test_custom_stats_var_anytime_update(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + PgStat_EntryRef *entry_ref; + PgStat_StatCustomVarEntry *pending_entry; + + /* Get pending entry in local memory */ + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL); + + pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending; + pending_entry->numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + + PG_RETURN_VOID(); +} -- 2.34.1 --NVvBxFuyV/R+1/8/ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v10-0003-Add-GUC-to-specify-non-transactional-statistics-.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v6 2/5] Add anytime flush tests for custom stats @ 2026-02-05 05:54 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Bertrand Drouvot @ 2026-02-05 05:54 UTC (permalink / raw) --- .../test_custom_stats/t/001_custom_stats.pl | 41 ++++++++++++ .../test_custom_fixed_stats--1.0.sql | 10 +++ .../test_custom_fixed_stats.c | 66 +++++++++++++++++++ .../test_custom_var_stats--1.0.sql | 5 ++ .../test_custom_stats/test_custom_var_stats.c | 27 ++++++++ 5 files changed, 149 insertions(+) 31.4% src/test/modules/test_custom_stats/t/ 68.5% src/test/modules/test_custom_stats/ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a38577..36d9fc3fde1 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -156,5 +156,46 @@ $result = $node->safe_psql('postgres', ); is($result, "0", "report of fixed-sized after manual reset"); +# Test FLUSH_ANYTIME mechanism with custom fixed stats +# This verifies that custom stats can be flushed during a transaction + +# Reset stats first +$node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +my $anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); + -- Force anytime flush (inside transaction!) + select pg_stat_force_anytime_flush(); + -- Check + select 'anytime:'||numcalls from test_custom_stats_fixed_report(); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^anytime:2/m, + "anytime fixed stats flushed during transaction"); + +# Test FLUSH_ANYTIME mechanism with custom variable stats +# This verifies that custom stats can be flushed during a transaction + +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +$anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_var_anytime_update('entry2'); + select test_custom_stats_var_anytime_update('entry2'); + -- Force anytime flush (inside transaction!) + select pg_stat_force_anytime_flush(); + -- Check + select * from test_custom_stats_var_report('entry2'); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^entry2|2|/m, + "anytime var stats flushed during transaction"); + # Test completed successfully done_testing(); diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql index 69a93b5241f..c0a418c3ae3 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql @@ -18,3 +18,13 @@ CREATE FUNCTION test_custom_stats_fixed_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_fixed_reset' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_fixed_anytime_update() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION pg_stat_force_anytime_flush() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c index 908bd18a7c7..6b3bc3257ab 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c @@ -18,6 +18,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_fixed_stats", @@ -43,11 +44,13 @@ typedef struct PgStatShared_CustomFixedEntry static void test_custom_stats_fixed_init_shmem_cb(void *stats); static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts); static void test_custom_stats_fixed_snapshot_cb(void); +static bool test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only); static const PgStat_KindInfo custom_stats = { .name = "test_custom_fixed_stats", .fixed_amount = true, /* exactly one entry */ .write_to_file = true, /* persist to stats file */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .shared_size = sizeof(PgStat_StatCustomFixedEntry), .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats), @@ -56,8 +59,12 @@ static const PgStat_KindInfo custom_stats = { .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb, .reset_all_cb = test_custom_stats_fixed_reset_all_cb, .snapshot_cb = test_custom_stats_fixed_snapshot_cb, + .flush_static_cb = test_custom_stats_fixed_flush_cb, }; +/* Pending statistics */ +static PgStat_StatCustomFixedEntry PendingCustomStats = {0}; + /* * Kind ID for test_custom_fixed_stats. */ @@ -141,6 +148,38 @@ test_custom_stats_fixed_snapshot_cb(void) #undef FIXED_COMP } +/* + * test_custom_stats_fixed_flush_cb + * Flush pending stats to shared memory + */ +static bool +test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only) +{ + PgStatShared_CustomFixedEntry *stats_shmem; + + /* Nothing to flush if no calls were made */ + if (PendingCustomStats.numcalls == 0) + return false; + + stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS); + + if (nowait && !LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; /* failed to flush */ + + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + + pgstat_begin_changecount_write(&stats_shmem->changecount); + stats_shmem->stats.numcalls += PendingCustomStats.numcalls; + pgstat_end_changecount_write(&stats_shmem->changecount); + + LWLockRelease(&stats_shmem->lock); + + /* Reset pending stats */ + PendingCustomStats.numcalls = 0; + + return false; /* successfully flushed */ +} + /*-------------------------------------------------------------------------- * SQL-callable functions *-------------------------------------------------------------------------- @@ -222,3 +261,30 @@ test_custom_stats_fixed_report(PG_FUNCTION_ARGS) /* Return as tuple */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* + * test_custom_stats_fixed_anytime_update + * Increment call counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_fixed_anytime_update); +Datum +test_custom_stats_fixed_anytime_update(PG_FUNCTION_ARGS) +{ + /* Accumulate in pending stats */ + PendingCustomStats.numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + pgstat_report_fixed = true; + + PG_RETURN_VOID(); +} + +/* Helper function for testing ANYTIME flush */ +PG_FUNCTION_INFO_V1(pg_stat_force_anytime_flush); +Datum +pg_stat_force_anytime_flush(PG_FUNCTION_ARGS) +{ + pgstat_report_anytime_stat(true); + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 5ed8cfc2dcf..ed66d38981e 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -24,3 +24,8 @@ CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_var_anytime_update(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_anytime_update' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index bc0b5d6e0eb..207e841911b 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -17,6 +17,7 @@ #include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_var_stats", @@ -107,6 +108,7 @@ static const PgStat_KindInfo custom_stats = { .name = "test_custom_var_stats", .fixed_amount = false, /* variable number of entries */ .write_to_file = true, /* persist across restarts */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .track_entry_count = true, /* count active entries */ .accessed_across_databases = true, /* global statistics */ .shared_size = sizeof(PgStatShared_CustomVarEntry), @@ -689,3 +691,28 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * test_custom_stats_var_anytime_update + * Increment custom statistic counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_anytime_update); +Datum +test_custom_stats_var_anytime_update(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + PgStat_EntryRef *entry_ref; + PgStat_StatCustomVarEntry *pending_entry; + + /* Get pending entry in local memory */ + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL); + + pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending; + pending_entry->numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + + PG_RETURN_VOID(); +} -- 2.34.1 --DxqFthphbM+ha9Dy Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v6-0003-Add-GUC-to-specify-non-transactional-statistics-f.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v7 2/5] Add anytime flush tests for custom stats @ 2026-02-05 05:54 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Bertrand Drouvot @ 2026-02-05 05:54 UTC (permalink / raw) --- .../test_custom_stats/t/001_custom_stats.pl | 41 +++++++++++++ .../test_custom_fixed_stats--1.0.sql | 5 ++ .../test_custom_fixed_stats.c | 57 +++++++++++++++++++ .../test_custom_var_stats--1.0.sql | 5 ++ .../test_custom_stats/test_custom_var_stats.c | 27 +++++++++ 5 files changed, 135 insertions(+) 33.8% src/test/modules/test_custom_stats/t/ 66.1% src/test/modules/test_custom_stats/ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a38577..7be1b281776 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -156,5 +156,46 @@ $result = $node->safe_psql('postgres', ); is($result, "0", "report of fixed-sized after manual reset"); +# Test FLUSH_ANYTIME mechanism with custom fixed stats +# This verifies that custom stats can be flushed during a transaction + +# Reset stats first +$node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +my $anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'anytime:'||numcalls from test_custom_stats_fixed_report(); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^anytime:2/m, + "anytime fixed stats flushed during transaction"); + +# Test FLUSH_ANYTIME mechanism with custom variable stats +# This verifies that custom stats can be flushed during a transaction + +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +$anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_var_anytime_update('entry2'); + select test_custom_stats_var_anytime_update('entry2'); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select * from test_custom_stats_var_report('entry2'); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^entry2|2|/m, + "anytime var stats flushed during transaction"); + # Test completed successfully done_testing(); diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql index 69a93b5241f..da3a798f289 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql @@ -18,3 +18,8 @@ CREATE FUNCTION test_custom_stats_fixed_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_fixed_reset' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_fixed_anytime_update() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c index 908bd18a7c7..30b0fbcbdc7 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c @@ -18,6 +18,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_fixed_stats", @@ -43,11 +44,13 @@ typedef struct PgStatShared_CustomFixedEntry static void test_custom_stats_fixed_init_shmem_cb(void *stats); static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts); static void test_custom_stats_fixed_snapshot_cb(void); +static bool test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only); static const PgStat_KindInfo custom_stats = { .name = "test_custom_fixed_stats", .fixed_amount = true, /* exactly one entry */ .write_to_file = true, /* persist to stats file */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .shared_size = sizeof(PgStat_StatCustomFixedEntry), .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats), @@ -56,8 +59,12 @@ static const PgStat_KindInfo custom_stats = { .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb, .reset_all_cb = test_custom_stats_fixed_reset_all_cb, .snapshot_cb = test_custom_stats_fixed_snapshot_cb, + .flush_static_cb = test_custom_stats_fixed_flush_cb, }; +/* Pending statistics */ +static PgStat_StatCustomFixedEntry PendingCustomStats = {0}; + /* * Kind ID for test_custom_fixed_stats. */ @@ -141,6 +148,38 @@ test_custom_stats_fixed_snapshot_cb(void) #undef FIXED_COMP } +/* + * test_custom_stats_fixed_flush_cb + * Flush pending stats to shared memory + */ +static bool +test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only) +{ + PgStatShared_CustomFixedEntry *stats_shmem; + + /* Nothing to flush if no calls were made */ + if (PendingCustomStats.numcalls == 0) + return false; + + stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS); + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + stats_shmem->stats.numcalls += PendingCustomStats.numcalls; + pgstat_end_changecount_write(&stats_shmem->changecount); + + LWLockRelease(&stats_shmem->lock); + + /* Reset pending stats */ + PendingCustomStats.numcalls = 0; + + return false; /* successfully flushed */ +} + /*-------------------------------------------------------------------------- * SQL-callable functions *-------------------------------------------------------------------------- @@ -222,3 +261,21 @@ test_custom_stats_fixed_report(PG_FUNCTION_ARGS) /* Return as tuple */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* + * test_custom_stats_fixed_anytime_update + * Increment call counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_fixed_anytime_update); +Datum +test_custom_stats_fixed_anytime_update(PG_FUNCTION_ARGS) +{ + /* Accumulate in pending stats */ + PendingCustomStats.numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + pgstat_report_fixed = true; + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 5ed8cfc2dcf..ed66d38981e 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -24,3 +24,8 @@ CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_var_anytime_update(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_anytime_update' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index bc0b5d6e0eb..207e841911b 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -17,6 +17,7 @@ #include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_var_stats", @@ -107,6 +108,7 @@ static const PgStat_KindInfo custom_stats = { .name = "test_custom_var_stats", .fixed_amount = false, /* variable number of entries */ .write_to_file = true, /* persist across restarts */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .track_entry_count = true, /* count active entries */ .accessed_across_databases = true, /* global statistics */ .shared_size = sizeof(PgStatShared_CustomVarEntry), @@ -689,3 +691,28 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * test_custom_stats_var_anytime_update + * Increment custom statistic counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_anytime_update); +Datum +test_custom_stats_var_anytime_update(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + PgStat_EntryRef *entry_ref; + PgStat_StatCustomVarEntry *pending_entry; + + /* Get pending entry in local memory */ + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL); + + pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending; + pending_entry->numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + + PG_RETURN_VOID(); +} -- 2.34.1 --C2xzVbxFmVrP7k6O Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0003-Add-GUC-to-specify-non-transactional-statistics-f.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v10 2/5] Add anytime flush tests for custom stats @ 2026-02-05 05:54 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Bertrand Drouvot @ 2026-02-05 05:54 UTC (permalink / raw) --- .../test_custom_stats/t/001_custom_stats.pl | 41 +++++++++++++ .../test_custom_fixed_stats--1.0.sql | 5 ++ .../test_custom_fixed_stats.c | 57 +++++++++++++++++++ .../test_custom_var_stats--1.0.sql | 5 ++ .../test_custom_stats/test_custom_var_stats.c | 27 +++++++++ 5 files changed, 135 insertions(+) 33.8% src/test/modules/test_custom_stats/t/ 66.1% src/test/modules/test_custom_stats/ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a38577..7be1b281776 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -156,5 +156,46 @@ $result = $node->safe_psql('postgres', ); is($result, "0", "report of fixed-sized after manual reset"); +# Test FLUSH_ANYTIME mechanism with custom fixed stats +# This verifies that custom stats can be flushed during a transaction + +# Reset stats first +$node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +my $anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'anytime:'||numcalls from test_custom_stats_fixed_report(); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^anytime:2/m, + "anytime fixed stats flushed during transaction"); + +# Test FLUSH_ANYTIME mechanism with custom variable stats +# This verifies that custom stats can be flushed during a transaction + +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +$anytime_test = q[ + BEGIN; + -- Accumulate stats + select test_custom_stats_var_anytime_update('entry2'); + select test_custom_stats_var_anytime_update('entry2'); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select * from test_custom_stats_var_report('entry2'); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^entry2|2|/m, + "anytime var stats flushed during transaction"); + # Test completed successfully done_testing(); diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql index 69a93b5241f..da3a798f289 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql @@ -18,3 +18,8 @@ CREATE FUNCTION test_custom_stats_fixed_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_fixed_reset' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_fixed_anytime_update() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c index 908bd18a7c7..30b0fbcbdc7 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c @@ -18,6 +18,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_fixed_stats", @@ -43,11 +44,13 @@ typedef struct PgStatShared_CustomFixedEntry static void test_custom_stats_fixed_init_shmem_cb(void *stats); static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts); static void test_custom_stats_fixed_snapshot_cb(void); +static bool test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only); static const PgStat_KindInfo custom_stats = { .name = "test_custom_fixed_stats", .fixed_amount = true, /* exactly one entry */ .write_to_file = true, /* persist to stats file */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .shared_size = sizeof(PgStat_StatCustomFixedEntry), .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats), @@ -56,8 +59,12 @@ static const PgStat_KindInfo custom_stats = { .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb, .reset_all_cb = test_custom_stats_fixed_reset_all_cb, .snapshot_cb = test_custom_stats_fixed_snapshot_cb, + .flush_static_cb = test_custom_stats_fixed_flush_cb, }; +/* Pending statistics */ +static PgStat_StatCustomFixedEntry PendingCustomStats = {0}; + /* * Kind ID for test_custom_fixed_stats. */ @@ -141,6 +148,38 @@ test_custom_stats_fixed_snapshot_cb(void) #undef FIXED_COMP } +/* + * test_custom_stats_fixed_flush_cb + * Flush pending stats to shared memory + */ +static bool +test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only) +{ + PgStatShared_CustomFixedEntry *stats_shmem; + + /* Nothing to flush if no calls were made */ + if (PendingCustomStats.numcalls == 0) + return false; + + stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS); + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + stats_shmem->stats.numcalls += PendingCustomStats.numcalls; + pgstat_end_changecount_write(&stats_shmem->changecount); + + LWLockRelease(&stats_shmem->lock); + + /* Reset pending stats */ + PendingCustomStats.numcalls = 0; + + return false; /* successfully flushed */ +} + /*-------------------------------------------------------------------------- * SQL-callable functions *-------------------------------------------------------------------------- @@ -222,3 +261,21 @@ test_custom_stats_fixed_report(PG_FUNCTION_ARGS) /* Return as tuple */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* + * test_custom_stats_fixed_anytime_update + * Increment call counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_fixed_anytime_update); +Datum +test_custom_stats_fixed_anytime_update(PG_FUNCTION_ARGS) +{ + /* Accumulate in pending stats */ + PendingCustomStats.numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + pgstat_report_fixed = true; + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 5ed8cfc2dcf..ed66d38981e 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -24,3 +24,8 @@ CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_var_anytime_update(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_anytime_update' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index 4c207611236..e9f1bda6b32 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -18,6 +18,7 @@ #include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_var_stats", @@ -108,6 +109,7 @@ static const PgStat_KindInfo custom_stats = { .name = "test_custom_var_stats", .fixed_amount = false, /* variable number of entries */ .write_to_file = true, /* persist across restarts */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .track_entry_count = true, /* count active entries */ .accessed_across_databases = true, /* global statistics */ .shared_size = sizeof(PgStatShared_CustomVarEntry), @@ -690,3 +692,28 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * test_custom_stats_var_anytime_update + * Increment custom statistic counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_anytime_update); +Datum +test_custom_stats_var_anytime_update(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + PgStat_EntryRef *entry_ref; + PgStat_StatCustomVarEntry *pending_entry; + + /* Get pending entry in local memory */ + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL); + + pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending; + pending_entry->numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + + PG_RETURN_VOID(); +} -- 2.34.1 --NVvBxFuyV/R+1/8/ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v10-0003-Add-GUC-to-specify-non-transactional-statistics-.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v11 2/5] Add anytime flush tests for custom stats @ 2026-02-05 05:54 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Bertrand Drouvot @ 2026-02-05 05:54 UTC (permalink / raw) --- .../test_custom_stats/t/001_custom_stats.pl | 43 ++++++++++++++ .../test_custom_fixed_stats--1.0.sql | 5 ++ .../test_custom_fixed_stats.c | 57 +++++++++++++++++++ .../test_custom_var_stats--1.0.sql | 5 ++ .../test_custom_stats/test_custom_var_stats.c | 27 +++++++++ 5 files changed, 137 insertions(+) 35.8% src/test/modules/test_custom_stats/t/ 64.1% src/test/modules/test_custom_stats/ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a38577..6ba4022418f 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -156,5 +156,48 @@ $result = $node->safe_psql('postgres', ); is($result, "0", "report of fixed-sized after manual reset"); +# Test FLUSH_ANYTIME mechanism with custom fixed stats +# This verifies that custom stats can be flushed during a transaction + +# Reset stats first +$node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +my $anytime_test = q[ + BEGIN; + SET LOCAL stats_fetch_consistency = none; + -- Accumulate stats + select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'fixed_anytime:'||numcalls from test_custom_stats_fixed_report(); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^fixed_anytime:2/m, + "anytime fixed stats flushed during transaction"); + +# Test FLUSH_ANYTIME mechanism with custom variable stats +# This verifies that custom stats can be flushed during a transaction + +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +$anytime_test = q[ + BEGIN; + SET LOCAL stats_fetch_consistency = none; + -- Accumulate stats + select test_custom_stats_var_anytime_update('entry2'); + select test_custom_stats_var_anytime_update('entry2'); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'var_anytime:'||calls from test_custom_stats_var_report('entry2'); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^var_anytime:2/m, + "anytime var stats flushed during transaction"); + # Test completed successfully done_testing(); diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql index 69a93b5241f..da3a798f289 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql @@ -18,3 +18,8 @@ CREATE FUNCTION test_custom_stats_fixed_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_fixed_reset' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_fixed_anytime_update() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c index 485e08e5c19..e7fbb2737ef 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c @@ -18,6 +18,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" #include "utils/timestamp.h" PG_MODULE_MAGIC_EXT( @@ -44,11 +45,13 @@ typedef struct PgStatShared_CustomFixedEntry static void test_custom_stats_fixed_init_shmem_cb(void *stats); static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts); static void test_custom_stats_fixed_snapshot_cb(void); +static bool test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only); static const PgStat_KindInfo custom_stats = { .name = "test_custom_fixed_stats", .fixed_amount = true, /* exactly one entry */ .write_to_file = true, /* persist to stats file */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .shared_size = sizeof(PgStat_StatCustomFixedEntry), .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats), @@ -57,8 +60,12 @@ static const PgStat_KindInfo custom_stats = { .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb, .reset_all_cb = test_custom_stats_fixed_reset_all_cb, .snapshot_cb = test_custom_stats_fixed_snapshot_cb, + .flush_static_cb = test_custom_stats_fixed_flush_cb, }; +/* Pending statistics */ +static PgStat_StatCustomFixedEntry PendingCustomStats = {0}; + /* * Kind ID for test_custom_fixed_stats. */ @@ -142,6 +149,38 @@ test_custom_stats_fixed_snapshot_cb(void) #undef FIXED_COMP } +/* + * test_custom_stats_fixed_flush_cb + * Flush pending stats to shared memory + */ +static bool +test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only) +{ + PgStatShared_CustomFixedEntry *stats_shmem; + + /* Nothing to flush if no calls were made */ + if (PendingCustomStats.numcalls == 0) + return false; + + stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS); + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + stats_shmem->stats.numcalls += PendingCustomStats.numcalls; + pgstat_end_changecount_write(&stats_shmem->changecount); + + LWLockRelease(&stats_shmem->lock); + + /* Reset pending stats */ + PendingCustomStats.numcalls = 0; + + return false; /* successfully flushed */ +} + /*-------------------------------------------------------------------------- * SQL-callable functions *-------------------------------------------------------------------------- @@ -223,3 +262,21 @@ test_custom_stats_fixed_report(PG_FUNCTION_ARGS) /* Return as tuple */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* + * test_custom_stats_fixed_anytime_update + * Increment call counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_fixed_anytime_update); +Datum +test_custom_stats_fixed_anytime_update(PG_FUNCTION_ARGS) +{ + /* Accumulate in pending stats */ + PendingCustomStats.numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + pgstat_report_fixed = true; + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 5ed8cfc2dcf..ed66d38981e 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -24,3 +24,8 @@ CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_var_anytime_update(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_anytime_update' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index 4c207611236..e9f1bda6b32 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -18,6 +18,7 @@ #include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_var_stats", @@ -108,6 +109,7 @@ static const PgStat_KindInfo custom_stats = { .name = "test_custom_var_stats", .fixed_amount = false, /* variable number of entries */ .write_to_file = true, /* persist across restarts */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .track_entry_count = true, /* count active entries */ .accessed_across_databases = true, /* global statistics */ .shared_size = sizeof(PgStatShared_CustomVarEntry), @@ -690,3 +692,28 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * test_custom_stats_var_anytime_update + * Increment custom statistic counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_anytime_update); +Datum +test_custom_stats_var_anytime_update(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + PgStat_EntryRef *entry_ref; + PgStat_StatCustomVarEntry *pending_entry; + + /* Get pending entry in local memory */ + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL); + + pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending; + pending_entry->numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + + PG_RETURN_VOID(); +} -- 2.34.1 --2MEBAGW8+kohXisi Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v11-0003-Add-GUC-to-specify-non-transactional-statistics-.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v11 2/5] Add anytime flush tests for custom stats @ 2026-02-05 05:54 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Bertrand Drouvot @ 2026-02-05 05:54 UTC (permalink / raw) --- .../test_custom_stats/t/001_custom_stats.pl | 43 ++++++++++++++ .../test_custom_fixed_stats--1.0.sql | 5 ++ .../test_custom_fixed_stats.c | 57 +++++++++++++++++++ .../test_custom_var_stats--1.0.sql | 5 ++ .../test_custom_stats/test_custom_var_stats.c | 27 +++++++++ 5 files changed, 137 insertions(+) 35.8% src/test/modules/test_custom_stats/t/ 64.1% src/test/modules/test_custom_stats/ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a38577..6ba4022418f 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -156,5 +156,48 @@ $result = $node->safe_psql('postgres', ); is($result, "0", "report of fixed-sized after manual reset"); +# Test FLUSH_ANYTIME mechanism with custom fixed stats +# This verifies that custom stats can be flushed during a transaction + +# Reset stats first +$node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +my $anytime_test = q[ + BEGIN; + SET LOCAL stats_fetch_consistency = none; + -- Accumulate stats + select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'fixed_anytime:'||numcalls from test_custom_stats_fixed_report(); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^fixed_anytime:2/m, + "anytime fixed stats flushed during transaction"); + +# Test FLUSH_ANYTIME mechanism with custom variable stats +# This verifies that custom stats can be flushed during a transaction + +$node->safe_psql('postgres', q(select pg_stat_force_next_flush())); + +$anytime_test = q[ + BEGIN; + SET LOCAL stats_fetch_consistency = none; + -- Accumulate stats + select test_custom_stats_var_anytime_update('entry2'); + select test_custom_stats_var_anytime_update('entry2'); + -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + select pg_sleep(1.5); + -- Check + select 'var_anytime:'||calls from test_custom_stats_var_report('entry2'); +]; + +$result = $node->safe_psql('postgres', $anytime_test); +like($result, qr/^var_anytime:2/m, + "anytime var stats flushed during transaction"); + # Test completed successfully done_testing(); diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql index 69a93b5241f..da3a798f289 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats--1.0.sql @@ -18,3 +18,8 @@ CREATE FUNCTION test_custom_stats_fixed_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_fixed_reset' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_fixed_anytime_update() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c index 485e08e5c19..e7fbb2737ef 100644 --- a/src/test/modules/test_custom_stats/test_custom_fixed_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_fixed_stats.c @@ -18,6 +18,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" #include "utils/timestamp.h" PG_MODULE_MAGIC_EXT( @@ -44,11 +45,13 @@ typedef struct PgStatShared_CustomFixedEntry static void test_custom_stats_fixed_init_shmem_cb(void *stats); static void test_custom_stats_fixed_reset_all_cb(TimestampTz ts); static void test_custom_stats_fixed_snapshot_cb(void); +static bool test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only); static const PgStat_KindInfo custom_stats = { .name = "test_custom_fixed_stats", .fixed_amount = true, /* exactly one entry */ .write_to_file = true, /* persist to stats file */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .shared_size = sizeof(PgStat_StatCustomFixedEntry), .shared_data_off = offsetof(PgStatShared_CustomFixedEntry, stats), @@ -57,8 +60,12 @@ static const PgStat_KindInfo custom_stats = { .init_shmem_cb = test_custom_stats_fixed_init_shmem_cb, .reset_all_cb = test_custom_stats_fixed_reset_all_cb, .snapshot_cb = test_custom_stats_fixed_snapshot_cb, + .flush_static_cb = test_custom_stats_fixed_flush_cb, }; +/* Pending statistics */ +static PgStat_StatCustomFixedEntry PendingCustomStats = {0}; + /* * Kind ID for test_custom_fixed_stats. */ @@ -142,6 +149,38 @@ test_custom_stats_fixed_snapshot_cb(void) #undef FIXED_COMP } +/* + * test_custom_stats_fixed_flush_cb + * Flush pending stats to shared memory + */ +static bool +test_custom_stats_fixed_flush_cb(bool nowait, bool anytime_only) +{ + PgStatShared_CustomFixedEntry *stats_shmem; + + /* Nothing to flush if no calls were made */ + if (PendingCustomStats.numcalls == 0) + return false; + + stats_shmem = pgstat_get_custom_shmem_data(PGSTAT_KIND_TEST_CUSTOM_FIXED_STATS); + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + stats_shmem->stats.numcalls += PendingCustomStats.numcalls; + pgstat_end_changecount_write(&stats_shmem->changecount); + + LWLockRelease(&stats_shmem->lock); + + /* Reset pending stats */ + PendingCustomStats.numcalls = 0; + + return false; /* successfully flushed */ +} + /*-------------------------------------------------------------------------- * SQL-callable functions *-------------------------------------------------------------------------- @@ -223,3 +262,21 @@ test_custom_stats_fixed_report(PG_FUNCTION_ARGS) /* Return as tuple */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* + * test_custom_stats_fixed_anytime_update + * Increment call counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_fixed_anytime_update); +Datum +test_custom_stats_fixed_anytime_update(PG_FUNCTION_ARGS) +{ + /* Accumulate in pending stats */ + PendingCustomStats.numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + pgstat_report_fixed = true; + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 5ed8cfc2dcf..ed66d38981e 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -24,3 +24,8 @@ CREATE FUNCTION test_custom_stats_var_report(INOUT name TEXT, RETURNS SETOF record AS 'MODULE_PATHNAME', 'test_custom_stats_var_report' LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION test_custom_stats_var_anytime_update(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_anytime_update' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index 4c207611236..e9f1bda6b32 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -18,6 +18,7 @@ #include "storage/dsm_registry.h" #include "utils/builtins.h" #include "utils/pgstat_internal.h" +#include "utils/timeout.h" PG_MODULE_MAGIC_EXT( .name = "test_custom_var_stats", @@ -108,6 +109,7 @@ static const PgStat_KindInfo custom_stats = { .name = "test_custom_var_stats", .fixed_amount = false, /* variable number of entries */ .write_to_file = true, /* persist across restarts */ + .flush_mode = FLUSH_ANYTIME, /* can be flushed anytime */ .track_entry_count = true, /* count active entries */ .accessed_across_databases = true, /* global statistics */ .shared_size = sizeof(PgStatShared_CustomVarEntry), @@ -690,3 +692,28 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * test_custom_stats_var_anytime_update + * Increment custom statistic counter and schedule anytime flush + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_anytime_update); +Datum +test_custom_stats_var_anytime_update(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + PgStat_EntryRef *entry_ref; + PgStat_StatCustomVarEntry *pending_entry; + + /* Get pending entry in local memory */ + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid, + PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL); + + pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending; + pending_entry->numcalls++; + + /* Schedule anytime stats update */ + pgstat_schedule_anytime_update(); + + PG_RETURN_VOID(); +} -- 2.34.1 --2MEBAGW8+kohXisi Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v11-0003-Add-GUC-to-specify-non-transactional-statistics-.patch" ^ permalink raw reply [nested|flat] 10+ messages in thread
end of thread, other threads:[~2026-02-05 05:54 UTC | newest] Thread overview: 10+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2023-06-25 11:48 [PATCH v1 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]> 2026-02-05 05:54 [PATCH v11 2/5] Add anytime flush tests for custom stats Bertrand Drouvot <[email protected]> 2026-02-05 05:54 [PATCH v9 2/5] Add anytime flush tests for custom stats Bertrand Drouvot <[email protected]> 2026-02-05 05:54 [PATCH v10 2/5] Add anytime flush tests for custom stats Bertrand Drouvot <[email protected]> 2026-02-05 05:54 [PATCH v6 2/5] Add anytime flush tests for custom stats Bertrand Drouvot <[email protected]> 2026-02-05 05:54 [PATCH v7 2/5] Add anytime flush tests for custom stats Bertrand Drouvot <[email protected]> 2026-02-05 05:54 [PATCH v10 2/5] Add anytime flush tests for custom stats Bertrand Drouvot <[email protected]> 2026-02-05 05:54 [PATCH v11 2/5] Add anytime flush tests for custom stats Bertrand Drouvot <[email protected]> 2026-02-05 05:54 [PATCH v7 2/5] Add anytime flush tests for custom stats Bertrand Drouvot <[email protected]> 2026-02-05 05:54 [PATCH v8 2/5] Add anytime flush tests for custom stats Bertrand Drouvot <[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