From c2cf434d356c74d5de6f0b56d26af5dd7ba07903 Mon Sep 17 00:00:00 2001 From: Shibagaki Koshi Date: Fri, 23 Oct 2026 16:18:20 +0900 Subject: [PATCH v1] Add an assignment cast from numeric to oid8 Integer literals above INT64_MAX are parsed as numeric, so a value such as 18446744073709551615 cannot currently be cast directly to oid8 even though it is representable. The built-in signed integer types cannot represent the upper half of oid8's range, while numeric can represent every oid8 value exactly. Allow numeric-to-oid8 conversion in assignment and explicit contexts. Round to the nearest integer, with ties away from zero, and require the rounded magnitude to fit in uint64. Wrap negative results modulo 2^64, consistently with oid8 text input. Reject NaN and infinite values, reporting soft errors when requested by the caller. Keep the cast out of implicit expression contexts to avoid rounding numeric values during comparisons. Include documentation and focused regression coverage for conversion, assignment and expression coercion. --- doc/src/sgml/datatype.sgml | 12 +++++++++ src/backend/utils/adt/numeric.c | 34 ++++++++++++++++++++++++ src/include/catalog/pg_cast.dat | 4 ++- src/include/catalog/pg_proc.dat | 3 +++ src/test/regress/expected/oid8.out | 42 ++++++++++++++++++++++++++++++ src/test/regress/sql/oid8.sql | 15 +++++++++++ 6 files changed, 109 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 83bf0ad8ed..8a10e52899 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -4843,6 +4843,18 @@ INSERT INTO mytable VALUES(-1); -- fails signed-versus-unsigned confusion if you do this.) + + Values of type numeric can be cast to oid8, + either explicitly or when assigned to an oid8 column. + They are rounded to the nearest integer, with ties + away from zero. The absolute value of the rounded result must not + exceed 18446744073709551615. + Negative results wrap around modulo 264, + as with negative textual input; for example, -1 + converts to 18446744073709551615. + NaN and infinite values cannot be converted. + + The OID alias types have no operations of their own except for specialized input and output routines. These routines are able diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 405cea6b6a..504aa47a1d 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -4747,6 +4747,40 @@ numeric_pg_lsn(PG_FUNCTION_ARGS) PG_RETURN_LSN(result); } +Datum +numeric_oid8(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + NumericVar x; + uint64 result; + + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_NAN(num)) + ereturn(fcinfo->context, (Datum) 0, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert NaN to %s", "oid8"))); + else + ereturn(fcinfo->context, (Datum) 0, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert infinity to %s", "oid8"))); + } + + /* Convert the rounded magnitude, then wrap negatives as oid8in() does. */ + init_var_from_num(num, &x); + x.sign = NUMERIC_POS; + + if (!numericvar_to_uint64(&x, &result)) + ereturn(fcinfo->context, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("oid8 out of range"))); + + if (NUMERIC_SIGN(num) == NUMERIC_NEG) + result = -result; + + PG_RETURN_OID8(result); +} + /* ---------------------------------------------------------------------- * diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat index a7b6d812c5..2fc4ad614f 100644 --- a/src/include/catalog/pg_cast.dat +++ b/src/include/catalog/pg_cast.dat @@ -306,9 +306,11 @@ castcontext => 'i', castmethod => 'f' }, { castsource => 'oid8', casttarget => 'int8', castfunc => '0', castcontext => 'a', castmethod => 'b' }, -# Assignment coercion from oid to oid8. +# Assignment coercion from oid and numeric to oid8. { castsource => 'oid', casttarget => 'oid8', castfunc => 'oid8(oid)', castcontext => 'a', castmethod => 'f' }, +{ castsource => 'numeric', casttarget => 'oid8', castfunc => 'oid8(numeric)', + castcontext => 'a', castmethod => 'f' }, # String category { castsource => 'text', casttarget => 'bpchar', castfunc => '0', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 5e4c9e503f..6a5515c5f1 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -4790,6 +4790,9 @@ { oid => '6103', descr => 'convert numeric to pg_lsn', proname => 'pg_lsn', prorettype => 'pg_lsn', proargtypes => 'numeric', prosrc => 'numeric_pg_lsn' }, +{ oid => '2775', descr => 'convert numeric to oid8', + proname => 'oid8', prorettype => 'oid8', proargtypes => 'numeric', + prosrc => 'numeric_oid8' }, { oid => '3556', descr => 'convert jsonb to boolean', proname => 'bool', prorettype => 'bool', proargtypes => 'jsonb', diff --git a/src/test/regress/expected/oid8.out b/src/test/regress/expected/oid8.out index 2e114f1ce7..a5bed7163c 100644 --- a/src/test/regress/expected/oid8.out +++ b/src/test/regress/expected/oid8.out @@ -202,6 +202,48 @@ SELECT 1::oid8::oid; -- not ok ERROR: cannot cast type oid8 to oid LINE 1: SELECT 1::oid8::oid; ^ +-- Numeric casts round the magnitude, then wrap negative values. +BEGIN; +INSERT INTO OID8_TBL VALUES (18446744073709551615) RETURNING f1; + f1 +---------------------- + 18446744073709551615 +(1 row) + +ROLLBACK; +SELECT 18446744073709551616::numeric::oid8; +ERROR: oid8 out of range +SELECT (-1)::numeric::oid8; + oid8 +---------------------- + 18446744073709551615 +(1 row) + +SELECT (-1.5)::numeric::oid8; + oid8 +---------------------- + 18446744073709551614 +(1 row) + +SELECT (-18446744073709551615)::numeric::oid8; + oid8 +------ + 1 +(1 row) + +SELECT (-18446744073709551616)::numeric::oid8; +ERROR: oid8 out of range +SELECT 'NaN'::numeric::oid8; +ERROR: cannot convert NaN to oid8 +SELECT 'Infinity'::numeric::oid8; +ERROR: cannot convert infinity to oid8 +-- Numeric casts are not implicit in expressions. +SELECT 2::oid8 = 1.5; +ERROR: operator does not exist: oid8 = numeric +LINE 1: SELECT 2::oid8 = 1.5; + ^ +DETAIL: No operator of that name accepts the given argument types. +HINT: You might need to add explicit type casts. -- Aggregates SELECT min(f1), max(f1) FROM OID8_TBL; min | max diff --git a/src/test/regress/sql/oid8.sql b/src/test/regress/sql/oid8.sql index 44cb79dd55..24445f9ad8 100644 --- a/src/test/regress/sql/oid8.sql +++ b/src/test/regress/sql/oid8.sql @@ -56,6 +56,21 @@ SELECT 1::oid8::int8; SELECT 1::oid::oid8; -- ok SELECT 1::oid8::oid; -- not ok +-- Numeric casts round the magnitude, then wrap negative values. +BEGIN; +INSERT INTO OID8_TBL VALUES (18446744073709551615) RETURNING f1; +ROLLBACK; +SELECT 18446744073709551616::numeric::oid8; +SELECT (-1)::numeric::oid8; +SELECT (-1.5)::numeric::oid8; +SELECT (-18446744073709551615)::numeric::oid8; +SELECT (-18446744073709551616)::numeric::oid8; +SELECT 'NaN'::numeric::oid8; +SELECT 'Infinity'::numeric::oid8; + +-- Numeric casts are not implicit in expressions. +SELECT 2::oid8 = 1.5; + -- Aggregates SELECT min(f1), max(f1) FROM OID8_TBL; base-commit: 7787aec8ab000932a8704a988b175b39ff816f2f -- 2.34.1