Skip to content

Commit dbccb70

Browse files
committed
Fix DATE/TIMESTAMP/TIMESTAMP_TZ ref leak in Python binding
PyDate_FromDate, PyDateTime_FromDateAndTime and PyDateTimeAPI->DateTime_FromDateAndTime each return a new reference. Wrapping them with py::cast<py::object>(PyObject*) treats the pointer as borrowed and adds another reference, leaking one Python object per value returned via get_next/get_all/get_n/rows_as_dict/iteration (including inside LIST/STRUCT/node/rel). Steal the new reference with py::reinterpret_steal instead, propagating the Python error when construction fails. Fixes #78
1 parent 2826a2f commit dbccb70

1 file changed

Lines changed: 15 additions & 4 deletions

File tree

‎src_cpp/py_query_result.cpp‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,26 @@ py::dict buildCSRResult(lbug::main::ArrowQueryResult::CSRArrowArrays arrays) {
118118

119119
} // namespace
120120

121+
// PyDate_FromDate, PyDateTime_FromDateAndTime and
122+
// PyDateTimeAPI->DateTime_FromDateAndTime each return a *new* reference.
123+
// py::cast<py::object>(PyObject*) treats the pointer as borrowed and adds its
124+
// own reference, leaking the original. Steal the new reference instead, and
125+
// propagate the Python error if construction failed (returns nullptr). NOLINTNEXTLINE
126+
static py::object stealNewReference(PyObject* object) {
127+
if (object == nullptr) {
128+
throw py::error_already_set();
129+
}
130+
return py::reinterpret_steal<py::object>(object);
131+
}
132+
121133
static py::object converTimestampToPyObject(timestamp_t& timestamp) {
122134
int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, micros = 0;
123135
date_t date;
124136
dtime_t time;
125137
Timestamp::convert(timestamp, date, time);
126138
Date::convert(date, year, month, day);
127139
Time::convert(time, hour, min, sec, micros);
128-
return py::cast<py::object>(
129-
PyDateTime_FromDateAndTime(year, month, day, hour, min, sec, micros));
140+
return stealNewReference(PyDateTime_FromDateAndTime(year, month, day, hour, min, sec, micros));
130141
}
131142

132143
py::object PyQueryResult::convertValueToPyObject(const Value& value) {
@@ -200,7 +211,7 @@ py::object PyQueryResult::convertValueToPyObject(const Value& value) {
200211
auto dateVal = value.getValue<date_t>();
201212
int32_t year = 0, month = 0, day = 0;
202213
Date::convert(dateVal, year, month, day);
203-
return py::cast<py::object>(PyDate_FromDate(year, month, day));
214+
return stealNewReference(PyDate_FromDate(year, month, day));
204215
}
205216
case LogicalTypeID::TIMESTAMP: {
206217
auto timestampVal = value.getValue<timestamp_t>();
@@ -215,7 +226,7 @@ py::object PyQueryResult::convertValueToPyObject(const Value& value) {
215226
Date::convert(date, year, month, day);
216227
Time::convert(time, hour, min, sec, micros);
217228

218-
return py::cast<py::object>(PyDateTimeTZ_FromDateAndTime(year, month, day, hour, min, sec,
229+
return stealNewReference(PyDateTimeTZ_FromDateAndTime(year, month, day, hour, min, sec,
219230
micros, PyDateTime_TimeZone_UTC));
220231
}
221232
case LogicalTypeID::TIMESTAMP_NS: {

0 commit comments

Comments
 (0)