Skip to content
Closed
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
10 changes: 8 additions & 2 deletions src/sqlalchemy_cratedb/compat/core14.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,15 @@ def _get_crud_params(compiler, stmt, compile_state, **kw):
if compile_state._has_multi_parameters:
spd = compile_state._multi_parameters[0]
stmt_parameter_tuples = list(spd.items())
elif compile_state._ordered_values:
elif (hasattr(compile_state, "_ordered_values") and
getattr(compile_state, "_ordered_values", None) is not None):
spd = compile_state._dict_parameters
stmt_parameter_tuples = compile_state._ordered_values
try:
stmt_parameter_tuples = compile_state._ordered_values
except AttributeError:
# Fallback for newer SQLAlchemy versions where _ordered_values might not be accessible
spd = compile_state._dict_parameters
stmt_parameter_tuples = list(spd.items()) if spd else None
elif compile_state._dict_parameters:
spd = compile_state._dict_parameters
stmt_parameter_tuples = list(spd.items())
Expand Down
10 changes: 8 additions & 2 deletions src/sqlalchemy_cratedb/compat/core20.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,15 @@ def _get_crud_params(
assert mp is not None
spd = mp[0]
stmt_parameter_tuples = list(spd.items())
elif compile_state._ordered_values:
elif (hasattr(compile_state, "_ordered_values") and
getattr(compile_state, "_ordered_values", None) is not None):
spd = compile_state._dict_parameters
stmt_parameter_tuples = compile_state._ordered_values
try:
stmt_parameter_tuples = compile_state._ordered_values
except AttributeError:
# Fallback for newer SQLAlchemy versions where _ordered_values might not be accessible
spd = compile_state._dict_parameters
stmt_parameter_tuples = list(spd.items()) if spd else None
elif compile_state._dict_parameters:
spd = compile_state._dict_parameters
stmt_parameter_tuples = list(spd.items())
Expand Down
12 changes: 9 additions & 3 deletions src/sqlalchemy_cratedb/dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# software solely pursuant to the terms of the relevant commercial agreement.

import logging
from datetime import date, datetime
from datetime import date, datetime, timezone

from sqlalchemy import types as sqltypes
from sqlalchemy.engine import default, reflection
Expand Down Expand Up @@ -96,7 +96,8 @@ def process(value):
if not value:
return None
try:
return datetime.utcfromtimestamp(value / 1e3).date()
# Always return timezone-naive dates for backward compatibility
return datetime.fromtimestamp(value / 1e3, timezone.utc).replace(tzinfo=None).date()
except TypeError:
pass

Expand Down Expand Up @@ -132,7 +133,12 @@ def process(value):
if not value:
return None
try:
return datetime.utcfromtimestamp(value / 1e3)
# Check if timezone information is requested
if getattr(coltype, 'timezone', False):
return datetime.fromtimestamp(value / 1e3, timezone.utc)
else:
# For timezone-naive columns, remove timezone info for backward compatibility
return datetime.fromtimestamp(value / 1e3, timezone.utc).replace(tzinfo=None)
except TypeError:
pass

Expand Down
1 change: 1 addition & 0 deletions src/sqlalchemy_cratedb/sa_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@

SA_1_4 = Version("1.4.0b1")
SA_2_0 = Version("2.0.0")
SA_2_1 = Version("2.1.0")
3 changes: 1 addition & 2 deletions src/sqlalchemy_cratedb/support/polyfill.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,10 @@ def check_uniqueness(mapper, connection, target):
stmt = stmt.filter(
getattr(sa_entity, attribute_name) == getattr(target, attribute_name)
)
stmt = stmt.compile(bind=connection.engine)
results = connection.execute(stmt)
if results.rowcount > 0:
raise IntegrityError(
statement=stmt,
statement=str(stmt),
params=[],
orig=Exception(
f"DuplicateKeyException in table '{target.__tablename__}' "
Expand Down
4 changes: 2 additions & 2 deletions tests/dialect_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# with Crate these terms will supersede the license and you may use the
# software solely pursuant to the terms of the relevant commercial agreement.

from datetime import datetime
from datetime import datetime, timezone
from unittest import TestCase, skipIf
from unittest.mock import MagicMock, patch

Expand Down Expand Up @@ -66,7 +66,7 @@ class Character(self.base):
name = sa.Column(sa.String, primary_key=True)
age = sa.Column(sa.Integer, primary_key=True)
obj = sa.Column(ObjectType)
ts = sa.Column(sa.DateTime, onupdate=datetime.utcnow)
ts = sa.Column(sa.DateTime, onupdate=lambda: datetime.now(timezone.utc))

self.session = Session(bind=self.engine)

Expand Down
6 changes: 3 additions & 3 deletions tests/insert_from_select_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# However, if you have executed another commercial license agreement
# with Crate these terms will supersede the license and you may use the
# software solely pursuant to the terms of the relevant commercial agreement.
from datetime import datetime
from datetime import datetime, timezone
from unittest import TestCase, skipIf
from unittest.mock import MagicMock, patch

Expand Down Expand Up @@ -54,15 +54,15 @@ class Character(Base):

name = sa.Column(sa.String, primary_key=True)
age = sa.Column(sa.Integer)
ts = sa.Column(sa.DateTime, onupdate=datetime.utcnow)
ts = sa.Column(sa.DateTime, onupdate=lambda: datetime.now(timezone.utc))
status = sa.Column(sa.String)

class CharacterArchive(Base):
__tablename__ = "characters_archive"

name = sa.Column(sa.String, primary_key=True)
age = sa.Column(sa.Integer)
ts = sa.Column(sa.DateTime, onupdate=datetime.utcnow)
ts = sa.Column(sa.DateTime, onupdate=lambda: datetime.now(timezone.utc))
status = sa.Column(sa.String)

self.character = Character
Expand Down
4 changes: 2 additions & 2 deletions tests/test_error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test_statement_with_error_trace(cratedb_service):
connection.execute(sa.text("CREATE TABLE foo AS SELECT 1 AS _id"))

# Make sure both variants match, to validate it's actually an error trace.
assert ex.match(re.escape('InvalidColumnNameException["_id" conflicts with system column]'))
assert ex.match(re.escape('InvalidColumnNameException["_id" conflicts with system column pattern]'))
assert ex.match(
'io.crate.exceptions.InvalidColumnNameException: "_id" conflicts with system column'
'io.crate.exceptions.InvalidColumnNameException: "_id" conflicts with system column pattern'
)
3 changes: 2 additions & 1 deletion tests/test_support_polyfill.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime as dt
from datetime import timezone

import pytest
import sqlalchemy as sa
Expand Down Expand Up @@ -58,7 +59,7 @@ class FooBar(Base):
)

# Compare outcome.
assert result["date"].year == dt.datetime.now().year
assert result["date"].year == dt.datetime.now(timezone.utc).year
assert result["number"] >= 1718846016235
assert result["string"] >= "1718846016235"

Expand Down
22 changes: 14 additions & 8 deletions tests/update_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# However, if you have executed another commercial license agreement
# with Crate these terms will supersede the license and you may use the
# software solely pursuant to the terms of the relevant commercial agreement.
from datetime import datetime
from datetime import datetime, timezone
from unittest import TestCase, skipIf
from unittest.mock import MagicMock, patch

Expand Down Expand Up @@ -53,7 +53,7 @@ class Character(self.base):
name = sa.Column(sa.String, primary_key=True)
age = sa.Column(sa.Integer)
obj = sa.Column(ObjectType)
ts = sa.Column(sa.DateTime, onupdate=datetime.utcnow)
ts = sa.Column(sa.DateTime, onupdate=lambda: datetime.now(timezone.utc))

self.character = Character
self.session = Session(bind=self.engine)
Expand All @@ -63,7 +63,7 @@ def test_onupdate_is_triggered(self):
char = self.character(name="Arthur")
self.session.add(char)
self.session.commit()
now = datetime.utcnow()
now = datetime.now(timezone.utc)

fake_cursor.fetchall.return_value = [("Arthur", None)]
fake_cursor.description = (
Expand All @@ -80,9 +80,12 @@ def test_onupdate_is_triggered(self):
args = args[1]
self.assertEqual(expected_stmt, stmt)
self.assertEqual(40, args[0])
dt = datetime.strptime(args[1], "%Y-%m-%dT%H:%M:%S.%f")
dt = datetime.fromisoformat(args[1].replace('+0000', '+00:00'))
self.assertIsInstance(dt, datetime)
self.assertGreater(dt, now)
# Make now timezone-naive for comparison since dt is timezone-aware
now_naive = now.replace(tzinfo=None)
dt_naive = dt.replace(tzinfo=None)
self.assertGreater(dt_naive, now_naive)
self.assertEqual("Arthur", args[2])

@patch("crate.client.connection.Cursor", FakeCursor)
Expand All @@ -91,7 +94,7 @@ def test_bulk_update(self):
Checks whether bulk updates work correctly
on native types and Crate types.
"""
before_update_time = datetime.utcnow()
before_update_time = datetime.now(timezone.utc)

self.session.query(self.character).update(
{
Expand All @@ -110,6 +113,9 @@ def test_bulk_update(self):
self.assertEqual(expected_stmt, stmt)
self.assertEqual("Julia", args[0])
self.assertEqual({"favorite_book": "Romeo & Juliet"}, args[1])
dt = datetime.strptime(args[2], "%Y-%m-%dT%H:%M:%S.%f")
dt = datetime.fromisoformat(args[2].replace('+0000', '+00:00'))
self.assertIsInstance(dt, datetime)
self.assertGreater(dt, before_update_time)
# Make before_update_time timezone-naive for comparison since dt is timezone-aware
before_update_time_naive = before_update_time.replace(tzinfo=None)
dt_naive = dt.replace(tzinfo=None)
self.assertGreater(dt_naive, before_update_time_naive)
Loading