From 1871c892021055532344266d7429b63f76a892c2 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Tue, 11 Nov 2014 21:44:46 +0900 Subject: [PATCH] Add generate_series(numeric, numeric). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Платон Малюгин Reviewed by Michael Paquier, Ali Akbar and Marti Raudsepp --- doc/src/sgml/func.sgml | 16 +++- src/backend/utils/adt/numeric.c | 124 ++++++++++++++++++++++++++ src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_proc.h | 4 + src/include/utils/builtins.h | 2 + src/test/regress/expected/numeric.out | 52 +++++++++++ src/test/regress/sql/numeric.sql | 17 ++++ 7 files changed, 212 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 7e5bcd98dd..b58cfa578d 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -14076,8 +14076,8 @@ AND generate_series(start, stop) - int or bigint - setof int or setof bigint (same as argument type) + int, bigint or numeric + setof int, setof bigint, or setof numeric (same as argument type) Generate a series of values, from start to stop with a step size of one @@ -14086,8 +14086,8 @@ AND generate_series(start, stop, step) - int or bigint - setof int or setof bigint (same as argument type) + int, bigint or numeric + setof int, setof bigint or setof numeric (same as argument type) Generate a series of values, from start to stop with a step size of step @@ -14137,6 +14137,14 @@ SELECT * FROM generate_series(4,3); ----------------- (0 rows) +SELECT generate_series(1.1, 4, 1.3); + generate_series +----------------- + 1.1 + 2.4 + 3.7 +(3 rows) + -- this example relies on the date-plus-integer operator SELECT current_date + s.a AS dates FROM generate_series(0,14,7) AS s(a); dates diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 9b3f5b9410..d61af920cd 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -28,6 +28,7 @@ #include "access/hash.h" #include "catalog/pg_type.h" +#include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" @@ -260,6 +261,18 @@ typedef struct NumericVar } NumericVar; +/* ---------- + * Data for generate_series + * ---------- + */ +typedef struct +{ + NumericVar current; + NumericVar stop; + NumericVar step; +} generate_series_numeric_fctx; + + /* ---------- * Some preinitialized constants * ---------- @@ -1229,6 +1242,117 @@ numeric_floor(PG_FUNCTION_ARGS) PG_RETURN_NUMERIC(res); } + +/* + * generate_series_numeric() - + * + * Generate series of numeric. + */ +Datum +generate_series_numeric(PG_FUNCTION_ARGS) +{ + return generate_series_step_numeric(fcinfo); +} + +Datum +generate_series_step_numeric(PG_FUNCTION_ARGS) +{ + generate_series_numeric_fctx *fctx; + FuncCallContext *funcctx; + MemoryContext oldcontext; + + if (SRF_IS_FIRSTCALL()) + { + Numeric start_num = PG_GETARG_NUMERIC(0); + Numeric stop_num = PG_GETARG_NUMERIC(1); + NumericVar steploc = const_one; + + /* handle NaN in start and stop values */ + if (NUMERIC_IS_NAN(start_num)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("start value cannot be NaN"))); + + if (NUMERIC_IS_NAN(stop_num)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("stop value cannot be NaN"))); + + /* see if we were given an explicit step size */ + if (PG_NARGS() == 3) + { + Numeric step_num = PG_GETARG_NUMERIC(2); + + if (NUMERIC_IS_NAN(step_num)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("step size cannot be NaN"))); + + init_var_from_num(step_num, &steploc); + + if (cmp_var(&steploc, &const_zero) == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("step size cannot equal zero"))); + } + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * Switch to memory context appropriate for multiple function calls. + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* allocate memory for user context */ + fctx = (generate_series_numeric_fctx *) + palloc(sizeof(generate_series_numeric_fctx)); + + /* + * Use fctx to keep state from call to call. Seed current with the + * original start value. + */ + init_var_from_num(start_num, &fctx->current); + init_var_from_num(stop_num, &fctx->stop); + init_var(&fctx->step); + set_var_from_var(&steploc, &fctx->step); + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + /* + * Get the saved state and use current state as the result of this + * iteration. + */ + fctx = funcctx->user_fctx; + + if ((fctx->step.sign == NUMERIC_POS && + cmp_var(&fctx->current, &fctx->stop) <= 0) || + (fctx->step.sign == NUMERIC_NEG && + cmp_var(&fctx->current, &fctx->stop) >= 0)) + { + Numeric result = make_result(&fctx->current); + + /* switch to memory context appropriate for iteration calculation */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* increment current in preparation for next iteration */ + add_var(&fctx->current, &fctx->step, &fctx->current); + MemoryContextSwitchTo(oldcontext); + + /* do when there is more left to send */ + SRF_RETURN_NEXT(funcctx, NumericGetDatum(result)); + } + else + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); +} + + /* * Implements the numeric version of the width_bucket() function * defined by SQL2003. See also width_bucket_float8(). diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index b5c5e7aa5e..bdaf4176f6 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201411071 +#define CATALOG_VERSION_NO 201411111 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 497e652674..5d4e889af4 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3952,6 +3952,10 @@ DATA(insert OID = 1068 ( generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t DESCR("non-persistent series generator"); DATA(insert OID = 1069 ( generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 20 "20 20" _null_ _null_ _null_ _null_ generate_series_int8 _null_ _null_ _null_ )); DESCR("non-persistent series generator"); +DATA(insert OID = 3259 ( generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 1700 "1700 1700 1700" _null_ _null_ _null_ _null_ generate_series_step_numeric _null_ _null_ _null_ )); +DESCR("non-persistent series generator"); +DATA(insert OID = 3260 ( generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t i 2 0 1700 "1700 1700" _null_ _null_ _null_ _null_ generate_series_numeric _null_ _null_ _null_ )); +DESCR("non-persistent series generator"); DATA(insert OID = 938 ( generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t i 3 0 1114 "1114 1114 1186" _null_ _null_ _null_ _null_ generate_series_timestamp _null_ _null_ _null_ )); DESCR("non-persistent series generator"); DATA(insert OID = 939 ( generate_series PGNSP PGUID 12 1 1000 0 0 f f f f t t s 3 0 1184 "1184 1184 1186" _null_ _null_ _null_ _null_ generate_series_timestamptz _null_ _null_ _null_ )); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 4e74d85d22..3ba34f88ee 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1029,6 +1029,8 @@ extern Datum int8_avg(PG_FUNCTION_ARGS); extern Datum int2int4_sum(PG_FUNCTION_ARGS); extern Datum width_bucket_numeric(PG_FUNCTION_ARGS); extern Datum hash_numeric(PG_FUNCTION_ARGS); +extern Datum generate_series_numeric(PG_FUNCTION_ARGS); +extern Datum generate_series_step_numeric(PG_FUNCTION_ARGS); /* ri_triggers.c */ extern Datum RI_FKey_check_ins(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index 5fafdaf13f..ee6cb50d5a 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -1409,3 +1409,55 @@ select 10.0 ^ 2147483647 as overflows; ERROR: value overflows numeric format select 117743296169.0 ^ 1000000000 as overflows; ERROR: value overflows numeric format +-- +-- Tests for generate_series +-- +select * from generate_series(0.0::numeric, 4.0::numeric); + generate_series +----------------- + 0.0 + 1.0 + 2.0 + 3.0 + 4.0 +(5 rows) + +select * from generate_series(0.1::numeric, 4.0::numeric, 1.3::numeric); + generate_series +----------------- + 0.1 + 1.4 + 2.7 + 4.0 +(4 rows) + +select * from generate_series(4.0::numeric, -1.5::numeric, -2.2::numeric); + generate_series +----------------- + 4.0 + 1.8 + -0.4 +(3 rows) + +-- Trigger errors +select * from generate_series(-100::numeric, 100::numeric, 0::numeric); +ERROR: step size cannot equal zero +select * from generate_series(-100::numeric, 100::numeric, 'nan'::numeric); +ERROR: step size cannot be NaN +select * from generate_series('nan'::numeric, 100::numeric, 10::numeric); +ERROR: start value cannot be NaN +select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric); +ERROR: stop value cannot be NaN +-- Checks maximum, output is truncated +select (i / (10::numeric ^ 131071))::numeric(1,0) + from generate_series(6 * (10::numeric ^ 131071), + 9 * (10::numeric ^ 131071), + 10::numeric ^ 131071) as a(i); + numeric +--------- + 6 + 7 + 8 + 9 +(4 rows) + diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index 5c08717e7a..a7e92ac3cc 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -837,3 +837,20 @@ select 10.0 ^ -2147483648 as rounds_to_zero; select 10.0 ^ -2147483647 as rounds_to_zero; select 10.0 ^ 2147483647 as overflows; select 117743296169.0 ^ 1000000000 as overflows; + +-- +-- Tests for generate_series +-- +select * from generate_series(0.0::numeric, 4.0::numeric); +select * from generate_series(0.1::numeric, 4.0::numeric, 1.3::numeric); +select * from generate_series(4.0::numeric, -1.5::numeric, -2.2::numeric); +-- Trigger errors +select * from generate_series(-100::numeric, 100::numeric, 0::numeric); +select * from generate_series(-100::numeric, 100::numeric, 'nan'::numeric); +select * from generate_series('nan'::numeric, 100::numeric, 10::numeric); +select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric); +-- Checks maximum, output is truncated +select (i / (10::numeric ^ 131071))::numeric(1,0) + from generate_series(6 * (10::numeric ^ 131071), + 9 * (10::numeric ^ 131071), + 10::numeric ^ 131071) as a(i); -- GitLab