Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
does not change the default provider or ship any Rust driver binaries.

### Changed
- DATE, TIME, and TIMESTAMP fetch conversion uses checked CPython constructors
for the standard datetime types, while preserving cached substitute constructors,
their positional arguments and exceptions, and fractional-second truncation.
DATETIMEOFFSET, UUID, and Decimal conversion are unchanged.
- `mssql-python` now depends on `mssql-python-rs==0.1.0` for `mssql_py_core`
instead of embedding files owned by that separately published distribution.
- **GH-769 deprecation policy:** The misplaced `GetInfoConstants` members
Expand Down
38 changes: 15 additions & 23 deletions mssql_python/pybind/ddbc_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "param_detect.hpp"
#include "py_ref.hpp"
#include "py_type_cache.hpp"
#include "fetch_temporal.hpp"
#include "utf_utils.h"
#include "fetch_text.hpp"

Expand Down Expand Up @@ -3761,8 +3762,8 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
ret =
SQLGetData_ptr(hStmt, i, SQL_C_TYPE_DATE, &dateValue, sizeof(dateValue), NULL);
if (SQL_SUCCEEDED(ret)) {
row.append(PyTypeCache::get_date_class_obj()(dateValue.year, dateValue.month,
dateValue.day));
row.append(
FetchTemporal::date(dateValue.year, dateValue.month, dateValue.day));
} else {
row.append(py::none());
}
Expand All @@ -3774,7 +3775,7 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
SQLLEN indicator = 0;
ret = SQLGetData_ptr(hStmt, i, SQL_C_SS_TIME2, &t2, sizeof(t2), &indicator);
if (SQL_SUCCEEDED(ret) && indicator != SQL_NULL_DATA) {
row.append(PyTypeCache::get_time_class_obj()(
row.append(FetchTemporal::time(
t2.hour, t2.minute, t2.second, t2.fraction / 1000)); // ns to µs
} else {
if (!SQL_SUCCEEDED(ret)) {
Expand All @@ -3793,7 +3794,7 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
ret = SQLGetData_ptr(hStmt, i, SQL_C_TYPE_TIMESTAMP, &timestampValue,
sizeof(timestampValue), NULL);
if (SQL_SUCCEEDED(ret)) {
row.append(PyTypeCache::get_datetime_class_obj()(
row.append(FetchTemporal::datetime(
timestampValue.year, timestampValue.month, timestampValue.day,
timestampValue.hour, timestampValue.minute, timestampValue.second,
timestampValue.fraction / 1000 // Convert back ns to µs
Expand Down Expand Up @@ -4431,32 +4432,23 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
case SQL_TYPE_TIMESTAMP:
case SQL_DATETIME: {
const SQL_TIMESTAMP_STRUCT& ts = buffers.timestampBuffers[col - 1][i];
PyObject* datetimeObj = PyTypeCache::get_datetime_class_obj()(
ts.year, ts.month, ts.day, ts.hour, ts.minute,
ts.second, ts.fraction / 1000)
.release()
.ptr();
PyList_SET_ITEM(row, col - 1, datetimeObj);
py::object datetimeObj = FetchTemporal::datetime(
ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second,
ts.fraction / 1000);
PyList_SET_ITEM(row, col - 1, datetimeObj.release().ptr());
break;
}
case SQL_TYPE_DATE: {
PyObject* dateObj =
PyTypeCache::get_date_class_obj()(buffers.dateBuffers[col - 1][i].year,
buffers.dateBuffers[col - 1][i].month,
buffers.dateBuffers[col - 1][i].day)
.release()
.ptr();
PyList_SET_ITEM(row, col - 1, dateObj);
const SQL_DATE_STRUCT& value = buffers.dateBuffers[col - 1][i];
py::object dateObj = FetchTemporal::date(value.year, value.month, value.day);
PyList_SET_ITEM(row, col - 1, dateObj.release().ptr());
break;
}
case SQL_SS_TIME2: {
const SQL_SS_TIME2_STRUCT& t2 = buffers.timeBuffers[col - 1][i];
PyObject* timeObj =
PyTypeCache::get_time_class_obj()(t2.hour, t2.minute, t2.second,
t2.fraction / 1000) // ns to µs
.release()
.ptr();
PyList_SET_ITEM(row, col - 1, timeObj);
py::object timeObj =
FetchTemporal::time(t2.hour, t2.minute, t2.second, t2.fraction / 1000);
PyList_SET_ITEM(row, col - 1, timeObj.release().ptr());
break;
}
case SQL_SS_TIMESTAMPOFFSET: {
Expand Down
59 changes: 59 additions & 0 deletions mssql_python/pybind/fetch_temporal.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#pragma once

#include <Python.h>
#include <datetime.h>

#include "py_ref.hpp"
#include "py_type_cache.hpp"

namespace FetchTemporal {

// datetime.h keeps PyDateTimeAPI per translation unit, so these helpers must too.
static inline void ensure_datetime_api() {
if (PyDateTimeAPI == nullptr) {
PyDateTime_IMPORT;
if (PyDateTimeAPI == nullptr) throw py::error_already_set();
}
}

static inline py::object date(int year, int month, int day) {
ensure_datetime_api();
// Cached substitutes must still receive the original constructor call.
if (PyTypeCache::get_date_class() !=
reinterpret_cast<PyObject*>(PyDateTimeAPI->DateType)) {
return PyTypeCache::get_date_class_obj()(year, month, day);
}
py::object result = steal(PyDate_FromDate(year, month, day));
if (!result) throw py::error_already_set();
return result;
}

static inline py::object time(int hour, int minute, int second, int microsecond) {
ensure_datetime_api();
if (PyTypeCache::get_time_class() !=
reinterpret_cast<PyObject*>(PyDateTimeAPI->TimeType)) {
return PyTypeCache::get_time_class_obj()(hour, minute, second, microsecond);
}
py::object result = steal(PyTime_FromTime(hour, minute, second, microsecond));
if (!result) throw py::error_already_set();
return result;
}

static inline py::object datetime(int year, int month, int day, int hour, int minute, int second,
int microsecond) {
ensure_datetime_api();
if (PyTypeCache::get_datetime_class() !=
reinterpret_cast<PyObject*>(PyDateTimeAPI->DateTimeType)) {
return PyTypeCache::get_datetime_class_obj()(year, month, day, hour, minute, second,
microsecond);
}
py::object result =
steal(PyDateTime_FromDateAndTime(year, month, day, hour, minute, second, microsecond));
if (!result) throw py::error_already_set();
return result;
}

} // namespace FetchTemporal
Loading
Loading