From ca0e982215d1335d015a2b515f038b7186935af0 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Sun, 14 Apr 2024 14:49:39 +1200 Subject: [PATCH v1] Add support function for generate_series for timestamps This provides the planner with row estimates for generate_series(TIMESTAMP, TIMESTAMP, INTERVAL) and generate_series(TIMESTAMPTZ, TIMESTAMPTZ, INTERVAL) --- src/backend/utils/adt/timestamp.c | 83 +++++++++++++++++++++++++++++++ src/include/catalog/pg_proc.dat | 9 +++- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 963f2ec74a..25f2680243 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -27,6 +27,7 @@ #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" +#include "optimizer/optimizer.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" #include "parser/scansup.h" @@ -6668,6 +6669,88 @@ generate_series_timestamptz_at_zone(PG_FUNCTION_ARGS) return generate_series_timestamptz_internal(fcinfo); } +/* + * Planner support function for generate_series(timestamp, timestamp, interval) + */ +Datum +generate_series_timestamp_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestRows)) + { + /* Try to estimate the number of rows returned */ + SupportRequestRows *req = (SupportRequestRows *) rawreq; + + if (is_funcclause(req->node)) /* be paranoid */ + { + List *args = ((FuncExpr *) req->node)->args; + Node *arg1, *arg2, *arg3; + + /* We can use estimated argument values here */ + arg1 = estimate_expression_value(req->root, linitial(args)); + arg2 = estimate_expression_value(req->root, lsecond(args)); + arg3 = estimate_expression_value(req->root, lthird(args)); + + /* + * If any argument is constant NULL, we can safely assume that + * zero rows are returned. Otherwise, if they're all non-NULL + * constants, we can calculate the number of rows that will be + * returned. + */ + if ((IsA(arg1, Const) && ((Const *) arg1)->constisnull) || + (IsA(arg2, Const) && ((Const *) arg2)->constisnull) || + (IsA(arg3, Const) && ((Const *) arg3)->constisnull)) + { + req->rows = 0; + ret = (Node *) req; + } + else if (IsA(arg1, Const) && IsA(arg2, Const) && IsA(arg3, Const)) + { + Timestamp start, finish; + Interval *step; + Datum diff; + double dstep; + int64 dummy; + + start = DatumGetTimestamp(((Const *) arg1)->constvalue); + finish = DatumGetTimestamp(((Const *) arg2)->constvalue); + step = DatumGetIntervalP(((Const *) arg3)->constvalue); + + /* + * Protect against overflows in timestamp_mi. XXX convert to + * ereturn one day? + */ + if (!TIMESTAMP_NOT_FINITE(start) && !TIMESTAMP_NOT_FINITE(finish) && + !pg_sub_s64_overflow(finish, start, &dummy)) + { + diff = DirectFunctionCall2(timestamp_mi, + TimestampGetDatum(finish), + TimestampGetDatum(start)); + +#define INTERVAL_TO_MICROSECONDS(i) ((((double) (i)->month * DAYS_PER_MONTH + (i)->day)) * USECS_PER_DAY + (i)->time) + + dstep = INTERVAL_TO_MICROSECONDS(step); + + if (dstep != 0.0) + { + Interval *idiff = DatumGetIntervalP(diff); + double ddiff = INTERVAL_TO_MICROSECONDS(idiff); + + req->rows = floor(ddiff / dstep + 1.0); + ret = (Node *) req; + } +#undef INTERVAL_TO_MICROSECONDS + } + } + } + } + + PG_RETURN_POINTER(ret); +} + + /* timestamp_at_local() * timestamptz_at_local() * diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 134e3b22fd..279ff2384b 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8328,15 +8328,20 @@ prorettype => 'timestamp', proargtypes => 'timestamp timestamp interval', prosrc => 'generate_series_timestamp' }, { oid => '939', descr => 'non-persistent series generator', - proname => 'generate_series', prorows => '1000', proretset => 't', + proname => 'generate_series', prorows => '1000', + prosupport => 'generate_series_timestamp_support', proretset => 't', provolatile => 's', prorettype => 'timestamptz', proargtypes => 'timestamptz timestamptz interval', prosrc => 'generate_series_timestamptz' }, { oid => '6274', descr => 'non-persistent series generator', - proname => 'generate_series', prorows => '1000', proretset => 't', + proname => 'generate_series', prorows => '1000', + prosupport => 'generate_series_timestamp_support', proretset => 't', prorettype => 'timestamptz', proargtypes => 'timestamptz timestamptz interval text', prosrc => 'generate_series_timestamptz_at_zone' }, +{ oid => '8402', descr => 'planner support for generate_series', + proname => 'generate_series_timestamp_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'generate_series_timestamp_support' }, # boolean aggregates { oid => '2515', descr => 'aggregate transition function', -- 2.40.1.windows.1