Skip to content

Commit 44e81a6

Browse files
committed
Merge branch 'release-2.0.4' into master-2.0
2 parents 8b05e4a + 5186cf1 commit 44e81a6

14 files changed

+247
-27
lines changed

CHANGES.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
88
Full release notes:
99
http://dev.mysql.com/doc/relnotes/connector-python/en/
1010

11+
v2.0.4
12+
======
13+
14+
- BUG20324089: Fix HASH based sharding with MySQL Fabric
15+
- BUG20462427: Fix receiving large field data from server
16+
- BUG20301989: Fix conversion of empty set
17+
- BUG20407036: Fix incorrect arguments to mysld_stmt_execute error
18+
- BUG20106629: Support Django Safetext and SafeBytes type
19+
1120
v2.0.3
1221
======
1322

cpyint

lib/mysql/connector/conversion.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# MySQL Connector/Python - MySQL driver written in Python.
2-
# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
33

44
# MySQL Connector/Python is licensed under the terms of the GPLv2
55
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -526,6 +526,8 @@ def _SET_to_python(self, value, dsc=None): # pylint: disable=C0103
526526
"""
527527
set_type = None
528528
val = value.decode(self.charset)
529+
if not val:
530+
return set()
529531
try:
530532
set_type = set(val.split(','))
531533
except ValueError:

lib/mysql/connector/django/base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ def _DATETIME_to_python(self, value, dsc=None):
9898
dt = dt.replace(tzinfo=timezone.utc)
9999
return dt
100100

101+
def _safetext_to_mysql(self, value):
102+
return self._str_to_mysql(value)
103+
104+
def _safebytes_to_mysql(self, value):
105+
return self._bytes_to_mysql(value)
106+
101107

102108
class CursorWrapper(object):
103109
"""Wrapper around MySQL Connector/Python's cursor class.

lib/mysql/connector/fabric/caching.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# MySQL Connector/Python - MySQL driver written in Python.
2-
# Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
33

44
# MySQL Connector/Python is licensed under the terms of the GPLv2
55
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -130,6 +130,8 @@ def add_partition(self, key, group):
130130
))
131131
elif self.shard_type == 'RANGE_STRING':
132132
pass
133+
elif self.shard_type == "HASH":
134+
pass
133135
else:
134136
raise ValueError("Unsupported sharding type {0}".format(
135137
self.shard_type

lib/mysql/connector/network.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# MySQL Connector/Python - MySQL driver written in Python.
2-
# Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
33

44
# MySQL Connector/Python is licensed under the terms of the GPLv2
55
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -221,11 +221,13 @@ def recv_plain(self):
221221
try:
222222
# Read the header of the MySQL packet, 4 bytes
223223
packet = bytearray(b'')
224-
while len(packet) < 4:
225-
chunk = self.sock.recv(4)
224+
packet_len = 0
225+
while packet_len < 4:
226+
chunk = self.sock.recv(4 - packet_len)
226227
if not chunk:
227228
raise errors.InterfaceError(errno=2013)
228229
packet += chunk
230+
packet_len = len(packet)
229231

230232
# Save the packet number and payload length
231233
self._packet_number = packet[3]
@@ -257,12 +259,13 @@ def recv_py26_plain(self):
257259
try:
258260
# Read the header of the MySQL packet, 4 bytes
259261
header = bytearray(b'')
260-
261-
while len(header) < 4:
262-
chunk = self.sock.recv(4)
262+
header_len = 0
263+
while header_len < 4:
264+
chunk = self.sock.recv(4 - header_len)
263265
if not chunk:
264266
raise errors.InterfaceError(errno=2013)
265267
header += chunk
268+
header_len = len(header)
266269

267270
# Save the packet number and payload length
268271
self._packet_number = header[3]

lib/mysql/connector/protocol.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# MySQL Connector/Python - MySQL driver written in Python.
2-
# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
33

44
# MySQL Connector/Python is licensed under the terms of the GPLv2
55
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -313,11 +313,8 @@ def read_text_result(self, sock, count=1):
313313
while packet.startswith(b'\xff\xff\xff'):
314314
datas.append(packet[4:])
315315
packet = sock.recv()
316-
if packet[4] == 254:
317-
eof = self.parse_eof(packet)
318-
else:
319-
datas.append(packet[4:])
320-
rowdata = utils.read_lc_string_list(b''.join(datas))
316+
datas.append(packet[4:])
317+
rowdata = utils.read_lc_string_list(bytearray(b'').join(datas))
321318
elif packet[4] == 254:
322319
eof = self.parse_eof(packet)
323320
rowdata = None

lib/mysql/connector/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# MySQL Connector/Python - MySQL driver written in Python.
2-
# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
33

44
# MySQL Connector/Python is licensed under the terms of the GPLv2
55
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -144,7 +144,7 @@ def lc_int(i):
144144
if i < 0 or i > 18446744073709551616:
145145
raise ValueError('Requires 0 <= i <= 2^64')
146146

147-
if i <= 255:
147+
if i < 251:
148148
return bytearray(struct.pack('<B', i))
149149
elif i <= 65535:
150150
return b'\xfc' + bytearray(struct.pack('<H', i))

lib/mysql/connector/version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# MySQL Connector/Python - MySQL driver written in Python.
2-
# Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
33

44
# MySQL Connector/Python is licensed under the terms of the GPLv2
55
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -26,7 +26,7 @@
2626
as mysql.connector.version.
2727
"""
2828

29-
VERSION = (2, 0, 3, '', 0)
29+
VERSION = (2, 0, 4, '', 0)
3030

3131
if VERSION[3] and VERSION[4]:
3232
VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION)

tests/test_bugs.py

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
# MySQL Connector/Python - MySQL driver written in Python.
3-
# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
3+
# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
44

55
# MySQL Connector/Python is licensed under the terms of the GPLv2
66
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -3056,7 +3056,6 @@ def tearDown(self):
30563056
self.cnx.close()
30573057

30583058

3059-
30603059
class BugOra19777815(tests.MySQLConnectorTests):
30613060
"""BUG#19777815: CALLPROC() DOES NOT SUPPORT WARNINGS
30623061
"""
@@ -3110,3 +3109,143 @@ def test_warning_with_rows(self):
31103109
exp = [(u'Warning', 1642, u'TEST WARNING')]
31113110
self.assertEqual(exp, cur.fetchwarnings())
31123111
cur.close()
3112+
3113+
3114+
class BugOra20407036(tests.MySQLConnectorTests):
3115+
"""BUG#20407036: INCORRECT ARGUMENTS TO MYSQLD_STMT_EXECUTE ERROR
3116+
"""
3117+
def setUp(self):
3118+
config = tests.get_mysql_config()
3119+
self.cnx = connection.MySQLConnection(**config)
3120+
self.cur = self.cnx.cursor()
3121+
3122+
self.tbl = 'Bug20407036'
3123+
self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl))
3124+
3125+
create = ("CREATE TABLE {0} ( id int(10) unsigned NOT NULL, "
3126+
"text VARCHAR(70000) CHARACTER SET utf8 NOT NULL, "
3127+
"rooms tinyint(3) unsigned NOT NULL) "
3128+
"ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 "
3129+
"COLLATE=utf8_unicode_ci".format(self.tbl))
3130+
self.cur.execute(create)
3131+
3132+
def tearDown(self):
3133+
self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl))
3134+
self.cur.close()
3135+
self.cnx.close()
3136+
3137+
def test_binary_charset(self):
3138+
cur = self.cnx.cursor(prepared=True)
3139+
sql = "INSERT INTO {0}(text, rooms) VALUES(%s, %s)".format(self.tbl)
3140+
cur.execute(sql, ('a'*252, 1))
3141+
cur.execute(sql, ('a'*253, 2))
3142+
cur.execute(sql, ('a'*255, 3))
3143+
cur.execute(sql, ('a'*251, 4))
3144+
cur.execute(sql, ('a'*65535, 5))
3145+
3146+
exp = [
3147+
(0, 'a'*252, 1),
3148+
(0, 'a'*253, 2),
3149+
(0, 'a'*255, 3),
3150+
(0, 'a'*251, 4),
3151+
(0, 'a'*65535, 5),
3152+
]
3153+
3154+
self.cur.execute("SELECT * FROM {0}".format(self.tbl))
3155+
self.assertEqual(exp, self.cur.fetchall())
3156+
3157+
3158+
class BugOra20301989(tests.MySQLConnectorTests):
3159+
"""BUG#20301989: SET DATA TYPE NOT TRANSLATED CORRECTLY WHEN EMPTY
3160+
"""
3161+
def setUp(self):
3162+
config = tests.get_mysql_config()
3163+
cnx = connection.MySQLConnection(**config)
3164+
cur = cnx.cursor()
3165+
3166+
self.tbl = 'Bug20301989'
3167+
cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl))
3168+
3169+
create = ("CREATE TABLE {0} (col1 SET('val1', 'val2')) "
3170+
"DEFAULT CHARSET latin1".format(self.tbl))
3171+
cur.execute(create)
3172+
cur.close()
3173+
cnx.close()
3174+
3175+
def tearDown(self):
3176+
config = tests.get_mysql_config()
3177+
cnx = connection.MySQLConnection(**config)
3178+
cur = cnx.cursor()
3179+
cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl))
3180+
cur.close()
3181+
cnx.close()
3182+
3183+
def test_set(self):
3184+
config = tests.get_mysql_config()
3185+
cnx = connection.MySQLConnection(**config)
3186+
cur = cnx.cursor()
3187+
sql = "INSERT INTO {0} VALUES(%s)".format(self.tbl)
3188+
cur.execute(sql, ('val1,val2',))
3189+
cur.execute(sql, ('val1',))
3190+
cur.execute(sql, ('',))
3191+
cur.execute(sql, (None,))
3192+
3193+
exp = [
3194+
(set([u'val1', u'val2']),),
3195+
(set([u'val1']),),
3196+
(set([]),),
3197+
(None,)
3198+
]
3199+
3200+
cur.execute("SELECT * FROM {0}".format(self.tbl))
3201+
self.assertEqual(exp, cur.fetchall())
3202+
3203+
3204+
class BugOra20462427(tests.MySQLConnectorTests):
3205+
"""BUG#20462427: BYTEARRAY INDEX OUT OF RANGE
3206+
"""
3207+
def setUp(self):
3208+
config = tests.get_mysql_config()
3209+
config['autocommit'] = True
3210+
config['connection_timeout'] = 100
3211+
self.cnx = connection.MySQLConnection(**config)
3212+
self.cur = self.cnx.cursor()
3213+
3214+
self.tbl = 'BugOra20462427'
3215+
self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl))
3216+
3217+
create = ("CREATE TABLE {0} ("
3218+
"id INT PRIMARY KEY, "
3219+
"a LONGTEXT "
3220+
") ENGINE=Innodb DEFAULT CHARSET utf8".format(self.tbl))
3221+
3222+
self.cur.execute(create)
3223+
3224+
def tearDown(self):
3225+
self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl))
3226+
self.cur.close()
3227+
self.cnx.close()
3228+
3229+
def test_bigdata(self):
3230+
temp = 'a'*16777210
3231+
insert = "INSERT INTO {0} (a) VALUES ('{1}')".format(self.tbl, temp)
3232+
3233+
self.cur.execute(insert)
3234+
self.cur.execute("SELECT a FROM {0}".format(self.tbl))
3235+
res = self.cur.fetchall()
3236+
self.assertEqual(16777210, len(res[0][0]))
3237+
3238+
self.cur.execute("UPDATE {0} SET a = concat(a, 'a')".format(self.tbl))
3239+
self.cur.execute("SELECT a FROM {0}".format(self.tbl))
3240+
res = self.cur.fetchall()
3241+
self.assertEqual(16777211, len(res[0][0]))
3242+
3243+
self.cur.execute("UPDATE {0} SET a = concat(a, 'a')".format(self.tbl))
3244+
self.cur.execute("SELECT a FROM {0}".format(self.tbl))
3245+
res = self.cur.fetchall()
3246+
self.assertEqual(16777212, len(res[0][0]))
3247+
3248+
self.cur.execute("UPDATE {0} SET a = concat(a, 'a')".format(self.tbl))
3249+
self.cur.execute("SELECT a FROM {0}".format(self.tbl))
3250+
res = self.cur.fetchall()
3251+
self.assertEqual(16777213, len(res[0][0]))

tests/test_django.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
if tests.DJANGO_VERSION >= (1, 6):
8585
from django.db.backends import FieldInfo
8686
from django.db.backends.signals import connection_created
87+
from django.utils.safestring import SafeBytes, SafeText
8788

8889
import mysql.connector
8990
from mysql.connector.django.base import (DatabaseWrapper, DatabaseOperations,
@@ -298,3 +299,25 @@ def test__DATETIME_to_python(self):
298299
self.assertEqual(None,
299300
django_converter._DATETIME_to_python(value, dsc=None))
300301
settings.USE_TZ = False
302+
303+
304+
class BugOra20106629(tests.MySQLConnectorTests):
305+
"""CONNECTOR/PYTHON DJANGO BACKEND DOESN'T SUPPORT SAFETEXT"""
306+
def setUp(self):
307+
dbconfig = tests.get_mysql_config()
308+
self.conn = mysql.connector.connect(**dbconfig)
309+
self.cnx = DatabaseWrapper(settings.DATABASES['default'])
310+
self.cur = self.cnx.cursor()
311+
self.tbl = "BugOra20106629"
312+
self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl), ())
313+
self.cur.execute("CREATE TABLE {0}(col1 TEXT, col2 BLOB)".format(self.tbl), ())
314+
315+
def teardown(self):
316+
self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl), ())
317+
318+
def test_safe_string(self):
319+
safe_text = SafeText("dummy & safe data <html> ")
320+
safe_bytes = SafeBytes(b"\x00\x00\x4c\x6e\x67\x39")
321+
self.cur.execute("INSERT INTO {0} VALUES(%s, %s)".format(self.tbl), (safe_text, safe_bytes))
322+
self.cur.execute("SELECT * FROM {0}".format(self.tbl), ())
323+
self.assertEqual(self.cur.fetchall(), [(safe_text, safe_bytes)])

tests/test_fabric.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# MySQL Connector/Python - MySQL driver written in Python.
2-
# Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
33

44
# MySQL Connector/Python is licensed under the terms of the GPLv2
55
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -626,3 +626,39 @@ def test_bug19331658(self):
626626
)
627627

628628
mysql.connector._CONNECTION_POOLS = {}
629+
630+
def test_range_hash(self):
631+
self.assertTrue(self._check_table(
632+
"employees.employees_hash", 'HASH'))
633+
tbl_name = "employees_hash"
634+
635+
tables = ["employees.{0}".format(tbl_name)]
636+
637+
self.cnx.set_property(tables=tables,
638+
scope=fabric.SCOPE_GLOBAL,
639+
mode=fabric.MODE_READWRITE)
640+
641+
cur = self.cnx.cursor()
642+
gtid_executed = self._truncate(cur, tbl_name)
643+
self.cnx.commit()
644+
645+
insert = ("INSERT INTO {0} "
646+
"VALUES (%s, %s, %s, %s, %s, %s)").format(tbl_name)
647+
648+
self._populate(self.cnx, gtid_executed, tbl_name, insert,
649+
self.emp_data[1985] + self.emp_data[2000], 3)
650+
651+
time.sleep(2)
652+
653+
emp_exp_hash = self.emp_data[1985] + self.emp_data[2000]
654+
655+
rows = []
656+
self.cnx.reset_properties()
657+
str_keys = ['group1', 'group2']
658+
for str_key in str_keys:
659+
self.cnx.set_property(group=str_key, mode=fabric.MODE_READONLY)
660+
cur = self.cnx.cursor()
661+
cur.execute("SELECT * FROM {0}".format(tbl_name))
662+
rows += cur.fetchall()
663+
664+
self.assertEqual(rows, emp_exp_hash)

0 commit comments

Comments
 (0)