diff --git a/pyathena/sqlalchemy/types.py b/pyathena/sqlalchemy/types.py index 89c6a4b0..906b6c16 100644 --- a/pyathena/sqlalchemy/types.py +++ b/pyathena/sqlalchemy/types.py @@ -79,8 +79,10 @@ class AthenaDate(TypeEngine[date]): render_bind_cast = True @staticmethod - def process(value: date | datetime | Any) -> str: - if isinstance(value, (date, datetime)): + def process(value: date | Any) -> str: + # datetime is a subclass of date, so this branch also covers datetime, + # which is truncated to its date part. + if isinstance(value, date): return f"DATE '{value:%Y-%m-%d}'" return f"DATE '{value!s}'" diff --git a/tests/pyathena/sqlalchemy/test_types.py b/tests/pyathena/sqlalchemy/test_types.py index 9a4f3943..3eb9c10c 100644 --- a/tests/pyathena/sqlalchemy/test_types.py +++ b/tests/pyathena/sqlalchemy/test_types.py @@ -12,6 +12,7 @@ AthenaDate, AthenaMap, AthenaStruct, + AthenaTimestamp, get_double_type, ) @@ -178,3 +179,32 @@ class TestAthenaDate: ) def test_process_renders_date_only_literal(self, value, expected): assert AthenaDate.process(value) == expected + + def test_process_falls_back_to_str(self): + assert AthenaDate.process("2017-01-01") == "DATE '2017-01-01'" + + +class TestAthenaTimestamp: + @pytest.mark.parametrize( + ("value", "expected"), + [ + # Athena TIMESTAMP has millisecond precision, so the six digits + # strftime("%f") emits are truncated to three. + ( + datetime(2017, 1, 1, 12, 34, 56, 789012), + "TIMESTAMP '2017-01-01 12:34:56.789'", + ), + ( + datetime(2017, 1, 1, 12, 34, 56), + "TIMESTAMP '2017-01-01 12:34:56.000'", + ), + ], + ) + def test_process_renders_millisecond_precision_literal(self, value, expected): + assert AthenaTimestamp.process(value) == expected + + def test_process_falls_back_to_str(self): + assert ( + AthenaTimestamp.process("2017-01-01 12:34:56.789") + == "TIMESTAMP '2017-01-01 12:34:56.789'" + )