Skip to content

Commit 691d62c

Browse files
committed
BUG29195610: Fix cursor.callproc() using namedtuple and dictionary cursors
This patch fixes the exception raised by namedtuple cursor due to the invalid naming of columns generated. It also fixes the incorrect type of result generated by dictionary cursors. Tests were added for all cursor types.
1 parent 48e97c3 commit 691d62c

File tree

3 files changed

+111
-9
lines changed

3 files changed

+111
-9
lines changed

lib/mysql/connector/cursor.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -774,10 +774,17 @@ def callproc(self, procname, args=()):
774774
can_consume_results = self._connection._consume_results
775775
for result in self._connection.cmd_query_iter(call):
776776
self._connection._consume_results = False
777-
if self._raw:
778-
tmp = MySQLCursorBufferedRaw(self._connection._get_self())
777+
if isinstance(self, (MySQLCursorDict,
778+
MySQLCursorBufferedDict)):
779+
cursor_class = MySQLCursorBufferedDict
780+
elif isinstance(self, (MySQLCursorNamedTuple,
781+
MySQLCursorBufferedNamedTuple)):
782+
cursor_class = MySQLCursorBufferedNamedTuple
783+
elif self._raw:
784+
cursor_class = MySQLCursorBufferedRaw
779785
else:
780-
tmp = MySQLCursorBuffered(self._connection._get_self())
786+
cursor_class = MySQLCursorBuffered
787+
tmp = cursor_class(self._connection._get_self())
781788
tmp._executed = "(a result of {0})".format(call)
782789
tmp._handle_result(result)
783790
if tmp._warnings is not None:
@@ -788,7 +795,12 @@ def callproc(self, procname, args=()):
788795
# pylint: enable=W0212
789796

790797
if argnames:
791-
select = "SELECT {0}".format(','.join(argtypes))
798+
# Create names aliases to be compatible with namedtuples
799+
args = [
800+
"{} AS {}".format(name, alias) for name, alias in
801+
zip(argtypes, [arg.lstrip("@_") for arg in argnames])
802+
]
803+
select = "SELECT {}".format(",".join(args))
792804
self.execute(select)
793805
self._stored_results = results
794806
return self.fetchone()

lib/mysql/connector/cursor_cext.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2014, 2020, Oracle and/or its affiliates.
22
#
33
# This program is free software; you can redistribute it and/or modify
44
# it under the terms of the GNU General Public License, version 2.0, as
@@ -450,10 +450,17 @@ def callproc(self, procname, args=()):
450450
while self._cnx.result_set_available:
451451
result = self._cnx.fetch_eof_columns()
452452
# pylint: disable=W0212
453-
if self._raw:
454-
cur = CMySQLCursorBufferedRaw(self._cnx._get_self())
453+
if isinstance(self, (CMySQLCursorDict,
454+
CMySQLCursorBufferedDict)):
455+
cursor_class = CMySQLCursorBufferedDict
456+
elif isinstance(self, (CMySQLCursorNamedTuple,
457+
CMySQLCursorBufferedNamedTuple)):
458+
cursor_class = CMySQLCursorBufferedNamedTuple
459+
elif self._raw:
460+
cursor_class = CMySQLCursorBufferedRaw
455461
else:
456-
cur = CMySQLCursorBuffered(self._cnx._get_self())
462+
cursor_class = CMySQLCursorBuffered
463+
cur = cursor_class(self._cnx._get_self())
457464
cur._executed = "(a result of {0})".format(call)
458465
cur._handle_result(result)
459466
# pylint: enable=W0212
@@ -464,7 +471,12 @@ def callproc(self, procname, args=()):
464471

465472
if argnames:
466473
self.reset()
467-
select = "SELECT {0}".format(','.join(argtypes))
474+
# Create names aliases to be compatible with namedtuples
475+
args = [
476+
"{} AS {}".format(name, alias) for name, alias in
477+
zip(argtypes, [arg.lstrip("@_") for arg in argnames])
478+
]
479+
select = "SELECT {}".format(",".join(args))
468480
self.execute(select)
469481

