Skip to content

Commit bc65a0c

Browse files
authored
SNOW-966444: Fix date binding issue (#1822)
1 parent d296b0d commit bc65a0c

File tree

3 files changed

+32
-27
lines changed

3 files changed

+32
-27
lines changed

src/snowflake/connector/converter.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,18 @@ def _str_to_snowflake_bindings(self, _, value: str) -> str:
360360
# NOTE: str type is always taken as a text data and never binary
361361
return str(value)
362362

363+
def _date_to_snowflake_bindings_in_bulk_insertion(self, value: date) -> str:
364+
# notes: this is for date type bulk insertion, it's different from non-bulk date type insertion flow
365+
milliseconds = _convert_date_to_epoch_milliseconds(value)
366+
# according to https://docs.snowflake.com/en/sql-reference/functions/to_date
367+
# through test, value in seconds will lead to wrong date
368+
# millisecond and nanoarrow second are good
369+
# if the milliseconds is beyond the range of 31536000000000, we switch to use nanoseconds
370+
# otherwise we will hit overflow error in snowflake
371+
if int(milliseconds) < 31536000000000:
372+
return milliseconds
373+
return _convert_date_to_epoch_nanoseconds(value)
374+
363375
_int_to_snowflake_bindings = _str_to_snowflake_bindings
364376
_long_to_snowflake_bindings = _str_to_snowflake_bindings
365377
_float_to_snowflake_bindings = _str_to_snowflake_bindings
@@ -378,15 +390,9 @@ def _nonetype_to_snowflake_bindings(self, *_) -> None:
378390
return None
379391

380392
def _date_to_snowflake_bindings(self, _, value: date) -> str:
381-
milliseconds = _convert_date_to_epoch_milliseconds(value)
382-
# according to https://docs.snowflake.com/en/sql-reference/functions/to_date
383-
# through test, value in seconds will lead to wrong date
384-
# millisecond and nanoarrow second are good
385-
# if the milliseconds is beyond the range of 31536000000000, we switch to use nanoseconds
386-
# otherwise we will hit overflow error in snowflake
387-
if int(milliseconds) < 31536000000000:
388-
return milliseconds
389-
return _convert_date_to_epoch_nanoseconds(value)
393+
# this is for date type non-bulk insertion, it's different from bulk date type insertion flow
394+
# milliseconds
395+
return _convert_date_to_epoch_milliseconds(value)
390396

391397
def _time_to_snowflake_bindings(self, _, value: dt_t) -> str:
392398
# nanoseconds
@@ -662,6 +668,11 @@ def to_csv_bindings(self, value: tuple[str, Any] | Any) -> str | None:
662668
else:
663669
if isinstance(value, (dt_t, timedelta)):
664670
val = self.to_snowflake(value)
671+
elif isinstance(value, date) and not isinstance(value, datetime):
672+
# FIX SNOW-770678 and SNOW-966444
673+
# bulk insertion congestion is different from non-bulk insertion
674+
# to_csv_bindings is only used in bulk insertion logic
675+
val = self._date_to_snowflake_bindings_in_bulk_insertion(value)
665676
else:
666677
_type = self.snowflake_type(value)
667678
val = self.to_snowflake_bindings(_type, value)

test/integ/test_bindings.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ def test_binding_bulk_insert_date(conn_cnx, db_parameters):
449449
c = cnx.cursor()
450450
cnx._session_parameters[CLIENT_STAGE_ARRAY_BINDING_THRESHOLD] = 1
451451
dates = [
452+
[date.fromisoformat("1750-05-09")],
453+
[date.fromisoformat("1969-01-01")],
452454
[date.fromisoformat("1970-01-01")],
453455
[date.fromisoformat("2023-05-12")],
454456
[date.fromisoformat("2999-12-31")],
@@ -459,27 +461,14 @@ def test_binding_bulk_insert_date(conn_cnx, db_parameters):
459461
assert c.rowcount == len(dates)
460462
ret = c.execute(f'SELECT c1 from {db_parameters["name"]}').fetchall()
461463
assert ret == [
464+
(date(1750, 5, 9),),
465+
(date(1969, 1, 1),),
462466
(date(1970, 1, 1),),
463467
(date(2023, 5, 12),),
464468
(date(2999, 12, 31),),
465469
(date(3000, 12, 31),),
466470
(date(9999, 12, 31),),
467471
]
468-
cnx.cursor().execute(f"TRUNCATE TABLE {db_parameters['name']}")
469-
# TODO: bulk insert mixing date < 1970-01-01 and >= 1970-01-01 can return wrong result
470-
# here we split the insertion to make sure the client logic is correct
471-
# this could be a bug in snowflake
472-
dates = [
473-
[date.fromisoformat("1750-05-09")],
474-
[date.fromisoformat("1969-12-31")],
475-
]
476-
c.executemany(f'INSERT INTO {db_parameters["name"]}(c1) VALUES (?)', dates)
477-
assert c.rowcount == len(dates)
478-
ret = c.execute(f'SELECT c1 from {db_parameters["name"]}').fetchall()
479-
assert ret == [
480-
(date(1750, 5, 9),),
481-
(date(1969, 12, 31),),
482-
]
483472
finally:
484473
with conn_cnx() as cnx:
485474
cnx.cursor().execute(

test/integ/test_cursor.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -816,15 +816,20 @@ def test_executemany_qmark_types(conn, db_parameters):
816816
cur.execute(f"create temp table {table_name} (birth_date date)")
817817

818818
insert_qy = f"INSERT INTO {table_name} (birth_date) values (?)"
819-
date_1, date_2 = date(1969, 2, 7), date(1969, 1, 1)
819+
date_1, date_2, date_3, date_4 = (
820+
date(1969, 2, 7),
821+
date(1969, 1, 1),
822+
date(2999, 12, 31),
823+
date(9999, 1, 1),
824+
)
820825

821826
# insert two dates, one in tuple format which specifies
822827
# the snowflake type similar to how we support it in this
823828
# example:
824829
# https://docs.snowflake.com/en/user-guide/python-connector-example.html#using-qmark-or-numeric-binding-with-datetime-objects
825830
cur.executemany(
826831
insert_qy,
827-
[[date_1], [("DATE", date_2)]],
832+
[[date_1], [("DATE", date_2)], [date_3], [date_4]],
828833
# test that kwargs get passed through executemany properly
829834
_statement_params={
830835
PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT: "json"
@@ -835,7 +840,7 @@ def test_executemany_qmark_types(conn, db_parameters):
835840
)
836841

837842
cur.execute(f"select * from {table_name}")
838-
assert {row[0] for row in cur} == {date_1, date_2}
843+
assert {row[0] for row in cur} == {date_1, date_2, date_3, date_4}
839844

840845

841846
@pytest.mark.skipolddriver

0 commit comments

Comments
 (0)