Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented CAST from VARBINARY to DATETIME in Babelfish. #3481

Open
wants to merge 19 commits into
base: BABEL_3_X_DEV
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions contrib/babelfishpg_common/sql/datetime.sql
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,13 @@ LANGUAGE C VOLATILE STRICT PARALLEL SAFE;
CREATE CAST (TIMESTAMP AS DATETIME)
WITH FUNCTION sys.timestamp2datetime(TIMESTAMP) AS ASSIGNMENT;

CREATE OR REPLACE FUNCTION sys.varbinary2datetime(sys.BBF_VARBINARY)
RETURNS sys.DATETIME
AS 'babelfishpg_common', 'varbinary_datetime'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE CAST (sys.BBF_VARBINARY AS sys.DATETIME)
WITH FUNCTION sys.varbinary2datetime(sys.BBF_VARBINARY) AS IMPLICIT;

CREATE OR REPLACE FUNCTION sys.timestamptz2datetime(TIMESTAMPTZ)
RETURNS DATETIME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,17 @@
------------------------------------------------------------------------------

-- complain if script is sourced in psql, rather than via ALTER EXTENSION
\echo Use "ALTER EXTENSION ""babelfishpg_common"" UPDATE TO "3.9.0"" to load this file. \quit
\echo Use "ALTER EXTENSION ""babelfishpg_common"" UPDATE TO '3.9.0'" to load this file. \quit

SELECT set_config('search_path', 'sys, '|| current_setting('search_path'), false);

CREATE OR REPLACE FUNCTION sys.varbinary2datetime(sys.BBF_VARBINARY)
RETURNS sys.DATETIME
AS 'babelfishpg_common', 'varbinary_datetime'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE CAST (sys.BBF_VARBINARY AS sys.DATETIME)
WITH FUNCTION sys.varbinary2datetime(sys.BBF_VARBINARY) AS IMPLICIT;

-- Reset search_path to not affect any subsequent scripts
SELECT set_config('search_path', trim(leading 'sys, ' from current_setting('search_path')), false);
71 changes: 71 additions & 0 deletions contrib/babelfishpg_common/src/datetime.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ PG_FUNCTION_INFO_V1(datetime_recv);
PG_FUNCTION_INFO_V1(date_datetime);
PG_FUNCTION_INFO_V1(time_datetime);
PG_FUNCTION_INFO_V1(timestamp_datetime);
PG_FUNCTION_INFO_V1(varbinary_datetime);
PG_FUNCTION_INFO_V1(timestamptz_datetime);
PG_FUNCTION_INFO_V1(datetime_varchar);
PG_FUNCTION_INFO_V1(varchar_datetime);
Expand Down Expand Up @@ -60,6 +61,7 @@ PG_FUNCTION_INFO_V1(timestamp_diff_big);

void CheckDatetimeRange(const Timestamp time);
void CheckDatetimePrecision(fsec_t fsec);
bool is_valid_datetime_for_4byte_varbinary(int64 total_usecs);

#define DTK_NANO 32

Expand Down Expand Up @@ -754,6 +756,75 @@ timestamp_datetime(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(result);
}

/* For 4 byte varbinary datetime range from 1900-01-01 00:00:00.000 to 1900-01-01 23:59:59:999*/
bool
is_valid_datetime_for_4byte_varbinary(int64 total_usecs)
{
return (total_usecs >= TSQL_DEFAULT_DATETIME && total_usecs <= MAX_4_BYTE_VARBINARY_DATETIME);
}
Comment on lines +759 to +764
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This should be macro instead of function.
  2. If it is not getting used at multiple places then macro is also not needed.


/*
* varbinary_datetime()
* Convert varbinary to datetime
*/
Datum
varbinary_datetime(PG_FUNCTION_ARGS)
{
bytea *arg = PG_GETARG_BYTEA_PP(0);
int32 size = VARSIZE_ANY_EXHDR(arg);
int32 days;
int32 time_part;
int64 ms_value;
int64 usecs;
Timestamp result;
unsigned char *buffer = (unsigned char *)VARDATA_ANY(arg);

/* TSQL datetime is 8 bytes */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean input should be either 4 or 8 bytes, right? Lets update the comment.

if (size != sizeof(int64) && size != sizeof(int32))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As spoken offline, Cast seems to accept multitude of binary lengths, I think MAX ones as well. Lets add support for all lens.

ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid binary size for datetime conversion")));

/* Extract days and time parts from the binary data */
if (size == sizeof(int32))
{
days = 0;
time_part = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
}
else
{
days = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
time_part = (buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7];
}

/* Convert time_part to microseconds */
ms_value = ((int64)time_part * 10LL) / 3LL;
usecs = ms_value * 1000;

if (days < 0)
{
/* Handle pre-1900 dates */
int64 day_value = (int64) (((int64) days) & ((int64) 0xFFFFFFFF));
int64 total_usecs = (day_value - 0xFFFF2E46LL) * USECS_PER_DAY + usecs;
result = MIN_DATETIME + total_usecs;
}
else
{
/* Handle post-1900 dates */
int64 total_usecs = days * USECS_PER_DAY + usecs;
result = TSQL_DEFAULT_DATETIME + total_usecs;
}

if (size == sizeof(int32) && !is_valid_datetime_for_4byte_varbinary(result))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("data out of range for datetime")));

CheckDatetimeRange(result);

PG_RETURN_TIMESTAMP(result);
}

