bulkcopy() silently truncates fractional seconds in string datetime values to microseconds
Les mainteneurs répondent en général sous 2 jours
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 48/100
Piste de recherche
Start by tracing how Cursor.bulkcopy() parses string values into DATETIME2 and DATETIMEOFFSET, then compare that path with execute()/executemany() and the Arrow timestamp path. Reproduce with the supplied seven-digit fractional value and trailing-Z example, checking behavior on Python 3.10 and 3.11. Done means bulkcopy preserves all seven digits like executemany and accepts server-valid input, or rejects precision loss explicitly.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Summary
When a string is bound to a DATETIME2 column through Cursor.bulkcopy(), the Rust core parses it with Python's datetime.fromisoformat before sending it. datetime only holds microseconds, so a seven-digit fraction is truncated to six with no warning. The same string through execute()/executemany() is sent to the server as text and SQL Server stores all seven digits.
On Python 3.10 the same parser also rejects a trailing Z, which the server accepts. From 3.11 that case works. This is probably fine since python 3.10 is EOL in a few days, but I wanted to raise since your docs explicitly say you need >=3.10 for bulkcopy.
To reproduce
import uuid
import mssql_python
conn = mssql_python.connect(CONN_STR, autocommit=True)
cur = conn.cursor()
table = f"dbo.Repro_{uuid.uuid4().hex[:8]}"
cur.execute(f"CREATE TABLE {table} (lbl VARCHAR(20), dt DATETIME2)")
value = "2024-07-01 12:00:00.1234567"
cur.executemany(f"INSERT INTO {table} VALUES (?, ?)", [("executemany", value)])
cur.bulkcopy(table, [("bulkcopy", value)])
cur.execute(f"SELECT lbl, CONVERT(varchar(27), dt, 121) FROM {table}")
print(cur.fetchall())
cur.execute(f"DROP TABLE {table}")
Observed:
Python 3.11:
[('executemany', '2024-07-01 12:00:00.1234567'),
('bulkcopy', '2024-07-01 12:00:00.1234560')]
Python 3.10, where fromisoformat also rejects Z:
cur.bulkcopy(table, [("bulkcopy", "2024-07-01T12:00:00Z")])
ValueError: Invalid datetime format '2024-07-01T12:00:00Z'. Expected ISO format like
'2024-01-15 09:30:00' or '2024-01-15T09:30:00': ValueError: Invalid isoformat string: ...
Note:
bulkcopy_arrow() with a timestamp[ns] column stores all seven digits, into both DATETIME2 and DATETIMEOFFSET:
import pyarrow as pa
ns = pa.array([1719835200_123456700], pa.timestamp("ns")) # 2024-07-01 12:00:00.1234567
cur.bulkcopy_arrow(table, pa.table({"lbl": ["arrow"], "dt": ns}))
# -> '2024-07-01 12:00:00.1234567'
Expected behavior
bulkcopy() stores the same value as executemany() for the same input string. In particular, a DATETIME2(7) column receives all seven digits, and inputs that SQL Server accepts are not rejected on the client.
Suggested fixes, in order of preference
- Parse ISO 8601 strings in Rust directly into the TDS DATETIME2/DATETIMEOFFSET representation, which has 100 ns resolution, the same way the Arrow path already consumes nanosecond buffers. This also removes the dependence on the running interpreter's fromisoformat behaviour.
- Alternatively, send string values for datetime columns to the server as text and let it convert, matching the execute path.
- At minimum, raise when a fraction has more digits than can be represented instead of truncating. Silent data loss is the worst of the three outcomes.
- Langage dominant
- Python
- Étoiles
- 481
- Forks
- 62
- Merge moyen
- 2 j 11 h
- PR mergées (30 j)
- 12
Préparer son environnement
Lance le conteneur de développement du projet dans votre navigateur, avec votre propre compte GitHub.
- Aucun Dockerfile ni fichier Docker Compose
- Propose un modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de microsoft/mssql-python
-
Make azure-identity an optional dependencyPeut-être pris @jahnvi480 l’a pris il y a 3 jours. Ouvertearea: connectivity-auth dependencies enhancement triage needed
Difficulté 4/5 3-5 jours Accessibilité débutants 48/100
microsoft/mssql-python#862 · 3 commentaires · 1 personne assignée ·
Les mainteneurs répondent en général sous 2 jours
-
triage needed
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 38/100
microsoft/mssql-python#850 · 2 commentaires · 1 réaction ·
Les mainteneurs répondent en général sous 2 jours
-
bulkcopy() & bulkcopy_arrow() fail with Schannel AcquireCredentialsHandleW error 0x8009030D (SEC_E_UNKNOWN_CREDENTIALS)Peut-être pris @jahnvi480 l’a pris il y a 11 jours. Ouvertearea: bulk-copy bug inADO triage done
Difficulté 4/5 3-5 jours Accessibilité débutants 20/100
microsoft/mssql-python#848 · 5 commentaires · 1 personne assignée ·
Les mainteneurs répondent en général sous 2 jours
-
macOS: encrypted connections fail after Homebrew moved the `openssl` alias to `openssl@4`Peut-être à nouveau libre @bewithgaurav l’a pris il y a 31 jours, et aucune pull request n’est ouverte. Ouvertearea: packaging-platform bug triage needed
Difficulté 4/5 3-5 jours Accessibilité débutants 55/100
microsoft/mssql-python#826 · 2 commentaires · 4 réactions · 1 personne assignée ·
Les mainteneurs répondent en général sous 2 jours
-
Announcement: mssql-python is coming to Django through mssql-django 2.0Peut-être à nouveau libre @bewithgaurav l’a pris il y a 57 jours, et aucune pull request n’est ouverte. Ouverteannouncement
microsoft/mssql-python#748 · 1 personne assignée ·
Les mainteneurs répondent en général sous 2 jours
Toutes les issues de microsoft/mssql-python
Issues similaires
-
bug
Difficulté 2/5 1-3 heures Accessibilité débutants 86/100
NousResearch/hermes-agent#147104 ·
Les mainteneurs répondent en général sous 1 jour
-
premises-missing
Difficulté 1/5 Moins d'une heure Accessibilité débutants 88/100
WingedGuardian/GENesis-AGI#3801 ·
Les mainteneurs répondent en général sous 1 jour
-
[oblt-aw][autodoc] Document new required `shared-org-key` input for `obs-aw-dependency-review`Peut-être pris @github-actions l’a pris aujourd’hui. Ouverte
Difficulté 1/5 Moins d'une heure Accessibilité débutants 90/100
Les mainteneurs répondent en général sous 1 jour
-
bot-triaged module: inductor oncall: pt2 topic: fuzzer triaged
Difficulté 2/5 1-3 heures Accessibilité débutants 82/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
Les mainteneurs répondent en général sous 4 jours