470482
return self.fetchone()

tests/test_bugs.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import os
4646
import gc
4747
import tempfile
48+
from collections import namedtuple
4849
from datetime import date, datetime, timedelta, time
4950
from threading import Thread
5051
import traceback
@@ -5703,3 +5704,80 @@ def test_cext_pool_support(self):
57035704

57045705
exp = ('2',) if PY2 else (b'2',)
57055706
self.assertNotEqual(exp, cnx.get_rows()[0][0])
5707+
5708+
5709+
class BugOra29195610(tests.MySQLConnectorTests):
5710+
"""BUG#29195610: CALLPROC() NOT SUPPORTED WITH NAMED TUPLE CURSOR AND FOR
5711+
DICT CURSOR IS IGNORED
5712+
"""
5713+
def setUp(self):
5714+
config = tests.get_mysql_config()
5715+
with connection.MySQLConnection(**config) as cnx:
5716+
cnx.cmd_query("DROP TABLE IF EXISTS bug29195610")
5717+
cnx.cmd_query("DROP PROCEDURE IF EXISTS sp_bug29195610")
5718+
cnx.cmd_query("CREATE TABLE bug29195610 (id INT, name VARCHAR(5))")
5719+
cnx.cmd_query(
5720+
"INSERT INTO bug29195610 (id, name) VALUES (2020, 'Foo')"
5721+
)
5722+
cnx.cmd_query(
5723+
"CREATE PROCEDURE sp_bug29195610 (in_id INT) "
5724+
"SELECT id, name FROM bug29195610 WHERE id = in_id;"
5725+
)
5726+
5727+
def tearDown(self):
5728+
config = tests.get_mysql_config()
5729+
with connection.MySQLConnection(**config) as cnx:
5730+
cnx.cmd_query("DROP TABLE IF EXISTS bug29195610")
5731+
cnx.cmd_query("DROP PROCEDURE IF EXISTS sp_bug29195610")
5732+
5733+
@foreach_cnx()
5734+
def test_callproc_cursor_types(self):
5735+
named_tuple = namedtuple("Row", ["id", "name"])
5736+
cases = [
5737+
(
5738+
{},
5739+
[(2020, "Foo")]
5740+
),
5741+
(
5742+
{"buffered": True},
5743+
[(2020, "Foo")]
5744+
),
5745+
(
5746+
{"raw": True},
5747+
[(bytearray(b"2020"), bytearray(b"Foo"))]
5748+
),
5749+
(
5750+
{"raw": True, "buffered": True},
5751+
[(bytearray(b"2020"), bytearray(b"Foo"))]
5752+
),
5753+
(
5754+
{"raw": True, "buffered": True},
5755+
[(bytearray(b"2020"), bytearray(b"Foo"))]
5756+
),
5757+
(
5758+
{"dictionary": True},
5759+
[{"id": 2020, "name": "Foo"}]
5760+
),
5761+
(
5762+
{"dictionary": True, "buffered": True},
5763+
[{"id": 2020, "name": "Foo"}]
5764+
),
5765+
(
5766+
{"named_tuple": True},
5767+
[named_tuple(2020, "Foo")]
5768+
),
5769+
(
5770+
{"named_tuple": True, "buffered": True},
5771+
[named_tuple(2020, "Foo")]
5772+
)
5773+
]
5774+
5775+
for cursor_type, exp in cases:
5776+
with self.cnx.cursor(**cursor_type) as cur:
5777+
cur.callproc("sp_bug29195610", (2020,))
5778+
for res in cur.stored_results():
5779+
self.assertEqual(exp, res.fetchall())
5780+
5781+
with self.cnx.cursor(prepared=True) as cur:
5782+
self.assertRaises(errors.NotSupportedError,
5783+
cur.callproc, 'sp_bug29195610', (2020,))

0 commit comments

Comments
 (0)