From f286c0f68b2b57a009d28e843a8769655d9ffbae Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Wed, 10 Sep 2014 07:44:52 +0200 Subject: [PATCH 01/50] BUG19584116: Fix extra signal causing runtime error in Django Using connection_created signal defined in django.db.backends.signals causes maximum recursion depth reached runtime error. We fix this by not sending the extra signal after the connection to the database is setup. Unit tests are updated. (cherry picked from commit c86a295) --- lib/mysql/connector/django/base.py | 2 +- tests/test_django.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/mysql/connector/django/base.py b/lib/mysql/connector/django/base.py index 573c30bd..ee6aa32d 100644 --- a/lib/mysql/connector/django/base.py +++ b/lib/mysql/connector/django/base.py @@ -584,7 +584,6 @@ def get_new_connection(self, conn_params): cnx = mysql.connector.connect(**conn_params) self.server_version = cnx.get_server_version() cnx.set_converter_class(DjangoMySQLConverter) - connection_created.send(sender=self.__class__, connection=self) return cnx @@ -609,6 +608,7 @@ def create_cursor(self): def _connect(self): """Setup the connection with MySQL""" self.connection = self.get_new_connection(self.get_connection_params()) + connection_created.send(sender=self.__class__, connection=self) self.init_connection_state() def _cursor(self): diff --git a/tests/test_django.py b/tests/test_django.py index 77b59e51..7383e6bd 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -26,6 +26,7 @@ import datetime import sys +import unittest import tests @@ -82,6 +83,7 @@ import django.db # pylint: disable=W0611 if tests.DJANGO_VERSION >= (1, 6): from django.db.backends import FieldInfo +from django.db.backends.signals import connection_created import mysql.connector from mysql.connector.django.base import (DatabaseWrapper, DatabaseOperations, @@ -207,6 +209,23 @@ def test__init__(self): exp = self.conn.converter._time_to_mysql(value) self.assertEqual(exp, self.cnx.ops.value_to_db_time(value)) + def test_signal(self): + from django.db import connection + + def conn_setup(*args, **kwargs): + conn = kwargs['connection'] + cur = conn.cursor() + cur.execute("SET @xyz=10") + cur.close() + + connection_created.connect(conn_setup) + cursor = connection.cursor() + cursor.execute("SELECT @xyz") + + self.assertEqual((10,), cursor.fetchone()) + cursor.close() + self.cnx.close() + class DjangoDatabaseOperations(tests.MySQLConnectorTests): From a297798990650758d06376898164be7d65c87c34 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Tue, 30 Sep 2014 08:45:53 +0200 Subject: [PATCH 02/50] BUG19522948: Fix data corruption with TEXT and prepared statements Using prepared statements while inserting sufficiently large data in a table with TEXT type column, data was getting corrupted. This was due to wrong encoding of data length while sending prepared statement packet. We fix the issue by implementing a new function utils.lc_int(). A unit test has been added for BUG#19522948. (cherry picked from commit 12f82ab) --- lib/mysql/connector/protocol.py | 13 ++--- lib/mysql/connector/utils.py | 22 +++++++- tests/test_bugs.py | 90 +++++++++++++++++++++++++++++++++ tests/test_utils.py | 12 +++++ 4 files changed, 129 insertions(+), 8 deletions(-) diff --git a/lib/mysql/connector/protocol.py b/lib/mysql/connector/protocol.py index 4ced446f..855ffccc 100644 --- a/lib/mysql/connector/protocol.py +++ b/lib/mysql/connector/protocol.py @@ -636,24 +636,25 @@ def make_stmt_execute(self, statement_id, data=(), parameters=(), values.append(packed) elif isinstance(value, str): if PY2: - values.append(utils.intstore(len(value)) + value) + values.append(utils.lc_int(len(value)) + + value) else: value = value.encode(charset) values.append( - utils.intstore(len(value)) + value) + utils.lc_int(len(value)) + value) field_type = FieldType.VARCHAR elif isinstance(value, bytes): - values.append(utils.intstore(len(value)) + value) + values.append(utils.lc_int(len(value)) + value) field_type = FieldType.BLOB elif PY2 and \ isinstance(value, unicode): # pylint: disable=E0602 value = value.encode(charset) - values.append(utils.intstore(len(value)) + value) + values.append(utils.lc_int(len(value)) + value) field_type = FieldType.VARCHAR elif isinstance(value, Decimal): values.append( - utils.intstore(len(str(value).encode(charset))) + - str(value).encode(charset)) + utils.lc_int(len(str(value).encode( + charset))) + str(value).encode(charset)) field_type = FieldType.DECIMAL elif isinstance(value, float): values.append(struct.pack('d', value)) diff --git a/lib/mysql/connector/utils.py b/lib/mysql/connector/utils.py index 586b422e..f8b10c76 100644 --- a/lib/mysql/connector/utils.py +++ b/lib/mysql/connector/utils.py @@ -100,12 +100,12 @@ def int4store(i): def int8store(i): """ - Takes an unsigned integer (4 bytes) and packs it as string. + Takes an unsigned integer (8 bytes) and packs it as string. Returns string. """ if i < 0 or i > 18446744073709551616: - raise ValueError('int4store requires 0 <= i <= 2^64') + raise ValueError('int8store requires 0 <= i <= 2^64') else: return bytearray(struct.pack(' 18446744073709551616: + raise ValueError('Requires 0 <= i <= 2^64') + + if i <= 255: + return bytearray(struct.pack(' Date: Thu, 25 Sep 2014 14:31:03 +0200 Subject: [PATCH 03/50] BUG19584051: Fix comparision of type_code of columns for PEP-249 The type_code in cursor.description was not comparing equal to any of the type objects defined in mysql.connector.dbapi. We fix the issue by removing __cmp__ and implementing the rich comparison operators for the PEP-249 class DBAPITypeObject. A unit test has been added for BUG#19584051. (cherry picked from commit f985cfc) --- lib/mysql/connector/dbapi.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/mysql/connector/dbapi.py b/lib/mysql/connector/dbapi.py index aaf7e81f..35cb03b9 100644 --- a/lib/mysql/connector/dbapi.py +++ b/lib/mysql/connector/dbapi.py @@ -36,18 +36,22 @@ from . import constants -class _DBAPITypeObject: +class _DBAPITypeObject(object): def __init__(self, *values): self.values = values - def __cmp__(self, other): + def __eq__(self, other): if other in self.values: - return 0 - if other < self.values: - return 1 + return True else: - return -1 + return False + + def __ne__(self, other): + if other in self.values: + return False + else: + return True Date = datetime.date Time = datetime.time @@ -64,8 +68,8 @@ def TimestampFromTicks(ticks): Binary = bytes -STRING = _DBAPITypeObject(constants.FieldType.get_string_types()) -BINARY = _DBAPITypeObject(constants.FieldType.get_binary_types()) -NUMBER = _DBAPITypeObject(constants.FieldType.get_number_types()) -DATETIME = _DBAPITypeObject(constants.FieldType.get_timestamp_types()) +STRING = _DBAPITypeObject(*constants.FieldType.get_string_types()) +BINARY = _DBAPITypeObject(*constants.FieldType.get_binary_types()) +NUMBER = _DBAPITypeObject(*constants.FieldType.get_number_types()) +DATETIME = _DBAPITypeObject(*constants.FieldType.get_timestamp_types()) ROWID = _DBAPITypeObject() From 5ad222d784916b09429f3b7d0abfd4f39e9f1aa9 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Wed, 1 Oct 2014 08:26:35 +0200 Subject: [PATCH 04/50] BUG19660283: Fix failing unit tests with MySQL server 5.7.5 MySQL server 5.7.5 creates no user while bootstrapping a server. We add extra DML to create the root@ocalhost user, making sure unit test can run. (cherry picked from commit 062cbde) --- tests/mysqld.py | 10 ++++++++++ tests/test_bugs.py | 3 +++ tests/test_mysql_datatypes.py | 25 +++++++++++++++---------- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/tests/mysqld.py b/tests/mysqld.py index 4e61d3df..6a00aa80 100644 --- a/tests/mysqld.py +++ b/tests/mysqld.py @@ -419,6 +419,16 @@ def bootstrap(self): "CREATE DATABASE myconnpy;" ] + if self._version[0:3] >= (5, 7, 5): + # MySQL 5.7.5 creates no user while bootstrapping + extra_sql.append( + "INSERT INTO mysql.user VALUES ('localhost','root',''," + "'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y'," + "'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y'," + "'Y','Y','Y','Y','Y','','','','',0,0,0,0," + "@@default_authentication_plugin,'','N'," + "CURRENT_TIMESTAMP,NULL);" + ) if self._version[0:3] >= (5, 7, 4): # MySQL 5.7.4 only creates root@localhost extra_sql.append( diff --git a/tests/test_bugs.py b/tests/test_bugs.py index 18367563..b195e22b 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -2305,6 +2305,9 @@ def test_parameters(self): err) +@unittest.skipIf(tests.MYSQL_VERSION >= (5, 7, 5), + "MySQL {0} does not support old password auth".format( + tests.MYSQL_VERSION_TXT)) class BugOra18415927(tests.MySQLConnectorTests): """BUG#18415927: AUTH_RESPONSE VARIABLE INCREMENTED WITHOUT BEING DEFINED """ diff --git a/tests/test_mysql_datatypes.py b/tests/test_mysql_datatypes.py index ba538092..cd74324d 100644 --- a/tests/test_mysql_datatypes.py +++ b/tests/test_mysql_datatypes.py @@ -28,7 +28,7 @@ import time import datetime -from mysql.connector import connection +from mysql.connector import connection, errors import tests @@ -339,18 +339,23 @@ def test_temporal_datetime(self): # Testing YEAR(2), which is now obsolete since MySQL 5.6.6 tblname = self.tables['temporal_year'] - cur.execute( + stmt = ( "CREATE TABLE {table} (" "`id` int NOT NULL AUTO_INCREMENT KEY, " "`t_year_2` YEAR(2))".format(table=tblname) - ) - cur.execute(_get_insert_stmt(tblname, ['t_year_2']), (10,)) - cur.execute(_get_select_stmt(tblname, ['t_year_2'])) - row = cur.fetchone() - - if tests.MYSQL_VERSION >= (5, 6, 6): - self.assertEqual(2010, row[0]) + ) + if tests.MYSQL_VERSION >= (5, 7, 5): + # Support for YEAR(2) removed in MySQL 5.7.5 + self.assertRaises(errors.DatabaseError, cur.execute, stmt) else: - self.assertEqual(10, row[0]) + cur.execute(stmt) + cur.execute(_get_insert_stmt(tblname, ['t_year_2']), (10,)) + cur.execute(_get_select_stmt(tblname, ['t_year_2'])) + row = cur.fetchone() + + if tests.MYSQL_VERSION >= (5, 6, 6): + self.assertEqual(2010, row[0]) + else: + self.assertEqual(10, row[0]) cur.close() From e7b9eef52f8d1f6bdfb7677fa75350bcbc49cc61 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Wed, 1 Oct 2014 10:31:13 +0200 Subject: [PATCH 05/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 15a74ca8..9f4dc7c9 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 15a74ca84478e597d844e4a95f9cfd584f01345c +Subproject commit 9f4dc7c9c97e4e7dbef50169a3d0ff77188f63c0 From 3ca12b075f992dfcad98e5c12ebcbc2763872f0d Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Wed, 8 Oct 2014 11:54:12 +0200 Subject: [PATCH 06/50] BUG19667984: Fix converting invalid datetime values in Django backend Connector/Python Django backend was raising error while converting an invalid datetime value instead of returning None. We fix this issue by adding a check for invalid values. Unit tests are updated. (cherry picked from commit a57f885) --- lib/mysql/connector/django/base.py | 2 ++ tests/test_django.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/mysql/connector/django/base.py b/lib/mysql/connector/django/base.py index ee6aa32d..e6844d14 100644 --- a/lib/mysql/connector/django/base.py +++ b/lib/mysql/connector/django/base.py @@ -92,6 +92,8 @@ def _DATETIME_to_python(self, value, dsc=None): if not value: return None dt = MySQLConverter._DATETIME_to_python(self, value) + if dt is None: + return None if settings.USE_TZ and timezone.is_naive(dt): dt = dt.replace(tzinfo=timezone.utc) return dt diff --git a/tests/test_django.py b/tests/test_django.py index 7383e6bd..219e68fc 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -267,3 +267,16 @@ def test__TIME_to_python(self): django_converter = DjangoMySQLConverter() self.assertEqual(datetime.time(10, 11, 12), django_converter._TIME_to_python(value, dsc=None)) + + def test__DATETIME_to_python(self): + value = b'1990-11-12 00:00:00' + django_converter = DjangoMySQLConverter() + self.assertEqual(datetime.datetime(1990, 11, 12, 0, 0, 0), + django_converter._DATETIME_to_python(value, dsc=None)) + + settings.USE_TZ = True + value = b'0000-00-00 00:00:00' + django_converter = DjangoMySQLConverter() + self.assertEqual(None, + django_converter._DATETIME_to_python(value, dsc=None)) + settings.USE_TZ = False From f7c5ee8ed10737b13cd52a7fb1d4f5e4617c5329 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Wed, 8 Oct 2014 12:03:59 +0200 Subject: [PATCH 07/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 9f4dc7c9..aef6c252 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 9f4dc7c9c97e4e7dbef50169a3d0ff77188f63c0 +Subproject commit aef6c252dc930a86cb1bb2912e1099eb6a117e55 From a1328a74e61506895ed374abeff4e8d673ba4c90 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Wed, 8 Oct 2014 14:19:15 +0200 Subject: [PATCH 08/50] BUG19711759: Fix Pylint issue in network module We fix Pylint W0633 warning in mysql.connector.network module with message "Attempting to unpack a non-sequence define at line 438". (cherry picked from commit d40192e) --- lib/mysql/connector/network.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mysql/connector/network.py b/lib/mysql/connector/network.py index b5b44f00..f7d10143 100644 --- a/lib/mysql/connector/network.py +++ b/lib/mysql/connector/network.py @@ -435,7 +435,7 @@ def open_connection(self): """Open the TCP/IP connection to the MySQL server """ # Get address information - addrinfo = None + addrinfo = [None] * 5 try: addrinfos = socket.getaddrinfo(self.server_host, self.server_port, @@ -449,10 +449,10 @@ def open_connection(self): elif info[0] == socket.AF_INET: addrinfo = info break - if self.force_ipv6 and not addrinfo: + if self.force_ipv6 and addrinfo[0] is None: raise errors.InterfaceError( "No IPv6 address found for {0}".format(self.server_host)) - if not addrinfo: + if addrinfo[0] is None: addrinfo = addrinfos[0] except IOError as err: raise errors.InterfaceError( From fae86c03d56fecfd642ff9be0bb5cc723f95c7df Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Fri, 10 Oct 2014 20:18:59 +0200 Subject: [PATCH 09/50] BUG19642249: Improve errors reporting invalid sharding keys We improve the errors reported when invalid sharding keys are given for RANGE_STRING and RANGE_DATETIME types. We additionally improve performance by moving the sorting of partition keys when saving cache entries. Previously, sorting was done each time a partition was looked up. Unit test for BUG#19642249 was added in test_fabric module. (cherry picked from commit a95e9c6) --- lib/mysql/connector/fabric/caching.py | 23 ++++++++ lib/mysql/connector/fabric/connection.py | 35 +++++++----- tests/test_fabric.py | 68 ++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 14 deletions(-) diff --git a/lib/mysql/connector/fabric/caching.py b/lib/mysql/connector/fabric/caching.py index ffcc11bd..d9a8a072 100644 --- a/lib/mysql/connector/fabric/caching.py +++ b/lib/mysql/connector/fabric/caching.py @@ -24,6 +24,7 @@ """Implementing caching mechanisms for MySQL Fabric""" +import bisect from datetime import datetime, timedelta from hashlib import sha1 import logging @@ -35,6 +36,24 @@ _CACHE_TTL = 1 * 60 # 1 minute +def insort_right_rev(a, x, lo=0, hi=None): + """Similar to bisect.insort_right but for reverse sorted lists + + This code is similar to the Python code found in Lib/bisect.py. + We simply change the comparison from 'less than' to 'greater than'. + """ + + if lo < 0: + raise ValueError('lo must be non-negative') + if hi is None: + hi = len(a) + while lo < hi: + mid = (lo+hi)//2 + if x > a[mid]: hi = mid + else: lo = mid+1 + a.insert(lo, x) + + class CacheEntry(object): """Base class for MySQL Fabric cache entries""" @@ -83,6 +102,8 @@ def __init__(self, shard, version=None, fabric_uuid=None): fabric_uuid=fabric_uuid) self.partitioning = {} self._shard = shard + self.keys = [] + self.keys_reversed = [] if shard.key and shard.group: self.add_partition(shard.key, shard.group) @@ -115,6 +136,8 @@ def add_partition(self, key, group): 'group': group, } self.reset_ttl() + bisect.insort_right(self.keys, key) + insort_right_rev(self.keys_reversed, key) @classmethod def hash_index(cls, part1, part2=None): diff --git a/lib/mysql/connector/fabric/connection.py b/lib/mysql/connector/fabric/connection.py index ebdd4370..c3a650e8 100644 --- a/lib/mysql/connector/fabric/connection.py +++ b/lib/mysql/connector/fabric/connection.py @@ -753,36 +753,43 @@ def get_shard_server(self, tables, key, scope=SCOPE_LOCAL, mode=None): return self.get_group_server(entry.global_group, mode=mode) if entry.shard_type == 'RANGE': - partitions = sorted(entry.partitioning.keys()) - index = partitions[bisect(partitions, int(key)) - 1] + try: + range_key = int(key) + except ValueError: + raise ValueError("Key must be an integer for RANGE") + partitions = entry.keys + index = partitions[bisect(partitions, range_key) - 1] partition = entry.partitioning[index] elif entry.shard_type == 'RANGE_DATETIME': if not isinstance(key, (datetime.date, datetime.datetime)): raise ValueError( "Key must be datetime.date or datetime.datetime for " "RANGE_DATETIME") - partition_keys = sorted(entry.partitioning.keys(), reverse=True) - for partkey in partition_keys: + index = None + for partkey in entry.keys_reversed: if key >= partkey: index = partkey break - partition = entry.partitioning[index] + try: + partition = entry.partitioning[index] + except KeyError: + raise ValueError("Key invalid; was '{0}'".format(key)) elif entry.shard_type == 'RANGE_STRING': if not isunicode(key): raise ValueError("Key must be a unicode value") - partition_keys = sorted(entry.partitioning.keys(), reverse=True) - for partkey in partition_keys: - size = len(partkey) - if key[0:size] >= partkey[0:size]: + index = None + for partkey in entry.keys_reversed: + if key >= partkey: index = partkey break - partition = entry.partitioning[index] + try: + partition = entry.partitioning[index] + except KeyError: + raise ValueError("Key invalid; was '{0}'".format(key)) elif entry.shard_type == 'HASH': md5key = md5(str(key)) - partition_keys = sorted( - entry.partitioning.keys(), reverse=True) - index = partition_keys[-1] - for partkey in partition_keys: + index = entry.keys_reversed[-1] + for partkey in entry.keys_reversed: if md5key.digest() >= b16decode(partkey): index = partkey break diff --git a/tests/test_fabric.py b/tests/test_fabric.py index fe175233..70b00ad4 100644 --- a/tests/test_fabric.py +++ b/tests/test_fabric.py @@ -424,6 +424,43 @@ def _truncate(self, cur, table): cur.execute("SELECT @@global.gtid_executed") return cur.fetchone()[0] + def test_range(self): + self.assertTrue(self._check_table("employees.employees_range", 'RANGE')) + tbl_name = "employees_range" + + tables = ["employees.{0}".format(tbl_name)] + + self.cnx.set_property(tables=tables, + scope=fabric.SCOPE_GLOBAL, + mode=fabric.MODE_READWRITE) + cur = self.cnx.cursor() + gtid_executed = self._truncate(cur, tbl_name) + self.cnx.commit() + + insert = ("INSERT INTO {0} " + "VALUES (%s, %s, %s, %s, %s, %s)").format(tbl_name) + + self._populate(self.cnx, gtid_executed, tbl_name, insert, + self.emp_data[1985] + self.emp_data[2000], 0) + + time.sleep(2) + + # Year is key of self.emp_data, second value is emp_no for RANGE key + exp_keys = [(1985, 10002), (2000, 47291)] + for year, emp_no in exp_keys: + self.cnx.set_property(tables=tables, + scope=fabric.SCOPE_LOCAL, + key=emp_no, mode=fabric.MODE_READONLY) + cur = self.cnx.cursor() + cur.execute("SELECT * FROM {0}".format(tbl_name)) + rows = cur.fetchall() + self.assertEqual(rows, self.emp_data[year]) + + self.cnx.set_property(tables=tables, + key='spam', mode=fabric.MODE_READONLY) + self.assertRaises(ValueError, self.cnx.cursor) + + def test_range_datetime(self): self.assertTrue(self._check_table( "employees.employees_range_datetime", 'RANGE_DATETIME')) @@ -510,3 +547,34 @@ def test_range_string(self): key='not unicode str', mode=fabric.MODE_READONLY) self.assertRaises(ValueError, self.cnx.cursor) + + def test_bug19642249(self): + self.assertTrue(self._check_table( + "employees.employees_range_string", 'RANGE_STRING')) + + # Invalid key for RANGE_STRING + tbl_name = "employees_range_string" + tables = ["employees.{0}".format(tbl_name)] + + self.cnx.set_property(tables=tables, + key=u'1', mode=fabric.MODE_READONLY) + try: + cur = self.cnx.cursor() + except ValueError as exc: + self.assertEqual("Key invalid; was '1'", str(exc)) + else: + self.fail("ValueError not raised") + + # Invalid key for RANGE_DATETIME + tbl_name = "employees_range_datetime" + tables = ["employees.{0}".format(tbl_name)] + + self.cnx.set_property(tables=tables, + key=datetime.date(1977, 1, 1), + mode=fabric.MODE_READONLY) + try: + cur = self.cnx.cursor() + except ValueError as exc: + self.assertEqual("Key invalid; was '1977-01-01'", str(exc)) + else: + self.fail("ValueError not raised") From 740912f0ef87646c9507da7666cdba02951ee12c Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Fri, 10 Oct 2014 14:31:53 +0200 Subject: [PATCH 10/50] BUG19549363: Raise error when compression used with reset_session Change user command raises packets out of order error when used with compression. This causes error when a pooled connection tries to reset session which internally sends change user command for MySQL server 5.7.2 or earlier. We fix this by raising a NotSupportedError whenever someone tries to reset a session on MySQL server earlier than 5.7.3. A test case has been added for BUG#19549363. (cherry picked from commit 1b412b7) --- lib/mysql/connector/connection.py | 15 ++++++- lib/mysql/connector/pooling.py | 11 +++++ tests/test_bugs.py | 67 +++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/lib/mysql/connector/connection.py b/lib/mysql/connector/connection.py index 79f64e71..79c09268 100644 --- a/lib/mysql/connector/connection.py +++ b/lib/mysql/connector/connection.py @@ -123,6 +123,7 @@ def __init__(self, *args, **kwargs): self._ssl_active = False self._auth_plugin = None self._pool_config_version = None + self._compress = False if len(kwargs) > 0: self.connect(**kwargs) @@ -271,6 +272,7 @@ def config(self, **kwargs): try: if config['compress']: + self._compress = True self.set_client_flags([ClientFlag.COMPRESS]) except KeyError: pass # Missing compress argument is OK @@ -884,6 +886,10 @@ def cmd_change_user(self, username='', password='', database='', if self.unread_result: raise errors.InternalError("Unread result found.") + if self._compress: + raise errors.NotSupportedError("Change user is not supported with " + "compression.") + packet = self._protocol.make_change_user( handshake=self._handshake, username=username, password=password, database=database, @@ -943,8 +949,13 @@ def reset_session(self, user_variables=None, session_variables=None): try: self.cmd_reset_connection() except errors.NotSupportedError: - self.cmd_change_user(self._user, self._password, - self._database, self._charset_id) + if self._compress: + raise errors.NotSupportedError( + "Reset session is not supported with compression for " + "MySQL server version 5.7.2 or earlier.") + else: + self.cmd_change_user(self._user, self._password, + self._database, self._charset_id) cur = self.cursor() if user_variables: diff --git a/lib/mysql/connector/pooling.py b/lib/mysql/connector/pooling.py index 579a3af4..8937662b 100644 --- a/lib/mysql/connector/pooling.py +++ b/lib/mysql/connector/pooling.py @@ -269,6 +269,17 @@ def add_connection(self, cnx=None): if not cnx: cnx = MySQLConnection(**self._cnx_config) + try: + if (self._reset_session and self._cnx_config['compress'] + and cnx.get_server_version() < (5, 7, 3)): + raise errors.NotSupportedError("Pool reset session is " + "not supported with " + "compression for MySQL " + "server version 5.7.2 " + "or earlier.") + except KeyError: + pass + # pylint: disable=W0201,W0212 cnx._pool_config_version = self._config_version # pylint: enable=W0201,W0212 diff --git a/tests/test_bugs.py b/tests/test_bugs.py index b195e22b..95e80f9f 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -2960,3 +2960,70 @@ def test_row_to_python(self): cur.execute("INSERT INTO {0} (c1) VALUES (?)".format(self.tbl), (data,)) self.cur.execute("SELECT * FROM {0}".format(self.tbl)) self.assertEqual((data,), self.cur.fetchone()) + + +class BugOra19500097(tests.MySQLConnectorTests): + """BUG#19500097: BETTER SUPPORT FOR RAW/BINARY DATA + """ + def setUp(self): + config = tests.get_mysql_config() + self.cnx = connection.MySQLConnection(**config) + self.cur = self.cnx.cursor() + + self.tbl = 'Bug19500097' + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + + create = ("CREATE TABLE {0} (col1 VARCHAR(10), col2 INT) " + "DEFAULT CHARSET latin1".format(self.tbl)) + self.cur.execute(create) + + def tearDown(self): + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + self.cur.close() + self.cnx.close() + + def test_binary_charset(self): + + sql = "INSERT INTO {0} VALUES(%s, %s)".format(self.tbl) + self.cur.execute(sql, ('foo', 1)) + self.cur.execute(sql, ('ëëë', 2)) + self.cur.execute(sql, (u'ááá', 5)) + + self.cnx.set_charset_collation('binary') + self.cur.execute(sql, ('bar', 3)) + self.cur.execute(sql, ('ëëë', 4)) + self.cur.execute(sql, (u'ááá', 6)) + + exp = [ + (bytearray(b'foo'), 1), + (bytearray(b'\xeb\xeb\xeb'), 2), + (bytearray(b'\xe1\xe1\xe1'), 5), + (bytearray(b'bar'), 3), + (bytearray(b'\xc3\xab\xc3\xab\xc3\xab'), 4), + (bytearray(b'\xc3\xa1\xc3\xa1\xc3\xa1'), 6) + ] + + self.cur.execute("SELECT * FROM {0}".format(self.tbl)) + self.assertEqual(exp, self.cur.fetchall()) + + +@unittest.skipIf(tests.MYSQL_VERSION < (5, 7, 3), + "MySQL {0} does not support COM_RESET_CONNECTION".format( + tests.MYSQL_VERSION_TXT)) +class BugOra19549363(tests.MySQLConnectorTests): + """BUG#19549363: Compression does not work with Change User + """ + def test_compress(self): + config = tests.get_mysql_config() + config['compress'] = True + + mysql.connector._CONNECTION_POOLS = {} + config['pool_name'] = 'mypool' + config['pool_size'] = 3 + config['pool_reset_session'] = True + cnx1 = mysql.connector.connect(**config) + + try: + cnx1.close() + except: + self.fail("Reset session with compression test failed.") From 57504c8483a3adac79d3bc2131178fa877fa4c66 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Tue, 30 Sep 2014 08:30:52 +0200 Subject: [PATCH 11/50] BUG19677659: Move testings of errors to internal repository We move the unit tests checking error code generation date from main repository to the internal repository. These tests need to be run only while preparing release. (cherry picked from commit d86e4ba) --- tests/test_errorcode.py | 13 ------------- tests/test_locales.py | 19 ------------------- 2 files changed, 32 deletions(-) diff --git a/tests/test_errorcode.py b/tests/test_errorcode.py index d63531a1..829eacc8 100644 --- a/tests/test_errorcode.py +++ b/tests/test_errorcode.py @@ -31,19 +31,6 @@ class ErrorCodeTests(tests.MySQLConnectorTests): - def test__GENERATED_ON(self): - self.assertTrue(isinstance(errorcode._GENERATED_ON, str)) - try: - generatedon = datetime.strptime(errorcode._GENERATED_ON, - '%Y-%m-%d').date() - except ValueError as err: - self.fail(err) - - delta = (datetime.now().date() - generatedon).days - self.assertTrue( - delta < 120, - "errorcode.py is more than 120 days old ({0})".format(delta)) - def test__MYSQL_VERSION(self): minimum = (5, 6, 6) self.assertTrue(isinstance(errorcode._MYSQL_VERSION, tuple)) diff --git a/tests/test_locales.py b/tests/test_locales.py index 35d371a6..e6e1b672 100644 --- a/tests/test_locales.py +++ b/tests/test_locales.py @@ -84,25 +84,6 @@ class LocalesEngClientErrorTests(tests.MySQLConnectorTests): """Testing locales.eng.client_error""" - def test__GENERATED_ON(self): - try: - from mysql.connector.locales.eng import client_error - except ImportError: - self.fail("locales.eng.client_error could not be imported") - - self.assertTrue(isinstance(client_error._GENERATED_ON, str)) - try: - generatedon = datetime.strptime(client_error._GENERATED_ON, - '%Y-%m-%d').date() - except ValueError as err: - self.fail(err) - - delta = datetime.now().date() - generatedon - self.assertTrue( - delta.days < 120, # pylint disable=E1103 - "eng/client_error.py is more than 120 days old ({0})".format( - delta.days)) # pylint disable=E1103 - def test__MYSQL_VERSION(self): try: from mysql.connector.locales.eng import client_error From 89a731e7962ef61ec8a495eaa4d44ec15d57bb09 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Wed, 1 Oct 2014 13:01:49 +0200 Subject: [PATCH 12/50] BUG19500097: Fix string decoding with binary character set When connection character set is set to 'binary', the string data failed to decode since it is not a valid Python character set. We fix this by returning string data without decoding when the character set is set to 'binary'. A unit test has been added for BUG#19500097. (cherry picked from commit dcd5b63) --- lib/mysql/connector/connection.py | 2 +- lib/mysql/connector/conversion.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/mysql/connector/connection.py b/lib/mysql/connector/connection.py index 79c09268..5e680082 100644 --- a/lib/mysql/connector/connection.py +++ b/lib/mysql/connector/connection.py @@ -1146,7 +1146,7 @@ def python_charset(self): Returns a string. """ encoding = CharacterSet.get_info(self._charset_id)[0] - if encoding == 'utf8mb4': + if encoding in ('utf8mb4', 'binary'): return 'utf8' else: return encoding diff --git a/lib/mysql/connector/conversion.py b/lib/mysql/connector/conversion.py index 4f577fd8..b054d8b1 100644 --- a/lib/mysql/connector/conversion.py +++ b/lib/mysql/connector/conversion.py @@ -178,10 +178,15 @@ def _str_to_mysql(self, value): def _unicode_to_mysql(self, value): """Convert unicode""" - encoded = value.encode(self.charset) - if self.charset_id in CharacterSet.slash_charsets: + charset = self.charset + charset_id = self.charset_id + if charset == 'binary': + charset = 'utf8' + charset_id = CharacterSet.get_charset_info(charset)[0] + encoded = value.encode(charset) + if charset_id in CharacterSet.slash_charsets: if b'\x5c' in encoded: - return HexLiteral(value, self.charset) + return HexLiteral(value, charset) return encoded def _bytes_to_mysql(self, value): @@ -537,6 +542,8 @@ def _STRING_to_python(self, value, dsc=None): # pylint: disable=C0103 if dsc[7] & FieldFlag.BINARY: return value + if self.charset == 'binary': + return value if isinstance(value, (bytes, bytearray)) and self.use_unicode: return value.decode(self.charset) From 282e37149c29539040679a828c59276605a1c6a8 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Fri, 10 Oct 2014 20:54:59 +0200 Subject: [PATCH 13/50] BUG19642249: Improve errors reporting invalid sharding keys An extra patch in which we fix the Pylint errors for the the insort_right_rev() function. (cherry picked from commit 5454250) --- lib/mysql/connector/fabric/caching.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/mysql/connector/fabric/caching.py b/lib/mysql/connector/fabric/caching.py index d9a8a072..b128b06b 100644 --- a/lib/mysql/connector/fabric/caching.py +++ b/lib/mysql/connector/fabric/caching.py @@ -36,22 +36,24 @@ _CACHE_TTL = 1 * 60 # 1 minute -def insort_right_rev(a, x, lo=0, hi=None): +def insort_right_rev(alist, new_element, low=0, high=None): """Similar to bisect.insort_right but for reverse sorted lists This code is similar to the Python code found in Lib/bisect.py. We simply change the comparison from 'less than' to 'greater than'. """ - if lo < 0: - raise ValueError('lo must be non-negative') - if hi is None: - hi = len(a) - while lo < hi: - mid = (lo+hi)//2 - if x > a[mid]: hi = mid - else: lo = mid+1 - a.insert(lo, x) + if low < 0: + raise ValueError('low must be non-negative') + if high is None: + high = len(alist) + while low < high: + middle = (low + high) // 2 + if new_element > alist[middle]: + high = middle + else: + low = middle + 1 + alist.insert(low, new_element) class CacheEntry(object): From cb3ccf5460cea3e3b494ecb9f78e0a35c23f78b0 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Fri, 10 Oct 2014 21:32:37 +0200 Subject: [PATCH 14/50] Prepare release 2.0.2 --- CHANGES.txt | 15 +++ cpyint | 2 +- lib/mysql/connector/errorcode.py | 112 +++++++++++++----- .../connector/locales/eng/client_error.py | 6 +- lib/mysql/connector/version.py | 2 +- 5 files changed, 100 insertions(+), 37 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 18859e2a..a45dff60 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,21 @@ Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. Full release notes: http://dev.mysql.com/doc/relnotes/connector-python/en/ +v2.0.2 +====== + +- BUG19500097: Fix string decoding with binary character set +- BUG19677659: Move testings of errors to internal repository +- BUG19549363: Raise error when compression used with reset_session +- BUG19642249: Improve errors reporting invalid sharding keys +- BUG19711759: Fix Pylint issue in network module +- BUG19667984: Fix converting invalid datetime values in Django backend +- BUG19660283: Fix failing unit tests with MySQL server 5.7.5 +- BUG19584051: Fix comparison of type_code of columns for PEP-249 +- BUG19522948: Fix data corruption with TEXT and prepared statements +- BUG19584116: Fix extra signal causing runtime error in Django + + v2.0.1 ====== diff --git a/cpyint b/cpyint index aef6c252..a5e697b1 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit aef6c252dc930a86cb1bb2912e1099eb6a117e55 +Subproject commit a5e697b1063582f7d923feb5a7094d5df6eb6394 diff --git a/lib/mysql/connector/errorcode.py b/lib/mysql/connector/errorcode.py index 756f8e7a..03814824 100644 --- a/lib/mysql/connector/errorcode.py +++ b/lib/mysql/connector/errorcode.py @@ -24,8 +24,8 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # This file was auto-generated. -_GENERATED_ON = '2014-05-23' -_MYSQL_VERSION = (5, 7, 4) +_GENERATED_ON = '2014-10-10' +_MYSQL_VERSION = (5, 7, 5) """This module contains the MySQL Server and Client error codes""" @@ -649,7 +649,7 @@ ER_DELAYED_NOT_SUPPORTED = 1616 WARN_NO_MASTER_INFO = 1617 WARN_OPTION_IGNORED = 1618 -WARN_PLUGIN_DELETE_BUILTIN = 1619 +ER_PLUGIN_DELETE_BUILTIN = 1619 WARN_PLUGIN_BUSY = 1620 ER_VARIABLE_IS_READONLY = 1621 ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622 @@ -913,34 +913,82 @@ ER_OLD_TEMPORALS_UPGRADED = 1880 ER_INNODB_FORCED_RECOVERY = 1881 ER_AES_INVALID_IV = 1882 -ER_FILE_CORRUPT = 1883 -ER_ERROR_ON_MASTER = 1884 -ER_INCONSISTENT_ERROR = 1885 -ER_STORAGE_ENGINE_NOT_LOADED = 1886 -ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 1887 -ER_WARN_LEGACY_SYNTAX_CONVERTED = 1888 -ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 1889 -ER_CANNOT_DISCARD_TEMPORARY_TABLE = 1890 -ER_FK_DEPTH_EXCEEDED = 1891 -ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 1892 -ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 1893 -ER_REFERENCED_TRG_DOES_NOT_EXIST = 1894 -ER_EXPLAIN_NOT_SUPPORTED = 1895 -ER_INVALID_FIELD_SIZE = 1896 -ER_MISSING_HA_CREATE_OPTION = 1897 -ER_ENGINE_OUT_OF_MEMORY = 1898 -ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 1899 -ER_SLAVE_SQL_THREAD_MUST_STOP = 1900 -ER_NO_FT_MATERIALIZED_SUBQUERY = 1901 -ER_INNODB_UNDO_LOG_FULL = 1902 -ER_INVALID_ARGUMENT_FOR_LOGARITHM = 1903 -ER_SLAVE_IO_THREAD_MUST_STOP = 1904 -ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 1905 -ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 1906 -ER_QUERY_TIMEOUT = 1907 -ER_NON_RO_SELECT_DISABLE_TIMER = 1908 -ER_DUP_LIST_ENTRY = 1909 -ER_SQL_MODE_NO_EFFECT = 1910 +ER_PLUGIN_CANNOT_BE_UNINSTALLED = 1883 +ER_GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_GTID_GROUP = 1884 +ER_FILE_CORRUPT = 1885 +ER_ERROR_ON_MASTER = 1886 +ER_INCONSISTENT_ERROR = 1887 +ER_STORAGE_ENGINE_NOT_LOADED = 1888 +ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 1889 +ER_WARN_LEGACY_SYNTAX_CONVERTED = 1890 +ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 1891 +ER_CANNOT_DISCARD_TEMPORARY_TABLE = 1892 +ER_FK_DEPTH_EXCEEDED = 1893 +ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 1894 +ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 1895 +ER_REFERENCED_TRG_DOES_NOT_EXIST = 1896 +ER_EXPLAIN_NOT_SUPPORTED = 1897 +ER_INVALID_FIELD_SIZE = 1898 +ER_MISSING_HA_CREATE_OPTION = 1899 +ER_ENGINE_OUT_OF_MEMORY = 1900 +ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 1901 +ER_SLAVE_SQL_THREAD_MUST_STOP = 1902 +ER_NO_FT_MATERIALIZED_SUBQUERY = 1903 +ER_INNODB_UNDO_LOG_FULL = 1904 +ER_INVALID_ARGUMENT_FOR_LOGARITHM = 1905 +ER_SLAVE_IO_THREAD_MUST_STOP = 1906 +ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 1907 +ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 1908 +ER_QUERY_TIMEOUT = 1909 +ER_NON_RO_SELECT_DISABLE_TIMER = 1910 +ER_DUP_LIST_ENTRY = 1911 +ER_SQL_MODE_NO_EFFECT = 1912 +ER_AGGREGATE_ORDER_FOR_UNION = 1913 +ER_AGGREGATE_ORDER_NON_AGG_QUERY = 1914 +ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 1915 +ER_DONT_SUPPORT_SLAVE_PRESERVE_COMMIT_ORDER = 1916 +ER_SERVER_OFFLINE_MODE = 1917 +ER_GIS_DIFFERENT_SRIDS = 1918 +ER_GIS_UNSUPPORTED_ARGUMENT = 1919 +ER_GIS_UNKNOWN_ERROR = 1920 +ER_GIS_UNKNOWN_EXCEPTION = 1921 +ER_GIS_INVALID_DATA = 1922 +ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 1923 +ER_BOOST_GEOMETRY_CENTROID_EXCEPTION = 1924 +ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 1925 +ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 1926 +ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 1927 +ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 1928 +ER_STD_BAD_ALLOC_ERROR = 1929 +ER_STD_DOMAIN_ERROR = 1930 +ER_STD_LENGTH_ERROR = 1931 +ER_STD_INVALID_ARGUMENT = 1932 +ER_STD_OUT_OF_RANGE_ERROR = 1933 +ER_STD_OVERFLOW_ERROR = 1934 +ER_STD_RANGE_ERROR = 1935 +ER_STD_UNDERFLOW_ERROR = 1936 +ER_STD_LOGIC_ERROR = 1937 +ER_STD_RUNTIME_ERROR = 1938 +ER_STD_UNKNOWN_EXCEPTION = 1939 +ER_GIS_DATA_WRONG_ENDIANESS = 1940 +ER_CHANGE_MASTER_PASSWORD_LENGTH = 1941 +ER_USER_LOCK_WRONG_NAME = 1942 +ER_USER_LOCK_DEADLOCK = 1943 +ER_REPLACE_INACCESSIBLE_ROWS = 1944 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 1945 +ER_ILLEGAL_USER_VAR = 1946 +ER_GTID_MODE_OFF = 1947 +ER_UNSUPPORTED_BY_REPLICATION_THREAD = 1948 +ER_INCORRECT_TYPE = 1949 +ER_FIELD_IN_ORDER_NOT_SELECT = 1950 +ER_AGGREGATE_IN_ORDER_NOT_SELECT = 1951 +ER_INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 1952 +ER_NET_OK_PACKET_TOO_LARGE = 1953 +ER_INVALID_JSON_DATA = 1954 +ER_INVALID_GEOJSON_MISSING_MEMBER = 1955 +ER_INVALID_GEOJSON_WRONG_TYPE = 1956 +ER_INVALID_GEOJSON_UNSPECIFIED = 1957 +ER_DIMENSION_UNSUPPORTED = 1958 CR_UNKNOWN_ERROR = 2000 CR_SOCKET_CREATE_ERROR = 2001 CR_CONNECTION_ERROR = 2002 @@ -990,7 +1038,7 @@ CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046 CR_CONN_UNKNOW_PROTOCOL = 2047 CR_INVALID_CONN_HANDLE = 2048 -CR_SECURE_AUTH = 2049 +CR_UNUSED_1 = 2049 CR_FETCH_CANCELED = 2050 CR_NO_DATA = 2051 CR_NO_STMT_METADATA = 2052 diff --git a/lib/mysql/connector/locales/eng/client_error.py b/lib/mysql/connector/locales/eng/client_error.py index 6aaa42d7..c448d034 100644 --- a/lib/mysql/connector/locales/eng/client_error.py +++ b/lib/mysql/connector/locales/eng/client_error.py @@ -24,8 +24,8 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # This file was auto-generated. -_GENERATED_ON = '2014-05-23' -_MYSQL_VERSION = (5, 7, 4) +_GENERATED_ON = '2014-10-10' +_MYSQL_VERSION = (5, 7, 5) # Start MySQL Error messages CR_UNKNOWN_ERROR = u"Unknown MySQL error" @@ -77,7 +77,7 @@ CR_SHARED_MEMORY_CONNECT_SET_ERROR = u"Can't open shared memory; cannot send request event to server (%s)" CR_CONN_UNKNOW_PROTOCOL = u"Wrong or unknown protocol" CR_INVALID_CONN_HANDLE = u"Invalid connection handle" -CR_SECURE_AUTH = u"Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" +CR_UNUSED_1 = u"Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" CR_FETCH_CANCELED = u"Row retrieval was canceled by mysql_stmt_close() call" CR_NO_DATA = u"Attempt to read column without prior row fetch" CR_NO_STMT_METADATA = u"Prepared statement contains no metadata" diff --git a/lib/mysql/connector/version.py b/lib/mysql/connector/version.py index 405f9eec..0c3ebeeb 100644 --- a/lib/mysql/connector/version.py +++ b/lib/mysql/connector/version.py @@ -26,7 +26,7 @@ as mysql.connector.version. """ -VERSION = (2, 0, 1, '', 0) +VERSION = (2, 0, 2, '', 0) if VERSION[3] and VERSION[4]: VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION) From 29a45f09767c444f725523e1c4a9bbc745796d44 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Sat, 11 Oct 2014 09:53:23 +0200 Subject: [PATCH 15/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index a5e697b1..603f7e32 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit a5e697b1063582f7d923feb5a7094d5df6eb6394 +Subproject commit 603f7e32e677b6eca515163394ed51fd728fa131 From bd21ac969653992b07e68712948be350017cf9ea Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Tue, 14 Oct 2014 12:24:39 +0200 Subject: [PATCH 16/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 603f7e32..248a2095 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 603f7e32e677b6eca515163394ed51fd728fa131 +Subproject commit 248a2095dab9657add43b93dbb528215cfbf162a From 3476c09c8675142ffcb03b73a8f79411202f5061 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Fri, 14 Nov 2014 06:37:13 +0000 Subject: [PATCH 17/50] BUG19803702: Fix reporting errors with non-ascii characters We fix reporting errors containing non ascii characters with Python v2. We fix this by encoding error message with 'utf8' when used with Python v2. A unit test has been added for BUG#19803702. (cherry picked from commit c2a7630) --- lib/mysql/connector/errors.py | 3 ++- tests/test_bugs.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/mysql/connector/errors.py b/lib/mysql/connector/errors.py index 0dff38c2..7368ffaf 100644 --- a/lib/mysql/connector/errors.py +++ b/lib/mysql/connector/errors.py @@ -26,6 +26,7 @@ from . import utils from .locales import get_client_error +from .catch23 import PY2 # _CUSTOM_ERROR_EXCEPTIONS holds custom exceptions and is ued by the # function custom_error_exception. _ERROR_EXCEPTIONS (at bottom of module) @@ -187,7 +188,7 @@ def __init__(self, msg=None, errno=None, values=None, sqlstate=None): if self.msg and self.errno != -1: fields = { 'errno': self.errno, - 'msg': self.msg + 'msg': self.msg.encode('utf8') if PY2 else self.msg } if self.sqlstate: fmt = '{errno} ({state}): {msg}' diff --git a/tests/test_bugs.py b/tests/test_bugs.py index 95e80f9f..b5935191 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -3027,3 +3027,26 @@ def test_compress(self): cnx1.close() except: self.fail("Reset session with compression test failed.") + + +class BugOra19803702(tests.MySQLConnectorTests): + """BUG#19803702: CAN'T REPORT ERRORS THAT HAVE NON-ASCII CHARACTERS + """ + def test_errors(self): + config = tests.get_mysql_config() + self.cnx = connection.MySQLConnection(**config) + self.cur = self.cnx.cursor() + + self.tbl = 'áááëëëááá' + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + + create = ("CREATE TABLE {0} (col1 VARCHAR(10), col2 INT) " + "DEFAULT CHARSET latin1".format(self.tbl)) + + self.cur.execute(create) + self.assertRaises(errors.DatabaseError, self.cur.execute, create) + + def tearDown(self): + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + self.cur.close() + self.cnx.close() From 6bce47182d63071b485de76742db96b047651a82 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Thu, 30 Oct 2014 17:54:03 +0530 Subject: [PATCH 18/50] BUG74483: Lost connection to server error during query We fix lost connection to server error caused when server is present at a remote location. --- lib/mysql/connector/network.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/mysql/connector/network.py b/lib/mysql/connector/network.py index f7d10143..42d760bd 100644 --- a/lib/mysql/connector/network.py +++ b/lib/mysql/connector/network.py @@ -220,10 +220,12 @@ def recv_plain(self): """Receive packets from the MySQL server""" try: # Read the header of the MySQL packet, 4 bytes - packet = bytearray(4) - read = self.sock.recv_into(packet, 4) - if read != 4: - raise errors.InterfaceError(errno=2013) + packet = bytearray(b'') + while len(packet) < 4: + chunk = self.sock.recv(4) + if not chunk: + raise errors.InterfaceError(errno=2013) + packet += chunk # Save the packet number and payload length self._packet_number = packet[3] From 142dba95c57b60970c0a39d635bb98bd68118543 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Wed, 12 Nov 2014 13:13:26 +0530 Subject: [PATCH 19/50] BUG19331658: Fix connection pooling with MySQL Fabric Using connection pooling with MySQL Fabric was raising AttributeError. We fix this by creating a pool of connections to a server as and when first time it is required to be connected. To use pooling with Fabric, 'pool_size' argument should be used. Using 'pool_name' with Fabric raises AttributeError. This is because we can't have two pools with same names on different servers. A unit test has been added for BUG#19331658. --- lib/mysql/connector/__init__.py | 3 ++ lib/mysql/connector/fabric/connection.py | 6 ++-- tests/test_fabric.py | 40 ++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/mysql/connector/__init__.py b/lib/mysql/connector/__init__.py index 3161cece..a0e68008 100644 --- a/lib/mysql/connector/__init__.py +++ b/lib/mysql/connector/__init__.py @@ -139,6 +139,9 @@ def connect(*args, **kwargs): raise InterfaceError("fabric and failover arguments can not be used") if 'fabric' in kwargs: + if 'pool_name' in kwargs: + raise AttributeError("'pool_name' argument is not supported with " + " MySQL Fabric. Use 'pool_size' instead.") from .fabric import connect as fabric_connect return fabric_connect(*args, **kwargs) diff --git a/lib/mysql/connector/fabric/connection.py b/lib/mysql/connector/fabric/connection.py index c3a650e8..f352938d 100644 --- a/lib/mysql/connector/fabric/connection.py +++ b/lib/mysql/connector/fabric/connection.py @@ -61,7 +61,7 @@ HAVE_SSL = True # pylint: enable=F0401,E0611 -from ..connection import MySQLConnection +import ...connector import connect from ..pooling import MySQLConnectionPool from ..errors import ( Error, InterfaceError, NotSupportedError, MySQLFabricError, InternalError, @@ -1123,6 +1123,8 @@ def store_config(self, **kwargs): del test_config['pool_name'] if 'pool_size' in test_config: del test_config['pool_size'] + if 'pool_reset_session' in test_config: + del test_config['pool_reset_session'] try: pool = MySQLConnectionPool(pool_name=str(uuid.uuid4())) pool.set_config(**test_config) @@ -1186,7 +1188,7 @@ def _connect(self): dbconfig['host'] = mysqlserver.host dbconfig['port'] = mysqlserver.port try: - self._mysql_cnx = MySQLConnection(**dbconfig) + self._mysql_cnx = connect(**dbconfig) except Error as exc: if counter == attempts: self.reset_cache(mysqlserver.group) diff --git a/tests/test_fabric.py b/tests/test_fabric.py index 70b00ad4..20087375 100644 --- a/tests/test_fabric.py +++ b/tests/test_fabric.py @@ -41,6 +41,7 @@ from mysql.connector import fabric, errorcode from mysql.connector.fabric import connection, balancing from mysql.connector.catch23 import UNICODE_TYPES, PY2 +from mysql.connector.pooling import PooledMySQLConnection ERR_NO_FABRIC_CONFIG = "Fabric configuration not available" @@ -578,3 +579,42 @@ def test_bug19642249(self): self.assertEqual("Key invalid; was '1977-01-01'", str(exc)) else: self.fail("ValueError not raised") + + def test_bug19331658(self): + """Pooling not working with fabric + """ + self.assertRaises( + AttributeError, mysql.connector.connect, + fabric=tests.FABRIC_CONFIG, user='root', database='employees', + pool_name='mypool') + + pool_size = 2 + cnx = mysql.connector.connect( + fabric=tests.FABRIC_CONFIG, user='root', database='employees', + pool_size=pool_size, pool_reset_session=False + ) + tbl_name = "employees_range" + + tables = ["employees.{0}".format(tbl_name)] + + cnx.set_property(tables=tables, + scope=fabric.SCOPE_GLOBAL, + mode=fabric.MODE_READWRITE) + cnx.cursor() + self.assertTrue(isinstance(cnx._mysql_cnx, PooledMySQLConnection)) + + data = self.emp_data[1985] + + for emp in data: + cnx.set_property(tables=tables, + key=emp[0], + scope=fabric.SCOPE_LOCAL, + mode=fabric.MODE_READWRITE) + cnx.cursor() + mysqlserver = cnx._fabric_mysql_server + config = cnx._mysql_config + self.assertEqual( + cnx._mysql_cnx.pool_name, "{0}_{1}_{2}_{3}".format( + mysqlserver.host, mysqlserver.port, config['user'], + config['database']) + ) From f27093ec8e617f4da372efe62ee1dd4f0b731484 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Thu, 4 Dec 2014 13:05:09 +0530 Subject: [PATCH 20/50] BUG19972427: Fix creating of redundant connections in Django The Connector/Python Django backend was creating too many database connections since as soon as an object of DatabaseWrapper was instanciated, a connection to the MySQL server was set up. This is required to enable some version specific MySQL features. We fix this by adding the cached_property decorator to the mysql_version()-method. Unit tests are updated and also fixes BUG#19954882. --- lib/mysql/connector/conversion.py | 6 +++- lib/mysql/connector/django/base.py | 45 +++++++++----------------- lib/mysql/connector/django/creation.py | 3 +- tests/test_django.py | 20 +++++++++++- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/lib/mysql/connector/conversion.py b/lib/mysql/connector/conversion.py index b054d8b1..51332d27 100644 --- a/lib/mysql/connector/conversion.py +++ b/lib/mysql/connector/conversion.py @@ -66,7 +66,11 @@ def set_unicode(self, value=True): def to_mysql(self, value): """Convert Python data type to MySQL""" - return value + type_name = value.__class__.__name__.lower() + try: + return getattr(self, "_{0}_to_mysql".format(type_name))(value) + except AttributeError: + return value def to_python(self, vtype, value): """Convert MySQL data type to Python""" diff --git a/lib/mysql/connector/django/base.py b/lib/mysql/connector/django/base.py index e6844d14..ccdae2c7 100644 --- a/lib/mysql/connector/django/base.py +++ b/lib/mysql/connector/django/base.py @@ -192,7 +192,7 @@ def __init__(self, connection): self.supports_microsecond_precision = self._microseconds_precision() def _microseconds_precision(self): - if self.connection.server_version >= (5, 6, 3): + if self.connection.mysql_version >= (5, 6, 3): return True return False @@ -212,7 +212,7 @@ def _mysql_storage_engine(self): cursor.execute(droptable) cursor.execute('CREATE TABLE {table} (X INT)'.format(table=tblname)) - if self.connection.server_version >= (5, 0, 0): + if self.connection.mysql_version >= (5, 0, 0): cursor.execute( "SELECT ENGINE FROM INFORMATION_SCHEMA.TABLES " "WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s", @@ -399,7 +399,7 @@ def sequence_reset_by_name_sql(self, style, sequences): # Truncate already resets the AUTO_INCREMENT field from # MySQL version 5.0.13 onwards. Refs #16961. res = [] - if self.connection.server_version < (5, 0, 13): + if self.connection.mysql_version < (5, 0, 13): fmt = "{alter} {table} {{tablename}} {auto_inc} {field};".format( alter=style.SQL_KEYWORD('ALTER'), table=style.SQL_KEYWORD('TABLE'), @@ -431,13 +431,7 @@ def value_to_db_datetime(self, value): "MySQL backend does not support timezone-aware times." ) - try: - # Django 1.6 - self.connection.ensure_connection() - except AttributeError: - if not self.connection.connection: - self.connection._connect() - return self.connection.connection.converter._datetime_to_mysql(value) + return self.connection.converter.to_mysql(value) def value_to_db_time(self, value): if value is None: @@ -448,13 +442,7 @@ def value_to_db_time(self, value): raise ValueError("MySQL backend does not support timezone-aware " "times.") - try: - # Django 1.6 - self.connection.ensure_connection() - except AttributeError: - if not self.connection.connection: - self.connection._connect() - return self.connection.connection.converter._time_to_mysql(value) + return self.connection.converter.to_mysql(value) def year_lookup_bounds(self, value): # Again, no microseconds @@ -467,7 +455,7 @@ def year_lookup_bounds_for_datetime_field(self, value): # Again, no microseconds first, second = super(DatabaseOperations, self).year_lookup_bounds_for_datetime_field(value) - if self.connection.server_version >= (5, 6, 4): + if self.connection.mysql_version >= (5, 6, 4): return [first.replace(microsecond=0), second] else: return [first.replace(microsecond=0), @@ -522,15 +510,8 @@ class DatabaseWrapper(BaseDatabaseWrapper): def __init__(self, *args, **kwargs): super(DatabaseWrapper, self).__init__(*args, **kwargs) - self.server_version = None - - # Since some features depend on the MySQL version, we need to connect - try: - # Django 1.6 - self.ensure_connection() - except AttributeError: - self._connect() + self.converter = DjangoMySQLConverter() self.ops = DatabaseOperations(self) self.features = DatabaseFeatures(self) self.client = DatabaseClient(self) @@ -584,14 +565,13 @@ def get_connection_params(self): def get_new_connection(self, conn_params): # Django 1.6 cnx = mysql.connector.connect(**conn_params) - self.server_version = cnx.get_server_version() cnx.set_converter_class(DjangoMySQLConverter) return cnx def init_connection_state(self): # Django 1.6 - if self.server_version < (5, 5, 3): + if self.mysql_version < (5, 5, 3): # See sysvar_sql_auto_is_null in MySQL Reference manual self.connection.cmd_query("SET SQL_AUTO_IS_NULL = 0") @@ -737,6 +717,11 @@ def is_usable(self): # Django 1.6 return self.connection.is_connected() - @property + @cached_property def mysql_version(self): - return self.server_version + config = self.get_connection_params() + temp_conn = mysql.connector.connect(**config) + server_version = temp_conn.get_server_version() + temp_conn.close() + + return server_version diff --git a/lib/mysql/connector/django/creation.py b/lib/mysql/connector/django/creation.py index 4d4df1bf..f34e54bd 100644 --- a/lib/mysql/connector/django/creation.py +++ b/lib/mysql/connector/django/creation.py @@ -30,6 +30,7 @@ def __init__(self, connection): 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', + 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', @@ -42,7 +43,7 @@ def __init__(self, connection): } # Support for microseconds - if self.connection.get_server_version() >= (5, 6, 4): + if self.connection.mysql_version >= (5, 6, 4): self.data_types.update({ 'DateTimeField': 'datetime(6)', 'TimeField': 'time(6)', diff --git a/tests/test_django.py b/tests/test_django.py index 219e68fc..5cea41bd 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -198,7 +198,7 @@ def setUp(self): def test__init__(self): exp = self.conn.get_server_version() - self.assertEqual(exp, self.cnx.server_version) + self.assertEqual(exp, self.cnx.mysql_version) value = datetime.time(2, 5, 7) exp = self.conn.converter._time_to_mysql(value) @@ -214,7 +214,9 @@ def test_signal(self): def conn_setup(*args, **kwargs): conn = kwargs['connection'] + settings.DEBUG = True cur = conn.cursor() + settings.DEBUG = False cur.execute("SET @xyz=10") cur.close() @@ -226,6 +228,22 @@ def conn_setup(*args, **kwargs): cursor.close() self.cnx.close() + def count_conn(self, *args, **kwargs): + try: + self.connections += 1 + except AttributeError: + self.connection = 1 + + def test_connections(self): + connection_created.connect(self.count_conn) + self.connections = 0 + + # Checking if DatabaseWrapper object creates a connection by default + conn = DatabaseWrapper(settings.DATABASES['default']) + dbo = DatabaseOperations(conn) + dbo.value_to_db_time(datetime.time(3, 3, 3)) + self.assertEqual(self.connections, 0) + class DjangoDatabaseOperations(tests.MySQLConnectorTests): From f45273245cc43866ff9bad28d51cc121aa2a091e Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Fri, 5 Dec 2014 23:31:13 +0530 Subject: [PATCH 21/50] BUG19331658: Fix connection pooling with MySQL Fabric We fix a SyntaxError introduced while pushing. --- lib/mysql/connector/fabric/connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mysql/connector/fabric/connection.py b/lib/mysql/connector/fabric/connection.py index f352938d..ffd20469 100644 --- a/lib/mysql/connector/fabric/connection.py +++ b/lib/mysql/connector/fabric/connection.py @@ -61,7 +61,7 @@ HAVE_SSL = True # pylint: enable=F0401,E0611 -import ...connector import connect +import mysql.connector from ..pooling import MySQLConnectionPool from ..errors import ( Error, InterfaceError, NotSupportedError, MySQLFabricError, InternalError, @@ -1188,7 +1188,7 @@ def _connect(self): dbconfig['host'] = mysqlserver.host dbconfig['port'] = mysqlserver.port try: - self._mysql_cnx = connect(**dbconfig) + self._mysql_cnx = mysql.connector.connect(**dbconfig) except Error as exc: if counter == attempts: self.reset_cache(mysqlserver.group) From 3889e8665f3c3d3d61bd6bf67e18a8c4b442092f Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Fri, 12 Dec 2014 12:11:29 +0530 Subject: [PATCH 22/50] BUG19777815: Add support for warnings with MySQLCursor.callproc() MySQLCursor.fetchwarnings() was not returning any warnings even when warnings were generated by calling a stored procedure using MySQLCursor.callproc() method. We add support to fetch warnings generated by a stored procedure. A unit test has been added for BUG#19777815. --- lib/mysql/connector/cursor.py | 10 ++++--- tests/test_bugs.py | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/lib/mysql/connector/cursor.py b/lib/mysql/connector/cursor.py index 8a0602cc..3e6a2b47 100644 --- a/lib/mysql/connector/cursor.py +++ b/lib/mysql/connector/cursor.py @@ -697,12 +697,14 @@ def callproc(self, procname, args=()): call = "CALL {0}({1})".format(procname, ','.join(argnames)) for result in self._connection.cmd_query_iter(call): + # pylint: disable=W0212 + tmp = MySQLCursorBuffered(self._connection._get_self()) + tmp._handle_result(result) + if tmp._warnings is not None: + self._warnings = tmp._warnings + # pylint: enable=W0212 if 'columns' in result: - # pylint: disable=W0212 - tmp = MySQLCursorBuffered(self._connection._get_self()) - tmp._handle_result(result) results.append(tmp) - # pylint: enable=W0212 if argnames: select = "SELECT {0}".format(','.join(argtypes)) diff --git a/tests/test_bugs.py b/tests/test_bugs.py index b5935191..61b6518d 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -3050,3 +3050,54 @@ def tearDown(self): self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) self.cur.close() self.cnx.close() + + + +class BugOra19777815(tests.MySQLConnectorTests): + """BUG#19777815: CALLPROC() DOES NOT SUPPORT WARNINGS + """ + def setUp(self): + config = tests.get_mysql_config() + config['get_warnings'] = True + self.cnx = connection.MySQLConnection(**config) + cur = self.cnx.cursor() + self.sp1 = 'BUG19777815' + self.sp2 = 'BUG19777815_with_result' + create1 = ( + "CREATE PROCEDURE {0}() BEGIN SIGNAL SQLSTATE '01000' " + "SET MESSAGE_TEXT = 'TEST WARNING'; END;".format(self.sp1) + ) + create2 = ( + "CREATE PROCEDURE {0}() BEGIN SELECT 1; SIGNAL SQLSTATE '01000' " + "SET MESSAGE_TEXT = 'TEST WARNING'; END;".format(self.sp2) + ) + + cur.execute("DROP PROCEDURE IF EXISTS {0}".format(self.sp1)) + cur.execute("DROP PROCEDURE IF EXISTS {0}".format(self.sp2)) + cur.execute(create1) + cur.execute(create2) + cur.close() + + def tearDown(self): + cur = self.cnx.cursor() + cur.execute("DROP PROCEDURE IF EXISTS {0}".format(self.sp1)) + cur.execute("DROP PROCEDURE IF EXISTS {0}".format(self.sp2)) + cur.close() + self.cnx.close() + + def test_warning(self): + cur = self.cnx.cursor() + cur.callproc(self.sp1) + exp = [(u'Warning', 1642, u'TEST WARNING')] + self.assertEqual(exp, cur.fetchwarnings()) + cur.close() + + def test_warning_with_rows(self): + cur = self.cnx.cursor() + cur.callproc(self.sp2) + + exp = [(1,)] + self.assertEqual(exp, cur.stored_results().next().fetchall()) + exp = [(u'Warning', 1642, u'TEST WARNING')] + self.assertEqual(exp, cur.fetchwarnings()) + cur.close() From cdacb3f3a9bc62d2cca2573e3a28e4f24e9f7f79 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Tue, 4 Nov 2014 11:26:56 +0100 Subject: [PATCH 23/50] BUG19703022: Fix using passwords with integers only in option files Passwords consisting of only integers were raising error when used with option files. We fix this by not evaluating value for password option in option files. Unit tests are updated. (cherry picked from commit 677b310) --- lib/mysql/connector/optionfiles.py | 6 +++++- tests/data/option_files/my.cnf | 1 + tests/test_optionfiles.py | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/mysql/connector/optionfiles.py b/lib/mysql/connector/optionfiles.py index 6db43fd6..e4a0366c 100644 --- a/lib/mysql/connector/optionfiles.py +++ b/lib/mysql/connector/optionfiles.py @@ -103,10 +103,14 @@ def read_option_files(**config): except KeyError: continue + not_evaluate = ('password', 'passwd') for option, value in config_options.items(): if option not in config: try: - config[option] = eval(value[0]) # pylint: disable=W0123 + if option in not_evaluate: + config[option] = value[0] + else: + config[option] = eval(value[0]) # pylint: disable=W0123 except (NameError, SyntaxError): config[option] = value[0] diff --git a/tests/data/option_files/my.cnf b/tests/data/option_files/my.cnf index afafa4b7..8c27afcd 100644 --- a/tests/data/option_files/my.cnf +++ b/tests/data/option_files/my.cnf @@ -1,5 +1,6 @@ [client] +password=12345 port=1000 socket=/var/run/mysqld/mysqld.sock ssl-ca=dummyCA diff --git a/tests/test_optionfiles.py b/tests/test_optionfiles.py index 36444343..6b930cf5 100644 --- a/tests/test_optionfiles.py +++ b/tests/test_optionfiles.py @@ -99,6 +99,7 @@ def test_read(self,): def test_get_groups(self): exp = { + 'password': '12345', 'port': '1001', 'socket': '/var/run/mysqld/mysqld2.sock', 'ssl-ca': 'dummyCA', @@ -122,6 +123,7 @@ def test_get_groups(self): def test_get_groups_as_dict(self): exp = dict([ ('client', {'port': '1000', + 'password': '12345', 'socket': '/var/run/mysqld/mysqld.sock', 'ssl-ca': 'dummyCA', 'ssl-cert': 'dummyCert', @@ -167,6 +169,7 @@ def test_read_option_files(self): option_file_dir = os.path.join('tests', 'data', 'option_files') exp = { + 'password': '12345', 'port': 1000, 'unix_socket': '/var/run/mysqld/mysqld.sock', 'ssl_ca': 'dummyCA', @@ -177,6 +180,7 @@ def test_read_option_files(self): option_file_dir, 'my.cnf')) self.assertEqual(exp, result) exp = { + 'password': '12345', 'port': 1001, 'unix_socket': '/var/run/mysqld/mysqld2.sock', 'ssl_ca': 'dummyCA', From c522648d0757838f8fc0abae0c0553c042e8e217 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Mon, 15 Dec 2014 08:41:49 +0100 Subject: [PATCH 24/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 248a2095..13db873b 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 248a2095dab9657add43b93dbb528215cfbf162a +Subproject commit 13db873bbae264d5b49fed2ee1537b00ef2d3836 From d942d375028abf0435408abf12efcdb94d13929d Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Mon, 15 Dec 2014 12:31:06 +0100 Subject: [PATCH 25/50] Prepare release 2.0.3 --- CHANGES.txt | 10 ++++++++++ cpyint | 2 +- lib/mysql/connector/version.py | 2 +- tests/test_bugs.py | 11 ++++++++++- tests/test_fabric.py | 14 +++++++++++--- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a45dff60..676ec3df 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,16 @@ Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. Full release notes: http://dev.mysql.com/doc/relnotes/connector-python/en/ +v2.0.3 +====== + +- BUG19703022: Fix using passwords with integers only in option files +- BUG19777815: Add support for warnings with MySQLCursor.callproc() +- BUG19972427: Fix creating of redundant connections in Django +- BUG19331658: Fix connection pooling with MySQL Fabric +- BUG19930054: Lost connection to server error during query +- BUG19803702: Fix reporting errors with non-ascii characters + v2.0.2 ====== diff --git a/cpyint b/cpyint index 13db873b..8da3fc72 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 13db873bbae264d5b49fed2ee1537b00ef2d3836 +Subproject commit 8da3fc72278d2d82ca69d38113c397e7ec70d684 diff --git a/lib/mysql/connector/version.py b/lib/mysql/connector/version.py index 0c3ebeeb..c2a81500 100644 --- a/lib/mysql/connector/version.py +++ b/lib/mysql/connector/version.py @@ -26,7 +26,7 @@ as mysql.connector.version. """ -VERSION = (2, 0, 2, '', 0) +VERSION = (2, 0, 3, '', 0) if VERSION[3] and VERSION[4]: VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION) diff --git a/tests/test_bugs.py b/tests/test_bugs.py index 61b6518d..db268641 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -3013,6 +3013,10 @@ def test_binary_charset(self): class BugOra19549363(tests.MySQLConnectorTests): """BUG#19549363: Compression does not work with Change User """ + + def tearDown(self): + mysql.connector._CONNECTION_POOLS = {} + def test_compress(self): config = tests.get_mysql_config() config['compress'] = True @@ -3097,7 +3101,12 @@ def test_warning_with_rows(self): cur.callproc(self.sp2) exp = [(1,)] - self.assertEqual(exp, cur.stored_results().next().fetchall()) + try: + select_result = cur.stored_results().next() + except AttributeError: + # Python 3 + select_result = next(cur.stored_results()) + self.assertEqual(exp, select_result.fetchall()) exp = [(u'Warning', 1642, u'TEST WARNING')] self.assertEqual(exp, cur.fetchwarnings()) cur.close() diff --git a/tests/test_fabric.py b/tests/test_fabric.py index 20087375..22599377 100644 --- a/tests/test_fabric.py +++ b/tests/test_fabric.py @@ -535,9 +535,15 @@ def test_range_string(self): rows = cur.fetchall() self.assertEqual(rows, emp_exp_range_string[str_key]) - self.cnx.set_property(tables=tables, - key=b'not unicode str', mode=fabric.MODE_READONLY) - self.assertRaises(ValueError, self.cnx.cursor) + if not PY2: + self.assertRaises(TypeError, self.cnx.set_property, + tables=tables, key=b'not unicode str', + mode=fabric.MODE_READONLY) + else: + self.cnx.set_property(tables=tables, + key=b'not unicode str', + mode=fabric.MODE_READONLY) + self.assertRaises(ValueError, self.cnx.cursor) self.cnx.set_property(tables=tables, key=12345, mode=fabric.MODE_READONLY) @@ -618,3 +624,5 @@ def test_bug19331658(self): mysqlserver.host, mysqlserver.port, config['user'], config['database']) ) + + mysql.connector._CONNECTION_POOLS = {} From cf3cd960dc53bc768c9bbf08f472cbafc0f90e53 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Tue, 16 Dec 2014 14:40:46 +0100 Subject: [PATCH 26/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 8da3fc72..2843ad00 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 8da3fc72278d2d82ca69d38113c397e7ec70d684 +Subproject commit 2843ad0058c40c4b39f86d5b5b218b5c06505c22 From 02d6729fe86517f1e2aa323d1f1f59e717d342d0 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Wed, 17 Dec 2014 21:57:21 +0100 Subject: [PATCH 27/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 2843ad00..927a0071 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 2843ad0058c40c4b39f86d5b5b218b5c06505c22 +Subproject commit 927a00713b35cbcb4ab91da3c173ad45c0436f94 From 36dfa8180e59f2cba972375d05e6a7082a4cee22 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Thu, 18 Dec 2014 11:10:39 +0100 Subject: [PATCH 28/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 927a0071..ebdfd801 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 927a00713b35cbcb4ab91da3c173ad45c0436f94 +Subproject commit ebdfd801b26f49d0326ee8aec59e9f24fce1f0df From 732df1324fd56071dd15c939ef0c63bbd02bc4dc Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Thu, 18 Dec 2014 14:09:53 +0100 Subject: [PATCH 29/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index ebdfd801..852e0399 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit ebdfd801b26f49d0326ee8aec59e9f24fce1f0df +Subproject commit 852e0399f23d0c1d9b7832dc1d0a77c4aae89383 From 5802db9ddd1da3eb86a39e7f60843241be8eb5c9 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Mon, 12 Jan 2015 21:27:32 +0100 Subject: [PATCH 30/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 852e0399..b0118ba9 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 852e0399f23d0c1d9b7832dc1d0a77c4aae89383 +Subproject commit b0118ba96ddc37cc6378a3461bfa77f48061dd78 From 9de5f81f5a6846cb82e254fef288cd7f162edbea Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Tue, 13 Jan 2015 20:29:27 +0100 Subject: [PATCH 31/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index b0118ba9..d83cd8da 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit b0118ba96ddc37cc6378a3461bfa77f48061dd78 +Subproject commit d83cd8da3c6c95b16f17f0993591d715af71731e From 6a8f266d209019862123c1bf70a44eb2750bea57 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Wed, 14 Jan 2015 11:45:17 +0100 Subject: [PATCH 32/50] Change copyright year to 2015 --- CHANGES.txt | 2 +- README.txt | 2 +- cpyint | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 676ec3df..b455aacc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,7 +3,7 @@ MySQL Connector/Python 2.0 - Release Notes & Changes ==================================================== MySQL Connector/Python -Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. Full release notes: http://dev.mysql.com/doc/relnotes/connector-python/en/ diff --git a/README.txt b/README.txt index a4c973d9..96a63dd7 100644 --- a/README.txt +++ b/README.txt @@ -3,7 +3,7 @@ MySQL Connector/Python 2.0 ========================== MySQL Connector/Python -Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. License information can be found in the LICENSE.txt file. diff --git a/cpyint b/cpyint index d83cd8da..930c7e1d 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit d83cd8da3c6c95b16f17f0993591d715af71731e +Subproject commit 930c7e1d5c0b72d0acaaaab5739bccea520799fa From 07cabd3827304c4c46a80096d8dc0c7e32d18e34 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Mon, 19 Jan 2015 11:56:31 +0100 Subject: [PATCH 33/50] Change copyright year to 2015 in README.txt --- README.txt | 2 +- cpyint | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index 96a63dd7..2f88034b 100644 --- a/README.txt +++ b/README.txt @@ -28,7 +28,7 @@ doubt, this particular copy of the software is released under the version 2 of the GNU General Public License. MySQL Connector/Python is brought to you by Oracle. -Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. License information can be found in the LICENSE.txt file. diff --git a/cpyint b/cpyint index 930c7e1d..155414b2 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 930c7e1d5c0b72d0acaaaab5739bccea520799fa +Subproject commit 155414b20c5db1becb2137f5730498dbf22bad89 From af58f237a3f442d026482ec268bcfbbd1f27a519 Mon Sep 17 00:00:00 2001 From: Geert Vanderkelen Date: Fri, 23 Jan 2015 10:00:05 +0100 Subject: [PATCH 34/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 155414b2..90da2c7b 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 155414b20c5db1becb2137f5730498dbf22bad89 +Subproject commit 90da2c7b4040254b6a1f4ca2d8bd4b5418c01da5 From 990d6010f97acb2de082e7e902270bb56a8ffed6 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Tue, 2 Dec 2014 09:41:59 +0100 Subject: [PATCH 35/50] BUG20106629: Support Django Safetext and SafeBytes type We this fix we support Django SafeText and SafeBytes type fields. A test case has been added for BUG#20106629. (cherry picked from commit 18b69e4) --- lib/mysql/connector/django/base.py | 6 ++++++ tests/test_django.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lib/mysql/connector/django/base.py b/lib/mysql/connector/django/base.py index ccdae2c7..3791f6ed 100644 --- a/lib/mysql/connector/django/base.py +++ b/lib/mysql/connector/django/base.py @@ -98,6 +98,12 @@ def _DATETIME_to_python(self, value, dsc=None): dt = dt.replace(tzinfo=timezone.utc) return dt + def _safetext_to_mysql(self, value): + return self._str_to_mysql(value) + + def _safebytes_to_mysql(self, value): + return self._bytes_to_mysql(value) + class CursorWrapper(object): """Wrapper around MySQL Connector/Python's cursor class. diff --git a/tests/test_django.py b/tests/test_django.py index 5cea41bd..364e2c01 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -84,6 +84,7 @@ if tests.DJANGO_VERSION >= (1, 6): from django.db.backends import FieldInfo from django.db.backends.signals import connection_created +from django.utils.safestring import SafeBytes, SafeText import mysql.connector from mysql.connector.django.base import (DatabaseWrapper, DatabaseOperations, @@ -298,3 +299,25 @@ def test__DATETIME_to_python(self): self.assertEqual(None, django_converter._DATETIME_to_python(value, dsc=None)) settings.USE_TZ = False + + +class BugOra20106629(tests.MySQLConnectorTests): + """CONNECTOR/PYTHON DJANGO BACKEND DOESN'T SUPPORT SAFETEXT""" + def setUp(self): + dbconfig = tests.get_mysql_config() + self.conn = mysql.connector.connect(**dbconfig) + self.cnx = DatabaseWrapper(settings.DATABASES['default']) + self.cur = self.cnx.cursor() + self.tbl = "BugOra20106629" + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl), ()) + self.cur.execute("CREATE TABLE {0}(col1 TEXT, col2 BLOB)".format(self.tbl), ()) + + def teardown(self): + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl), ()) + + def test_safe_string(self): + safe_text = SafeText("dummy & safe data ") + safe_bytes = SafeBytes(b"\x00\x00\x4c\x6e\x67\x39") + self.cur.execute("INSERT INTO {0} VALUES(%s, %s)".format(self.tbl), (safe_text, safe_bytes)) + self.cur.execute("SELECT * FROM {0}".format(self.tbl), ()) + self.assertEqual(self.cur.fetchall(), [(safe_text, safe_bytes)]) From 6c069896b1f6d81a19a00272bb85ba843e34074c Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Tue, 27 Jan 2015 12:15:23 +0100 Subject: [PATCH 36/50] BUG20407036: Fix incorrect arguments to mysld_stmt_execute error Inserting a string of length 251 to 255 using prepared statements was raising a ProgrammingError exception: 1210 (HY000): Incorrect arguments to mysqld_stmt_execute We fix this by correctly encoding the length of the string while sending it to the server. A test case has been added for BUG#20407036. (cherry picked from commit 955cfb7) --- lib/mysql/connector/utils.py | 4 ++-- tests/test_bugs.py | 46 +++++++++++++++++++++++++++++++++++- tests/test_utils.py | 10 ++++---- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/lib/mysql/connector/utils.py b/lib/mysql/connector/utils.py index f8b10c76..acbde809 100644 --- a/lib/mysql/connector/utils.py +++ b/lib/mysql/connector/utils.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -144,7 +144,7 @@ def lc_int(i): if i < 0 or i > 18446744073709551616: raise ValueError('Requires 0 <= i <= 2^64') - if i <= 255: + if i < 251: return bytearray(struct.pack(', like most @@ -3110,3 +3110,47 @@ def test_warning_with_rows(self): exp = [(u'Warning', 1642, u'TEST WARNING')] self.assertEqual(exp, cur.fetchwarnings()) cur.close() + + +class BugOra20407036(tests.MySQLConnectorTests): + """BUG#20407036: INCORRECT ARGUMENTS TO MYSQLD_STMT_EXECUTE ERROR + """ + def setUp(self): + config = tests.get_mysql_config() + self.cnx = connection.MySQLConnection(**config) + self.cur = self.cnx.cursor() + + self.tbl = 'Bug20407036' + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + + create = ("CREATE TABLE {0} ( id int(10) unsigned NOT NULL, " + "text VARCHAR(70000) CHARACTER SET utf8 NOT NULL, " + "rooms tinyint(3) unsigned NOT NULL) " + "ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 " + "COLLATE=utf8_unicode_ci".format(self.tbl)) + self.cur.execute(create) + + def tearDown(self): + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + self.cur.close() + self.cnx.close() + + def test_binary_charset(self): + cur = self.cnx.cursor(prepared=True) + sql = "INSERT INTO {0}(text, rooms) VALUES(%s, %s)".format(self.tbl) + cur.execute(sql, ('a'*252, 1)) + cur.execute(sql, ('a'*253, 2)) + cur.execute(sql, ('a'*255, 3)) + cur.execute(sql, ('a'*251, 4)) + cur.execute(sql, ('a'*65535, 5)) + + exp = [ + (0, 'a'*252, 1), + (0, 'a'*253, 2), + (0, 'a'*255, 3), + (0, 'a'*251, 4), + (0, 'a'*65535, 5), + ] + + self.cur.execute("SELECT * FROM {0}".format(self.tbl)) + self.assertEqual(exp, self.cur.fetchall()) diff --git a/tests/test_utils.py b/tests/test_utils.py index 47cf1158..150bb799 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -125,12 +125,12 @@ def test_intstore(self): def test_lc_int(self): prefix = (b'', b'\xfc', b'\xfd', b'\xfe') + intstore = (1, 2, 3, 8) try: - for i, j in enumerate((8, 16, 24, 64)): - val = 2 ** (j - 1) - lenenc = utils.lc_int(val) + for i, j in enumerate((128, 251, 2**24-1, 2**64-1)): + lenenc = utils.lc_int(j) exp = prefix[i] + \ - getattr(utils, 'int{0}store'.format(int(j/8)))(val) + getattr(utils, 'int{0}store'.format(intstore[i]))(j) self.assertEqual(exp, lenenc) except ValueError as err: self.fail("length_encoded_int failed for size {0}".format(j, err)) From 004edd067bdd3b0624fe76d2304cd0bd36699fb9 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Thu, 12 Mar 2015 07:36:41 +0100 Subject: [PATCH 37/50] BUG20301989: Fix conversion of empty set We fix conversion of SET data coming from MySQL server when the set is empty. We now return an empty set(set([])) which was previously returned as a set of empty string(set([''])). A test case is added for BUG#20301989. (cherry picked from commit f9b22fa) --- lib/mysql/connector/conversion.py | 4 ++- tests/test_bugs.py | 46 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/mysql/connector/conversion.py b/lib/mysql/connector/conversion.py index 51332d27..59d41331 100644 --- a/lib/mysql/connector/conversion.py +++ b/lib/mysql/connector/conversion.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -526,6 +526,8 @@ def _SET_to_python(self, value, dsc=None): # pylint: disable=C0103 """ set_type = None val = value.decode(self.charset) + if not val: + return set() try: set_type = set(val.split(',')) except ValueError: diff --git a/tests/test_bugs.py b/tests/test_bugs.py index 288d84a3..e6cb56c2 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -3154,3 +3154,49 @@ def test_binary_charset(self): self.cur.execute("SELECT * FROM {0}".format(self.tbl)) self.assertEqual(exp, self.cur.fetchall()) + + +class BugOra20301989(tests.MySQLConnectorTests): + """BUG#20301989: SET DATA TYPE NOT TRANSLATED CORRECTLY WHEN EMPTY + """ + def setUp(self): + config = tests.get_mysql_config() + cnx = connection.MySQLConnection(**config) + cur = cnx.cursor() + + self.tbl = 'Bug20301989' + cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + + create = ("CREATE TABLE {0} (col1 SET('val1', 'val2')) " + "DEFAULT CHARSET latin1".format(self.tbl)) + cur.execute(create) + cur.close() + cnx.close() + + def tearDown(self): + config = tests.get_mysql_config() + cnx = connection.MySQLConnection(**config) + cur = cnx.cursor() + cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + cur.close() + cnx.close() + + def test_set(self): + config = tests.get_mysql_config() + cnx = connection.MySQLConnection(**config) + cur = cnx.cursor() + sql = "INSERT INTO {0} VALUES(%s)".format(self.tbl) + cur.execute(sql, ('val1,val2',)) + cur.execute(sql, ('val1',)) + cur.execute(sql, ('',)) + cur.execute(sql, (None,)) + + exp = [ + (set([u'val1', u'val2']),), + (set([u'val1']),), + (set([]),), + (None,) + ] + + cur.execute("SELECT * FROM {0}".format(self.tbl)) + self.assertEqual(exp, cur.fetchall()) From 2a371471ba7192ddb4f49254da1ffe6330292b52 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Fri, 20 Mar 2015 11:42:46 +0100 Subject: [PATCH 38/50] BUG20462427: Fix receiving large field data from server We fix receiving a large amount of data in a field from server The connector did not process packets correctly when distributed across several packets. Also receiving text greater then 16777215 bytes raised an index out of range error. A test case has been added for BUG#20462427. (cherry picked from commit 7221b81) --- lib/mysql/connector/network.py | 15 ++++++---- lib/mysql/connector/protocol.py | 9 ++---- tests/test_bugs.py | 51 ++++++++++++++++++++++++++++++++- unittests.py | 3 ++ 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/lib/mysql/connector/network.py b/lib/mysql/connector/network.py index 42d760bd..31f3f8cc 100644 --- a/lib/mysql/connector/network.py +++ b/lib/mysql/connector/network.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -221,11 +221,13 @@ def recv_plain(self): try: # Read the header of the MySQL packet, 4 bytes packet = bytearray(b'') - while len(packet) < 4: - chunk = self.sock.recv(4) + packet_len = 0 + while packet_len < 4: + chunk = self.sock.recv(4 - packet_len) if not chunk: raise errors.InterfaceError(errno=2013) packet += chunk + packet_len = len(packet) # Save the packet number and payload length self._packet_number = packet[3] @@ -257,12 +259,13 @@ def recv_py26_plain(self): try: # Read the header of the MySQL packet, 4 bytes header = bytearray(b'') - - while len(header) < 4: - chunk = self.sock.recv(4) + header_len = 0 + while header_len < 4: + chunk = self.sock.recv(4 - header_len) if not chunk: raise errors.InterfaceError(errno=2013) header += chunk + header_len = len(header) # Save the packet number and payload length self._packet_number = header[3] diff --git a/lib/mysql/connector/protocol.py b/lib/mysql/connector/protocol.py index 855ffccc..79d41a40 100644 --- a/lib/mysql/connector/protocol.py +++ b/lib/mysql/connector/protocol.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -313,11 +313,8 @@ def read_text_result(self, sock, count=1): while packet.startswith(b'\xff\xff\xff'): datas.append(packet[4:]) packet = sock.recv() - if packet[4] == 254: - eof = self.parse_eof(packet) - else: - datas.append(packet[4:]) - rowdata = utils.read_lc_string_list(b''.join(datas)) + datas.append(packet[4:]) + rowdata = utils.read_lc_string_list(bytearray(b'').join(datas)) elif packet[4] == 254: eof = self.parse_eof(packet) rowdata = None diff --git a/tests/test_bugs.py b/tests/test_bugs.py index e6cb56c2..081b6414 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -3056,7 +3056,6 @@ def tearDown(self): self.cnx.close() - class BugOra19777815(tests.MySQLConnectorTests): """BUG#19777815: CALLPROC() DOES NOT SUPPORT WARNINGS """ @@ -3200,3 +3199,53 @@ def test_set(self): cur.execute("SELECT * FROM {0}".format(self.tbl)) self.assertEqual(exp, cur.fetchall()) + + +class BugOra20462427(tests.MySQLConnectorTests): + """BUG#20462427: BYTEARRAY INDEX OUT OF RANGE + """ + def setUp(self): + config = tests.get_mysql_config() + config['autocommit'] = True + config['connection_timeout'] = 100 + self.cnx = connection.MySQLConnection(**config) + self.cur = self.cnx.cursor() + + self.tbl = 'BugOra20462427' + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + + create = ("CREATE TABLE {0} (" + "id INT PRIMARY KEY, " + "a LONGTEXT " + ") ENGINE=Innodb DEFAULT CHARSET utf8".format(self.tbl)) + + self.cur.execute(create) + + def tearDown(self): + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + self.cur.close() + self.cnx.close() + + def test_bigdata(self): + temp = 'a'*16777210 + insert = "INSERT INTO {0} (a) VALUES ('{1}')".format(self.tbl, temp) + + self.cur.execute(insert) + self.cur.execute("SELECT a FROM {0}".format(self.tbl)) + res = self.cur.fetchall() + self.assertEqual(16777210, len(res[0][0])) + + self.cur.execute("UPDATE {0} SET a = concat(a, 'a')".format(self.tbl)) + self.cur.execute("SELECT a FROM {0}".format(self.tbl)) + res = self.cur.fetchall() + self.assertEqual(16777211, len(res[0][0])) + + self.cur.execute("UPDATE {0} SET a = concat(a, 'a')".format(self.tbl)) + self.cur.execute("SELECT a FROM {0}".format(self.tbl)) + res = self.cur.fetchall() + self.assertEqual(16777212, len(res[0][0])) + + self.cur.execute("UPDATE {0} SET a = concat(a, 'a')".format(self.tbl)) + self.cur.execute("SELECT a FROM {0}".format(self.tbl)) + res = self.cur.fetchall() + self.assertEqual(16777213, len(res[0][0])) diff --git a/unittests.py b/unittests.py index ea0dcc39..93b59d5c 100644 --- a/unittests.py +++ b/unittests.py @@ -120,6 +120,7 @@ language = {lc_messages_dir}/english [mysqld] +max_allowed_packet=26777216 basedir = {basedir} datadir = {datadir} tmpdir = {tmpdir} @@ -135,6 +136,8 @@ log-bin = mysqld_{name}_bin local_infile = 1 innodb_flush_log_at_trx_commit = 2 +innodb_log_file_size = 1Gb +general_log_file = general_{name}.log ssl """ From 06aa8608c2444af0cd0bf5eff68ab64de6ad4099 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Wed, 18 Feb 2015 11:49:48 +0100 Subject: [PATCH 39/50] BUG20324089: Fix HASH based sharding with MySQL Fabric We fix HASH based sharding which was causing the following runtime error: ValueError: Unsupported sharding type HASH A unit test has been added for BUG#20324089. (cherry picked from commit 0f7a8e7) --- lib/mysql/connector/fabric/caching.py | 4 ++- tests/test_fabric.py | 38 ++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/mysql/connector/fabric/caching.py b/lib/mysql/connector/fabric/caching.py index b128b06b..78f8999c 100644 --- a/lib/mysql/connector/fabric/caching.py +++ b/lib/mysql/connector/fabric/caching.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -130,6 +130,8 @@ def add_partition(self, key, group): )) elif self.shard_type == 'RANGE_STRING': pass + elif self.shard_type == "HASH": + pass else: raise ValueError("Unsupported sharding type {0}".format( self.shard_type diff --git a/tests/test_fabric.py b/tests/test_fabric.py index 22599377..f0fd2433 100644 --- a/tests/test_fabric.py +++ b/tests/test_fabric.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -626,3 +626,39 @@ def test_bug19331658(self): ) mysql.connector._CONNECTION_POOLS = {} + + def test_range_hash(self): + self.assertTrue(self._check_table( + "employees.employees_hash", 'HASH')) + tbl_name = "employees_hash" + + tables = ["employees.{0}".format(tbl_name)] + + self.cnx.set_property(tables=tables, + scope=fabric.SCOPE_GLOBAL, + mode=fabric.MODE_READWRITE) + + cur = self.cnx.cursor() + gtid_executed = self._truncate(cur, tbl_name) + self.cnx.commit() + + insert = ("INSERT INTO {0} " + "VALUES (%s, %s, %s, %s, %s, %s)").format(tbl_name) + + self._populate(self.cnx, gtid_executed, tbl_name, insert, + self.emp_data[1985] + self.emp_data[2000], 3) + + time.sleep(2) + + emp_exp_hash = self.emp_data[1985] + self.emp_data[2000] + + rows = [] + self.cnx.reset_properties() + str_keys = ['group1', 'group2'] + for str_key in str_keys: + self.cnx.set_property(group=str_key, mode=fabric.MODE_READONLY) + cur = self.cnx.cursor() + cur.execute("SELECT * FROM {0}".format(tbl_name)) + rows += cur.fetchall() + + self.assertEqual(rows, emp_exp_hash) From 8e0cd7e255170513b12a2c5e3aa42718cd81a8b1 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Mon, 30 Mar 2015 12:17:38 +0530 Subject: [PATCH 40/50] Prepare release 2.0.4 --- CHANGES.txt | 9 +++++++++ lib/mysql/connector/version.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b455aacc..7ee20de8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,15 @@ Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. Full release notes: http://dev.mysql.com/doc/relnotes/connector-python/en/ +v2.0.4 +====== + +- BUG20324089: Fix HASH based sharding with MySQL Fabric +- BUG20462427: Fix receiving large field data from server +- BUG20301989: Fix conversion of empty set +- BUG20407036: Fix incorrect arguments to mysld_stmt_execute error +- BUG20106629: Support Django Safetext and SafeBytes type + v2.0.3 ====== diff --git a/lib/mysql/connector/version.py b/lib/mysql/connector/version.py index c2a81500..99267f28 100644 --- a/lib/mysql/connector/version.py +++ b/lib/mysql/connector/version.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -26,7 +26,7 @@ as mysql.connector.version. """ -VERSION = (2, 0, 3, '', 0) +VERSION = (2, 0, 4, '', 0) if VERSION[3] and VERSION[4]: VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION) From 78bec22e1c045de3f0cad17f275631653b1c99dc Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Tue, 31 Mar 2015 13:48:08 +0530 Subject: [PATCH 41/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 90da2c7b..d403a3bd 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 90da2c7b4040254b6a1f4ca2d8bd4b5418c01da5 +Subproject commit d403a3bdfd0ab04d6b8f629871ad1641f81f8d91 From f45b5d90608a3a5a8f6efcd7d5225b854211ca66 Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Tue, 31 Mar 2015 16:43:01 +0530 Subject: [PATCH 42/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index d403a3bd..bedc1060 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit d403a3bdfd0ab04d6b8f629871ad1641f81f8d91 +Subproject commit bedc10608ad82533cd8dce267a65bd39f3e91038 From 5186cf1b3dc6a2a68cc6cbd62e8c1789bf7909cc Mon Sep 17 00:00:00 2001 From: Peeyush Gupta Date: Wed, 1 Apr 2015 11:58:05 +0530 Subject: [PATCH 43/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index bedc1060..0b7ec933 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit bedc10608ad82533cd8dce267a65bd39f3e91038 +Subproject commit 0b7ec933f5e8d8849d8c523686023b90c53bd6b6 From c70adb3c3c8eaeddb7757b647af4e79f06720b10 Mon Sep 17 00:00:00 2001 From: Nuno Mariz Date: Tue, 4 Oct 2016 09:02:27 +0100 Subject: [PATCH 44/50] Remove old python-internal submodule Signed-off-by: Nuno Mariz --- .gitmodules | 4 ---- cpyint | 1 - 2 files changed, 5 deletions(-) delete mode 160000 cpyint diff --git a/.gitmodules b/.gitmodules index 9d4973fb..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +0,0 @@ -[submodule "cpyint"] - path = cpyint - url = ../python-internal.git - branch = master diff --git a/cpyint b/cpyint deleted file mode 160000 index 0b7ec933..00000000 --- a/cpyint +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0b7ec933f5e8d8849d8c523686023b90c53bd6b6 From 7be8e736bc6005f5868a6177f601a5e66b10858c Mon Sep 17 00:00:00 2001 From: Nuno Mariz Date: Tue, 4 Oct 2016 09:07:09 +0100 Subject: [PATCH 45/50] Add connector-python-internal submodule --- .gitmodules | 4 ++++ cpyint | 1 + 2 files changed, 5 insertions(+) create mode 160000 cpyint diff --git a/.gitmodules b/.gitmodules index e69de29b..e628c2cc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "cpyint"] + path = cpyint + url = ../connector-python-internal + branch = master-2.0 diff --git a/cpyint b/cpyint new file mode 160000 index 00000000..2154ef4e --- /dev/null +++ b/cpyint @@ -0,0 +1 @@ +Subproject commit 2154ef4e11b222fa232e589e2a4c87e5579f4128 From c026c37f5de186bd4862de895d7962cddf96ead8 Mon Sep 17 00:00:00 2001 From: Nuno Mariz Date: Tue, 4 Oct 2016 09:32:34 +0100 Subject: [PATCH 46/50] BUG22529828: Fix potential SQL injection This patch fixes a potential SQL injection that may occur when the parameters expansion is done in multiple steps and under some circumstances a substring in an incoming parameter value can be expanded several times. --- CHANGES.txt | 7 ++++- lib/mysql/connector/cursor.py | 53 +++++++++++++++++++++++++++++++---- tests/test_cursor.py | 46 +++++++++++++++++------------- 3 files changed, 80 insertions(+), 26 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7ee20de8..fbdca18f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,11 +3,16 @@ MySQL Connector/Python 2.0 - Release Notes & Changes ==================================================== MySQL Connector/Python -Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved. Full release notes: http://dev.mysql.com/doc/relnotes/connector-python/en/ + +v2.0.5 +====== +- BUG22529828: Fix potencial SQL injection + v2.0.4 ====== diff --git a/lib/mysql/connector/cursor.py b/lib/mysql/connector/cursor.py index 3e6a2b47..b9bfd9a1 100644 --- a/lib/mysql/connector/cursor.py +++ b/lib/mysql/connector/cursor.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -29,6 +29,7 @@ import weakref from . import errors +from .catch23 import PY2 SQL_COMMENT = r"\/\*.*?\*\/" RE_SQL_COMMENT = re.compile( @@ -42,6 +43,14 @@ re.I | re.M | re.S) RE_SQL_INSERT_VALUES = re.compile(r'.*VALUES\s*(\(.*\)).*', re.I | re.M | re.S) RE_PY_PARAM = re.compile(b'(%s)') +RE_PY_MAPPING_PARAM = re.compile( + br''' + % + \((?P[^)]+)\) + (?P[diouxXeEfFgGcrs%]) + ''', + re.X +) RE_SQL_SPLIT_STMTS = re.compile( b''';(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)''') RE_SQL_FIND_PARAM = re.compile( @@ -73,6 +82,35 @@ def remaining(self): return len(self.params) - self.index +def _bytestr_format_dict(bytestr, value_dict): + """ + >>> _bytestr_format_dict(b'%(a)s', {b'a': b'foobar'}) + b'foobar + >>> _bytestr_format_dict(b'%%(a)s', {b'a': b'foobar'}) + b'%%(a)s' + >>> _bytestr_format_dict(b'%%%(a)s', {b'a': b'foobar'}) + b'%%foobar' + >>> _bytestr_format_dict(b'%(x)s %(y)s', + ... {b'x': b'x=%(y)s', b'y': b'y=%(x)s'}) + b'x=%(y)s y=%(x)s' + """ + def replace(matchobj): + value = None + groups = matchobj.groupdict() + if groups["conversion_type"] == b"%": + value = b"%" + if groups["conversion_type"] == b"s": + key = groups["mapping_key"].encode("utf-8") \ + if PY2 else groups["mapping_key"] + value = value_dict[key] + if value is None: + raise ValueError("Unsupported conversion_type: {0}" + "".format(groups["conversion_type"])) + return value.decode("utf-8") if PY2 else value + return RE_PY_MAPPING_PARAM.sub(replace, bytestr.decode("utf-8") + if PY2 else bytestr) + + class CursorBase(object): """ Base for defining MySQLCursor. This class is a skeleton and defines @@ -355,7 +393,10 @@ def _process_params_dict(self, params): conv = to_mysql(conv) conv = escape(conv) conv = quote(conv) - res["%({0})s".format(key).encode()] = conv + if PY2: + res[key] = conv + else: + res[key.encode()] = conv except Exception as err: raise errors.ProgrammingError( "Failed processing pyformat-parameters; %s" % err) @@ -488,8 +529,8 @@ def execute(self, operation, params=None, multi=False): if params is not None: if isinstance(params, dict): - for key, value in self._process_params_dict(params).items(): - stmt = stmt.replace(key, value) + stmt = _bytestr_format_dict( + stmt, self._process_params_dict(params)) elif isinstance(params, (list, tuple)): psub = _ParamSubstitutor(self._process_params(params)) stmt = RE_PY_PARAM.sub(psub, stmt) @@ -543,8 +584,8 @@ def remove_comments(match): for params in seq_params: tmp = fmt if isinstance(params, dict): - for key, value in self._process_params_dict(params).items(): - tmp = tmp.replace(key, value) + tmp = _bytestr_format_dict( + tmp, self._process_params_dict(params)) else: psub = _ParamSubstitutor(self._process_params(params)) tmp = RE_PY_PARAM.sub(psub, tmp) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index e07342c3..e2aef9f8 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -360,6 +360,8 @@ def test__process_params(self): datetime.time(20, 3, 23), st_now, datetime.timedelta(hours=40, minutes=30, seconds=12), + 'foo %(t)s', + 'foo %(s)s', ) exp = ( b'NULL', @@ -381,6 +383,8 @@ def test__process_params(self): b"'" + time.strftime('%Y-%m-%d %H:%M:%S', st_now).encode('ascii') + b"'", b"'40:30:12'", + b"'foo %(t)s'", + b"'foo %(s)s'", ) self.cnx = connection.MySQLConnection(**tests.get_mysql_config()) @@ -420,28 +424,32 @@ def test__process_params_dict(self): 'p': datetime.time(20, 3, 23), 'q': st_now, 'r': datetime.timedelta(hours=40, minutes=30, seconds=12), + 's': 'foo %(t)s', + 't': 'foo %(s)s', } exp = { - b'%(a)s': b'NULL', - b'%(b)s': b'128', - b'%(c)s': b'1281288', - b'%(d)s': repr(float(3.14)) if PY2 else b'3.14', - b'%(e)s': b"'3.14'", - b'%(f)s': b"'back\\\\slash'", - b'%(g)s': b"'newline\\n'", - b'%(h)s': b"'return\\r'", - b'%(i)s': b"'\\'single\\''", - b'%(j)s': b'\'\\"double\\"\'', - b'%(k)s': b"'windows\\\x1a'", - b'%(l)s': b"'Strings are sexy'", - b'%(m)s': b"'\xe8\x8a\xb1'", - b'%(n)s': b"'2008-05-07 20:01:23'", - b'%(o)s': b"'2008-05-07'", - b'%(p)s': b"'20:03:23'", - b'%(q)s': b"'" + + b'a': b'NULL', + b'b': b'128', + b'c': b'1281288', + b'd': repr(float(3.14)) if PY2 else b'3.14', + b'e': b"'3.14'", + b'f': b"'back\\\\slash'", + b'g': b"'newline\\n'", + b'h': b"'return\\r'", + b'i': b"'\\'single\\''", + b'j': b'\'\\"double\\"\'', + b'k': b"'windows\\\x1a'", + b'l': b"'Strings are sexy'", + b'm': b"'\xe8\x8a\xb1'", + b'n': b"'2008-05-07 20:01:23'", + b'o': b"'2008-05-07'", + b'p': b"'20:03:23'", + b'q': b"'" + time.strftime('%Y-%m-%d %H:%M:%S', st_now).encode('ascii') + b"'", - b'%(r)s': b"'40:30:12'", + b'r': b"'40:30:12'", + b's': b"'foo %(t)s'", + b't': b"'foo %(s)s'", } self.cnx = connection.MySQLConnection(**tests.get_mysql_config()) From 5b680bee881172b2f3822fa3d315f1538316c2a1 Mon Sep 17 00:00:00 2001 From: Nuno Mariz Date: Tue, 4 Oct 2016 16:14:02 +0100 Subject: [PATCH 47/50] Fix version and update CPYINT revision --- cpyint | 2 +- lib/mysql/connector/version.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpyint b/cpyint index 2154ef4e..26826cf3 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 2154ef4e11b222fa232e589e2a4c87e5579f4128 +Subproject commit 26826cf33c166b773f6a033d61ce24cee9735801 diff --git a/lib/mysql/connector/version.py b/lib/mysql/connector/version.py index 99267f28..9431b1b9 100644 --- a/lib/mysql/connector/version.py +++ b/lib/mysql/connector/version.py @@ -1,5 +1,5 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. # MySQL Connector/Python is licensed under the terms of the GPLv2 # , like most @@ -26,7 +26,7 @@ as mysql.connector.version. """ -VERSION = (2, 0, 4, '', 0) +VERSION = (2, 0, 5, '', 0) if VERSION[3] and VERSION[4]: VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION) From 641a56fac63d0f913412d1e86d68e0ea6d0f84fe Mon Sep 17 00:00:00 2001 From: Nuno Mariz Date: Thu, 6 Oct 2016 09:49:02 +0100 Subject: [PATCH 48/50] Update CPYINT revision --- cpyint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpyint b/cpyint index 26826cf3..9b499655 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 26826cf33c166b773f6a033d61ce24cee9735801 +Subproject commit 9b4996552ef7ffa50628910b4b22630bd7730591 From 9373f5c74cb81c256209828607a334a929af8e24 Mon Sep 17 00:00:00 2001 From: Nuno Mariz Date: Wed, 12 Oct 2016 16:39:05 +0100 Subject: [PATCH 49/50] Update copyright year in README.txt --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index 2f88034b..ba1eeb5d 100644 --- a/README.txt +++ b/README.txt @@ -3,7 +3,7 @@ MySQL Connector/Python 2.0 ========================== MySQL Connector/Python -Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved. License information can be found in the LICENSE.txt file. From 36d868ecf96ce9536ba3304a09959d11d9a079dc Mon Sep 17 00:00:00 2001 From: Nuno Mariz Date: Mon, 17 Oct 2016 09:08:04 +0100 Subject: [PATCH 50/50] Fix copyright year in README.txt --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index ba1eeb5d..99be6286 100644 --- a/README.txt +++ b/README.txt @@ -28,7 +28,7 @@ doubt, this particular copy of the software is released under the version 2 of the GNU General Public License. MySQL Connector/Python is brought to you by Oracle. -Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. License information can be found in the LICENSE.txt file.