From 8d5e9d0aa49824c1247c340a588118731e495bb7 Mon Sep 17 00:00:00 2001 From: lpoulain Date: Tue, 23 Aug 2022 13:18:28 -0400 Subject: [PATCH] Add support for parametric datetimes This change starts sending the PARAMETRIC_DATETIME client capability in the X-Trino-Client-Capabilities request headers so that Trino sends back temporal types in their original precision instead of always sending back results with millisecond precision. --- tests/integration/test_dbapi_integration.py | 12 +- tests/integration/test_types_integration.py | 390 +++++++++++--------- tests/unit/test_client.py | 6 +- trino/client.py | 5 +- trino/constants.py | 2 + 5 files changed, 224 insertions(+), 191 deletions(-) diff --git a/tests/integration/test_dbapi_integration.py b/tests/integration/test_dbapi_integration.py index 57c6c604..9a18fb9c 100644 --- a/tests/integration/test_dbapi_integration.py +++ b/tests/integration/test_dbapi_integration.py @@ -287,7 +287,7 @@ def test_datetime_query_param(trino_connection): rows = cur.fetchall() assert rows[0][0] == params - assert cur.description[0][1] == "timestamp" + assert cur.description[0][1] == "timestamp(6)" def test_datetime_with_utc_time_zone_query_param(trino_connection): @@ -299,7 +299,7 @@ def test_datetime_with_utc_time_zone_query_param(trino_connection): rows = cur.fetchall() assert rows[0][0] == params - assert cur.description[0][1] == "timestamp with time zone" + assert cur.description[0][1] == "timestamp(6) with time zone" def test_datetime_with_numeric_offset_time_zone_query_param(trino_connection): @@ -313,7 +313,7 @@ def test_datetime_with_numeric_offset_time_zone_query_param(trino_connection): rows = cur.fetchall() assert rows[0][0] == params - assert cur.description[0][1] == "timestamp with time zone" + assert cur.description[0][1] == "timestamp(6) with time zone" def test_datetime_with_named_time_zone_query_param(trino_connection): @@ -325,7 +325,7 @@ def test_datetime_with_named_time_zone_query_param(trino_connection): rows = cur.fetchall() assert rows[0][0] == params - assert cur.description[0][1] == "timestamp with time zone" + assert cur.description[0][1] == "timestamp(6) with time zone" def test_datetime_with_trailing_zeros(trino_connection): @@ -463,6 +463,7 @@ def test_time_query_param(trino_connection): rows = cur.fetchall() assert rows[0][0] == params + assert cur.description[0][1] == "time(6)" def test_time_with_named_time_zone_query_param(trino_connection): @@ -492,6 +493,7 @@ def test_time(trino_connection): rows = cur.fetchall() assert rows[0][0] == time(1, 2, 3, 456000) + assert cur.description[0][1] == "time(3)" def test_null_time(trino_connection): @@ -512,6 +514,7 @@ def test_time_with_time_zone_negative_offset(trino_connection): tz = timezone(-timedelta(hours=8, minutes=0)) assert rows[0][0] == time(1, 2, 3, 456000, tzinfo=tz) + assert cur.description[0][1] == "time(3) with time zone" def test_time_with_time_zone_positive_offset(trino_connection): @@ -523,6 +526,7 @@ def test_time_with_time_zone_positive_offset(trino_connection): tz = timezone(timedelta(hours=8, minutes=0)) assert rows[0][0] == time(1, 2, 3, 456000, tzinfo=tz) + assert cur.description[0][1] == "time(3) with time zone" def test_null_date_with_time_zone(trino_connection): diff --git a/tests/integration/test_types_integration.py b/tests/integration/test_types_integration.py index 37201a1d..85675e22 100644 --- a/tests/integration/test_types_integration.py +++ b/tests/integration/test_types_integration.py @@ -191,13 +191,13 @@ def test_time(trino_connection): python=time(0, 0, 0, 1000)) .add_field( sql="TIME '00:00:00.0001'", - python=time(0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=time(0, 0, 0, 100)) .add_field( sql="TIME '00:00:00.00001'", - python=time(0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=time(0, 0, 0, 10)) .add_field( sql="TIME '00:00:00.000001'", - python=time(0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=time(0, 0, 0, 1)) # max value for each precision .add_field( sql="TIME '23:59:59'", @@ -211,69 +211,74 @@ def test_time(trino_connection): .add_field( sql="TIME '23:59:59.999'", python=time(23, 59, 59, 999000)) - # TODO: re-enable when Trino 404 is released - # .add_field( - # sql="TIME '23:59:59.9999'", - # python=time(0, 0, 0)) # PARAMETRIC_DATETIME not enabled - # .add_field( - # sql="TIME '23:59:59.99999'", - # python=time(0, 0, 0)) # PARAMETRIC_DATETIME not enabled - # .add_field( - # sql="TIME '23:59:59.999999'", - # python=time(0, 0, 0)) # PARAMETRIC_DATETIME not enabled - # round down .add_field( - sql="TIME '00:00:00.000100'", - python=time(0, 0, 0)) + sql="TIME '23:59:59.9999'", + python=time(23, 59, 59, 999900)) .add_field( - sql="TIME '00:00:00.0000001'", - python=time(0, 0, 0)) # PARAMETRIC_DATETIME not enabled + sql="TIME '23:59:59.99999'", + python=time(23, 59, 59, 999990)) + .add_field( + sql="TIME '23:59:59.999999'", + python=time(23, 59, 59, 999999)) + # round down .add_field( sql="TIME '12:34:56.1234561'", - python=time(12, 34, 56, 123000)) # PARAMETRIC_DATETIME not enabled - # TODO: re-enable when Trino 404 is released - # .add_field( - # sql="TIME '23:59:59.9999994'", - # python=time(0, 0, 0)) # PARAMETRIC_DATETIME not enabled - # .add_field( - # sql="TIME '23:59:59.9999994999'", - # python=time(0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=time(12, 34, 56, 123456)) + # round down, min value + .add_field( + sql="TIME '00:00:00.000000000001'", + python=time(0, 0, 0, 0)) + .add_field( + sql="TIME '00:00:00.0000001'", + python=time(0, 0, 0, 0)) # round down, max value .add_field( - sql="TIME '00:00:00.0004'", - python=time(0, 0, 0)) + sql="TIME '00:00:00.0000004'", + python=time(0, 0, 0, 0)) .add_field( - sql="TIME '00:00:00.00049'", - python=time(0, 0, 0)) - # round up, min value + sql="TIME '00:00:00.00000049'", + python=time(0, 0, 0, 0)) .add_field( - sql="TIME '00:00:00.0005'", - python=time(0, 0, 0, 1000)) + sql="TIME '00:00:00.0000005'", + python=time(0, 0, 0, 0)) .add_field( - sql="TIME '00:00:00.00050'", - python=time(0, 0, 0, 1000)) + sql="TIME '00:00:00.00000050'", + python=time(0, 0, 0, 0)) + .add_field( + sql="TIME '23:59:59.9999994'", + python=time(23, 59, 59, 999999)) + .add_field( + sql="TIME '23:59:59.9999994999'", + python=time(23, 59, 59, 999999)) + # round up + .add_field( + sql="TIME '12:34:56.123456789'", + python=time(12, 34, 56, 123457)) + # round up, min value + .add_field( + sql="TIME '00:00:00.000000500001'", + python=time(0, 0, 0, 1)) # round up, max value .add_field( - sql="TIME '00:00:00.0009'", - python=time(0, 0, 0, 1000)) + sql="TIME '00:00:00.0000009'", + python=time(0, 0, 0, 1)) .add_field( - sql="TIME '00:00:00.00099'", - python=time(0, 0, 0, 1000)) - # TODO: re-enable when Trino 404 is released + sql="TIME '00:00:00.00000099999'", + python=time(0, 0, 0, 1)) # round up to next day, min value - # .add_field( - # sql="TIME '23:59:59.9995'", - # python=time(0, 0, 0)) - # .add_field( - # sql="TIME '23:59:59.99950'", - # python=time(0, 0, 0)) - # # round up to next day, max value - # .add_field( - # sql="TIME '23:59:59.9999'", - # python=time(0, 0, 0)) - # .add_field( - # sql="TIME '23:59:59.99999'", - # python=time(0, 0, 0)) + .add_field( + sql="TIME '23:59:59.9999995'", + python=time(0, 0, 0)) + .add_field( + sql="TIME '23:59:59.999999500001'", + python=time(0, 0, 0)) + # round up to next day, max value + .add_field( + sql="TIME '23:59:59.9999999'", + python=time(0, 0, 0)) + .add_field( + sql="TIME '23:59:59.999999999'", + python=time(0, 0, 0)) ).execute() @@ -317,13 +322,13 @@ def query_time_with_timezone(trino_connection, tz_str: str): python=time(0, 0, 0, 1000).replace(tzinfo=tz)) .add_field( sql=f"TIME '00:00:00.0001 {tz_str}'", - python=time(0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=time(0, 0, 0, 100).replace(tzinfo=tz)) .add_field( sql=f"TIME '00:00:00.00001 {tz_str}'", - python=time(0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=time(0, 0, 0, 10).replace(tzinfo=tz)) .add_field( sql=f"TIME '00:00:00.000001 {tz_str}'", - python=time(0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=time(0, 0, 0, 1).replace(tzinfo=tz)) # max value for each precision .add_field( sql=f"TIME '23:59:59 {tz_str}'", @@ -337,69 +342,71 @@ def query_time_with_timezone(trino_connection, tz_str: str): .add_field( sql=f"TIME '23:59:59.999 {tz_str}'", python=time(23, 59, 59, 999000).replace(tzinfo=tz)) - # TODO: re-enable when Trino 404 is released - # .add_field( - # sql=f"TIME '23:59:59.9999 {tz_str}'", - # python=time(0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled - # .add_field( - # sql=f"TIME '23:59:59.99999 {tz_str}'", - # python=time(0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled - # .add_field( - # sql=f"TIME '23:59:59.999999 {tz_str}'", - # python=time(0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled - # round down .add_field( - sql=f"TIME '00:00:00.000100 {tz_str}'", - python=time(0, 0, 0).replace(tzinfo=tz)) + sql=f"TIME '23:59:59.9999 {tz_str}'", + python=time(23, 59, 59, 999900).replace(tzinfo=tz)) .add_field( - sql=f"TIME '00:00:00.0000001 {tz_str}'", - python=time(0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + sql=f"TIME '23:59:59.99999 {tz_str}'", + python=time(23, 59, 59, 999990).replace(tzinfo=tz)) + .add_field( + sql=f"TIME '23:59:59.999999 {tz_str}'", + python=time(23, 59, 59, 999999).replace(tzinfo=tz)) + # round down .add_field( sql=f"TIME '12:34:56.1234561 {tz_str}'", - python=time(12, 34, 56, 123000).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled - # TODO: re-enable when Trino 404 is released - # .add_field( - # sql=f"TIME '23:59:59.9999994 {tz_str}'", - # python=time(0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled - # .add_field( - # sql=f"TIME '23:59:59.9999994999 {tz_str}'", - # python=time(0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=time(12, 34, 56, 123456).replace(tzinfo=tz)) + # round down, min value + .add_field( + sql=f"TIME '00:00:00.000000000001 {tz_str}'", + python=time(0, 0, 0, 0).replace(tzinfo=tz)) + .add_field( + sql=f"TIME '00:00:00.0000001 {tz_str}'", + python=time(0, 0, 0, 0).replace(tzinfo=tz)) # round down, max value .add_field( - sql=f"TIME '00:00:00.0004 {tz_str}'", - python=time(0, 0, 0).replace(tzinfo=tz)) + sql=f"TIME '00:00:00.0000004 {tz_str}'", + python=time(0, 0, 0, 0).replace(tzinfo=tz)) .add_field( - sql=f"TIME '00:00:00.00049 {tz_str}'", - python=time(0, 0, 0).replace(tzinfo=tz)) - # round up, min value + sql=f"TIME '00:00:00.00000049 {tz_str}'", + python=time(0, 0, 0, 0).replace(tzinfo=tz)) .add_field( - sql=f"TIME '00:00:00.0005 {tz_str}'", - python=time(0, 0, 0, 1000).replace(tzinfo=tz)) + sql=f"TIME '00:00:00.0000005 {tz_str}'", + python=time(0, 0, 0, 0).replace(tzinfo=tz)) .add_field( - sql=f"TIME '00:00:00.00050 {tz_str}'", - python=time(0, 0, 0, 1000).replace(tzinfo=tz)) + sql=f"TIME '23:59:59.9999994 {tz_str}'", + python=time(23, 59, 59, 999999).replace(tzinfo=tz)) + .add_field( + sql=f"TIME '23:59:59.9999994999 {tz_str}'", + python=time(23, 59, 59, 999999).replace(tzinfo=tz)) + # round up + .add_field( + sql=f"TIME '12:34:56.123456789 {tz_str}'", + python=time(12, 34, 56, 123457).replace(tzinfo=tz)) + # round up, min value + .add_field( + sql=f"TIME '00:00:00.000000500001 {tz_str}'", + python=time(0, 0, 0, 1).replace(tzinfo=tz)) # round up, max value .add_field( - sql=f"TIME '00:00:00.0009 {tz_str}'", - python=time(0, 0, 0, 1000).replace(tzinfo=tz)) + sql=f"TIME '00:00:00.0000009 {tz_str}'", + python=time(0, 0, 0, 1).replace(tzinfo=tz)) .add_field( - sql=f"TIME '00:00:00.00099 {tz_str}'", - python=time(0, 0, 0, 1000).replace(tzinfo=tz)) - # TODO: re-enable when Trino 404 is released - # # round up to next day, min value - # .add_field( - # sql=f"TIME '23:59:59.9995 {tz_str}'", - # python=time(0, 0, 0).replace(tzinfo=tz)) - # .add_field( - # sql=f"TIME '23:59:59.99950 {tz_str}'", - # python=time(0, 0, 0).replace(tzinfo=tz)) - # # round up to next day, max value - # .add_field( - # sql=f"TIME '23:59:59.9999 {tz_str}'", - # python=time(0, 0, 0).replace(tzinfo=tz)) - # .add_field( - # sql=f"TIME '23:59:59.99999 {tz_str}'", - # python=time(0, 0, 0).replace(tzinfo=tz)) + sql=f"TIME '00:00:00.00000099999 {tz_str}'", + python=time(0, 0, 0, 1).replace(tzinfo=tz)) + # round up to next day, min value + .add_field( + sql=f"TIME '23:59:59.9999995 {tz_str}'", + python=time(0, 0, 0).replace(tzinfo=tz)) + .add_field( + sql=f"TIME '23:59:59.999999500001 {tz_str}'", + python=time(0, 0, 0).replace(tzinfo=tz)) + # round up to next day, max value + .add_field( + sql=f"TIME '23:59:59.9999999 {tz_str}'", + python=time(0, 0, 0).replace(tzinfo=tz)) + .add_field( + sql=f"TIME '23:59:59.999999999 {tz_str}'", + python=time(0, 0, 0).replace(tzinfo=tz)) ).execute() @@ -435,13 +442,13 @@ def test_timestamp(trino_connection): python=datetime(2001, 8, 22, 0, 0, 0, 1000)) .add_field( sql="TIMESTAMP '2001-08-22 00:00:00.0001'", - python=datetime(2001, 8, 22, 0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 0, 0, 0, 100)) .add_field( sql="TIMESTAMP '2001-08-22 00:00:00.00001'", - python=datetime(2001, 8, 22, 0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 0, 0, 0, 10)) .add_field( sql="TIMESTAMP '2001-08-22 00:00:00.000001'", - python=datetime(2001, 8, 22, 0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 0, 0, 0, 1)) # max value for each precision .add_field( sql="TIMESTAMP '2001-08-22 23:59:59'", @@ -457,64 +464,72 @@ def test_timestamp(trino_connection): python=datetime(2001, 8, 22, 23, 59, 59, 999000)) .add_field( sql="TIMESTAMP '2001-08-22 23:59:59.9999'", - python=datetime(2001, 8, 23, 0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 23, 59, 59, 999900)) .add_field( sql="TIMESTAMP '2001-08-22 23:59:59.99999'", - python=datetime(2001, 8, 23, 0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 23, 59, 59, 999990)) .add_field( sql="TIMESTAMP '2001-08-22 23:59:59.999999'", - python=datetime(2001, 8, 23, 0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 23, 59, 59, 999999)) # round down .add_field( - sql="TIMESTAMP '2001-08-22 00:00:00.000100'", - python=datetime(2001, 8, 22, 0, 0, 0)) + sql="TIMESTAMP '2001-08-22 12:34:56.1234561'", + python=datetime(2001, 8, 22, 12, 34, 56, 123456)) + # round down, min value + .add_field( + sql="TIMESTAMP '2001-08-22 00:00:00.000000000001'", + python=datetime(2001, 8, 22, 0, 0, 0, 0)) .add_field( sql="TIMESTAMP '2001-08-22 00:00:00.0000001'", - python=datetime(2001, 8, 22, 0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 0, 0, 0, 0)) + # round down, max value .add_field( - sql="TIMESTAMP '2001-08-22 12:34:56.1234561'", - python=datetime(2001, 8, 22, 12, 34, 56, 123000)) # PARAMETRIC_DATETIME not enabled + sql="TIMESTAMP '2001-08-22 00:00:00.0000004'", + python=datetime(2001, 8, 22, 0, 0, 0, 0)) + .add_field( + sql="TIMESTAMP '2001-08-22 00:00:00.00000049'", + python=datetime(2001, 8, 22, 0, 0, 0, 0)) + .add_field( + sql="TIMESTAMP '2001-08-22 00:00:00.0000005'", + python=datetime(2001, 8, 22, 0, 0, 0, 0)) + .add_field( + sql="TIMESTAMP '2001-08-22 00:00:00.00000050'", + python=datetime(2001, 8, 22, 0, 0, 0, 0)) .add_field( sql="TIMESTAMP '2001-08-22 23:59:59.9999994'", - python=datetime(2001, 8, 23, 0, 0, 0)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 23, 59, 59, 999999)) .add_field( sql="TIMESTAMP '2001-08-22 23:59:59.9999994999'", - python=datetime(2001, 8, 23, 0, 0, 0)) # PARAMETRIC_DATETIME not enabled - # round down, max value + python=datetime(2001, 8, 22, 23, 59, 59, 999999)) + # round up .add_field( - sql="TIMESTAMP '2001-08-22 00:00:00.0004'", - python=datetime(2001, 8, 22, 0, 0, 0)) - .add_field( - sql="TIMESTAMP '2001-08-22 00:00:00.00049'", - python=datetime(2001, 8, 22, 0, 0, 0)) + sql="TIMESTAMP '2001-08-22 12:34:56.123456789'", + python=datetime(2001, 8, 22, 12, 34, 56, 123457)) # round up, min value .add_field( - sql="TIMESTAMP '2001-08-22 00:00:00.0005'", - python=datetime(2001, 8, 22, 0, 0, 0, 1000)) - .add_field( - sql="TIMESTAMP '2001-08-22 00:00:00.00050'", - python=datetime(2001, 8, 22, 0, 0, 0, 1000)) + sql="TIMESTAMP '2001-08-22 00:00:00.000000500001'", + python=datetime(2001, 8, 22, 0, 0, 0, 1)) # round up, max value .add_field( - sql="TIMESTAMP '2001-08-22 00:00:00.0009'", - python=datetime(2001, 8, 22, 0, 0, 0, 1000)) + sql="TIMESTAMP '2001-08-22 00:00:00.0000009'", + python=datetime(2001, 8, 22, 0, 0, 0, 1)) .add_field( - sql="TIMESTAMP '2001-08-22 00:00:00.00099'", - python=datetime(2001, 8, 22, 0, 0, 0, 1000)) + sql="TIMESTAMP '2001-08-22 00:00:00.00000099999'", + python=datetime(2001, 8, 22, 0, 0, 0, 1)) # round up to next day, min value .add_field( - sql="TIMESTAMP '2001-08-22 23:59:59.9995'", - python=datetime(2001, 8, 23, 0, 0, 0)) + sql="TIMESTAMP '2001-08-22 23:59:59.9999995'", + python=datetime(2001, 8, 23, 0, 0, 0, 0)) .add_field( - sql="TIMESTAMP '2001-08-22 23:59:59.99950'", - python=datetime(2001, 8, 23, 0, 0, 0)) + sql="TIMESTAMP '2001-08-22 23:59:59.999999500001'", + python=datetime(2001, 8, 23, 0, 0, 0, 0)) # round up to next day, max value .add_field( - sql="TIMESTAMP '2001-08-22 23:59:59.9999'", - python=datetime(2001, 8, 23, 0, 0, 0)) + sql="TIMESTAMP '2001-08-22 23:59:59.9999999'", + python=datetime(2001, 8, 23, 0, 0, 0, 0)) .add_field( - sql="TIMESTAMP '2001-08-22 23:59:59.99999'", - python=datetime(2001, 8, 23, 0, 0, 0)) + sql="TIMESTAMP '2001-08-22 23:59:59.999999999'", + python=datetime(2001, 8, 23, 0, 0, 0, 0)) # ce .add_field( sql="TIMESTAMP '0001-01-01 01:23:45.123'", @@ -576,13 +591,13 @@ def query_timestamp_with_timezone(trino_connection, tz_str): python=datetime(2001, 8, 22, 0, 0, 0, 1000).replace(tzinfo=tz)) .add_field( sql=f"TIMESTAMP '2001-08-22 00:00:00.0001 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 0, 0, 0, 100).replace(tzinfo=tz)) .add_field( sql=f"TIMESTAMP '2001-08-22 00:00:00.00001 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 0, 0, 0, 10).replace(tzinfo=tz)) .add_field( sql=f"TIMESTAMP '2001-08-22 00:00:00.000001 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 0, 0, 0, 1).replace(tzinfo=tz)) # max value for each precision .add_field( sql=f"TIMESTAMP '2001-08-22 23:59:59 {tz_str}'", @@ -598,65 +613,72 @@ def query_timestamp_with_timezone(trino_connection, tz_str): python=datetime(2001, 8, 22, 23, 59, 59, 999000).replace(tzinfo=tz)) .add_field( sql=f"TIMESTAMP '2001-08-22 23:59:59.9999 {tz_str}'", - python=datetime(2001, 8, 23, 0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 23, 59, 59, 999900).replace(tzinfo=tz)) .add_field( sql=f"TIMESTAMP '2001-08-22 23:59:59.99999 {tz_str}'", - python=datetime(2001, 8, 23, 0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 23, 59, 59, 999990).replace(tzinfo=tz)) .add_field( sql=f"TIMESTAMP '2001-08-22 23:59:59.999999 {tz_str}'", - python=datetime(2001, 8, 23, 0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 23, 59, 59, 999999).replace(tzinfo=tz)) # round down .add_field( - sql=f"TIMESTAMP '2001-08-22 00:00:00.000100 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0).replace(tzinfo=tz)) + sql=f"TIMESTAMP '2001-08-22 12:34:56.1234561 {tz_str}'", + python=datetime(2001, 8, 22, 12, 34, 56, 123456).replace(tzinfo=tz)) + # round down, min value + .add_field( + sql=f"TIMESTAMP '2001-08-22 00:00:00.000000000001 {tz_str}'", + python=datetime(2001, 8, 22, 0, 0, 0, 0).replace(tzinfo=tz)) .add_field( sql=f"TIMESTAMP '2001-08-22 00:00:00.0000001 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0).replace(tzinfo=tz)) - # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 0, 0, 0, 0).replace(tzinfo=tz)) + # round down, max value .add_field( - sql=f"TIMESTAMP '2001-08-22 12:34:56.1234561 {tz_str}'", - python=datetime(2001, 8, 22, 12, 34, 56, 123000).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + sql=f"TIMESTAMP '2001-08-22 00:00:00.0000004 {tz_str}'", + python=datetime(2001, 8, 22, 0, 0, 0, 0).replace(tzinfo=tz)) + .add_field( + sql=f"TIMESTAMP '2001-08-22 00:00:00.00000049 {tz_str}'", + python=datetime(2001, 8, 22, 0, 0, 0, 0).replace(tzinfo=tz)) + .add_field( + sql=f"TIMESTAMP '2001-08-22 00:00:00.0000005 {tz_str}'", + python=datetime(2001, 8, 22, 0, 0, 0, 0).replace(tzinfo=tz)) + .add_field( + sql=f"TIMESTAMP '2001-08-22 00:00:00.00000050 {tz_str}'", + python=datetime(2001, 8, 22, 0, 0, 0, 0).replace(tzinfo=tz)) .add_field( sql=f"TIMESTAMP '2001-08-22 23:59:59.9999994 {tz_str}'", - python=datetime(2001, 8, 23, 0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled + python=datetime(2001, 8, 22, 23, 59, 59, 999999).replace(tzinfo=tz)) .add_field( sql=f"TIMESTAMP '2001-08-22 23:59:59.9999994999 {tz_str}'", - python=datetime(2001, 8, 23, 0, 0, 0).replace(tzinfo=tz)) # PARAMETRIC_DATETIME not enabled - # round down, max value - .add_field( - sql=f"TIMESTAMP '2001-08-22 00:00:00.0004 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0).replace(tzinfo=tz)) + python=datetime(2001, 8, 22, 23, 59, 59, 999999).replace(tzinfo=tz)) + # round up .add_field( - sql=f"TIMESTAMP '2001-08-22 00:00:00.00049 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0).replace(tzinfo=tz)) + sql=f"TIMESTAMP '2001-08-22 12:34:56.123456789 {tz_str}'", + python=datetime(2001, 8, 22, 12, 34, 56, 123457).replace(tzinfo=tz)) # round up, min value .add_field( - sql=f"TIMESTAMP '2001-08-22 00:00:00.0005 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0, 1000).replace(tzinfo=tz)) - .add_field( - sql=f"TIMESTAMP '2001-08-22 00:00:00.00050 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0, 1000).replace(tzinfo=tz)) + sql=f"TIMESTAMP '2001-08-22 00:00:00.000000500001 {tz_str}'", + python=datetime(2001, 8, 22, 0, 0, 0, 1).replace(tzinfo=tz)) # round up, max value .add_field( - sql=f"TIMESTAMP '2001-08-22 00:00:00.0009 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0, 1000).replace(tzinfo=tz)) + sql=f"TIMESTAMP '2001-08-22 00:00:00.0000009 {tz_str}'", + python=datetime(2001, 8, 22, 0, 0, 0, 1).replace(tzinfo=tz)) .add_field( - sql=f"TIMESTAMP '2001-08-22 00:00:00.00099 {tz_str}'", - python=datetime(2001, 8, 22, 0, 0, 0, 1000).replace(tzinfo=tz)) + sql=f"TIMESTAMP '2001-08-22 00:00:00.00000099999 {tz_str}'", + python=datetime(2001, 8, 22, 0, 0, 0, 1).replace(tzinfo=tz)) # round up to next day, min value .add_field( - sql=f"TIMESTAMP '2001-08-22 23:59:59.9995 {tz_str}'", - python=datetime(2001, 8, 23, 0, 0, 0).replace(tzinfo=tz)) + sql=f"TIMESTAMP '2001-08-22 23:59:59.9999995 {tz_str}'", + python=datetime(2001, 8, 23, 0, 0, 0, 0).replace(tzinfo=tz)) .add_field( - sql=f"TIMESTAMP '2001-08-22 23:59:59.99950 {tz_str}'", - python=datetime(2001, 8, 23, 0, 0, 0).replace(tzinfo=tz)) + sql=f"TIMESTAMP '2001-08-22 23:59:59.999999500001 {tz_str}'", + python=datetime(2001, 8, 23, 0, 0, 0, 0).replace(tzinfo=tz)) # round up to next day, max value .add_field( - sql=f"TIMESTAMP '2001-08-22 23:59:59.9999 {tz_str}'", - python=datetime(2001, 8, 23, 0, 0, 0).replace(tzinfo=tz)) + sql=f"TIMESTAMP '2001-08-22 23:59:59.9999999 {tz_str}'", + python=datetime(2001, 8, 23, 0, 0, 0, 0).replace(tzinfo=tz)) .add_field( - sql=f"TIMESTAMP '2001-08-22 23:59:59.99999 {tz_str}'", - python=datetime(2001, 8, 23, 0, 0, 0).replace(tzinfo=tz)) + sql=f"TIMESTAMP '2001-08-22 23:59:59.999999999 {tz_str}'", + python=datetime(2001, 8, 23, 0, 0, 0, 0).replace(tzinfo=tz)) # ce .add_field( sql=f"TIMESTAMP '0001-01-01 01:23:45.123 {tz_str}'", @@ -682,13 +704,13 @@ def test_timestamp_with_timezone_dst(trino_connection): ( SqlTest(trino_connection) .add_field( - sql=f"TIMESTAMP '2022-03-27 01:59:59.999999 {tz_str}'", + sql=f"TIMESTAMP '2022-03-27 01:59:59.999999999 {tz_str}'", # 2:00:00 (STD) becomes 3:00:00 (DST)) - python=datetime(2022, 3, 27, 3, 0, 0).replace(tzinfo=tz)) + python=datetime(2022, 3, 27, 2, 0, 0).replace(tzinfo=tz)) .add_field( - sql=f"TIMESTAMP '2022-10-30 02:59:59.999999 {tz_str}'", + sql=f"TIMESTAMP '2022-10-30 02:59:59.999999999 {tz_str}'", # 3:00:00 (DST) becomes 2:00:00 (STD)) - python=datetime(2022, 10, 30, 2, 0, 0).replace(tzinfo=tz)) + python=datetime(2022, 10, 30, 3, 0, 0).replace(tzinfo=tz)) ).execute() diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 8fd242ef..5f1a541b 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -118,9 +118,10 @@ def assert_headers(headers): assert headers[constants.HEADER_USER] == user assert headers[constants.HEADER_SESSION] == "" assert headers[constants.HEADER_TIMEZONE] == timezone + assert headers[constants.HEADER_CLIENT_CAPABILITIES] == "PARAMETRIC_DATETIME" assert headers[accept_encoding_header] == accept_encoding_value assert headers[client_info_header] == client_info_value - assert len(headers.keys()) == 9 + assert len(headers.keys()) == 10 req.post("URL") _, post_kwargs = post.call_args @@ -981,7 +982,8 @@ def json(self): sql = 'execute my_stament using 1, 2, 3' additional_headers = { - constants.HEADER_PREPARED_STATEMENT: 'my_statement=added_prepare_statement_header' + constants.HEADER_PREPARED_STATEMENT: 'my_statement=added_prepare_statement_header', + constants.HEADER_CLIENT_CAPABILITIES: 'PARAMETRIC_DATETIME' } # Patch the post function to avoid making the requests, as well as to diff --git a/trino/client.py b/trino/client.py index c945b11a..3e72c0b4 100644 --- a/trino/client.py +++ b/trino/client.py @@ -82,7 +82,8 @@ POWERS_OF_TEN: Dict[int, Decimal] = {} for i in range(0, 13): POWERS_OF_TEN[i] = Decimal(10 ** i) -MAX_PYTHON_TEMPORAL_PRECISION = POWERS_OF_TEN[6] +MAX_PYTHON_TEMPORAL_PRECISION_POWER = 6 +MAX_PYTHON_TEMPORAL_PRECISION = POWERS_OF_TEN[MAX_PYTHON_TEMPORAL_PRECISION_POWER] class ClientSession(object): @@ -435,6 +436,7 @@ def http_headers(self) -> Dict[str, str]: headers[constants.HEADER_SOURCE] = self._client_session.source headers[constants.HEADER_USER] = self._client_session.user headers[constants.HEADER_TIMEZONE] = self._client_session.timezone + headers[constants.HEADER_CLIENT_CAPABILITIES] = 'PARAMETRIC_DATETIME' if len(self._client_session.roles.values()): headers[constants.HEADER_ROLE] = ",".join( # ``name`` must not contain ``=`` @@ -932,6 +934,7 @@ def round_to(self, precision: int) -> TemporalType: In case the supplied value exceeds the specified precision, the value needs to be rounded. """ + precision = min(precision, MAX_PYTHON_TEMPORAL_PRECISION_POWER) remaining_fractional_seconds = self._remaining_fractional_seconds digits = abs(remaining_fractional_seconds.as_tuple().exponent) if digits > precision: diff --git a/trino/constants.py b/trino/constants.py index 64b819d1..6813bd28 100644 --- a/trino/constants.py +++ b/trino/constants.py @@ -51,3 +51,5 @@ HEADER_SET_SCHEMA = "X-Trino-Set-Schema" HEADER_SET_CATALOG = "X-Trino-Set-Catalog" + +HEADER_CLIENT_CAPABILITIES = "X-Trino-Client-Capabilities"