CONPY-31: Implement callproc method

Input/Output or Output parameters have to be retrieved by .fetch methods,
  the .sp_outparams attribute indicates if the result set contains output
  parameters.
This commit is contained in:
Georg Richter
2019-12-03 20:18:38 +01:00
parent a12b42e820
commit e218d19060
7 changed files with 194 additions and 15 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalise line endings:
* text=auto

View File

@ -32,6 +32,33 @@ The cursor class
same connection will fail, unless the entire result set was read. For buffering
the entire result set an additional parameter *buffered=True* must be specified.
.. method:: callproc(procedure_name, args=())
Executes a stored procedure. The args sequence must contain an entry for
each parameter the procedure expects.
Input/Output or Output parameters have to be retrieved by .fetch methods,
the .sp_outparams attribute indicates if the result set contains output
parameters.
Example::
>>>cursor.execute("CREATE PROCEDURE p1(IN i1 VAR CHAR(20), OUT o2 VARCHAR(40))"
"BEGIN"
" SELECT 'hello'"
" o2:= 'test'"
"END")
>>>cursor.callproc('p1', ('foo', 0))
>>> cursor.sp_outparams
False
>>> cursor.fetchone()
('hello',)
>>> cursor.nextset()
True
>>> cursor.sp_outparams
True
>>> cursor.fetchone()
('test',)
.. method:: executemany(statement, data)
Exactly behaves like .execute() but accepts a list of tuples, where each
@ -147,6 +174,11 @@ The cursor class
AUTO_INCREMENT attribute and LAST_INSERT_ID was not used, the returned
value will be zero
.. data:: sp_outparams
This read-only attribute undicates if the current result set contains inout
or out parameters from a previously executed stored procedure.
.. data:: rowcount
This read-only attribute specifies the number of rows that the last

View File

@ -10,7 +10,7 @@ Prerequisits
- Python 3 (minimum supported version is 3.6)
- MariaDB Server 10.x or MySQL Server
- MariaDB Connector/C 3.1.3 or newer
- MariaDB Connector/C 3.1.5 or newer
:: _build-prerequisits:
@ -24,16 +24,27 @@ installed from source distribution package or github.
- Python development files (Usually they are installed with package **python-dev**).
- MariaDB Connector/C libraries and header files (Either from MariaDB server package or
from MariaDB Connector/C package).
- For Posix systems: TLS libraries, e.g. GnuTLS or OpenSSL (default)
Binary installation
-------------------
MariaDB Connector/C is also available from PyPi as wheel packages for Linux, Windows and MacOS.
These binary packages are not intended for production use, since there might be several limitations
and bottlenecks, e.g.:
- Binaries for Posix systems come with their own version of libraries which will be used regardless
of other libraries installed on the system. This might lead to unexpected results, e.g. when using
different OpenSSL libraries.
- Dynamic MariaDB plugins (e.g. authentication plugins) are not part of the package and must
be installed separetly by installing MariaDB Connector/C or MariaDB Server package.
Make sure you have an up to date version of pip and install it with
.. code-block:: console
$ pip install mariadb
$ pip install mariadb-binary

View File

