diff --git a/.gitmodules b/.gitmodules index 9d4973fb..e628c2cc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "cpyint"] path = cpyint - url = ../python-internal.git - branch = master + url = ../connector-python-internal + branch = master-2.0 diff --git a/CHANGES.txt b/CHANGES.txt index 18859e2a..fbdca18f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,11 +3,50 @@ 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, 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 +====== + +- 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 +====== + +- 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 +====== + +- 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/README.txt b/README.txt index a4c973d9..99be6286 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, 2016, Oracle and/or its affiliates. All rights reserved. License information can be found in the LICENSE.txt file. @@ -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, 2016, 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 15a74ca8..9b499655 160000 --- a/cpyint +++ b/cpyint @@ -1 +1 @@ -Subproject commit 15a74ca84478e597d844e4a95f9cfd584f01345c +Subproject commit 9b4996552ef7ffa50628910b4b22630bd7730591 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/connection.py b/lib/mysql/connector/connection.py index 79f64e71..5e680082 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: @@ -1135,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..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 @@ -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""" @@ -178,10 +182,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): @@ -517,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: @@ -537,6 +548,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) diff --git a/lib/mysql/connector/cursor.py b/lib/mysql/connector/cursor.py index 8a0602cc..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) @@ -697,12 +738,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/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() diff --git a/lib/mysql/connector/django/base.py b/lib/mysql/connector/django/base.py index 573c30bd..3791f6ed 100644 --- a/lib/mysql/connector/django/base.py +++ b/lib/mysql/connector/django/base.py @@ -92,10 +92,18 @@ 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 + 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. @@ -190,7 +198,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 @@ -210,7 +218,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", @@ -397,7 +405,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'), @@ -429,13 +437,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: @@ -446,13 +448,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 @@ -465,7 +461,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), @@ -520,15 +516,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) @@ -582,15 +571,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) - connection_created.send(sender=self.__class__, connection=self) 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") @@ -609,6 +596,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): @@ -735,6 +723,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/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/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/lib/mysql/connector/fabric/caching.py b/lib/mysql/connector/fabric/caching.py index ffcc11bd..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 @@ -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,26 @@ _CACHE_TTL = 1 * 60 # 1 minute +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 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): """Base class for MySQL Fabric cache entries""" @@ -83,6 +104,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) @@ -107,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 @@ -115,6 +140,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..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 -from ..connection import MySQLConnection +import mysql.connector from ..pooling import MySQLConnectionPool from ..errors import ( Error, InterfaceError, NotSupportedError, MySQLFabricError, InternalError, @@ -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 @@ -1116,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) @@ -1179,7 +1188,7 @@ def _connect(self): dbconfig['host'] = mysqlserver.host dbconfig['port'] = mysqlserver.port try: - self._mysql_cnx = MySQLConnection(**dbconfig) + self._mysql_cnx = mysql.connector.connect(**dbconfig) except Error as exc: if counter == attempts: self.reset_cache(mysqlserver.group) 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/network.py b/lib/mysql/connector/network.py index b5b44f00..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 @@ -220,10 +220,14 @@ 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'') + 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] @@ -255,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] @@ -435,7 +440,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 +454,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( 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/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/lib/mysql/connector/protocol.py b/lib/mysql/connector/protocol.py index 4ced446f..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 @@ -636,24 +633,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..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 @@ -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 < 251: + return bytearray(struct.pack(', like most @@ -26,7 +26,7 @@ as mysql.connector.version. """ -VERSION = (2, 0, 1, '', 0) +VERSION = (2, 0, 5, '', 0) if VERSION[3] and VERSION[4]: VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION) 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/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 3efa41b0..081b6414 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # 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 @@ -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 """ @@ -2867,3 +2870,382 @@ def test_option_files_with_include(self): os.remove(temp_cnf_file) os.remove(temp_include_file) + + +class BugOra19584051(tests.MySQLConnectorTests): + """BUG#19584051: TYPE_CODE DOES NOT COMPARE EQUAL + """ + def setUp(self): + config = tests.get_mysql_config() + self.cnx = connection.MySQLConnection(**config) + self.cursor = self.cnx.cursor() + + self.tbl = 'Bug19584051' + self.cursor.execute("DROP TABLE IF EXISTS %s" % self.tbl) + + create = ('CREATE TABLE {0}(col1 INT NOT NULL, col2 BLOB, ' + 'col3 VARCHAR(10), col4 DECIMAL(4,2), ' + 'col5 DATETIME , col6 YEAR, ' + 'PRIMARY KEY(col1))'.format(self.tbl)) + + self.cursor.execute(create) + + def tearDown(self): + self.cursor.execute("DROP TABLE IF EXISTS %s" % self.tbl) + self.cursor.close() + self.cnx.close() + + def test_dbapi(self): + cur = self.cnx.cursor() + sql = ("INSERT INTO {0}(col1, col2, col3, col4, col5, col6) " + "VALUES (%s, %s, %s, %s, %s, %s)".format(self.tbl)) + params = (100, 'blob-data', 'foo', 1.2, datetime(2014, 8, 4, 9, 11, 14), + 2014) + + exp = [ + mysql.connector.NUMBER, + mysql.connector.BINARY, + mysql.connector.STRING, + mysql.connector.NUMBER, + mysql.connector.DATETIME, + mysql.connector.NUMBER, + ] + cur.execute(sql, params) + + sql = "SELECT * FROM {0}".format(self.tbl) + cur.execute(sql) + temp = cur.fetchone() + type_codes = [row[1] for row in cur.description] + self.assertEqual(exp, type_codes) + cur.close() + + +class BugOra19522948(tests.MySQLConnectorTests): + """BUG#19522948: DATA CORRUPTION WITH TEXT FIELDS + """ + def setUp(self): + config = tests.get_mysql_config() + self.cnx = connection.MySQLConnection(**config) + self.cur = self.cnx.cursor() + + self.tbl = 'Bug19522948' + self.cur.execute("DROP TABLE IF EXISTS {0}".format(self.tbl)) + + create = "CREATE TABLE {0} (c1 LONGTEXT NOT NULL)".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_row_to_python(self): + cur = self.cnx.cursor(prepared=True) + + data = "test_data"*10 + 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()) + self.cur.execute("TRUNCATE TABLE {0}".format(self.tbl)) + + data = "test_data"*1000 + 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()) + self.cur.execute("TRUNCATE TABLE {0}".format(self.tbl)) + + data = "test_data"*10000 + 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 tearDown(self): + mysql.connector._CONNECTION_POOLS = {} + + 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.") + + +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() + + +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,)] + 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() + + +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()) + + +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()) + + +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/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()) diff --git a/tests/test_django.py b/tests/test_django.py index 77b59e51..364e2c01 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,8 @@ 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 +from django.utils.safestring import SafeBytes, SafeText import mysql.connector from mysql.connector.django.base import (DatabaseWrapper, DatabaseOperations, @@ -196,7 +199,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) @@ -207,6 +210,41 @@ 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'] + settings.DEBUG = True + cur = conn.cursor() + settings.DEBUG = False + 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() + + 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): @@ -248,3 +286,38 @@ 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 + + +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)]) 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_fabric.py b/tests/test_fabric.py index fe175233..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 @@ -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" @@ -424,6 +425,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')) @@ -497,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) @@ -510,3 +554,111 @@ 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") + + 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']) + ) + + 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) 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 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() 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', diff --git a/tests/test_utils.py b/tests/test_utils.py index c694c6ab..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 @@ -123,6 +123,18 @@ def test_intstore(self): except ValueError as err: self.fail("intstore failed with 'int{0}store: {1}".format(i, err)) + def test_lc_int(self): + prefix = (b'', b'\xfc', b'\xfd', b'\xfe') + intstore = (1, 2, 3, 8) + try: + 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(intstore[i]))(j) + self.assertEqual(exp, lenenc) + except ValueError as err: + self.fail("length_encoded_int failed for size {0}".format(j, err)) + def test_read_bytes(self): """Read a number of bytes from a buffer""" buf = bytearray(b"ABCDEFghijklm") 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 """ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy