mirror of
https://github.com/mariadb-corporation/mariadb-connector-python.git
synced 2025-07-27 13:01:19 +00:00
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:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Normalise line endings:
|
||||
* text=auto
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
@ -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."
|
||||
);
|
||||
|
2
setup.py
2
setup.py
@ -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',
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
Reference in New Issue
Block a user