@ -128,6 +128,35 @@ PyDoc_STRVAR(
"Fetches all rows of a pending result set and returns a list of tuples.\n"
);
PyDoc_STRVAR(
cursor_callproc__doc__,
"callproc(procedure_name, args=())\n"
"--\n"
"\n"
"Executes a stored procedure. The args sequence must contain an entry for\n"
"each parameter the procedure expects.\n"
"Input/Output or Output parameters have to be retrieved by .fetch methods,\n"
"the .sp_outparams attribute indicates if the result set contains output\n"
"parameters\n\n"
"Example:\n\n"
">>>cursor.execute(\"CREATE PROCEDURE p1(IN i1 VAR CHAR(20), OUT o2 VARCHAR(40))\"\n"
" \"BEGIN\"\n"
" \" SELECT 'hello'\"\n"
" \" o2:= 'test'\"\n"
" \"END\")\n"
">>>cursor.callproc('p1', ('foo', 0))\n"
">>> cursor.sp_outparams\n"
"False\n"
">>> cursor.fetchone()\n"
"('hello',)\n"
">>> cursor.nextset()\n"
"True\n"
">>> cursor.sp_outparams\n"
"True\n"
">>> cursor.fetchone()\n"
"('test',)"
);
PyDoc_STRVAR(
cursor_fetchone__doc__,
"fetchone()\n"
@ -183,15 +212,6 @@ PyDoc_STRVAR(
"Required by PEP-249. Does nothing in MariaDB Connector/Python"
);
PyDoc_STRVAR(
cursor_callproc__doc__,
"callproc()\n"
"--\n"
"\n"
"Required by PEP-249. Does nothing in MariaDB Connector/Python,\n"
"use the execute method with syntax 'CALL {procedurename}' instead"
);
PyDoc_STRVAR(
cursor_next__doc__,
"next()\n"
@ -238,3 +258,10 @@ PyDoc_STRVAR(
"(read/write)\n\n"
"the number of rows to fetch"
);
PyDoc_STRVAR(
cursor_sp_outparam__doc__,
"(read)\n\n"
"Indicates if the current result set contains inout or out parameter\n"
"from a previous executed stored procedure."
);

View File

@ -18,7 +18,7 @@ if os.name == "nt":
cfg = get_config(options)
setup(name='mariadb',
version='0.9.4',
version='0.9.41',
python_requires='>=3.6',
classifiers = [
'Development Status :: 3 - Alpha',

View File

@ -36,6 +36,8 @@ static PyObject *MrdbCursor_fetchmany(MrdbCursor *self,
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);
@ -86,6 +88,7 @@ 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[]=
@ -102,12 +105,17 @@ static PyGetSetDef MrdbCursor_sets[]=
cursor_closed__doc__, NULL},
{"buffered", (getter)MrdbCursor_getbuffered, (setter)MrdbCursor_setbuffered,
cursor_buffered__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__},
@ -353,6 +361,7 @@ static PyObject *Mariadb_no_operation(MrdbCursor *self,
static
void MrdbCursor_clear(MrdbCursor *self)
{
if (!self->is_text && self->stmt) {
uint32_t val= 0;
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, 0);
@ -1347,3 +1356,58 @@ static PyObject *MrdbCursor_closed(MrdbCursor *self)
}
/* }}} */
/* {{{ MrdbCursor_inoutparam */
static PyObject *MrdbCursor_sp_outparams(MrdbCursor *self)
{
if (!self->is_closed && self->stmt &&
self->stmt->mysql &&
(self->stmt->mysql->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,
&PyTuple_Type, &data))
return NULL;
if (data)
param_count= PyTuple_Size(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);
Py_DECREF(new_args);
end:
if (stmt)
PyMem_RawFree(stmt);
return rc;
}

View File

@ -472,8 +472,8 @@ class TestCursor(unittest.TestCase):
cursor.execute("SELECT LAST_INSERT_ID()")
row = cursor.fetchone()
self.assertEqual(row[0], 1)
# del cursor
# cursor= self.connection.cursor()
del cursor
cursor= self.connection.cursor()
vals = [(3, "bar"), (4, "this")]
cursor.executemany("INSERT INTO test_conpy_15 VALUES (?,?)", vals)
self.assertEqual(cursor.lastrowid, 4)
@ -596,7 +596,50 @@ class TestCursor(unittest.TestCase):
self.assertEqual(row[0], 'foo')
del cursor, con
def test_sp1(self):
con= create_connection()
cursor= con.cursor()
cursor.execute("DROP PROCEDURE IF EXISTS p1")
cursor.execute("CREATE PROCEDURE p1( )\nBEGIN\n SELECT 1;\nEND")
cursor.callproc("p1")
row= cursor.fetchone()
self.assertEqual(row[0], 1)
cursor.execute("DROP PROCEDURE IF EXISTS p1")
def test_sp2(self):
con= create_connection()
cursor= con.cursor()
cursor.execute("DROP PROCEDURE IF EXISTS p2")
cursor.execute("CREATE PROCEDURE p2(IN s1 VARCHAR(20), IN s2 VARCHAR(20), OUT o1 VARCHAR(40) )\nBEGIN\n SET o1:=CONCAT(s1,s2);\nEND")
cursor.callproc("p2", ("foo", "bar", 1))
self.assertEqual(cursor.sp_outparams, True)
row= cursor.fetchone()
self.assertEqual(row[0], "foobar")
cursor.nextset()
del cursor
cursor=con.cursor()
cursor.execute("CALL p2(?,?,?)", ("foo", "bar", 0))
self.assertEqual(cursor.sp_outparams, True)
row= cursor.fetchone()
self.assertEqual(row[0], "foobar")
cursor.execute("DROP PROCEDURE IF EXISTS p2")
del cursor, con
if __name__ == '__main__':
def test_sp3(self):
con= create_connection()
cursor= con.cursor()
cursor.execute("DROP PROCEDURE IF EXISTS p3")
cursor.execute("CREATE PROCEDURE p3(IN s1 VARCHAR(20), IN s2 VARCHAR(20), OUT o1 VARCHAR(40) )\nBEGIN\n SELECT '1';SET o1:=CONCAT(s1,s2);\nEND")
cursor.callproc("p3", ("foo", "bar", 1))
self.assertEqual(cursor.sp_outparams, False)
row= cursor.fetchone()
self.assertEqual(row[0], "1")
cursor.nextset()
self.assertEqual(cursor.sp_outparams, True)
row= cursor.fetchone()
self.assertEqual(row[0], "foobar")
cursor.execute("DROP PROCEDURE IF EXISTS p3")
del cursor, con
f __name__ == '__main__':
unittest.main()