Skip to content

fix: use TIMESTAMP WITH TIME ZONE for PostgreSQL in PreciseTimestamp#4388

Open
vietnamesekid wants to merge 4 commits intogoogle:mainfrom
vietnamesekid:fix/precise-timestamp-timezone-postgresql
Open

fix: use TIMESTAMP WITH TIME ZONE for PostgreSQL in PreciseTimestamp#4388
vietnamesekid wants to merge 4 commits intogoogle:mainfrom
vietnamesekid:fix/precise-timestamp-timezone-postgresql

Conversation

@vietnamesekid
Copy link

@vietnamesekid vietnamesekid commented Feb 6, 2026

Summary

The PreciseTimestamp TypeDecorator uses DateTime (without timezone) as its default implementation. When used with PostgreSQL + asyncpg, this causes the SQL query to cast parameters as TIMESTAMP WITHOUT TIME ZONE, which conflicts with timezone-aware datetime objects (those with tzinfo=UTC) that ADK now creates internally (since v1.24.0).

This results in the asyncpg error:

asyncpg.exceptions.DataError: invalid input for query argument $5: datetime.datetime(2026, 2, 6, 4, 16, 14,... 
(can't subtract offset-naive and offset-aware datetimes)

[SQL: INSERT INTO sessions (app_name, user_id, id, state, create_time, update_time) 
VALUES ($1::VARCHAR, $2::VARCHAR, $3::VARCHAR, $4::JSONB, $5::TIMESTAMP WITHOUT TIME ZONE, $6::TIMESTAMP WITHOUT TIME ZONE)]

Associated Issue

Related to #1848

Root Cause

  • PreciseTimestamp.load_dialect_impl() only has special handling for MySQL (mysql.DATETIME(fsp=6)), falling back to plain DateTime for all other dialects including PostgreSQL.
  • Plain DateTime maps to TIMESTAMP WITHOUT TIME ZONE in PostgreSQL.
  • Since ADK v1.24.0, DatabaseSessionService creates datetime objects with tzinfo=datetime.timezone.utc (offset-aware).
  • asyncpg strictly validates parameter types and rejects offset-aware datetimes for TIMESTAMP WITHOUT TIME ZONE columns.

Fix

Add PostgreSQL-specific dialect implementation using postgresql.TIMESTAMP(timezone=True), consistent with how MySQL already has its own dialect-specific handling.

Testing Plan

  • Ran the full session test suite (tests/unittests/sessions/) — 109 tests passed, 0 failed
  • SQLite tests remain unaffected since PreciseTimestamp only returns TIMESTAMP(timezone=True) when dialect is postgresql, SQLite falls back to default DateTime
  • Manually tested against a live PostgreSQL database (DigitalOcean managed DB) with postgresql+asyncpg:// connection string — session creation, event appending, and session retrieval all work correctly after the fix

Verification

Before fix — session creation crashes:

sqlalchemy.exc.DBAPIError: (sqlalchemy.dialects.postgresql.asyncpg.Error)
<class 'asyncpg.exceptions.DataError'>: invalid input for query argument $5:
datetime.datetime(2026, 2, 6, 4, 16, 14,...
(can't subtract offset-naive and offset-aware datetimes)
[SQL: INSERT INTO sessions (app_name, user_id, id, state, create_time, update_time)
VALUES ($1::VARCHAR, $2::VARCHAR, $3::VARCHAR, $4::JSONB,
$5::TIMESTAMP WITHOUT TIME ZONE, $6::TIMESTAMP WITHOUT TIME ZONE)]

After fix — SQL now correctly uses TIMESTAMP WITH TIME ZONE:

[SQL: INSERT INTO sessions (app_name, user_id, id, state, create_time, update_time)
VALUES ($1::VARCHAR, $2::VARCHAR, $3::VARCHAR, $4::JSONB,
$5::TIMESTAMP WITH TIME ZONE, $6::TIMESTAMP WITH TIME ZONE)]

Unit tests:

$ python3 -m pytest tests/unittests/sessions/ -v
======================== 109 passed, 1 warning in 2.67s ========================

Database Migration Required

Users with existing PostgreSQL databases need to run:

ALTER TABLE sessions ALTER COLUMN create_time TYPE TIMESTAMP WITH TIME ZONE;
ALTER TABLE sessions ALTER COLUMN update_time TYPE TIMESTAMP WITH TIME ZONE;
ALTER TABLE events ALTER COLUMN timestamp TYPE TIMESTAMP WITH TIME ZONE;
ALTER TABLE app_states ALTER COLUMN update_time TYPE TIMESTAMP WITH TIME ZONE;
ALTER TABLE user_states ALTER COLUMN update_time TYPE TIMESTAMP WITH TIME ZONE;

A Python migration script is also included: src/google/adk/sessions/migration/migrate_postgresql_timestamptz.py

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @vietnamesekid, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical DataError encountered in PostgreSQL environments when the PreciseTimestamp TypeDecorator interacts with asyncpg and timezone-aware datetime objects. Previously, PreciseTimestamp defaulted to TIMESTAMP WITHOUT TIME ZONE for PostgreSQL, leading to conflicts with the offset-aware datetimes generated by ADK since version 1.24.0. The change introduces a specific dialect implementation for PostgreSQL to correctly map PreciseTimestamp to TIMESTAMP WITH TIME ZONE, ensuring seamless operation and preventing data integrity issues.

Highlights

  • PostgreSQL Timestamp Handling: Updated the PreciseTimestamp TypeDecorator to explicitly use TIMESTAMP WITH TIME ZONE for PostgreSQL dialects, resolving DataError when handling timezone-aware datetimes with asyncpg.
  • Dialect-Specific Implementation: Introduced a PostgreSQL-specific load_dialect_impl for PreciseTimestamp, aligning with existing MySQL-specific handling to ensure correct type mapping.
  • Database Migration: Noted that existing PostgreSQL databases require a migration to alter relevant columns to TIMESTAMP WITH TIME ZONE to ensure compatibility with the new type handling.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/google/adk/sessions/schemas/shared.py
    • Added a load_dialect_impl for PostgreSQL to ensure PreciseTimestamp uses postgresql.TIMESTAMP(timezone=True).
Activity
  • No specific activity (comments, reviews, etc.) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@google-cla
Copy link

google-cla bot commented Feb 6, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly fixes a crash that occurs when using timezone-aware datetime objects with PostgreSQL. The fix involves specifying TIMESTAMP WITH TIME ZONE for the PreciseTimestamp type decorator when the dialect is PostgreSQL. The change is logical and well-targeted. I have one minor suggestion to improve the code structure for better maintainability. Additionally, it would be beneficial to add integration tests with a PostgreSQL backend to verify this fix and prevent future regressions.

@adk-bot adk-bot added the services [Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc label Feb 6, 2026
@adk-bot
Copy link
Collaborator

adk-bot commented Feb 6, 2026

Response from ADK Triaging Agent

Hello @vietnamesekid, thank you for creating this PR!

To help reviewers evaluate your contribution, could you please provide the following information as outlined in our contribution guidelines:

  • Associated Issue: Please link the relevant GitHub issue in the PR description. If one doesn't exist, you can create one or describe the bug in the PR description following the issue template.
  • Testing Plan: Please add a "Testing Plan" section to your PR description explaining how you've tested these changes.
  • Verification: For bug fixes, please provide logs or screenshots demonstrating that the fix works as expected.

This information will help us review your PR more efficiently. Thanks!

@vietnamesekid vietnamesekid force-pushed the fix/precise-timestamp-timezone-postgresql branch from df0914c to 0c74725 Compare February 6, 2026 04:45
The `PreciseTimestamp` TypeDecorator uses `DateTime` (without timezone)
as its default implementation. When used with PostgreSQL + asyncpg,
this causes the SQL query to cast parameters as
`TIMESTAMP WITHOUT TIME ZONE`, which conflicts with timezone-aware
datetime objects (those with `tzinfo=UTC`) that ADK now creates
internally (since v1.24.0).

This results in the asyncpg error:
"can't subtract offset-naive and offset-aware datetimes"

The fix adds a PostgreSQL-specific dialect implementation that uses
`postgresql.TIMESTAMP(timezone=True)`, consistent with how MySQL
already has its own dialect-specific handling (`mysql.DATETIME(fsp=6)`).

Fixes google#1848
Adds a migration script that converts TIMESTAMP WITHOUT TIME ZONE
columns to TIMESTAMP WITH TIME ZONE for existing PostgreSQL databases.

Usage:
  python -m google.adk.sessions.migration.migrate_postgresql_timestamptz \
    --db_url postgresql+asyncpg://user:pass@host:port/dbname

The script checks each ADK table column, skips non-PostgreSQL databases
and columns already using TIMESTAMPTZ, and migrates only what's needed.
@vietnamesekid vietnamesekid force-pushed the fix/precise-timestamp-timezone-postgresql branch from 83b973c to ba39e55 Compare February 6, 2026 06:44
…ution

Verifies that PreciseTimestamp returns the correct SQLAlchemy type
for each database dialect:
- PostgreSQL: TIMESTAMP(timezone=True) to prevent asyncpg offset-naive
  vs offset-aware datetime errors
- MySQL: DATETIME(fsp=6) for microsecond precision
- SQLite: default DateTime fallback

Includes regression test proving the fix catches the bug:
without the PostgreSQL branch, load_dialect_impl returns plain DateTime
(timezone=False), which causes asyncpg DataError.
…on-SQLite dialects

Fix timezone-naive datetime creation in database_session_service.py
that causes incorrect timestamps on servers not running in UTC.

Changes:
- append_event: use datetime.fromtimestamp(ts, timezone.utc) for
  non-SQLite dialects when setting session update_time
- get_session: use datetime.fromtimestamp(ts, timezone.utc) for
  non-SQLite dialects when filtering events by after_timestamp

Why only database_session_service.py and not schemas/v0.py or v1.py:
The schema-level from_event() methods (StorageEvent.from_event) are
dialect-agnostic — they have no access to the database engine to know
which dialect is in use. Changing them to produce UTC-aware datetimes
breaks SQLite roundtrip: SQLite stores datetimes as naive strings, so
an aware datetime gets its tzinfo stripped on write, but .timestamp()
on the naive readback interprets it as local time, producing wrong
values on non-UTC servers (e.g. UTC+7: epoch 3 → stored as
1970-01-01 00:00:03 → read back as naive → .timestamp() = -28797).

The service layer (database_session_service.py) is the correct place
for this fix because it has access to self.db_engine.dialect.name and
can branch on SQLite vs PostgreSQL/MySQL accordingly.
@mynewestgitaccount
Copy link

mynewestgitaccount commented Feb 6, 2026

I hope #4365 will be merged rather than this. Solutions like this create major issues for migrating production workloads, and they they will continue to do so until #3343 is resolved.

@vietnamesekid
Copy link
Author

I hope #4365 will be merged rather than this. Solutions like this create major issues for migrating production workloads, and they they will continue to do so until #3343 is resolved.

Thanks for sharing your perspective. I agree that without better migration support (#3343), schema related changes are tough to roll out in production. What i was trying to address here is a correctness issue where timestamps depend on the server’s local timezone and can silently drift on non UTC setups. #4365 avoids the immediate error, but this change is mainly about making that behavior more explicit and predictable, scoped to the service layer where the dialect context is available. Totally open to feedback on whether this is the right trade-off at this stage

@vietnamesekid
Copy link
Author

I hope #4365 will be merged rather than this. Solutions like this create major issues for migrating production workloads, and they they will continue to do so until #3343 is resolved.

Thanks for sharing your perspective. I agree that without better migration support (#3343), schema related changes are tough to roll out in production. What i was trying to address here is a correctness issue where timestamps depend on the server’s local timezone and can silently drift on non UTC setups. #4365 avoids the immediate error, but this change is mainly about making that behavior more explicit and predictable, scoped to the service layer where the dialect context is available. Totally open to feedback on whether this is the right trade-off at this stage

am also running into this issue in production, which is why i put these PRs together. I upgraded to 1.24.0 earlier today, but it didn’t resolve the problem on our end 😂

@SemihAkar
Copy link

+1 here for 1.24 version
I need urgent fix for the production environment. please hurry up :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

services [Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants