Skip to content

Commit 1401bd7

Browse files
committed
BUG27489937: Support c extension for connection pools
The reset connection command was missing from the c extension implementation which was required to reuse a connection from the pool, this patch adds this functionality in order of use the c extension in connection pools.
1 parent aa0ab08 commit 1401bd7

File tree

9 files changed

+177
-29
lines changed

9 files changed

+177
-29
lines changed

lib/mysql/connector/connection_cext.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
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
@@ -652,6 +652,23 @@ def cmd_change_user(self, username='', password='', database='',
652652
self._charset_id = charset
653653
self._post_connection()
654654

655+
def cmd_reset_connection(self):
656+
"""Resets the session state without re-authenticating
657+
658+
Works only for MySQL server 5.7.3 or later.
659+
"""
660+
if self._server_version < (5, 7, 3):
661+
raise errors.NotSupportedError("MySQL version 5.7.2 and "
662+
"earlier does not support "
663+
"COM_RESET_CONNECTION.")
664+
try:
665+
self._cmysql.reset_connection()
666+
except MySQLInterfaceError as exc:
667+
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
668+
sqlstate=exc.sqlstate)
669+
670+
self._post_connection()
671+
655672
def cmd_refresh(self, options):
656673
"""Send the Refresh command to the MySQL server"""
657674
try:

lib/mysql/connector/pooling.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
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
@@ -41,7 +41,9 @@
4141
import threading
4242

4343
from . import errors
44+
from . import Connect
4445
from .connection import MySQLConnection
46+
from .connection_cext import CMySQLConnection
4547

4648
CONNECTION_POOL_LOCK = threading.RLock()
4749
CNX_POOL_MAXSIZE = 32
@@ -97,7 +99,7 @@ def __init__(self, pool, cnx):
9799
if not isinstance(pool, MySQLConnectionPool):
98100
raise AttributeError(
99101
"pool should be a MySQLConnectionPool")
100-
if not isinstance(cnx, MySQLConnection):
102+
if not isinstance(cnx, (MySQLConnection, CMySQLConnection)):
101103
raise AttributeError(
102104
"cnx should be a MySQLConnection")
103105
self._cnx_pool = pool
@@ -194,7 +196,7 @@ def set_config(self, **kwargs):
194196

195197
with CONNECTION_POOL_LOCK:
196198
try:
197-
test_cnx = MySQLConnection()
199+
test_cnx = Connect()
198200
if "use_pure" in kwargs:
199201
del kwargs["use_pure"]
200202
test_cnx.config(**kwargs)
@@ -243,7 +245,7 @@ def _queue_connection(self, cnx):
243245
244246
Raises PoolError on errors.
245247
"""
246-
if not isinstance(cnx, MySQLConnection):
248+
if not isinstance(cnx, (MySQLConnection, CMySQLConnection)):
247249
raise errors.PoolError(
248250
"Connection instance not subclass of MySQLConnection.")
249251

@@ -275,7 +277,7 @@ def add_connection(self, cnx=None):
275277
"Failed adding connection; queue is full")
276278

277279
if not cnx:
278-
cnx = MySQLConnection(**self._cnx_config)
280+
cnx = Connect(**self._cnx_config)
279281
try:
280282
if (self._reset_session and self._cnx_config['compress']
281283
and cnx.get_server_version() < (5, 7, 3)):
@@ -291,7 +293,7 @@ def add_connection(self, cnx=None):
291293
cnx._pool_config_version = self._config_version
292294
# pylint: enable=W0201,W0212
293295
else:
294-
if not isinstance(cnx, MySQLConnection):
296+
if not isinstance(cnx, (MySQLConnection, CMySQLConnection)):
295297
raise errors.PoolError(
296298
"Connection instance not subclass of MySQLConnection.")
297299

src/include/mysql_capi.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -208,6 +208,9 @@ MySQL_query(MySQL *self, PyObject *args, PyObject *kwds);
208208
PyObject*
209209
MySQL_refresh(MySQL *self, PyObject *args);
210210

211+
PyObject*
212+
MySQL_reset_connection(MySQL *self);
213+
211214
PyObject*
212215
MySQL_rollback(MySQL *self);
213216

src/include/mysql_connector.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as

src/mysql_capi.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2750,6 +2750,37 @@ MySQL_refresh(MySQL *self, PyObject *args)
27502750
Py_RETURN_NONE;
27512751
}
27522752

2753+
/**
2754+
Resets current connection.
2755+
2756+
Resets this connection to MySQL.
2757+
2758+
@param self MySQL instance
2759+
2760+
@return Boolean Object Py_True or Py_False
2761+
@retval Py_True for success
2762+
@retval Py_False if an error occurred
2763+
*/
2764+
PyObject*
2765+
MySQL_reset_connection(MySQL *self)
2766+
{
2767+
int res;
2768+
2769+
if (!self->connected)
2770+
{
2771+
Py_RETURN_FALSE;
2772+
}
2773+
2774+
res= mysql_reset_connection(&self->session);
2775+
2776+
if (!res)
2777+
{
2778+
Py_RETURN_TRUE;
2779+
}
2780+
2781+
Py_RETURN_FALSE;
2782+
}
2783+
27532784
/**
27542785
Shut down the MySQL server.
27552786

src/mysql_connector.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -204,6 +204,9 @@ static PyMethodDef MySQL_methods[]=
204204
{"refresh", (PyCFunction)MySQL_refresh,
205205
METH_VARARGS,
206206
"Flush tables, caches or reset replication server info"},
207+
{"reset_connection", (PyCFunction)MySQL_reset_connection,
208+
METH_NOARGS,
209+
"Resets current connection"},
207210
{"rollback", (PyCFunction)MySQL_rollback,
208211
METH_NOARGS,
209212
"Rolls back the current transaction"},

tests/cext/test_cext_connection.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3-
# Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
3+
# Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
44
#
55
# This program is free software; you can redistribute it and/or modify
66
# it under the terms of the GNU General Public License, version 2.0, as
@@ -32,7 +32,9 @@
3232
"""
3333

3434
import tests
35+
import unittest
3536

37+
from .. import PY2
3638
from mysql.connector import errors
3739
from mysql.connector.constants import ClientFlag, flag_is_set
3840
from mysql.connector.connection import MySQLConnection
@@ -126,4 +128,19 @@ def test_cmd_query(self):
126128
'warning_count': 0, 'insert_id': 0, 'affected_rows': 0,
127129
'server_status': 0, 'field_count': 0
128130
}
129-
self.assertEqual(exp, info)
131+
self.assertEqual(exp, info)
132+
133+
@unittest.skipIf(tests.MYSQL_VERSION < (5, 7, 3),
134+
"MySQL >= 5.7.3 is required for reset command")
135+
def test_cmd_reset_connection(self):
136+
"""Resets session without re-authenticating"""
137+
exp_session_id = self.cnx.connection_id
138+
self.cnx.cmd_query("SET @ham = 2")
139+
self.cnx.cmd_reset_connection()
140+
141+
self.cnx.cmd_query("SELECT @ham")
142+
self.assertEqual(exp_session_id, self.cnx.connection_id)
143+
if PY2:
144+
self.assertNotEqual(('2',), self.cnx.get_rows()[0][0])
145+
else:
146+
self.assertNotEqual((b'2',), self.cnx.get_rows()[0][0])

tests/test_bugs.py

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
from mysql.connector import (connection, cursor, conversion, protocol,
6464
errors, constants, pooling)
6565
from mysql.connector.optionfiles import read_option_files
66+
from mysql.connector.pooling import PooledMySQLConnection
6667
from mysql.connector.catch23 import STRING_TYPES
6768
import mysql.connector
6869
import cpy_distutils
@@ -3385,22 +3386,32 @@ def test_binary_charset(self):
33853386
class BugOra19549363(tests.MySQLConnectorTests):
33863387
"""BUG#19549363: Compression does not work with Change User
33873388
"""
3388-
def test_compress_reset_connection(self):
3389-
config = tests.get_mysql_config()
3390-
config['compress'] = True
3389+
def setUp(self):
3390+
self.config = tests.get_mysql_config()
3391+
self.config['compress'] = True
33913392

33923393
mysql.connector._CONNECTION_POOLS = {}
3393-
config['pool_name'] = 'mypool'
3394-
config['pool_size'] = 3
3395-
config['pool_reset_session'] = True
3396-
cnx1 = mysql.connector.connect(**config)
3394+
self.config['pool_name'] = 'mypool'
3395+
self.config['pool_size'] = 3
3396+
self.config['pool_reset_session'] = True
33973397

3398-
try:
3399-
cnx1.close()
3400-
except:
3401-
self.fail("Reset session with compression test failed.")
3402-
finally:
3403-
mysql.connector._CONNECTION_POOLS = {}
3398+
3399+
def tearDown(self):
3400+
# Remove pools created by test
3401+
mysql.connector._CONNECTION_POOLS = {}
3402+
3403+
def test_compress_reset_connection(self):
3404+
for use_pure in [True, False]:
3405+
self.config['use_pure'] = use_pure
3406+
3407+
cnx1 = mysql.connector.connect(**self.config)
3408+
3409+
try:
3410+
cnx1.close()
3411+
except:
3412+
self.fail("Reset session with compression test failed.")
3413+
finally:
3414+
mysql.connector._CONNECTION_POOLS = {}
34043415

34053416

