Skip to content

Commit 9f12a2e

Browse files
Fix for DATENAME() gives incorrect value with TZOFFSET part (babelfish-for-postgresql#3846)
Currently, DATENAME function returns a numeric value for TZOFFSET datepart, which does not match SQL Server's text output. Root cause: The function definition of sys.datename explicitly calls the DATEPART function for most argument values, including TZOFFSET, thereby returning a numerical offset value instead of the required text format (+/-HH:MM). Fix: Modified the sys function's SQL definition to extract and return the timezone offset substring from the input - similar to how the offset_string is determined in DATETRUNC/DATEBUCKET - if the argument matches tzoffset. Furthermore, translated the function from SQL language to plpgsql in order to enable raising an exception for unsupported input datetime datatypes such as date, time, datetime and smalldatetime. Task: BABEL-5846 Signed-off-by: Manisha Deshpande <mmdeshp@amazon.com> (cherry picked from commit debf90b)
1 parent 1595df5 commit 9f12a2e

21 files changed

+1754
-150
lines changed

contrib/babelfishpg_tsql/sql/sys_functions.sql

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2056,36 +2056,59 @@ LANGUAGE C IMMUTABLE PARALLEL SAFE;
20562056

20572057
CREATE OR REPLACE FUNCTION sys.datename(IN dp PG_CATALOG.TEXT, IN arg anyelement) RETURNS TEXT AS
20582058
$BODY$
2059-
SELECT
2060-
CASE
2061-
WHEN dp = 'month'::text THEN
2062-
to_char(arg::sys.DATETIME, 'TMMonth')
2059+
DECLARE
2060+
date_arg_datatype regtype;
2061+
result TEXT;
2062+
datetimeoffset_value sys.datetimeoffset;
2063+
BEGIN
2064+
date_arg_datatype := pg_typeof(arg);
2065+
2066+
IF dp = 'month'::text THEN
2067+
result := to_char(arg::sys.DATETIME, 'TMMonth');
20632068
-- '1969-12-28' is a Sunday
2064-
WHEN dp = 'dow'::text THEN
2065-
to_char(arg::sys.DATETIME, 'TMDay')
2069+
ELSIF dp = 'dow'::text THEN
2070+
result := to_char(arg::sys.DATETIME, 'TMDay');
2071+
ELSIF dp = 'tzoffset'::text THEN
2072+
IF date_arg_datatype IN ('sys.datetimeoffset'::regtype, 'sys.datetime2'::regtype) THEN
2073+
-- Explicitly cast to datetimeoffset to validate
2074+
-- This will throw an error if the timezone offset is invalid
2075+
datetimeoffset_value := sys.babelfish_conv_string_to_datetimeoffset('DATETIMEOFFSET', arg::TEXT);
2076+
result := PG_CATALOG.RIGHT(datetimeoffset_value::PG_CATALOG.TEXT, 6);
2077+
ELSE
2078+
RAISE EXCEPTION 'The datepart tzoffset is not supported by date function datename for data type %.', date_arg_datatype;
2079+
END IF;
20662080
ELSE
2067-
sys.datepart(dp, arg)::TEXT
2068-
END
2081+
result := sys.datepart(dp, arg)::TEXT;
2082+
END IF;
2083+
RETURN result;
2084+
END;
20692085
$BODY$
2070-
STRICT
2071-
LANGUAGE sql IMMUTABLE;
2086+
LANGUAGE plpgsql IMMUTABLE;
20722087

2073-
-- Duplicate functions with arg TEXT since ANYELEMENT cannot handle type unknown.
2088+
-- Duplicate function with arg TEXT since ANYELEMENT cannot handle type unknown.
20742089
CREATE OR REPLACE FUNCTION sys.datename(IN dp PG_CATALOG.TEXT, IN arg TEXT) RETURNS TEXT AS
20752090
$BODY$
2076-
SELECT
2077-
CASE
2078-
WHEN dp = 'month'::text THEN
2079-
to_char(arg::date, 'TMMonth')
2091+
DECLARE
2092+
result TEXT;
2093+
datetimeoffset_value sys.datetimeoffset;
2094+
BEGIN
2095+
IF dp = 'month'::text THEN
2096+
result := to_char(arg::date, 'TMMonth');
20802097
-- '1969-12-28' is a Sunday
2081-
WHEN dp = 'dow'::text THEN
2082-
to_char(arg::date, 'TMDay')
2098+
ELSIF dp = 'dow'::text THEN
2099+
result := to_char(arg::date, 'TMDay');
2100+
ELSIF dp = 'tzoffset'::text THEN
2101+
-- Explicitly cast to datetimeoffset to validate
2102+
-- This will throw an error if the timezone offset is invalid
2103+
datetimeoffset_value := sys.babelfish_conv_string_to_datetimeoffset('DATETIMEOFFSET', arg);
2104+
result := PG_CATALOG.RIGHT(datetimeoffset_value::PG_CATALOG.TEXT, 6);
20832105
ELSE
2084-
sys.datepart(dp, arg)::TEXT
2085-
END
2106+
result := sys.datepart(dp, arg)::TEXT;
2107+
END IF;
2108+
RETURN result;
2109+
END;
20862110
$BODY$
2087-
STRICT
2088-
LANGUAGE sql IMMUTABLE;
2111+
LANGUAGE plpgsql IMMUTABLE;
20892112

20902113
-- These come from the built-in pg_catalog.count in pg_aggregate.dat
20912114
CREATE AGGREGATE sys.count(*)

contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--4.6.0--4.7.0.sql

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,62 @@ END; $BODY$
977977
LANGUAGE plpgsql
978978
STABLE;
979979

980+
CREATE OR REPLACE FUNCTION sys.datename(IN dp PG_CATALOG.TEXT, IN arg anyelement) RETURNS TEXT AS
981+
$BODY$
982+
DECLARE
983+
date_arg_datatype regtype;
984+
result TEXT;
985+
datetimeoffset_value sys.datetimeoffset;
986+
BEGIN
987+
date_arg_datatype := pg_typeof(arg);
988+
989+
IF dp = 'month'::text THEN
990+
result := to_char(arg::sys.DATETIME, 'TMMonth');
991+
-- '1969-12-28' is a Sunday
992+
ELSIF dp = 'dow'::text THEN
993+
result := to_char(arg::sys.DATETIME, 'TMDay');
994+
ELSIF dp = 'tzoffset'::text THEN
995+
IF date_arg_datatype IN ('sys.datetimeoffset'::regtype, 'sys.datetime2'::regtype) THEN
996+
-- Explicitly cast to datetimeoffset to validate
997+
-- This will throw an error if the timezone offset is invalid
998+
datetimeoffset_value := sys.babelfish_conv_string_to_datetimeoffset('DATETIMEOFFSET', arg::TEXT);
999+
result := PG_CATALOG.RIGHT(datetimeoffset_value::PG_CATALOG.TEXT, 6);
1000+
ELSE
1001+
RAISE EXCEPTION 'The datepart tzoffset is not supported by date function datename for data type %.', date_arg_datatype;
1002+
END IF;
1003+
ELSE
1004+
result := sys.datepart(dp, arg)::TEXT;
1005+
END IF;
1006+
RETURN result;
1007+
END;
1008+
$BODY$
1009+
LANGUAGE plpgsql IMMUTABLE;
1010+
1011+
-- Duplicate function with arg TEXT since ANYELEMENT cannot handle type unknown.
1012+
CREATE OR REPLACE FUNCTION sys.datename(IN dp PG_CATALOG.TEXT, IN arg TEXT) RETURNS TEXT AS
1013+
$BODY$
1014+
DECLARE
1015+
result TEXT;
1016+
datetimeoffset_value sys.datetimeoffset;
1017+
BEGIN
1018+
IF dp = 'month'::text THEN
1019+
result := to_char(arg::date, 'TMMonth');
1020+
-- '1969-12-28' is a Sunday
1021+
ELSIF dp = 'dow'::text THEN
1022+
result := to_char(arg::date, 'TMDay');
1023+
ELSIF dp = 'tzoffset'::text THEN
1024+
-- Explicitly cast to datetimeoffset to validate
1025+
-- This will throw an error if the timezone offset is invalid
1026+
datetimeoffset_value := sys.babelfish_conv_string_to_datetimeoffset('DATETIMEOFFSET', arg);
1027+
result := PG_CATALOG.RIGHT(datetimeoffset_value::PG_CATALOG.TEXT, 6);
1028+
ELSE
1029+
result := sys.datepart(dp, arg)::TEXT;
1030+
END IF;
1031+
RETURN result;
1032+
END;
1033+
$BODY$
1034+
LANGUAGE plpgsql IMMUTABLE;
1035+
9801036
-- Drops the temporary procedure used by the upgrade script.
9811037
-- Please have this be one of the last statements executed in this upgrade script.
9821038
DROP PROCEDURE sys.babelfish_drop_deprecated_object(varchar, varchar, varchar);

test/JDBC/expected/Datetime_system_functions.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3292,7 +3292,7 @@ SELECT
32923292
GO
32933293
~~START~~
32943294
varchar
3295-
FAIL: TZOFFSET issue
3295+
PASS: TZOFFSET works correctly
32963296
~~END~~
32973297

32983298

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
DROP TABLE datename_tzoffset_test_cases;
2+
GO
3+
4+
DROP TABLE datename_stability_test;
5+
GO
6+
7+
DROP VIEW datename_tzoffset_no_offset_tests_view;
8+
GO
9+
10+
DROP TABLE datename_tzoffset_different_test_scenarios;
11+
GO

0 commit comments

Comments
 (0)