Files
mariadb-connector-python/mariadb/mariadb_cursor.c
Georg Richter 020017ee33 Fix for CONPY123: Free pending result sets when closing cursor
When using text protocol pending result sets weren't properly freed,
so executing another command on the same connection resulted in an error
"commands out of sync".
2020-10-12 09:05:49 +02:00

1661 lines
44 KiB
C

/*****************************************************************************
Copyright (C) 2018-2020 Georg Richter and MariaDB Corporation AB
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not see <http://www.gnu.org/licenses>
or write to the Free Software Foundation, Inc.,
51 Franklin St., Fifth Floor, Boston, MA 02110, USA
****************************************************************************/
#include <mariadb_python.h>
#include <docs/cursor.h>
static void
MrdbCursor_dealloc(MrdbCursor *self);
static PyObject *
MrdbCursor_close(MrdbCursor *self);
static PyObject *
MrdbCursor_execute(MrdbCursor *self,
PyObject *args,
PyObject *kwargs);
static PyObject *
MrdbCursor_nextset(MrdbCursor *self);
static PyObject *
MrdbCursor_executemany(MrdbCursor *self,
PyObject *args);
static PyObject *
MrdbCursor_description(MrdbCursor *self);
static PyObject *
MrdbCursor_fetchall(MrdbCursor *self);
static PyObject *
MrdbCursor_fetchone(MrdbCursor *self);
static PyObject
*MrdbCursor_fetchmany(MrdbCursor *self,
PyObject *args,
PyObject *kwargs);
static PyObject *
MrdbCursor_scroll(MrdbCursor *self,
PyObject *args,
PyObject *kwargs);
static PyObject *
MrdbCursor_callproc(MrdbCursor *self,
PyObject *args);
static PyObject *
MrdbCursor_fieldcount(MrdbCursor *self);
void
field_fetch_fromtext(MrdbCursor *self, char *data, unsigned int column);
void
field_fetch_callback(void *data, unsigned int column, unsigned char **row);
static PyObject *mariadb_get_sequence_or_tuple(MrdbCursor *self);
static PyObject * MrdbCursor_iter(PyObject *self);
static PyObject * MrdbCursor_iternext(PyObject *self);
static PyObject *MrdbCursor_enter(MrdbCursor *self, PyObject *args __attribute__((unused)));
static PyObject *MrdbCursor_exit(MrdbCursor *self, PyObject *args __attribute__((unused)));
/* todo: write more documentation, this is just a placeholder */
static char mariadb_cursor_documentation[] =
"Returns a MariaDB cursor object";
#define CURSOR_SET_STATEMENT(a,s,l)\
MARIADB_FREE_MEM((a)->statement);\
(a)->statement= PyMem_RawMalloc((l)+ 1);\
strncpy((a)->statement, (s), (l));\
(a)->statement_len= (unsigned long)(l);\
(a)->statement[(l)]= 0;
#define CURSOR_FIELD_COUNT(a)\
((a)->is_text ? mysql_field_count((a)->connection->mysql) : (a)->stmt ? mysql_stmt_field_count((a)->stmt) : 0)
#define CURSOR_WARNING_COUNT(a)\
(((a)->is_text) ? (long)mysql_warning_count((a)->connection->mysql) : ((a)->stmt) ? (long)mysql_stmt_warning_count((a)->stmt) : 0L)
#define CURSOR_AFFECTED_ROWS(a)\
(int64_t)((a)->is_text ? mysql_affected_rows((a)->connection->mysql) : (a)->stmt ? mysql_stmt_affected_rows((a)->stmt) : 0)
#define CURSOR_INSERT_ID(a)\
((a)->is_text ? mysql_insert_id((a)->connection->mysql) : (a)->stmt ? mysql_stmt_insert_id((a)->stmt) : 0)
#define CURSOR_NUM_ROWS(a)\
((a)->is_text ? mysql_num_rows((a)->result) : (a)->stmt ? mysql_stmt_num_rows((a)->stmt) : 0)
static char *mariadb_named_tuple_name= "Row";
static char *mariadb_named_tuple_desc= "Named tupled row";
static PyObject *Mariadb_no_operation(MrdbCursor *,
PyObject *);
static PyObject *Mariadb_row_count(MrdbCursor *self);
static PyObject *Mariadb_row_number(MrdbCursor *self);
static PyObject *MrdbCursor_warnings(MrdbCursor *self);
static PyObject *MrdbCursor_getbuffered(MrdbCursor *self);
static int MrdbCursor_setbuffered(MrdbCursor *self, PyObject *arg);
static PyObject *MrdbCursor_lastrowid(MrdbCursor *self);
static PyObject *MrdbCursor_closed(MrdbCursor *self);
static PyObject *MrdbCursor_sp_outparams(MrdbCursor *self);
static PyGetSetDef MrdbCursor_sets[]=
{
{"lastrowid", (getter)MrdbCursor_lastrowid, NULL,
cursor_lastrowid__doc__, NULL},
{"description", (getter)MrdbCursor_description, NULL,
cursor_description__doc__, NULL},
{"rowcount", (getter)Mariadb_row_count, NULL,
cursor_rowcount__doc__, NULL},
{"warnings", (getter)MrdbCursor_warnings, NULL,
cursor_warnings__doc__, NULL},
{"closed", (getter)MrdbCursor_closed, NULL,
cursor_closed__doc__, NULL},
{"buffered", (getter)MrdbCursor_getbuffered, (setter)MrdbCursor_setbuffered,
cursor_buffered__doc__, NULL},
{"rownumber", (getter)Mariadb_row_number, NULL,
cursor_rownumber__doc__, NULL},
{"sp_outparams", (getter)MrdbCursor_sp_outparams, NULL,
cursor_sp_outparam__doc__, NULL},
{NULL}
};
static PyMethodDef MrdbCursor_Methods[] =
{
/* PEP-249 methods */
{"callproc", (PyCFunction)MrdbCursor_callproc,
METH_VARARGS,
cursor_callproc__doc__},
{"close", (PyCFunction)MrdbCursor_close,
METH_NOARGS,
cursor_close__doc__},
{"execute", (PyCFunction)MrdbCursor_execute,
METH_VARARGS | METH_KEYWORDS,
cursor_execute__doc__,},
{"executemany", (PyCFunction)MrdbCursor_executemany,
METH_VARARGS,
cursor_executemany__doc__ },
{"fetchall", (PyCFunction)MrdbCursor_fetchall,
METH_NOARGS,
cursor_fetchall__doc__},
{"fetchone", (PyCFunction)MrdbCursor_fetchone,
METH_NOARGS,
cursor_fetchone__doc__,},
{"fetchmany", (PyCFunction)MrdbCursor_fetchmany,
METH_VARARGS | METH_KEYWORDS,
cursor_fetchmany__doc__},
{"fieldcount", (PyCFunction)MrdbCursor_fieldcount,
METH_NOARGS,
cursor_field_count__doc__},
{"nextset", (PyCFunction)MrdbCursor_nextset,
METH_NOARGS,
cursor_nextset__doc__},
{"setinputsizes", (PyCFunction)Mariadb_no_operation,
METH_VARARGS,
cursor_setinputsizes__doc__},
{"setoutputsize", (PyCFunction)Mariadb_no_operation,
METH_VARARGS,
cursor_setoutputsize__doc__},
{"next", (PyCFunction)MrdbCursor_fetchone,
METH_NOARGS,
cursor_next__doc__},
{"scroll", (PyCFunction)MrdbCursor_scroll,
METH_VARARGS | METH_KEYWORDS,
cursor_scroll__doc__},
{"__enter__", (PyCFunction)MrdbCursor_enter,
METH_NOARGS, cursor_enter__doc__},
{"__exit__", (PyCFunction)MrdbCursor_exit,
METH_VARARGS, cursor_exit__doc__},
{NULL} /* always last */
};
static struct PyMemberDef MrdbCursor_Members[] =
{
{"connection",
T_OBJECT,
offsetof(MrdbCursor, connection),
READONLY,
cursor_connection__doc__},
{"statement",
T_STRING,
offsetof(MrdbCursor, statement),
READONLY,
cursor_statement__doc__},
{"buffered",
T_BYTE,
offsetof(MrdbCursor, is_buffered),
0,
cursor_buffered__doc__},
{"arraysize",
T_LONG,
offsetof(MrdbCursor, row_array_size),
0,
cursor_arraysize__doc__},
{NULL}
};
/* {{{ MrdbCursor_initialize
Cursor initialization
Optional keywprds:
named_tuple (Boolean): return rows as named tuple instead of tuple
prefetch_size: Prefetch size for readonly cursors
cursor_type: Type of cursor: CURSOR_TYPE_READONLY or CURSOR_TYPE_NONE (default)
buffered: buffered or unbuffered result sets
*/
static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
PyObject *kwargs)
{
char *key_words[]= {"", "named_tuple", "dictionary", "prefetch_size", "cursor_type",
"buffered", "prepared", "binary", NULL};
PyObject *connection;
uint8_t is_named_tuple= 0;
uint8_t is_dictionary= 0;
unsigned long cursor_type= 0,
prefetch_rows= 0;
uint8_t is_buffered= 0;
uint8_t is_prepared= 0;
uint8_t is_binary= 0;
if (!self)
return -1;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"O!|bbkkbbb", key_words, &MrdbConnection_Type, &connection,
&is_named_tuple, &is_dictionary, &prefetch_rows, &cursor_type, &is_buffered,
&is_prepared, &is_binary))
return -1;
if (!((MrdbConnection *)connection)->mysql)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Connection isn't valid anymore");
return -1;
}
if (cursor_type != CURSOR_TYPE_READ_ONLY &&
cursor_type != CURSOR_TYPE_NO_CURSOR)
{
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
"Invalid value %ld for cursor_type", cursor_type);
return -1;
}
if (is_named_tuple && is_dictionary)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Results can be returned either as named tuple or as dictionary, but not as both.");
return -1;
}
if (is_named_tuple)
{
self->result_format= RESULT_NAMED_TUPLE;
} else if (is_dictionary)
{
self->result_format= RESULT_DICTIONARY;
}
Py_INCREF(connection);
self->connection= (MrdbConnection *)connection;
self->is_buffered= is_buffered ? is_buffered : self->connection->is_buffered;
self->is_binary= is_binary;
self->is_prepared= is_prepared;
self->is_text= 0;
self->stmt= NULL;
self->cursor_type= cursor_type;
self->prefetch_rows= prefetch_rows;
self->row_array_size= 1;
return 0;
}
/* }}} */
static int MrdbCursor_traverse(
MrdbCursor *self,
visitproc visit,
void *arg)
{
return 0;
}
PyTypeObject MrdbCursor_Type =
{
PyVarObject_HEAD_INIT(NULL, 0)
"mariadb.connection.cursor",
sizeof(MrdbCursor),
0,
(destructor)MrdbCursor_dealloc, /* tp_dealloc */
0, /*tp_print*/
0, /* tp_getattr */
0, /* tp_setattr */
0, /* PyAsyncMethods * */
0, /* tp_repr */
/* Method suites for standard classes */
0, /* (PyNumberMethods *) tp_as_number */
0, /* (PySequenceMethods *) tp_as_sequence */
0, /* (PyMappingMethods *) tp_as_mapping */
/* More standard operations (here for binary compatibility) */
0, /* (hashfunc) tp_hash */
0, /* (ternaryfunc) tp_call */
0, /* (reprfunc) tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
/* Functions to access object as input/output buffer */
0, /* (PyBufferProcs *) tp_as_buffer */
/* (tp_flags) Flags to define presence of optional/expanded features */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE,
mariadb_cursor_documentation, /* tp_doc Documentation string */
/* call function for all accessible objects */
(traverseproc)MrdbCursor_traverse,/* tp_traverse */
/* delete references to contained objects */
0, /* tp_clear */
/* rich comparisons */
0, /* (richcmpfunc) tp_richcompare */
/* weak reference enabler */
0, /* (long) tp_weaklistoffset */
/* Iterators */
(getiterfunc)MrdbCursor_iter,
(iternextfunc)MrdbCursor_iternext,
/* Attribute descriptor and subclassing stuff */
(struct PyMethodDef *)MrdbCursor_Methods, /* tp_methods */
(struct PyMemberDef *)MrdbCursor_Members, /* tp_members */
MrdbCursor_sets,
0, /* (struct _typeobject *) tp_base; */
0, /* (PyObject *) tp_dict */
0, /* (descrgetfunc) tp_descr_get */
0, /* (descrsetfunc) tp_descr_set */
0, /* (long) tp_dictoffset */
(initproc)MrdbCursor_initialize, /* tp_init */
PyType_GenericAlloc, //NULL, /* tp_alloc */
PyType_GenericNew, //NULL, /* tp_new */
NULL, /* tp_free Low-level free-memory routine */
0, /* (PyObject *) tp_bases */
0, /* (PyObject *) tp_mro method resolution order */
0, /* (PyObject *) tp_defined */
};
/* {{{ Mariadb_no_operation
This function is a stub and just returns Py_None
*/
static PyObject *Mariadb_no_operation(MrdbCursor *self,
PyObject *args)
{
Py_INCREF(Py_None);
return Py_None;
}
/* }}} */
/* {{{ 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)
{
do {
MYSQL_RES *res;
if ((res= mysql_use_result(self->connection->mysql)))
mysql_free_result(res);
} while (!mysql_next_result(self->connection->mysql));
}
}
/* CONPY-52: Avoid possible double free */
self->result= NULL;
}
/* {{{ MrdbCursor_clear
Resets statement attributes and frees
associated memory
*/
static
void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
{
/* clear pending result sets */
MrdbCursor_clear_result(self);
if (!self->is_text && self->stmt) {
if (new_stmt)
{
mysql_stmt_close(self->stmt);
self->stmt= mysql_stmt_init(self->connection->mysql);
}
else {
uint32_t val= 0;
mysql_stmt_reset(self->stmt);
/* we need to unset array size only */
mysql_stmt_attr_set(self->stmt, STMT_ATTR_ARRAY_SIZE, &val);
}
}
self->fetched= 0;
if (self->sequence_fields)
{
MARIADB_FREE_MEM(self->sequence_fields);
Py_DECREF((PyObject *)self->sequence_type);
}
self->fields= NULL;
self->row_count= 0;
self->affected_rows= 0;
self->param_count= 0;
MARIADB_FREE_MEM(self->values);
MARIADB_FREE_MEM(self->bind);
MARIADB_FREE_MEM(self->statement);
MARIADB_FREE_MEM(self->value);
MARIADB_FREE_MEM(self->params);
}
/* }}} */
static void ma_set_result_column_value(MrdbCursor *self, PyObject *row, uint32_t column)
{
switch (self->result_format) {
case RESULT_NAMED_TUPLE:
PyStructSequence_SET_ITEM(row, column, self->values[column]);
break;
case RESULT_DICTIONARY:
PyDict_SetItemString(row, self->fields[column].name, self->values[column]);
Py_DECREF(self->values[column]); /* CONPY-119 */
break;
default:
PyTuple_SET_ITEM(row, column, (self)->values[column]);
}
}
/* {{{ ma_cursor_close
closes the statement handle of current cursor. After call to
cursor_close the cursor can't be reused anymore
*/
static
void ma_cursor_close(MrdbCursor *self)
{
if (!self->is_closed)
{
MrdbCursor_clear_result(self);
if (!self->is_text && self->stmt)
{
/* Todo: check if all the cursor stuff is deleted (when using prepared
statements this should be handled in mysql_stmt_close) */
MARIADB_BEGIN_ALLOW_THREADS(self)
mysql_stmt_close(self->stmt);
MARIADB_END_ALLOW_THREADS(self)
self->stmt= NULL;
}
MrdbCursor_clear(self, 0);
if (self->is_text && self->stmt)
mysql_stmt_close(self->stmt);
if (self->parser)
{
MrdbParser_end(self->parser);
self->parser= NULL;
}
self->is_closed= 1;
}
}
static
PyObject * MrdbCursor_close(MrdbCursor *self)
{
ma_cursor_close(self);
Py_INCREF(Py_None);
return Py_None;
}
/* }}} */
/*{{{ MrDBCursor_dealloc */
void MrdbCursor_dealloc(MrdbCursor *self)
{
ma_cursor_close(self);
Py_TYPE(self)->tp_free((PyObject*)self);
}
/* }}} */
static int Mrdb_GetFieldInfo(MrdbCursor *self)
{
self->row_number= 0;
if (self->field_count)
{
if (self->is_text)
{
self->result= (self->is_buffered) ? mysql_store_result(self->connection->mysql) :
mysql_use_result(self->connection->mysql);
if (!self->result)
{
mariadb_throw_exception(self->connection->mysql, NULL, 0, NULL);
return 1;
}
}
else if (self->is_buffered)
{
if (mysql_stmt_store_result(self->stmt))
{
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
return 1;
}
}
self->affected_rows= CURSOR_AFFECTED_ROWS(self);
self->fields= (self->is_text) ? mysql_fetch_fields(self->result) :
mariadb_stmt_fetch_fields(self->stmt);
if (self->result_format == RESULT_NAMED_TUPLE) {
unsigned int i;
PyStructSequence_Desc sequence_desc;
if (!(self->sequence_fields= (PyStructSequence_Field *)
PyMem_RawCalloc(self->field_count + 1,
sizeof(PyStructSequence_Field))))
return 1;
sequence_desc.name= mariadb_named_tuple_name;
sequence_desc.doc= mariadb_named_tuple_desc;
sequence_desc.fields= self->sequence_fields;
sequence_desc.n_in_sequence= self->field_count;
for (i=0; i < self->field_count; i++)
{
self->sequence_fields[i].name= self->fields[i].name;
}
self->sequence_type= PyStructSequence_NewType(&sequence_desc);
}
}
return 0;
}
static int MrdbCursor_InitResultSet(MrdbCursor *self)
{
self->field_count= CURSOR_FIELD_COUNT(self);
MARIADB_FREE_MEM(self->sequence_fields);
MARIADB_FREE_MEM(self->values);
if (self->result)
{
mysql_free_result(self->result);
self->result= NULL;
}
if (Mrdb_GetFieldInfo(self))
return 1;
if (!(self->values= (PyObject**)PyMem_RawCalloc(self->field_count, sizeof(PyObject *))))
return 1;
if (!self->is_text)
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_RESULT, field_fetch_callback);
return 0;
}
static Py_ssize_t data_count(PyObject *data)
{
if (!data)
return 0;
if (CHECK_TYPE(data, &PyTuple_Type))
{
return PyTuple_Size(data);
} else if (CHECK_TYPE(data, &PyList_Type))
{
return PyList_Size(data);
} else if (CHECK_TYPE(data, &PyDict_Type))
{
return PyDict_Size(data);
}
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
PEP-249 execute() method
*/
static
PyObject *MrdbCursor_execute(MrdbCursor *self,
PyObject *args,
PyObject *kwargs)
{
PyObject *Data= NULL;
const char *statement= NULL;
Py_ssize_t statement_len= 0;
int rc= 0;
int8_t is_buffered= -1;
static char *key_words[]= {"", "", "buffered", NULL};
char errmsg[128];
MARIADB_CHECK_STMT(self);
if (PyErr_Occurred())
return NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"s#|Ob", key_words, &statement, &statement_len, &Data, &is_buffered))
return NULL;
/* If we don't have a prepared cursor, we need to end/free parser */
if (!self->is_prepared && self->parser)
{
MrdbParser_end(self->parser);
self->parser= NULL;
}
if (is_buffered != -1)
self->is_buffered= is_buffered;
/* if there are no parameters specified, we execute the statement in text protocol */
if (!data_count(Data) && !self->cursor_type && !self->is_binary)
{
/* in case statement was executed before, we need to clear, since we don't use
binary protocol */
MrdbParser_end(self->parser);
self->parser= NULL;
MrdbCursor_clear(self, 0);
MARIADB_BEGIN_ALLOW_THREADS(self);
rc= mysql_real_query(self->connection->mysql, statement, (unsigned long)statement_len);
MARIADB_END_ALLOW_THREADS(self);
if (rc)
{
mariadb_throw_exception(self->connection->mysql, NULL, 0, NULL);
goto error;
}
self->is_text= 1;
CURSOR_SET_STATEMENT(self, statement, statement_len);
}
else
{
uint8_t do_prepare= 1;
if (!self->stmt)
{
if (!(self->stmt= mysql_stmt_init(self->connection->mysql)))
{
mariadb_throw_exception(self->connection->mysql, NULL, 0, NULL);
goto error;
}
}
if ((self->is_prepared && self->statement))
do_prepare= 0;
/* if cursor type is not prepared, we need to clear the cursor first */
if (!self->is_prepared && self->statement)
{
if (!strcmp(self->statement, statement))
{
do_prepare= 0;
} else {
MrdbCursor_clear(self, 1);
MrdbParser_end(self->parser);
self->parser= NULL;
}
}
self->is_text= 0;
if (!self->parser)
{
self->parser= MrdbParser_init(statement, statement_len);
if (MrdbParser_parse(self->parser, 0, errmsg, 128))
{
PyErr_SetString(Mariadb_ProgrammingError, errmsg);
goto error;
}
CURSOR_SET_STATEMENT(self, statement, statement_len);
}
if (Data)
{
if (self->parser->paramstyle == PYFORMAT)
{
if (!CHECK_TYPE(Data, &PyDict_Type))
{
PyErr_SetString(PyExc_TypeError, "argument 2 must be dict");
goto error;
}
}
else if (!CHECK_TYPE(Data, &PyTuple_Type) &&
!CHECK_TYPE(Data, &PyList_Type))
{
PyErr_SetString(PyExc_TypeError, "Argument 2 must be Tuple or List!");
goto error;
}
self->array_size= 0;
self->data= Data;
if (mariadb_check_execute_parameters(self, Data))
goto error;
self->data= Data;
/* Load values */
if (mariadb_param_update(self, self->params, 0))
goto error;
}
if (do_prepare)
{
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CURSOR_TYPE, &self->cursor_type);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREFETCH_ROWS, &self->prefetch_rows);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->param_count);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, (void *)self);
mysql_stmt_bind_param(self->stmt, self->params);
rc= Mrdb_execute_direct(self);
if (rc)
{
/* in case statement is not supported via binary protocol, we try
to run the statement with text protocol */
if (mysql_stmt_errno(self->stmt) == ER_UNSUPPORTED_PS)
{
MARIADB_BEGIN_ALLOW_THREADS(self);
self->is_text= 0;
rc= mysql_real_query(self->connection->mysql, statement, (unsigned long)statement_len);
MARIADB_END_ALLOW_THREADS(self);
if (rc)
{
mariadb_throw_exception(self->stmt->mysql, NULL, 0, NULL);
goto error;
}
/* if we have a result set, we can't process it - so we will return
an error. (XA RECOVER is the only command which returns a result set
and can't be prepared) */
if (mysql_field_count(self->stmt->mysql))
{
MYSQL_RES *result;
/* we need to clear the result first, otherwise the cursor remains
in usuable state (query out of order) */
if ((result= mysql_store_result(self->stmt->mysql)))
mysql_free_result(result);
mariadb_throw_exception(NULL, Mariadb_NotSupportedError, 0, "This command is not supported by MariaDB Connector/Python");
goto error;
}
goto end;
}
/* throw exception from statement handle */
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
goto error;
}
} else {
/* We are already prepared, so just reexecute statement */
mysql_stmt_bind_param(self->stmt, self->params);
MARIADB_BEGIN_ALLOW_THREADS(self);
rc= mysql_stmt_execute(self->stmt);
MARIADB_END_ALLOW_THREADS(self);
if (rc)
{
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
goto error;
}
}
}
if (MrdbCursor_InitResultSet(self))
goto error;
if (self->field_count)
{
self->row_count= CURSOR_NUM_ROWS(self);
} else {
self->row_count= CURSOR_AFFECTED_ROWS(self);
}
self->lastrow_id= CURSOR_INSERT_ID(self);
end:
MARIADB_FREE_MEM(self->value);
Py_RETURN_NONE;
error:
self->row_count= -1;
self->lastrow_id= 0;
MrdbParser_end(self->parser);
self->parser= NULL;
MrdbCursor_clear(self, 0);
return NULL;
}
/* }}} */
/* {{{ MrdbCursor_fieldcount() */
PyObject *MrdbCursor_fieldcount(MrdbCursor *self)
{
if (PyErr_Occurred())
return NULL;
return PyLong_FromLong((long)self->field_count);
}
/* }}} */
/* {{{ MrdbCursor_description
PEP-249 description method()
Please note that the returned tuple contains eight (instead of
seven items, since we need the field flag
*/
static
PyObject *MrdbCursor_description(MrdbCursor *self)
{
PyObject *obj= NULL;
unsigned int field_count= self->field_count;
if (PyErr_Occurred())
return NULL;
if (self->fields && field_count)
{
uint32_t i;
if (!(obj= PyTuple_New(field_count)))
return NULL;
for (i=0; i < field_count; i++)
{
uint32_t precision= 0;
uint32_t decimals= 0;
MY_CHARSET_INFO cs;
unsigned long display_length;
long packed_len= 0;
PyObject *desc;
enum enum_extended_field_type ext_type= mariadb_extended_field_type(&self->fields[i]);
display_length= self->fields[i].max_length > self->fields[i].length ?
self->fields[i].max_length : self->fields[i].length;
mysql_get_character_set_info(self->connection->mysql, &cs);
if (cs.mbmaxlen > 1)
{
packed_len= display_length;
display_length/= cs.mbmaxlen;
}
else {
packed_len= mysql_ps_fetch_functions[self->fields[i].type].pack_len;
}
if (self->fields[i].decimals)
{
if (self->fields[i].decimals < 31)
{
decimals= self->fields[i].decimals;
precision= self->fields[i].length;
display_length= precision + 1;
}
}
if (ext_type == EXT_TYPE_JSON)
self->fields[i].type= MYSQL_TYPE_JSON;
if (!(desc= Py_BuildValue("(sIIiIIOI)",
self->fields[i].name,
self->fields[i].type,
display_length,
packed_len >= 0 ? packed_len : -1,
precision,
decimals,
PyBool_FromLong(!IS_NOT_NULL(self->fields[i].flags)),
self->fields[i].flags)))
{
Py_XDECREF(obj);
mariadb_throw_exception(NULL, Mariadb_OperationalError, 0,
"Can't build descriptor record");
return NULL;
}
PyTuple_SetItem(obj, i, desc);
}
return obj;
}
Py_INCREF(Py_None);
return Py_None;
}
/* }}} */
static int MrdbCursor_fetchinternal(MrdbCursor *self)
{
unsigned int field_count= self->field_count;
MYSQL_ROW row;
int rc;
unsigned int i;
self->fetched= 1;
if (!self->is_text)
{
rc= mysql_stmt_fetch(self->stmt);
if (rc == MYSQL_NO_DATA)
return 1;
return 0;
}
if (!(row= mysql_fetch_row(self->result)))
{
return 1;
}
for (i= 0; i < field_count; i++)
{
field_fetch_fromtext(self, row[i], i);
}
return 0;
}
static PyObject *
MrdbCursor_fetchone(MrdbCursor *self)
{
PyObject *row;
uint32_t i;
unsigned int field_count= self->field_count;
if (self->cursor_type == CURSOR_TYPE_READ_ONLY)
MARIADB_CHECK_STMT(self);
if (PyErr_Occurred())
{
return NULL;
}
if (!field_count)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Cursor doesn't have a result set");
return NULL;
}
if (MrdbCursor_fetchinternal(self))
{
Py_INCREF(Py_None);
return Py_None;
}
self->row_number++;
if (!(row= mariadb_get_sequence_or_tuple(self)))
{
return NULL;
}
for (i= 0; i < field_count; i++)
{
ma_set_result_column_value(self, row, i);
}
return row;
}
static PyObject *
MrdbCursor_scroll(MrdbCursor *self,
PyObject *args,
PyObject *kwargs)
{
char *modestr= NULL;
PyObject *Pos;
long position= 0;
unsigned long long new_position= 0;
uint8_t mode= 0; /* default: relative */
char *kw_list[]= {"", "mode", NULL};
const char *scroll_modes[]= {"relative", "absolute", NULL};
MARIADB_CHECK_STMT(self);
if (PyErr_Occurred())
{
return NULL;
}
if (!self->field_count)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Cursor doesn't have a result set");
return NULL;
}
if (!self->is_buffered)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"This method is available only for cursors with buffered result set "
"or a read only cursor type");
return NULL;
}
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"O!|s", kw_list, &PyLong_Type, &Pos, &modestr))
{
return NULL;
}
if (modestr != NULL)
{
while (scroll_modes[mode]) {
if (!strcmp(scroll_modes[mode], modestr))
break;
mode++;
};
}
else {
mode= 0;
}
if (!scroll_modes[mode]) {
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
"Invalid mode '%s'", modestr);
return NULL;
}
if (!(position= PyLong_AsLong(Pos)) && !mode)
{
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
"Invalid position value 0");
return NULL;
}
if (!mode)
{
if ((long long)self->row_number + position < 0 ||
self->row_number + position > CURSOR_NUM_ROWS(self))
{
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
"Position value is out of range");
return NULL;
}
new_position= self->row_number + position;
}
else {
new_position= position; /* absolute */
}
if (!self->is_text)
{
mysql_stmt_data_seek(self->stmt, new_position);
}
else {
mysql_data_seek(self->result, new_position);
}
self->row_number= (unsigned long)new_position;
Py_INCREF(Py_None);
return Py_None;
}
PyObject *
MrdbCursor_fetchmany(MrdbCursor *self,
PyObject *args,
PyObject *kwargs)
{
PyObject *List= NULL;
uint32_t i;
unsigned long rows= 0;
static char *kw_list[]= {"size", NULL};
unsigned int field_count= self->field_count;
MARIADB_CHECK_STMT(self);
if (PyErr_Occurred())
{
return NULL;
}
if (!field_count)
{
mariadb_throw_exception(0, Mariadb_ProgrammingError, 0,
"Cursor doesn't have a result set");
return NULL;
}
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"|l:fetchmany", kw_list, &rows))
{
return NULL;
}
if (!rows)
{
rows= self->row_array_size;
}
if (!(List= PyList_New(0)))
{
return NULL;
}
/* if rows=0, return an empty list */
if (!rows)
{
return List;
}
for (i=0; i < rows; i++)
{
uint32_t j;
PyObject *Row;
if (MrdbCursor_fetchinternal(self))
{
goto end;
}
self->row_count= CURSOR_NUM_ROWS(self);
if (!(Row= mariadb_get_sequence_or_tuple(self)))
{
return NULL;
}
for (j=0; j < field_count; j++)
{
ma_set_result_column_value(self, Row, j);
}
PyList_Append(List, Row);
Py_DECREF(Row);
}
end:
return List;
}
static PyObject *
mariadb_get_sequence_or_tuple(MrdbCursor *self)
{
switch (self->result_format)
{
case RESULT_NAMED_TUPLE:
return PyStructSequence_New(self->sequence_type);
case RESULT_DICTIONARY:
return PyDict_New();
default:
return PyTuple_New(self->field_count);
}
}
static PyObject *
MrdbCursor_fetchall(MrdbCursor *self)
{
PyObject *List;
unsigned int field_count= self->field_count;
MARIADB_CHECK_STMT(self);
if (PyErr_Occurred())
{
return NULL;
}
if (!field_count)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Cursor doesn't have a result set");
return NULL;
}
if (!(List= PyList_New(0)))
{
return NULL;
}
while (!MrdbCursor_fetchinternal(self))
{
uint32_t j;
PyObject *Row;
self->row_number++;
if (!(Row= mariadb_get_sequence_or_tuple(self)))
{
return NULL;
}
for (j=0; j < field_count; j++)
{
ma_set_result_column_value(self, Row, j);
}
PyList_Append(List, Row);
/* CONPY-99: Decrement Row to prevent memory leak */
Py_DECREF(Row);
}
self->row_count= (self->is_text) ? mysql_num_rows(self->result) :
mysql_stmt_num_rows(self->stmt);
return List;
}
/* MrdbCursor_executemany_fallback
bulk execution for server < 10.2.6
*/
static uint8_t
MrdbCursor_executemany_fallback(MrdbCursor *self,
const char *statement,
size_t len)
{
uint32_t i;
int rc= 0;
rc= mysql_query(self->stmt->mysql, "BEGIN");
if (rc)
goto error_mysql;
if (mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->param_count))
{
goto error;
}
self->row_count= 0;
for (i=0; i < self->array_size; i++)
{
/* Load values */
if (mariadb_param_update(self, self->params, i))
{
return 1;
}
if (mysql_stmt_bind_param(self->stmt, self->params))
{
goto error;
}
MARIADB_BEGIN_ALLOW_THREADS(self);
if (i==0)
{
rc= mysql_stmt_prepare(self->stmt, self->parser->statement.str,
(unsigned long)self->parser->statement.length);
}
if (!rc)
{
rc= mysql_stmt_execute(self->stmt);
}
MARIADB_END_ALLOW_THREADS(self);
if (rc)
{
goto error;
}
self->row_count++;
}
self->lastrow_id= CURSOR_INSERT_ID(self);
rc= mysql_query(self->stmt->mysql, "COMMIT");
if (!rc)
return 0;
error_mysql:
mariadb_throw_exception(self->stmt->mysql, NULL, 0, NULL);
return 1;
error:
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
return 1;
}
/*
Note: When conecting to a server < 10.2.6 this command will be emulated
by executing preparing and executing statement n times (where n is
the number of tuples in list)
*/
static PyObject *
MrdbCursor_executemany(MrdbCursor *self,
PyObject *Args)
{
char *statement= NULL;
Py_ssize_t statement_len= 0;
int rc;
uint8_t do_prepare= 1;
char errmsg[128];
MARIADB_CHECK_STMT(self);
if (PyErr_Occurred())
{
return NULL;
}
self->data= NULL;
if (!PyArg_ParseTuple(Args, "s#O", &statement, &statement_len,
&self->data))
{
return NULL;
}
if (!self->data)
{
PyErr_SetString(PyExc_TypeError, "No data provided");
return NULL;
}
if (self->is_prepared && self->statement)
do_prepare= 0;
/* if cursor type is not prepared, we need to clear the cursor first */
if (!self->is_prepared && self->statement)
{
MrdbCursor_clear(self, 0);
MrdbParser_end(self->parser);
self->parser= NULL;
}
if (!self->stmt)
{
if (!(self->stmt= mysql_stmt_init(self->connection->mysql)))
{
mariadb_throw_exception(self->connection->mysql, NULL, 0, NULL);
goto error;
}
}
self->is_text= 0;
if (!self->parser)
{
if (!(self->parser= MrdbParser_init(statement, (size_t)statement_len)))
{
exit(-1);
}
if (MrdbParser_parse(self->parser, 1, errmsg, 128))
{
PyErr_SetString(Mariadb_ProgrammingError, errmsg);
goto error;
}
CURSOR_SET_STATEMENT(self, statement, statement_len);
}
if (mariadb_check_bulk_parameters(self, self->data))
goto error;
/* If the server doesn't support bulk execution (< 10.2.6),
we need to call a fallback routine */
if (!(self->connection->extended_server_capabilities &
(MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)))
{
if (MrdbCursor_executemany_fallback(self, self->parser->statement.str,
self->parser->statement.length))
goto error;
MARIADB_FREE_MEM(self->values);
Py_RETURN_NONE;
}
mysql_stmt_attr_set(self->stmt, STMT_ATTR_ARRAY_SIZE, &self->array_size);
if (do_prepare)
{
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->param_count);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, (void *)self);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_PARAM, mariadb_param_update);
mysql_stmt_bind_param(self->stmt, self->params);
if (Mrdb_execute_direct(self))
{
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
goto error;
}
}
else {
MARIADB_BEGIN_ALLOW_THREADS(self);
rc= mysql_stmt_execute(self->stmt);
MARIADB_END_ALLOW_THREADS(self);
if (rc)
{
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
goto error;
}
}
self->row_count= CURSOR_AFFECTED_ROWS(self);
self->lastrow_id= CURSOR_INSERT_ID(self);
MARIADB_FREE_MEM(self->values);
Py_RETURN_NONE;
error:
MrdbCursor_clear(self, 0);
MrdbParser_end(self->parser);
self->parser= NULL;
return NULL;
}
static PyObject *
MrdbCursor_nextset(MrdbCursor *self)
{
int rc;
MARIADB_CHECK_STMT(self);
if (PyErr_Occurred())
{
return NULL;
}
/* hmmm */
if (!self->field_count)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Cursor doesn't have a result set");
return NULL;
}
MARIADB_BEGIN_ALLOW_THREADS(self);
if (!self->is_text)
rc= mysql_stmt_next_result(self->stmt);
else
{
if (self->result)
{
mysql_free_result(self->result);
self->result= NULL;
}
rc= mysql_next_result(self->connection->mysql);
}
MARIADB_END_ALLOW_THREADS(self);
if (rc)
{
Py_INCREF(Py_None);
return Py_None;
}
if (CURSOR_FIELD_COUNT(self))
{
if (MrdbCursor_InitResultSet(self))
{
return NULL;
}
}
else {
self->fields= 0;
}
Py_RETURN_TRUE;
}
static PyObject *
Mariadb_row_count(MrdbCursor *self)
{
/* PEP-249 requires to return -1 if the cursor was not executed before */
if (!self->statement)
{
return PyLong_FromLongLong(-1);
}
return PyLong_FromLongLong(self->row_count);
}
static PyObject *
Mariadb_row_number(MrdbCursor *self)
{
unsigned int field_count= self->field_count;
if (!field_count) {
Py_INCREF(Py_None);
return Py_None;
}
return PyLong_FromLongLong(self->row_number);
}
static PyObject *
MrdbCursor_warnings(MrdbCursor *self)
{
MARIADB_CHECK_STMT(self);
return PyLong_FromLong((long)CURSOR_WARNING_COUNT(self));
}
static PyObject *
MrdbCursor_getbuffered(MrdbCursor *self)
{
if (self->is_buffered)
{
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
static int
MrdbCursor_setbuffered(MrdbCursor *self, PyObject *arg)
{
if (!arg || !CHECK_TYPE(arg, &PyBool_Type))
{
PyErr_SetString(PyExc_TypeError, "Argument must be boolean");
return -1;
}
self->is_buffered= PyObject_IsTrue(arg);
return 0;
}
static PyObject *
MrdbCursor_lastrowid(MrdbCursor *self)
{
if (!self->lastrow_id)
{
Py_INCREF(Py_None);
return Py_None;
}
return PyLong_FromUnsignedLongLong(self->lastrow_id);
}
/* iterator protocol */
static PyObject *
MrdbCursor_iter(PyObject *self)
{
MARIADB_CHECK_STMT(((MrdbCursor *)self));
Py_INCREF(self);
return self;
}
static PyObject *
MrdbCursor_iternext(PyObject *self)
{
PyObject *res;
res= MrdbCursor_fetchone((MrdbCursor *)self);
if (res && res == Py_None)
{
Py_DECREF(res);
res= NULL;
}
return res;
}
static PyObject
*MrdbCursor_closed(MrdbCursor *self)
{
if (self->is_closed || self->connection->mysql == NULL)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
static PyObject *
MrdbCursor_sp_outparams(MrdbCursor *self)
{
if (!self->is_closed && self->stmt &&
self->stmt->mysql)
{
uint32_t server_status;
mariadb_get_infov(self->stmt->mysql, MARIADB_CONNECTION_SERVER_STATUS, &server_status);
if (server_status & SERVER_PS_OUT_PARAMS)
{
Py_RETURN_TRUE;
}
}
Py_RETURN_FALSE;
}
static PyObject *
MrdbCursor_callproc(MrdbCursor *self, PyObject *args)
{
const char *sp;
Py_ssize_t sp_len;
PyObject *data= NULL;
uint32_t i, param_count= 0;
char *stmt= NULL;
size_t stmt_len= 0;
PyObject *new_args= NULL;
PyObject *rc= NULL;
MARIADB_CHECK_STMT(((MrdbCursor *)self));
if (!PyArg_ParseTuple(args, "s#|O", &sp, &sp_len, &data))
return NULL;
if (data)
param_count= (uint32_t)data_count(data);
stmt_len= sp_len + 5 + 3 + param_count * 2 + 1;
if (!(stmt= (char *)PyMem_RawCalloc(1, stmt_len)))
goto end;
sprintf(stmt, "CALL %s(", sp);
for (i=0; i < param_count; i++)
{
if (i)
{
strcat(stmt, ",");
}
strcat(stmt, "?");
}
strcat(stmt, ")");
new_args= PyTuple_New(2);
PyTuple_SetItem(new_args, 0, PyUnicode_FromString(stmt));
PyTuple_SetItem(new_args, 1, data);
rc= MrdbCursor_execute(self, new_args, NULL);
end:
if (stmt)
{
PyMem_RawFree(stmt);
}
return rc;
}
static PyObject *
MrdbCursor_enter(MrdbCursor *self, PyObject *args __attribute__((unused)))
{
Py_INCREF(self);
return (PyObject *)self;
}
static PyObject *
MrdbCursor_exit(MrdbCursor *self, PyObject *args __attribute__((unused)))
{
PyObject *rc= NULL,
*tmp= NULL;
if ((tmp= PyObject_CallMethod((PyObject *)self, "close", "")))
{
rc= Py_None;
Py_INCREF(rc);
}
Py_XDECREF(tmp);
return rc;
}