diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 2d26896..04a3009 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -7514,6 +7514,29 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40'); display, see . + + + Extracting from infinite values differs for monotonic and oscillating + fields. + + + For monotonic ones (epoch, julian, + century, decade, isoyear, + millennium and year) appropriate +/-Infinity is + returned, for all other (oscillating) fields null is returned: + + + +SELECT EXTRACT(EPOCH FROM TIMESTAMP 'Infinity'); +Result: Infinity +SELECT EXTRACT(EPOCH FROM TIMESTAMP '-Infinity'); +Result: -Infinity +SELECT EXTRACT(DAY FROM TIMESTAMP 'Infinity') IS NULL AND + EXTRACT(DAY FROM TIMESTAMP '-Infinity') IS NULL; +Result: true + + + The date_part function is modeled on the traditional Ingres equivalent to the diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 6871092..8e4e7f6 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -4311,6 +4311,69 @@ date2isoyearday(int year, int mon, int mday) return date2j(year, mon, mday) - isoweek2j(date2isoyear(year, mon, mday), 1) + 1; } +/* + * HandleNonFiniteTimestampTzPart + * + * Used by timestamp_part and timestamptz_part when extracting from non-finite timestamp[tz]. + * For monotonic units sets result to +/-Inf and returns true. + * For oscillating ones leaves result as is and returns false. + * Return value is a flag whether +/-Inf from result must be returned or NULL. + */ +static bool +HandleNonFiniteTimestampTzPart(int type, int unit, char* lowunits, + float8 *result, bool isNegative) +{ + /* + * Units are in the same order as in timestamp_part and timestamptz_part. + * Monotonic units are moved to a separate block (but in the same order). + */ + if ((type != UNITS) && (type != RESERV)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("timestamp units \"%s\" not recognized", lowunits))); + + switch (unit) + { + /* Oscillating units */ + case DTK_MICROSEC: + case DTK_MILLISEC: + case DTK_SECOND: + case DTK_MINUTE: + case DTK_HOUR: + case DTK_DAY: + case DTK_MONTH: + case DTK_QUARTER: + case DTK_WEEK: + /* Monotonic units between WEEK and DOW are moved to the end of switch */ + case DTK_DOW: + case DTK_ISODOW: + case DTK_DOY: + case DTK_TZ: + case DTK_TZ_MINUTE: + case DTK_TZ_HOUR: + return false; + + /* Monotonic units */ + case DTK_YEAR: + case DTK_DECADE: + case DTK_CENTURY: + case DTK_MILLENNIUM: + case DTK_JULIAN: + case DTK_ISOYEAR: + case DTK_EPOCH: + *result = get_float8_infinity(); + if (isNegative) + *result = -(*result); + return true; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("timestamp units \"%s\" not supported", + lowunits))); + } +} + /* timestamp_part() * Extract specified field from timestamp. */ @@ -4327,12 +4390,6 @@ timestamp_part(PG_FUNCTION_ARGS) struct pg_tm tt, *tm = &tt; - if (TIMESTAMP_NOT_FINITE(timestamp)) - { - result = 0; - PG_RETURN_FLOAT8(result); - } - lowunits = downcase_truncate_identifier(VARDATA_ANY(units), VARSIZE_ANY_EXHDR(units), false); @@ -4341,6 +4398,16 @@ timestamp_part(PG_FUNCTION_ARGS) if (type == UNKNOWN_FIELD) type = DecodeSpecial(0, lowunits, &val); + if (TIMESTAMP_NOT_FINITE(timestamp)) + { + if (HandleNonFiniteTimestampTzPart(type, val, lowunits, + &result, + TIMESTAMP_IS_NOBEGIN(timestamp))) + PG_RETURN_FLOAT8(result); + else + PG_RETURN_NULL(); + } + if (type == UNITS) { if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) @@ -4538,12 +4605,6 @@ timestamptz_part(PG_FUNCTION_ARGS) struct pg_tm tt, *tm = &tt; - if (TIMESTAMP_NOT_FINITE(timestamp)) - { - result = 0; - PG_RETURN_FLOAT8(result); - } - lowunits = downcase_truncate_identifier(VARDATA_ANY(units), VARSIZE_ANY_EXHDR(units), false); @@ -4552,6 +4613,16 @@ timestamptz_part(PG_FUNCTION_ARGS) if (type == UNKNOWN_FIELD) type = DecodeSpecial(0, lowunits, &val); + if (TIMESTAMP_NOT_FINITE(timestamp)) + { + if (HandleNonFiniteTimestampTzPart(type, val, lowunits, + &result, + TIMESTAMP_IS_NOBEGIN(timestamp))) + PG_RETURN_FLOAT8(result); + else + PG_RETURN_NULL(); + } + if (type == UNITS) { if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out index 8923f60..56c5520 100644 --- a/src/test/regress/expected/date.out +++ b/src/test/regress/expected/date.out @@ -900,6 +900,27 @@ SELECT date 'tomorrow' - date 'yesterday' AS "Two days"; -- -- test extract! -- +-- epoch +-- +SELECT EXTRACT(EPOCH FROM DATE '1970-01-01'); -- 0 + date_part +----------- + 0 +(1 row) + +SELECT EXTRACT(EPOCH FROM TIMESTAMP '1970-01-01'); -- 0 + date_part +----------- + 0 +(1 row) + +SELECT EXTRACT(EPOCH FROM TIMESTAMPTZ '1970-01-01+00'); -- 0 + date_part +----------- + 0 +(1 row) + +-- -- century -- SELECT EXTRACT(CENTURY FROM DATE '0101-12-31 BC'); -- -2 @@ -1184,6 +1205,227 @@ select isfinite('infinity'::date), isfinite('-infinity'::date), isfinite('today' f | f | t (1 row) +-- +-- oscillating fields from non-finite date/timestamptz: +-- +SELECT EXTRACT(HOUR FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(HOUR FROM DATE '-infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(HOUR FROM TIMESTAMP 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(HOUR FROM TIMESTAMP '-infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(HOUR FROM TIMESTAMPTZ 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(HOUR FROM TIMESTAMPTZ '-infinity'); -- NULL + date_part +----------- + +(1 row) + +-- all possible fields +SELECT EXTRACT(MICROSECONDS FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(MILLISECONDS FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(SECOND FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(MINUTE FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(HOUR FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(DAY FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(MONTH FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(QUARTER FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(WEEK FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(DOW FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(ISODOW FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(DOY FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(TIMEZONE FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(TIMEZONE_M FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +SELECT EXTRACT(TIMEZONE_H FROM DATE 'infinity'); -- NULL + date_part +----------- + +(1 row) + +-- +-- monotonic fields from non-finite date/timestamptz: +-- +SELECT EXTRACT(EPOCH FROM DATE 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +SELECT EXTRACT(EPOCH FROM DATE '-infinity'); -- -Infinity + date_part +----------- + -Infinity +(1 row) + +SELECT EXTRACT(EPOCH FROM TIMESTAMP 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +SELECT EXTRACT(EPOCH FROM TIMESTAMP '-infinity'); -- -Infinity + date_part +----------- + -Infinity +(1 row) + +SELECT EXTRACT(EPOCH FROM TIMESTAMPTZ 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +SELECT EXTRACT(EPOCH FROM TIMESTAMPTZ '-infinity'); -- -Infinity + date_part +----------- + -Infinity +(1 row) + +-- all possible fields +SELECT EXTRACT(YEAR FROM DATE 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +SELECT EXTRACT(DECADE FROM DATE 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +SELECT EXTRACT(CENTURY FROM DATE 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +SELECT EXTRACT(MILLENNIUM FROM DATE 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +SELECT EXTRACT(JULIAN FROM DATE 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +SELECT EXTRACT(ISOYEAR FROM DATE 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +SELECT EXTRACT(EPOCH FROM DATE 'infinity'); -- Infinity + date_part +----------- + Infinity +(1 row) + +-- +-- wrong fields from non-finite date: +-- +SELECT EXTRACT(MICROSEC FROM DATE 'infinity'); -- ERROR: timestamp units "microsec" not recognized +ERROR: timestamp units "microsec" not recognized +CONTEXT: SQL function "date_part" statement 1 +SELECT EXTRACT(UNDEFINED FROM DATE 'infinity'); -- ERROR: timestamp units "undefined" not supported +ERROR: timestamp units "undefined" not supported +CONTEXT: SQL function "date_part" statement 1 -- test constructors select make_date(2013, 7, 15); make_date diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql index a62e92a..e40b4c4 100644 --- a/src/test/regress/sql/date.sql +++ b/src/test/regress/sql/date.sql @@ -212,6 +212,12 @@ SELECT date 'tomorrow' - date 'yesterday' AS "Two days"; -- -- test extract! -- +-- epoch +-- +SELECT EXTRACT(EPOCH FROM DATE '1970-01-01'); -- 0 +SELECT EXTRACT(EPOCH FROM TIMESTAMP '1970-01-01'); -- 0 +SELECT EXTRACT(EPOCH FROM TIMESTAMPTZ '1970-01-01+00'); -- 0 +-- -- century -- SELECT EXTRACT(CENTURY FROM DATE '0101-12-31 BC'); -- -2 @@ -276,6 +282,53 @@ select 'infinity'::date, '-infinity'::date; select 'infinity'::date > 'today'::date as t; select '-infinity'::date < 'today'::date as t; select isfinite('infinity'::date), isfinite('-infinity'::date), isfinite('today'::date); +-- +-- oscillating fields from non-finite date/timestamptz: +-- +SELECT EXTRACT(HOUR FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(HOUR FROM DATE '-infinity'); -- NULL +SELECT EXTRACT(HOUR FROM TIMESTAMP 'infinity'); -- NULL +SELECT EXTRACT(HOUR FROM TIMESTAMP '-infinity'); -- NULL +SELECT EXTRACT(HOUR FROM TIMESTAMPTZ 'infinity'); -- NULL +SELECT EXTRACT(HOUR FROM TIMESTAMPTZ '-infinity'); -- NULL +-- all possible fields +SELECT EXTRACT(MICROSECONDS FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(MILLISECONDS FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(SECOND FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(MINUTE FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(HOUR FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(DAY FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(MONTH FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(QUARTER FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(WEEK FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(DOW FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(ISODOW FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(DOY FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(TIMEZONE FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(TIMEZONE_M FROM DATE 'infinity'); -- NULL +SELECT EXTRACT(TIMEZONE_H FROM DATE 'infinity'); -- NULL +-- +-- monotonic fields from non-finite date/timestamptz: +-- +SELECT EXTRACT(EPOCH FROM DATE 'infinity'); -- Infinity +SELECT EXTRACT(EPOCH FROM DATE '-infinity'); -- -Infinity +SELECT EXTRACT(EPOCH FROM TIMESTAMP 'infinity'); -- Infinity +SELECT EXTRACT(EPOCH FROM TIMESTAMP '-infinity'); -- -Infinity +SELECT EXTRACT(EPOCH FROM TIMESTAMPTZ 'infinity'); -- Infinity +SELECT EXTRACT(EPOCH FROM TIMESTAMPTZ '-infinity'); -- -Infinity +-- all possible fields +SELECT EXTRACT(YEAR FROM DATE 'infinity'); -- Infinity +SELECT EXTRACT(DECADE FROM DATE 'infinity'); -- Infinity +SELECT EXTRACT(CENTURY FROM DATE 'infinity'); -- Infinity +SELECT EXTRACT(MILLENNIUM FROM DATE 'infinity'); -- Infinity +SELECT EXTRACT(JULIAN FROM DATE 'infinity'); -- Infinity +SELECT EXTRACT(ISOYEAR FROM DATE 'infinity'); -- Infinity +SELECT EXTRACT(EPOCH FROM DATE 'infinity'); -- Infinity +-- +-- wrong fields from non-finite date: +-- +SELECT EXTRACT(MICROSEC FROM DATE 'infinity'); -- ERROR: timestamp units "microsec" not recognized +SELECT EXTRACT(UNDEFINED FROM DATE 'infinity'); -- ERROR: timestamp units "undefined" not supported -- test constructors select make_date(2013, 7, 15);