Skip to content

Commit 1bbdc31

Browse files
committed
BUG32497631: Prepared statements fail when parameters are not given
This patch fixes the failure when using the C extension and executing prepared statements with placeholders but without given parameters. The change mimics the pure Python implementation by preparing but not executing the statement for this scenario. A test was added for regression.
1 parent 24dcf48 commit 1bbdc31

File tree

4 files changed

+40
-6
lines changed

4 files changed

+40
-6
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ v8.0.24
1515
- WL#14239: Remove Python 2.7 support
1616
- WL#14212: Support connection close notification
1717
- WL#14027: Add support for Python 3.9
18+
- BUG#32497631: Prepared statements fail when parameters are not given
1819
- BUG#32435181: Add support for Django 3.2
1920
- BUG#32162928: Change user command fails on pure python implementation
2021
- BUG#32120659: Prepared statements w/o parameters violates MySQL protocol

lib/mysql/connector/cursor.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2009, 2020, Oracle and/or its affiliates.
1+
# Copyright (c) 2009, 2021, 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
@@ -1174,7 +1174,7 @@ def _handle_result(self, result):
11741174
elif 'server_status' in result[2]:
11751175
self._handle_server_status(result[2]['server_status'])
11761176

1177-
def execute(self, operation, params=(), multi=False): # multi is unused
1177+
def execute(self, operation, params=None, multi=False): # multi is unused
11781178
"""Prepare and execute a MySQL Prepared Statement
11791179
11801180
This method will preare the given operation and execute it using
@@ -1211,12 +1211,14 @@ def execute(self, operation, params=(), multi=False): # multi is unused
12111211

12121212
if self._prepared['parameters'] and not params:
12131213
return
1214-
elif len(self._prepared['parameters']) != len(params):
1214+
elif params and len(self._prepared['parameters']) != len(params):
12151215
raise errors.ProgrammingError(
12161216
errno=1210,
12171217
msg="Incorrect number of arguments " \
12181218
"executing prepared statement")
12191219

1220+
if params is None:
1221+
params = ()
12201222
res = self._connection.cmd_stmt_execute(
12211223
self._prepared['statement_id'],
12221224
data=params,

lib/mysql/connector/cursor_cext.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2014, 2020, Oracle and/or its affiliates.
1+
# Copyright (c) 2014, 2021, 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
@@ -911,7 +911,7 @@ def reset(self, free=True):
911911
self._cnx.cmd_stmt_reset(self._stmt)
912912
super(CMySQLCursorPrepared, self).reset(free=free)
913913

914-
def execute(self, operation, params=(), multi=False): # multi is unused
914+
def execute(self, operation, params=None, multi=False): # multi is unused
915915
"""Prepare and execute a MySQL Prepared Statement
916916
917917
This method will preare the given operation and execute it using
@@ -956,12 +956,16 @@ def execute(self, operation, params=(), multi=False): # multi is unused
956956

957957
self._cnx.cmd_stmt_reset(self._stmt)
958958

959-
if params and self._stmt.param_count != len(params):
959+
if self._stmt.param_count > 0 and not params:
960+
return
961+
elif params and self._stmt.param_count != len(params):
960962
raise errors.ProgrammingError(
961963
errno=1210,
962964
msg="Incorrect number of arguments executing prepared "
963965
"statement")
964966

967+
if params is None:
968+
params = ()
965969
res = self._cnx.cmd_stmt_execute(self._stmt, *params)
966970
if res:
967971
self._handle_result(res)

tests/test_bugs.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6061,3 +6061,30 @@ def tearDown(self):
60616061
# cleanup users
60626062
for new_user in self.users:
60636063
cnx.cmd_query("DROP USER IF EXISTS '{user}'@'{host}'".format(**new_user))
6064+
6065+
6066+
class BugOra32497631(tests.MySQLConnectorTests):
6067+
"""BUG#32497631: PREPARED STMT FAILS ON CEXT WHEN PARAMS ARE NOT GIVEN"""
6068+
6069+
table_name = "BugOra32497631"
6070+
6071+
@foreach_cnx()
6072+
def test_prepared_statement_parameters(self):
6073+
self.cnx.cmd_query(f"DROP TABLE IF EXISTS {self.table_name}")
6074+
self.cnx.cmd_query(
6075+
f"CREATE TABLE {self.table_name} (name VARCHAR(255))"
6076+
)
6077+
with self.cnx.cursor(prepared=True) as cur:
6078+
query = f"INSERT INTO {self.table_name} (name) VALUES (?)"
6079+
# If the query has placeholders and no parameters is given,
6080+
# the statement is prepared but not executed
6081+
cur.execute(query)
6082+
6083+
# Test with parameters
6084+
cur.execute(query, ("foo",))
6085+
cur.execute(query, ("bar",))
6086+
6087+
cur.execute(f"SELECT name FROM {self.table_name}")
6088+
rows = cur.fetchall()
6089+
self.assertEqual(2, len(rows))
6090+
self.cnx.cmd_query(f"DROP TABLE IF EXISTS {self.table_name}")

0 commit comments

Comments
 (0)