diff --git a/src/google/adk/sessions/database_session_service.py b/src/google/adk/sessions/database_session_service.py index a8716d0c9b6..7b1ef27f4ed 100644 --- a/src/google/adk/sessions/database_session_service.py +++ b/src/google/adk/sessions/database_session_service.py @@ -83,6 +83,7 @@ _MARIADB_DIALECT = "mariadb" _MYSQL_DIALECT = "mysql" _POSTGRESQL_DIALECT = "postgresql" +_MSSQL_DIALECT = "mssql" # Dialects whose DATETIME/TIMESTAMP columns do not retain timezone info, so # timezone-aware datetimes must have their tzinfo stripped before storage. This # keeps the value written by create_session consistent with the value read back @@ -94,6 +95,7 @@ _POSTGRESQL_DIALECT, _MYSQL_DIALECT, _MARIADB_DIALECT, + _MSSQL_DIALECT, ) # Tuple key order for in-process per-session lock maps: # (app_name, user_id, session_id). diff --git a/src/google/adk/sessions/schemas/shared.py b/src/google/adk/sessions/schemas/shared.py index acebb369303..6c829aaf478 100644 --- a/src/google/adk/sessions/schemas/shared.py +++ b/src/google/adk/sessions/schemas/shared.py @@ -21,6 +21,7 @@ from sqlalchemy import Dialect from sqlalchemy import Text +from sqlalchemy.dialects import mssql from sqlalchemy.dialects import mysql from sqlalchemy.dialects import postgresql from sqlalchemy.types import DateTime @@ -94,6 +95,12 @@ class PreciseTimestamp(TypeDecorator[datetime.datetime]): # type: ignore[misc] def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]: if dialect.name == "mysql": return dialect.type_descriptor(mysql.DATETIME(fsp=6)) + if dialect.name == "mssql": + # SQL Server's legacy DATETIME has ~3.33ms precision, which destroys + # the microsecond update marker used by the optimistic-concurrency + # check (a session's second append is falsely rejected as stale). + # DATETIME2(6) retains microseconds. + return dialect.type_descriptor(mssql.DATETIME2(precision=6)) return self.impl_instance def result_processor( diff --git a/tests/unittests/sessions/test_schemas_shared.py b/tests/unittests/sessions/test_schemas_shared.py index 16b1d0111b5..c917e15613a 100644 --- a/tests/unittests/sessions/test_schemas_shared.py +++ b/tests/unittests/sessions/test_schemas_shared.py @@ -161,3 +161,14 @@ def test_precise_timestamp_result_processor_delegates_non_numeric_values( process = precise_timestamp.result_processor(_dialect("mysql"), None) assert process("2026-01-02 03:04:05.123456") == expected + + +def test_precise_timestamp_uses_datetime2_on_mssql(): + """SQL Server's legacy DATETIME rounds to ~3.33ms, which destroys the + microsecond update marker used by the optimistic-concurrency check.""" + from sqlalchemy.dialects import mssql as mssql_dialect + + ts = PreciseTimestamp() + impl = ts.load_dialect_impl(mssql_dialect.dialect()) + assert isinstance(impl, mssql_dialect.DATETIME2) + assert impl.precision == 6