34063417
class BugOra19803702(tests.MySQLConnectorTests):
@@ -4564,8 +4575,12 @@ def test_pool_exhaustion(self):
45644575
self.mysql_server.wait_down()
45654576
cur.execute(sql)
45664577
except (mysql.connector.errors.OperationalError,
4567-
mysql.connector.errors.ProgrammingError):
4578+
mysql.connector.errors.ProgrammingError,
4579+
mysql.connector.errors.DatabaseError) as err:
45684580
try:
4581+
if isinstance(err, mysql.connector.errors.DatabaseError):
4582+
if err.errno != 2006:
4583+
raise
45694584
cur.close()
45704585
cnx.close()
45714586
except mysql.connector.errors.OperationalError:
@@ -5689,3 +5704,62 @@ def test_read_default_file_alias(self):
56895704
self.assertEqual("my_pool", conn.pool_name)
56905705
mysql.connector._CONNECTION_POOLS = {}
56915706
conn.close()
5707+
5708+
@unittest.skipIf(tests.MYSQL_VERSION < (5, 7, 3),
5709+
"MySQL >= 5.7.3 is required for reset command")
5710+
@unittest.skipIf(CMySQLConnection is None,
5711+
"CMySQLConnection is required but is not available")
5712+
class Bug27489937(tests.MySQLConnectorTests):
5713+
"""BUG#17414258: SUPPORT C EXTENSION FOR CONNECTION POOLS
5714+
"""
5715+
5716+
def setUp(self):
5717+
self.config = tests.get_mysql_config()
5718+
self.config['pool_name'] = 'Bug27489937'
5719+
self.config['pool_size'] = 3
5720+
self.config['use_pure'] = False
5721+
try:
5722+
del mysql.connector._CONNECTION_POOLS[self.config['pool_name']]
5723+
except:
5724+
pass
5725+
5726+
def tearDown(self):
5727+
# Remove pools created by test
5728+
del mysql.connector._CONNECTION_POOLS[self.config['pool_name']]
5729+
5730+
def test_cext_pool_support(self):
5731+
"""Basic pool tests"""
5732+
cnx_list = []
5733+
session_ids = []
5734+
for _ in range(self.config['pool_size']):
5735+
cnx = mysql.connector.connect(**self.config)
5736+
self.assertIsInstance(cnx, PooledMySQLConnection,
5737+
"Expected a CMySQLConnection instance")
5738+
self.assertIsInstance(cnx._cnx, CMySQLConnection,
5739+
"Expected a CMySQLConnection instance")
5740+
cnx_list.append(cnx)
5741+
exp_session_id = cnx.connection_id
5742+
session_ids.append(exp_session_id)
5743+
cnx.cmd_query("SET @ham = 2")
5744+
cnx.cmd_reset_connection()
5745+
5746+
cnx.cmd_query("SELECT @ham")
5747+
self.assertEqual(exp_session_id, cnx.connection_id)
5748+
if PY2:
5749+
self.assertNotEqual(('2',), cnx.get_rows()[0][0])
5750+
else:
5751+
self.assertNotEqual((b'2',), cnx.get_rows()[0][0])
5752+
self.assertRaises(errors.PoolError, mysql.connector.connect,
5753+
**self.config)
5754+
5755+
for cnx in cnx_list:
5756+
cnx.close()
5757+
5758+
cnx = mysql.connector.connect(**self.config)
5759+
cnx.cmd_query("SELECT @ham")
5760+
self.assertIn(cnx.connection_id, session_ids,
5761+
"Pooled connection was not reused.")
5762+
if PY2:
5763+
self.assertNotEqual(('2',), cnx.get_rows()[0][0])
5764+
else:
5765+
self.assertNotEqual((b'2',), cnx.get_rows()[0][0])

tests/test_pooling.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import mysql.connector
4141
from mysql.connector import errors
4242
from mysql.connector.connection import MySQLConnection
43+
from mysql.connector.connection_cext import CMySQLConnection
4344
from mysql.connector import pooling
4445
from mysql.connector.constants import ClientFlag
4546

@@ -237,14 +238,14 @@ def test_add_connection(self):
237238
pcnx = pooling.PooledMySQLConnection(
238239
cnxpool,
239240
cnxpool._cnx_queue.get(block=False))
240-
self.assertTrue(isinstance(pcnx._cnx, MySQLConnection))
241+
self.assertTrue(isinstance(pcnx._cnx, (CMySQLConnection, MySQLConnection)))
241242
self.assertEqual(cnxpool, pcnx._cnx_pool)
242243
self.assertEqual(cnxpool._config_version,
243244
pcnx._cnx._pool_config_version)
244245

245246
cnx = pcnx._cnx
246247
pcnx.close()
247-
# We should get the same connectoin back
248+
# We should get the same connection back
248249
self.assertEqual(cnx, cnxpool._cnx_queue.get(block=False))
249250
cnxpool.add_connection(cnx)
250251

@@ -256,7 +257,7 @@ def test_add_connection(self):
256257
cnxpool._remove_connections()
257258
cnxpool._cnx_config['port'] = 9999999
258259
cnxpool._cnx_config['unix_socket'] = '/ham/spam/foobar.socket'
259-
self.assertRaises(errors.InterfaceError, cnxpool.add_connection)
260+
self.assertRaises(errors.Error, cnxpool.add_connection)
260261

261262
self.assertRaises(errors.PoolError, cnxpool.add_connection, cnx=str)
262263

0 commit comments

Comments
 (0)