mirror of
https://github.com/mariadb-corporation/mariadb-connector-python.git
synced 2025-08-04 08:04:45 +00:00
Fix for CONPY-108 (memory leak):
- initialize datetime API only once per object file - don't reparse same statement
This commit is contained in:
@ -17,6 +17,7 @@
|
|||||||
51 Franklin St., Fifth Floor, Boston, MA 02110, USA
|
51 Franklin St., Fifth Floor, Boston, MA 02110, USA
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
#define PY_SSIZE_T_CLEAN
|
#define PY_SSIZE_T_CLEAN
|
||||||
|
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include "bytesobject.h"
|
#include "bytesobject.h"
|
||||||
#include "structmember.h"
|
#include "structmember.h"
|
||||||
@ -29,7 +30,6 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <docs/common.h>
|
#include <docs/common.h>
|
||||||
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
typedef CRITICAL_SECTION pthread_mutex_t;
|
typedef CRITICAL_SECTION pthread_mutex_t;
|
||||||
@ -62,6 +62,8 @@ int clock_gettime(int dummy, struct timespec *ct);
|
|||||||
#define CLOCK_MONOTONIC_RAW 1
|
#define CLOCK_MONOTONIC_RAW 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int codecs_datetime_init();
|
||||||
|
|
||||||
|
|
||||||
#define REQUIRED_CC_VERSION 30103
|
#define REQUIRED_CC_VERSION 30103
|
||||||
|
|
||||||
|
@ -106,6 +106,17 @@ mariadb_module= {
|
|||||||
Mariadb_Methods
|
Mariadb_Methods
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int mariadb_datetime_init()
|
||||||
|
{
|
||||||
|
PyDateTime_IMPORT;
|
||||||
|
|
||||||
|
if (!PyDateTimeAPI) {
|
||||||
|
PyErr_SetString(PyExc_ImportError, "DateTimeAPI initialization failed");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void mariadb_add_exception(PyObject *module,
|
static void mariadb_add_exception(PyObject *module,
|
||||||
PyObject **exception,
|
PyObject **exception,
|
||||||
const char *exception_name,
|
const char *exception_name,
|
||||||
@ -132,6 +143,13 @@ PyMODINIT_FUNC PyInit__mariadb(void)
|
|||||||
pre_release= PY_MARIADB_PRE_RELEASE_SEGMENT;
|
pre_release= PY_MARIADB_PRE_RELEASE_SEGMENT;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Initialite DateTimeAPI */
|
||||||
|
if (mariadb_datetime_init() ||
|
||||||
|
codecs_datetime_init())
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
Py_TYPE(&MrdbConnection_Type) = &PyType_Type;
|
Py_TYPE(&MrdbConnection_Type) = &PyType_Type;
|
||||||
if (PyType_Ready(&MrdbConnection_Type) == -1)
|
if (PyType_Ready(&MrdbConnection_Type) == -1)
|
||||||
{
|
{
|
||||||
@ -304,11 +322,6 @@ Mariadb_date_from_ticks(PyObject *module, PyObject *args)
|
|||||||
struct tm *ts;
|
struct tm *ts;
|
||||||
time_t epoch;
|
time_t epoch;
|
||||||
|
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &o))
|
if (!PyArg_ParseTuple(args, "O", &o))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -328,11 +341,6 @@ Mariadb_time_from_ticks(PyObject *module, PyObject *args)
|
|||||||
time_t epoch;
|
time_t epoch;
|
||||||
PyObject *o, *Time= NULL;
|
PyObject *o, *Time= NULL;
|
||||||
|
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &o))
|
if (!PyArg_ParseTuple(args, "O", &o))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -352,11 +360,6 @@ Mariadb_timestamp_from_ticks(PyObject *module, PyObject *args)
|
|||||||
struct tm *ts;
|
struct tm *ts;
|
||||||
time_t epoch;
|
time_t epoch;
|
||||||
|
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &o))
|
if (!PyArg_ParseTuple(args, "O", &o))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -377,11 +380,6 @@ Mariadb_date(PyObject *self, PyObject *args)
|
|||||||
PyObject *date= NULL;
|
PyObject *date= NULL;
|
||||||
int32_t year=0, month=0, day= 0;
|
int32_t year=0, month=0, day= 0;
|
||||||
|
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "iii", &year, &month, &day))
|
if (!PyArg_ParseTuple(args, "iii", &year, &month, &day))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -398,11 +396,6 @@ Mariadb_timestamp(PyObject *self, PyObject *args)
|
|||||||
int32_t year=0, month=0, day= 0;
|
int32_t year=0, month=0, day= 0;
|
||||||
int32_t hour=0, min=0, sec= 0;
|
int32_t hour=0, min=0, sec= 0;
|
||||||
|
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "iiiiii", &year, &month, &day,
|
if (!PyArg_ParseTuple(args, "iiiiii", &year, &month, &day,
|
||||||
&hour, &min, &sec))
|
&hour, &min, &sec))
|
||||||
{
|
{
|
||||||
@ -419,10 +412,6 @@ Mariadb_time(PyObject *self, PyObject *args)
|
|||||||
{
|
{
|
||||||
PyObject *time= NULL;
|
PyObject *time= NULL;
|
||||||
int32_t hour=0, min=0, sec= 0;
|
int32_t hour=0, min=0, sec= 0;
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "iii", &hour, &min, &sec))
|
if (!PyArg_ParseTuple(args, "iii", &hour, &min, &sec))
|
||||||
{
|
{
|
||||||
|
@ -28,6 +28,18 @@
|
|||||||
#define IS_DECIMAL_TYPE(type) \
|
#define IS_DECIMAL_TYPE(type) \
|
||||||
((type) == MYSQL_TYPE_NEWDECIMAL || (type) == MYSQL_TYPE_DOUBLE || (type) == MYSQL_TYPE_FLOAT)
|
((type) == MYSQL_TYPE_NEWDECIMAL || (type) == MYSQL_TYPE_DOUBLE || (type) == MYSQL_TYPE_FLOAT)
|
||||||
|
|
||||||
|
|
||||||
|
int codecs_datetime_init()
|
||||||
|
{
|
||||||
|
PyDateTime_IMPORT;
|
||||||
|
|
||||||
|
if (!PyDateTimeAPI) {
|
||||||
|
PyErr_SetString(PyExc_ImportError, "DateTimeAPI initialization failed");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
converts a Python date/time/datetime object to MYSQL_TIME
|
converts a Python date/time/datetime object to MYSQL_TIME
|
||||||
*/
|
*/
|
||||||
@ -36,10 +48,6 @@ mariadb_pydate_to_tm(enum enum_field_types type,
|
|||||||
PyObject *obj,
|
PyObject *obj,
|
||||||
MYSQL_TIME *tm)
|
MYSQL_TIME *tm)
|
||||||
{
|
{
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
memset(tm, 0, sizeof(MYSQL_TIME));
|
memset(tm, 0, sizeof(MYSQL_TIME));
|
||||||
if (type == MYSQL_TYPE_TIME ||
|
if (type == MYSQL_TYPE_TIME ||
|
||||||
type == MYSQL_TYPE_DATETIME)
|
type == MYSQL_TYPE_DATETIME)
|
||||||
@ -387,11 +395,6 @@ field_fetch_fromtext(MrdbCursor *self, char *data, unsigned int column)
|
|||||||
MYSQL_TIME tm;
|
MYSQL_TIME tm;
|
||||||
unsigned long *length;
|
unsigned long *length;
|
||||||
|
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data)
|
if (!data)
|
||||||
{
|
{
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
@ -540,10 +543,6 @@ field_fetch_callback(void *data, unsigned int column, unsigned char **row)
|
|||||||
MrdbCursor *self= (MrdbCursor *)data;
|
MrdbCursor *self= (MrdbCursor *)data;
|
||||||
|
|
||||||
MARIADB_UNBLOCK_THREADS(self);
|
MARIADB_UNBLOCK_THREADS(self);
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!row)
|
if (!row)
|
||||||
{
|
{
|
||||||
@ -771,11 +770,6 @@ end:
|
|||||||
static uint8_t
|
static uint8_t
|
||||||
mariadb_get_column_info(PyObject *obj, MrdbParamInfo *paraminfo)
|
mariadb_get_column_info(PyObject *obj, MrdbParamInfo *paraminfo)
|
||||||
{
|
{
|
||||||
if (!PyDateTimeAPI)
|
|
||||||
{
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj == NULL)
|
if (obj == NULL)
|
||||||
{
|
{
|
||||||
paraminfo->type= MYSQL_TYPE_NULL;
|
paraminfo->type= MYSQL_TYPE_NULL;
|
||||||
@ -880,14 +874,14 @@ mariadb_get_parameter(MrdbCursor *self,
|
|||||||
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
|
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
|
||||||
"Can't access data at row %d, column %d",
|
"Can't access data at row %d, column %d",
|
||||||
row_nr + 1, column_nr + 1);
|
row_nr + 1, column_nr + 1);
|
||||||
return 1;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(row= ListOrTuple_GetItem(self->data, row_nr)))
|
if (!(row= ListOrTuple_GetItem(self->data, row_nr)))
|
||||||
{
|
{
|
||||||
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
|
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
|
||||||
"Can't access row number %d", row_nr + 1);
|
"Can't access row number %d", row_nr + 1);
|
||||||
return 1;
|
goto end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1191,7 +1185,7 @@ mariadb_check_execute_parameters(MrdbCursor *self,
|
|||||||
{
|
{
|
||||||
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
|
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
|
||||||
"Invalid number of parameters");
|
"Invalid number of parameters");
|
||||||
return 1;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!self->params &&
|
if (!self->params &&
|
||||||
|
@ -398,6 +398,39 @@ static PyObject *Mariadb_no_operation(MrdbCursor *self,
|
|||||||
}
|
}
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
|
/* {{{ MrdbCursor_clear_result(MrdbCursor *self)
|
||||||
|
clear pending result sets
|
||||||
|
*/
|
||||||
|
static void MrdbCursor_clear_result(MrdbCursor *self)
|
||||||
|
{
|
||||||
|
if (!self->is_text &&
|
||||||
|
self->stmt)
|
||||||
|
{
|
||||||
|
/* free current result */
|
||||||
|
mysql_stmt_free_result(self->stmt);
|
||||||
|
|
||||||
|
/* check if there are more pending result sets */
|
||||||
|
while (mysql_stmt_next_result(self->stmt) == 0)
|
||||||
|
{
|
||||||
|
mysql_stmt_free_result(self->stmt);
|
||||||
|
}
|
||||||
|
} else if (self->is_text)
|
||||||
|
{
|
||||||
|
/* free current result */
|
||||||
|
if (self->result)
|
||||||
|
{
|
||||||
|
mysql_free_result(self->result);
|
||||||
|
}
|
||||||
|
/* clear pending result sets */
|
||||||
|
if (self->connection->mysql)
|
||||||
|
{
|
||||||
|
while (!mysql_next_result(self->connection->mysql));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* CONPY-52: Avoid possible double free */
|
||||||
|
self->result= NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* {{{ MrdbCursor_clear
|
/* {{{ MrdbCursor_clear
|
||||||
Resets statement attributes and frees
|
Resets statement attributes and frees
|
||||||
associated memory
|
associated memory
|
||||||
@ -405,16 +438,10 @@ static PyObject *Mariadb_no_operation(MrdbCursor *self,
|
|||||||
static
|
static
|
||||||
void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
|
void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
|
||||||
{
|
{
|
||||||
|
/* clear pending result sets */
|
||||||
|
MrdbCursor_clear_result(self);
|
||||||
|
|
||||||
if (!self->is_text && self->stmt) {
|
if (!self->is_text && self->stmt) {
|
||||||
mysql_stmt_free_result(self->stmt);
|
|
||||||
|
|
||||||
/* CONPY-52: avoid possible double free */
|
|
||||||
self->result= NULL;
|
|
||||||
|
|
||||||
while (!mysql_stmt_next_result(self->stmt))
|
|
||||||
mysql_stmt_free_result(self->stmt);
|
|
||||||
|
|
||||||
if (new_stmt)
|
if (new_stmt)
|
||||||
{
|
{
|
||||||
mysql_stmt_close(self->stmt);
|
mysql_stmt_close(self->stmt);
|
||||||
@ -423,29 +450,15 @@ void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
|
|||||||
else {
|
else {
|
||||||
uint32_t val= 0;
|
uint32_t val= 0;
|
||||||
|
|
||||||
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, 0);
|
mysql_stmt_reset(self->stmt);
|
||||||
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_PARAM, 0);
|
|
||||||
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_RESULT, 0);
|
/* we need to unset array size only */
|
||||||
mysql_stmt_attr_set(self->stmt, STMT_ATTR_ARRAY_SIZE, &val);
|
mysql_stmt_attr_set(self->stmt, STMT_ATTR_ARRAY_SIZE, &val);
|
||||||
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
self->fetched= 0;
|
self->fetched= 0;
|
||||||
|
|
||||||
if (self->is_text)
|
|
||||||
{
|
|
||||||
if (self->result)
|
|
||||||
{
|
|
||||||
mysql_free_result(self->result);
|
|
||||||
self->result= 0;
|
|
||||||
self->is_text= 0;
|
|
||||||
}
|
|
||||||
/* clear also pending result sets */
|
|
||||||
if (self->connection->mysql)
|
|
||||||
while (!mysql_next_result(self->connection->mysql));
|
|
||||||
}
|
|
||||||
|
|
||||||
MARIADB_FREE_MEM(self->sequence_fields);
|
MARIADB_FREE_MEM(self->sequence_fields);
|
||||||
self->fields= NULL;
|
self->fields= NULL;
|
||||||
self->row_count= 0;
|
self->row_count= 0;
|
||||||
@ -456,7 +469,6 @@ void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
|
|||||||
MARIADB_FREE_MEM(self->statement);
|
MARIADB_FREE_MEM(self->statement);
|
||||||
MARIADB_FREE_MEM(self->value);
|
MARIADB_FREE_MEM(self->value);
|
||||||
MARIADB_FREE_MEM(self->params);
|
MARIADB_FREE_MEM(self->params);
|
||||||
|
|
||||||
}
|
}
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
@ -614,6 +626,30 @@ static Py_ssize_t data_count(PyObject *data)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int Mrdb_execute_direct(MrdbCursor *self)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
MARIADB_BEGIN_ALLOW_THREADS(self);
|
||||||
|
/* execute_direct was implemented together with bulk operations, so we need
|
||||||
|
to check if MARIADB_CLIENT_STMT_BULK_OPERATIONS is set in extended server
|
||||||
|
capabilities */
|
||||||
|
if (!(self->connection->extended_server_capabilities &
|
||||||
|
(MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)))
|
||||||
|
{
|
||||||
|
if (!(rc= mysql_stmt_prepare(self->stmt, self->parser->statement.str,
|
||||||
|
(unsigned long)self->parser->statement.length)))
|
||||||
|
{
|
||||||
|
rc= mysql_stmt_execute(self->stmt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rc= mariadb_stmt_execute_direct(self->stmt, self->parser->statement.str,
|
||||||
|
self->parser->statement.length);
|
||||||
|
}
|
||||||
|
MARIADB_END_ALLOW_THREADS(self);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
/* {{{ MrdbCursor_execute
|
/* {{{ MrdbCursor_execute
|
||||||
PEP-249 execute() method
|
PEP-249 execute() method
|
||||||
*/
|
*/
|
||||||
@ -671,19 +707,21 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
|
|||||||
{
|
{
|
||||||
uint8_t do_prepare= 1;
|
uint8_t do_prepare= 1;
|
||||||
|
|
||||||
if (self->is_prepared && self->statement)
|
if ((self->is_prepared && self->statement))
|
||||||
do_prepare= 0;
|
do_prepare= 0;
|
||||||
|
|
||||||
/* if cursor type is not prepared, we need to clear the cursor first */
|
/* if cursor type is not prepared, we need to clear the cursor first */
|
||||||
if (!self->is_prepared && self->statement)
|
if (!self->is_prepared && self->statement)
|
||||||
{
|
{
|
||||||
uint8_t new_stmt= 1;
|
|
||||||
if (!strcmp(self->statement, statement))
|
if (!strcmp(self->statement, statement))
|
||||||
new_stmt= 0;
|
{
|
||||||
MrdbCursor_clear(self, new_stmt);
|
do_prepare= 0;
|
||||||
|
} else {
|
||||||
|
MrdbCursor_clear(self, 1);
|
||||||
MrdbParser_end(self->parser);
|
MrdbParser_end(self->parser);
|
||||||
self->parser= NULL;
|
self->parser= NULL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self->is_text= 0;
|
self->is_text= 0;
|
||||||
|
|
||||||
if (!self->parser)
|
if (!self->parser)
|
||||||
@ -729,22 +767,7 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
|
|||||||
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, (void *)self);
|
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, (void *)self);
|
||||||
mysql_stmt_bind_param(self->stmt, self->params);
|
mysql_stmt_bind_param(self->stmt, self->params);
|
||||||
|
|
||||||
MARIADB_BEGIN_ALLOW_THREADS(self);
|
rc= Mrdb_execute_direct(self);
|
||||||
/* execute_direct was implemented together with bulk operations, so we need
|
|
||||||
to check if MARIADB_CLIENT_STMT_BULK_OPERATIONS is set in extended server
|
|
||||||
capabilities */
|
|
||||||
if ((self->connection->extended_server_capabilities &
|
|
||||||
(MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)))
|
|
||||||
{
|
|
||||||
rc= mysql_stmt_prepare(self->stmt, self->parser->statement.str,
|
|
||||||
(unsigned long)self->parser->statement.length);
|
|
||||||
if (!rc)
|
|
||||||
rc= mysql_stmt_execute(self->stmt);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
rc= mariadb_stmt_execute_direct(self->stmt, self->parser->statement.str,
|
|
||||||
self->parser->statement.length);
|
|
||||||
MARIADB_END_ALLOW_THREADS(self);
|
|
||||||
|
|
||||||
if (rc)
|
if (rc)
|
||||||
{
|
{
|
||||||
@ -1355,11 +1378,7 @@ MrdbCursor_executemany(MrdbCursor *self,
|
|||||||
|
|
||||||
mysql_stmt_bind_param(self->stmt, self->params);
|
mysql_stmt_bind_param(self->stmt, self->params);
|
||||||
|
|
||||||
MARIADB_BEGIN_ALLOW_THREADS(self);
|
if (Mrdb_execute_direct(self))
|
||||||
rc= mariadb_stmt_execute_direct(self->stmt, self->parser->statement.str,
|
|
||||||
(unsigned long)self->parser->statement.length);
|
|
||||||
MARIADB_END_ALLOW_THREADS(self);
|
|
||||||
if (rc)
|
|
||||||
{
|
{
|
||||||
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
|
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
|
||||||
goto error;
|
goto error;
|
||||||
|
Reference in New Issue
Block a user