/* timestamptz_datetime()
* Convert timestamptz to datetime
*/
Expand Down
4 changes: 4 additions & 0 deletions contrib/babelfishpg_common/src/datetime.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
#define MIN_DATETIME INT64CONST(-7794489600000000)
/* upper bond: 9999-12-31 23:59:29.999 */
#define END_DATETIME INT64CONST(252455615999999000)
/* TSQL default datetime: 1900-01-01 00:00:00.000 */
#define TSQL_DEFAULT_DATETIME INT64CONST(-3155673600000000)
/* TSQL MAX_DATETIME for 4 bytes varbinary: 1900-01-01 23:59:59.999 */
#define MAX_4_BYTE_VARBINARY_DATETIME INT64CONST(-3155587200004000)

#define strtoi64(str, endptr, base) ((int64) strtol(str, endptr, base))

Expand Down
89 changes: 85 additions & 4 deletions contrib/babelfishpg_tsql/sql/sys_function_helpers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9640,16 +9640,97 @@ $BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg sys.VARCHAR,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN sys.babelfish_conv_helper_to_datetime(arg::TEXT, try, p_style);
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg sys.NVARCHAR,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN sys.babelfish_conv_helper_to_datetime(arg::TEXT, try, p_style);
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg sys.bpchar,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN sys.babelfish_conv_helper_to_datetime(arg::TEXT, try, p_style);
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg sys.NCHAR,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN sys.babelfish_conv_helper_to_datetime(arg::TEXT, try, p_style);
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg anyelement,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
DECLARE
resdatetime sys.DATETIME;
BEGIN
IF try THEN
resdatetime := sys.babelfish_try_conv_to_datetime(arg);
ELSE
BEGIN
resdatetime := CAST(arg AS sys.DATETIME);
EXCEPTION
WHEN cannot_coerce THEN
RAISE USING MESSAGE := pg_catalog.format('Explicit conversion from data type %s to datetime is not allowed.', format_type(pg_typeof(arg)::oid, NULL));
WHEN datetime_field_overflow THEN
RAISE USING MESSAGE := 'Arithmetic overflow error converting expression to data type datetime.';
END;
END IF;

RETURN resdatetime;
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_try_conv_to_datetime(IN arg anyelement)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN CAST(arg AS TIMESTAMP);
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
RETURN CAST(arg AS sys.DATETIME);
EXCEPTION
WHEN cannot_coerce THEN
RAISE USING MESSAGE := pg_catalog.format('Explicit conversion from data type %s to datetime is not allowed.', format_type(pg_typeof(arg)::oid, NULL));
WHEN OTHERS THEN
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,101 @@ SELECT set_config('search_path', 'sys, '||current_setting('search_path'), false)
* So make sure that any SQL statement (DDL/DML) being added here can be executed multiple times without affecting
* final behaviour.
*/
CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg sys.VARCHAR,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN sys.babelfish_conv_helper_to_datetime(arg::TEXT, try, p_style);
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg sys.NVARCHAR,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN sys.babelfish_conv_helper_to_datetime(arg::TEXT, try, p_style);
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg sys.bpchar,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN sys.babelfish_conv_helper_to_datetime(arg::TEXT, try, p_style);
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg sys.NCHAR,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN sys.babelfish_conv_helper_to_datetime(arg::TEXT, try, p_style);
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_conv_helper_to_datetime(IN arg anyelement,
IN try BOOL,
IN p_style NUMERIC DEFAULT 0)
RETURNS sys.DATETIME
AS
$BODY$
DECLARE
resdatetime sys.DATETIME;
BEGIN
IF try THEN
resdatetime := sys.babelfish_try_conv_to_datetime(arg);
ELSE
BEGIN
resdatetime := CAST(arg AS sys.DATETIME);
EXCEPTION
WHEN cannot_coerce THEN
RAISE USING MESSAGE := pg_catalog.format('Explicit conversion from data type %s to datetime is not allowed.', format_type(pg_typeof(arg)::oid, NULL));
WHEN datetime_field_overflow THEN
RAISE USING MESSAGE := 'Arithmetic overflow error converting expression to data type datetime.';
END;
END IF;

RETURN resdatetime;
END;
$BODY$
LANGUAGE plpgsql
STABLE;

CREATE OR REPLACE FUNCTION sys.babelfish_try_conv_to_datetime(IN arg anyelement)
RETURNS sys.DATETIME
AS
$BODY$
BEGIN
RETURN CAST(arg AS sys.DATETIME);
EXCEPTION
WHEN cannot_coerce THEN
RAISE USING MESSAGE := pg_catalog.format('Explicit conversion from data type %s to datetime is not allowed.', format_type(pg_typeof(arg)::oid, NULL));
WHEN OTHERS THEN
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql
STABLE;

-- After upgrade, always run analyze for all babelfish catalogs.
CALL sys.analyze_babelfish_catalogs();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Clean up
drop table babel_datetime_vu_prepare_testing
go
drop table babel_datetime_vu_prepare_testing_1
go
drop view babel_datetime_vu_view1
go
~~ERROR (Code: 3701)~~

~~ERROR (Message: view "babel_datetime_vu_view1" does not exist)~~

drop view babel_datetime_vu_view2
go
~~ERROR (Code: 3701)~~

~~ERROR (Message: view "babel_datetime_vu_view2" does not exist)~~

drop procedure babel_datetime_vu_procedure
go
Loading
Loading