Skip to content

fix: report temporal fractional precision - #23

Merged
iamlinjunhong merged 1 commit into
mainfrom
codex/pyodbc-temporal-typeinfo-20260820
Aug 20, 2026
Merged

fix: report temporal fractional precision#23
iamlinjunhong merged 1 commit into
mainfrom
codex/pyodbc-temporal-typeinfo-20260820

Conversation

@iamlinjunhong

Copy link
Copy Markdown
Collaborator

Problem

Python datetime.datetime(2024, 2, 29, 23, 59, 59, 123456) bound by pyodbc 5.3.0 to a MatrixOne DATETIME(6) or TIMESTAMP(6) column is stored as .100000, losing five fractional-second digits.

Direct SQL literals preserve .123456, and a direct ODBC bind with SQL_TIMESTAMP_STRUCT.fraction=123456000, ColumnSize=26, and DecimalDigits=6 also preserves .123456.

Root cause

pyodbc calls SQLGetTypeInfo(SQL_TYPE_TIMESTAMP) once per connection and computes its datetime bind precision as COLUMN_SIZE - 20. The driver advertised:

  • datetime: COLUMN_SIZE=21, MINIMUM_SCALE=0, MAXIMUM_SCALE=0
  • timestamp: COLUMN_SIZE=14, MINIMUM_SCALE=0, MAXIMUM_SCALE=0

pyodbc therefore truncated the Python fraction to one digit before calling SQLBindParameter. MatrixOne supports DATETIME(6) and TIMESTAMP(6), whose full display width is 26.

Fix

Advertise COLUMN_SIZE=26, MINIMUM_SCALE=0, and MAXIMUM_SCALE=6 for both datetime and timestamp.

Add an end-to-end mo_odbc_deep regression that calls SQLGetTypeInfo(SQL_TYPE_TIMESTAMP), finds both rows, and asserts all three fields.

Reproduction

import datetime
import pyodbc

cn = pyodbc.connect(CONNECTION_STRING, autocommit=True)
cur = cn.cursor()
cur.execute("create table t(id int primary key, dt datetime(6), ts timestamp(6))")
value = datetime.datetime(2024, 2, 29, 23, 59, 59, 123456)
cur.execute("insert into t values (?,?,?)", 1, value, value)
print(cur.execute("select cast(dt as varchar), cast(ts as varchar) from t").fetchone())

Before / after

  • Before (8f1ca365): timestamp_type_info=FAIL; pyodbc stored .100000.
  • After (c0ea21ea): timestamp_type_info=PASS; raw timestamp fraction bind, SQLForeignKeys, timeout, and cancel remain PASS.

The direct verifier loads the built DLL itself and does not depend on replacing the installed driver.

Test scope

  • Windows x64 CMake build: Unicode and ANSI drivers + mo_odbc_deep: PASS
  • git diff --check: PASS
  • Direct DLL before/after SQLGetTypeInfo comparison: expected FAIL / PASS
  • Direct DLL SQL_TIMESTAMP_STRUCT fraction round trip: PASS
  • Direct DLL SQLForeignKeysW: PASS
  • Direct DLL query timeout (HYT00): PASS
  • Direct DLL cancel (HY008): PASS

Test server: MatrixOne 592ad2190a90e063e3877059f4018427a10a4998.

Note: Python datetime.time microseconds are a separate portability limitation because standard SQL_TIME_STRUCT has no fraction field. Binding the TIME value as an ISO string preserves microseconds and is not changed by this PR.

@iamlinjunhong
iamlinjunhong merged commit 43512a1 into main Aug 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant