Cleaned up the directories

This commit is contained in:
ComputerTech312 2024-02-19 15:34:25 +01:00
parent f708506d68
commit a683fcffea
1340 changed files with 554582 additions and 6840 deletions

View file

@ -0,0 +1,207 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
MySQL Connector/Python - MySQL driver written in Python
"""
try:
import _mysql_connector # pylint: disable=F0401
from .connection_cext import CMySQLConnection
except ImportError:
HAVE_CEXT = False
else:
HAVE_CEXT = True
from . import version
from .connection import MySQLConnection
from .errors import ( # pylint: disable=W0622
Error, Warning, InterfaceError, DatabaseError,
NotSupportedError, DataError, IntegrityError, ProgrammingError,
OperationalError, InternalError, custom_error_exception, PoolError)
from .constants import FieldFlag, FieldType, CharacterSet, \
RefreshOption, ClientFlag
from .dbapi import (
Date, Time, Timestamp, Binary, DateFromTicks,
TimestampFromTicks, TimeFromTicks,
STRING, BINARY, NUMBER, DATETIME, ROWID,
apilevel, threadsafety, paramstyle)
from .optionfiles import read_option_files
_CONNECTION_POOLS = {}
def _get_pooled_connection(**kwargs):
"""Return a pooled MySQL connection"""
# If no pool name specified, generate one
from .pooling import (
MySQLConnectionPool, generate_pool_name,
CONNECTION_POOL_LOCK)
try:
pool_name = kwargs['pool_name']
except KeyError:
pool_name = generate_pool_name(**kwargs)
# Setup the pool, ensuring only 1 thread can update at a time
with CONNECTION_POOL_LOCK:
if pool_name not in _CONNECTION_POOLS:
_CONNECTION_POOLS[pool_name] = MySQLConnectionPool(**kwargs)
elif isinstance(_CONNECTION_POOLS[pool_name], MySQLConnectionPool):
# pool_size must be the same
check_size = _CONNECTION_POOLS[pool_name].pool_size
if ('pool_size' in kwargs
and kwargs['pool_size'] != check_size):
raise PoolError("Size can not be changed "
"for active pools.")
# Return pooled connection
try:
return _CONNECTION_POOLS[pool_name].get_connection()
except AttributeError:
raise InterfaceError(
"Failed getting connection from pool '{0}'".format(pool_name))
def _get_failover_connection(**kwargs):
"""Return a MySQL connection and try to failover if needed
An InterfaceError is raise when no MySQL is available. ValueError is
raised when the failover server configuration contains an illegal
connection argument. Supported arguments are user, password, host, port,
unix_socket and database. ValueError is also raised when the failover
argument was not provided.
Returns MySQLConnection instance.
"""
config = kwargs.copy()
try:
failover = config['failover']
except KeyError:
raise ValueError('failover argument not provided')
del config['failover']
support_cnx_args = set(
['user', 'password', 'host', 'port', 'unix_socket',
'database', 'pool_name', 'pool_size'])
# First check if we can add all use the configuration
for server in failover:
diff = set(server.keys()) - support_cnx_args
if diff:
raise ValueError(
"Unsupported connection argument {0} in failover: {1}".format(
's' if len(diff) > 1 else '',
', '.join(diff)))
for server in failover:
new_config = config.copy()
new_config.update(server)
try:
return connect(**new_config)
except Error:
# If we failed to connect, we try the next server
pass
raise InterfaceError("Could not failover: no MySQL server available")
def connect(*args, **kwargs):
"""Create or get a MySQL connection object
In its simpliest form, Connect() will open a connection to a
MySQL server and return a MySQLConnection object.
When any connection pooling arguments are given, for example pool_name
or pool_size, a pool is created or a previously one is used to return
a PooledMySQLConnection.
Returns MySQLConnection or PooledMySQLConnection.
"""
# Option files
if 'option_files' in kwargs:
new_config = read_option_files(**kwargs)
return connect(**new_config)
if all(['fabric' in kwargs, 'failover' in 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)
# Failover
if 'failover' in kwargs:
return _get_failover_connection(**kwargs)
# Pooled connections
try:
from .constants import CNX_POOL_ARGS
if any([key in kwargs for key in CNX_POOL_ARGS]):
return _get_pooled_connection(**kwargs)
except NameError:
# No pooling
pass
use_pure = kwargs.setdefault('use_pure', True)
try:
del kwargs['use_pure']
except KeyError:
# Just making sure 'use_pure' is not kwargs
pass
if HAVE_CEXT and not use_pure:
return CMySQLConnection(*args, **kwargs)
else:
return MySQLConnection(*args, **kwargs)
Connect = connect # pylint: disable=C0103
__version_info__ = version.VERSION
__version__ = version.VERSION_TEXT
__all__ = [
'MySQLConnection', 'Connect', 'custom_error_exception',
# Some useful constants
'FieldType', 'FieldFlag', 'ClientFlag', 'CharacterSet', 'RefreshOption',
'HAVE_CEXT',
# Error handling
'Error', 'Warning',
'InterfaceError', 'DatabaseError',
'NotSupportedError', 'DataError', 'IntegrityError', 'ProgrammingError',
'OperationalError', 'InternalError',
# DBAPI PEP 249 required exports
'connect', 'apilevel', 'threadsafety', 'paramstyle',
'Date', 'Time', 'Timestamp', 'Binary',
'DateFromTicks', 'DateFromTicks', 'TimestampFromTicks', 'TimeFromTicks',
'STRING', 'BINARY', 'NUMBER',
'DATETIME', 'ROWID',
# C Extension
'CMySQLConnection',
]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,191 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Implementing support for MySQL Authentication Plugins"""
from hashlib import sha1
import struct
from . import errors
from .catch23 import PY2, isstr
class BaseAuthPlugin(object):
"""Base class for authentication plugins
Classes inheriting from BaseAuthPlugin should implement the method
prepare_password(). When instantiating, auth_data argument is
required. The username, password and database are optional. The
ssl_enabled argument can be used to tell the plugin whether SSL is
active or not.
The method auth_response() method is used to retrieve the password
which was prepared by prepare_password().
"""
requires_ssl = False
plugin_name = ''
def __init__(self, auth_data, username=None, password=None, database=None,
ssl_enabled=False):
"""Initialization"""
self._auth_data = auth_data
self._username = username
self._password = password
self._database = database
self._ssl_enabled = ssl_enabled
def prepare_password(self):
"""Prepares and returns password to be send to MySQL
This method needs to be implemented by classes inheriting from
this class. It is used by the auth_response() method.
Raises NotImplementedError.
"""
raise NotImplementedError
def auth_response(self):
"""Returns the prepared password to send to MySQL
Raises InterfaceError on errors. For example, when SSL is required
by not enabled.
Returns str
"""
if self.requires_ssl and not self._ssl_enabled:
raise errors.InterfaceError("{name} requires SSL".format(
name=self.plugin_name))
return self.prepare_password()
class MySQLNativePasswordAuthPlugin(BaseAuthPlugin):
"""Class implementing the MySQL Native Password authentication plugin"""
requires_ssl = False
plugin_name = 'mysql_native_password'
def prepare_password(self):
"""Prepares and returns password as native MySQL 4.1+ password"""
if not self._auth_data:
raise errors.InterfaceError("Missing authentication data (seed)")
if not self._password:
return b''
password = self._password
if isstr(self._password):
password = self._password.encode('utf-8')
else:
password = self._password
if PY2:
password = buffer(password) # pylint: disable=E0602
try:
auth_data = buffer(self._auth_data) # pylint: disable=E0602
except TypeError:
raise errors.InterfaceError("Authentication data incorrect")
else:
password = password
auth_data = self._auth_data
hash4 = None
try:
hash1 = sha1(password).digest()
hash2 = sha1(hash1).digest()
hash3 = sha1(auth_data + hash2).digest()
if PY2:
xored = [ord(h1) ^ ord(h3) for (h1, h3) in zip(hash1, hash3)]
else:
xored = [h1 ^ h3 for (h1, h3) in zip(hash1, hash3)]
hash4 = struct.pack('20B', *xored)
except Exception as exc:
raise errors.InterfaceError(
"Failed scrambling password; {0}".format(exc))
return hash4
class MySQLClearPasswordAuthPlugin(BaseAuthPlugin):
"""Class implementing the MySQL Clear Password authentication plugin"""
requires_ssl = True
plugin_name = 'mysql_clear_password'
def prepare_password(self):
"""Returns password as as clear text"""
if not self._password:
return b'\x00'
password = self._password
if PY2:
if isinstance(password, unicode): # pylint: disable=E0602
password = password.encode('utf8')
elif isinstance(password, str):
password = password.encode('utf8')
return password + b'\x00'
class MySQLSHA256PasswordAuthPlugin(BaseAuthPlugin):
"""Class implementing the MySQL SHA256 authentication plugin
Note that encrypting using RSA is not supported since the Python
Standard Library does not provide this OpenSSL functionality.
"""
requires_ssl = True
plugin_name = 'sha256_password'
def prepare_password(self):
"""Returns password as as clear text"""
if not self._password:
return b'\x00'
password = self._password
if PY2:
if isinstance(password, unicode): # pylint: disable=E0602
password = password.encode('utf8')
elif isinstance(password, str):
password = password.encode('utf8')
return password + b'\x00'
def get_auth_plugin(plugin_name):
"""Return authentication class based on plugin name
This function returns the class for the authentication plugin plugin_name.
The returned class is a subclass of BaseAuthPlugin.
Raises errors.NotSupportedError when plugin_name is not supported.
Returns subclass of BaseAuthPlugin.
"""
for authclass in BaseAuthPlugin.__subclasses__(): # pylint: disable=E1101
if authclass.plugin_name == plugin_name:
return authclass
raise errors.NotSupportedError(
"Authentication plugin '{0}' is not supported".format(plugin_name))

View file

@ -0,0 +1,114 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Python v2 to v3 migration module"""
from decimal import Decimal
import struct
import sys
from .custom_types import HexLiteral
# pylint: disable=E0602,E1103
PY2 = sys.version_info[0] == 2
if PY2:
NUMERIC_TYPES = (int, float, Decimal, HexLiteral, long)
INT_TYPES = (int, long)
UNICODE_TYPES = (unicode,)
STRING_TYPES = (str, unicode)
BYTE_TYPES = (bytearray,)
else:
NUMERIC_TYPES = (int, float, Decimal, HexLiteral)
INT_TYPES = (int,)
UNICODE_TYPES = (str,)
STRING_TYPES = (str,)
BYTE_TYPES = (bytearray, bytes)
def init_bytearray(payload=b'', encoding='utf-8'):
"""Initializes a bytearray from the payload"""
if isinstance(payload, bytearray):
return payload
if PY2:
return bytearray(payload)
if isinstance(payload, int):
return bytearray(payload)
elif not isinstance(payload, bytes):
try:
return bytearray(payload.encode(encoding=encoding))
except AttributeError:
raise ValueError("payload must be a str or bytes")
return bytearray(payload)
def isstr(obj):
"""Returns whether a variable is a string"""
if PY2:
return isinstance(obj, basestring)
else:
return isinstance(obj, str)
def isunicode(obj):
"""Returns whether a variable is a of unicode type"""
if PY2:
return isinstance(obj, unicode)
else:
return isinstance(obj, str)
if PY2:
def struct_unpack(fmt, buf):
"""Wrapper around struct.unpack handling buffer as bytes and strings"""
if isinstance(buf, (bytearray, bytes)):
return struct.unpack_from(fmt, buffer(buf))
return struct.unpack_from(fmt, buf)
else:
struct_unpack = struct.unpack # pylint: disable=C0103
def make_abc(base_class):
"""Decorator used to create a abstract base class
We use this decorator to create abstract base classes instead of
using the abc-module. The decorator makes it possible to do the
same in both Python v2 and v3 code.
"""
def wrapper(class_):
"""Wrapper"""
attrs = class_.__dict__.copy()
for attr in '__dict__', '__weakref__':
attrs.pop(attr, None) # ignore missing attributes
bases = class_.__bases__
if PY2:
attrs['__metaclass__'] = class_
else:
bases = (class_,) + bases
return base_class(class_.__name__, bases, attrs)
return wrapper

View file

@ -0,0 +1,286 @@
# -*- coding: utf-8 -*-
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# This file was auto-generated.
_GENERATED_ON = '2015-08-24'
_MYSQL_VERSION = (5, 7, 8)
"""This module contains the MySQL Server Character Sets"""
MYSQL_CHARACTER_SETS = [
# (character set name, collation, default)
None,
("big5", "big5_chinese_ci", True), # 1
("latin2", "latin2_czech_cs", False), # 2
("dec8", "dec8_swedish_ci", True), # 3
("cp850", "cp850_general_ci", True), # 4
("latin1", "latin1_german1_ci", False), # 5
("hp8", "hp8_english_ci", True), # 6
("koi8r", "koi8r_general_ci", True), # 7
("latin1", "latin1_swedish_ci", True), # 8
("latin2", "latin2_general_ci", True), # 9
("swe7", "swe7_swedish_ci", True), # 10
("ascii", "ascii_general_ci", True), # 11
("ujis", "ujis_japanese_ci", True), # 12
("sjis", "sjis_japanese_ci", True), # 13
("cp1251", "cp1251_bulgarian_ci", False), # 14
("latin1", "latin1_danish_ci", False), # 15
("hebrew", "hebrew_general_ci", True), # 16
None,
("tis620", "tis620_thai_ci", True), # 18
("euckr", "euckr_korean_ci", True), # 19
("latin7", "latin7_estonian_cs", False), # 20
("latin2", "latin2_hungarian_ci", False), # 21
("koi8u", "koi8u_general_ci", True), # 22
("cp1251", "cp1251_ukrainian_ci", False), # 23
("gb2312", "gb2312_chinese_ci", True), # 24
("greek", "greek_general_ci", True), # 25
("cp1250", "cp1250_general_ci", True), # 26
("latin2", "latin2_croatian_ci", False), # 27
("gbk", "gbk_chinese_ci", True), # 28
("cp1257", "cp1257_lithuanian_ci", False), # 29
("latin5", "latin5_turkish_ci", True), # 30
("latin1", "latin1_german2_ci", False), # 31
("armscii8", "armscii8_general_ci", True), # 32
("utf8", "utf8_general_ci", True), # 33
("cp1250", "cp1250_czech_cs", False), # 34
("ucs2", "ucs2_general_ci", True), # 35
("cp866", "cp866_general_ci", True), # 36
("keybcs2", "keybcs2_general_ci", True), # 37
("macce", "macce_general_ci", True), # 38
("macroman", "macroman_general_ci", True), # 39
("cp852", "cp852_general_ci", True), # 40
("latin7", "latin7_general_ci", True), # 41
("latin7", "latin7_general_cs", False), # 42
("macce", "macce_bin", False), # 43
("cp1250", "cp1250_croatian_ci", False), # 44
("utf8mb4", "utf8mb4_general_ci", True), # 45
("utf8mb4", "utf8mb4_bin", False), # 46
("latin1", "latin1_bin", False), # 47
("latin1", "latin1_general_ci", False), # 48
("latin1", "latin1_general_cs", False), # 49
("cp1251", "cp1251_bin", False), # 50
("cp1251", "cp1251_general_ci", True), # 51
("cp1251", "cp1251_general_cs", False), # 52
("macroman", "macroman_bin", False), # 53
("utf16", "utf16_general_ci", True), # 54
("utf16", "utf16_bin", False), # 55
("utf16le", "utf16le_general_ci", True), # 56
("cp1256", "cp1256_general_ci", True), # 57
("cp1257", "cp1257_bin", False), # 58
("cp1257", "cp1257_general_ci", True), # 59
("utf32", "utf32_general_ci", True), # 60
("utf32", "utf32_bin", False), # 61
("utf16le", "utf16le_bin", False), # 62
("binary", "binary", True), # 63
("armscii8", "armscii8_bin", False), # 64
("ascii", "ascii_bin", False), # 65
("cp1250", "cp1250_bin", False), # 66
("cp1256", "cp1256_bin", False), # 67
("cp866", "cp866_bin", False), # 68
("dec8", "dec8_bin", False), # 69
("greek", "greek_bin", False), # 70
("hebrew", "hebrew_bin", False), # 71
("hp8", "hp8_bin", False), # 72
("keybcs2", "keybcs2_bin", False), # 73
("koi8r", "koi8r_bin", False), # 74
("koi8u", "koi8u_bin", False), # 75
None,
("latin2", "latin2_bin", False), # 77
("latin5", "latin5_bin", False), # 78
("latin7", "latin7_bin", False), # 79
("cp850", "cp850_bin", False), # 80
("cp852", "cp852_bin", False), # 81
("swe7", "swe7_bin", False), # 82
("utf8", "utf8_bin", False), # 83
("big5", "big5_bin", False), # 84
("euckr", "euckr_bin", False), # 85
("gb2312", "gb2312_bin", False), # 86
("gbk", "gbk_bin", False), # 87
("sjis", "sjis_bin", False), # 88
("tis620", "tis620_bin", False), # 89
("ucs2", "ucs2_bin", False), # 90
("ujis", "ujis_bin", False), # 91
("geostd8", "geostd8_general_ci", True), # 92
("geostd8", "geostd8_bin", False), # 93
("latin1", "latin1_spanish_ci", False), # 94
("cp932", "cp932_japanese_ci", True), # 95
("cp932", "cp932_bin", False), # 96
("eucjpms", "eucjpms_japanese_ci", True), # 97
("eucjpms", "eucjpms_bin", False), # 98
("cp1250", "cp1250_polish_ci", False), # 99
None,
("utf16", "utf16_unicode_ci", False), # 101
("utf16", "utf16_icelandic_ci", False), # 102
("utf16", "utf16_latvian_ci", False), # 103
("utf16", "utf16_romanian_ci", False), # 104
("utf16", "utf16_slovenian_ci", False), # 105
("utf16", "utf16_polish_ci", False), # 106
("utf16", "utf16_estonian_ci", False), # 107
("utf16", "utf16_spanish_ci", False), # 108
("utf16", "utf16_swedish_ci", False), # 109
("utf16", "utf16_turkish_ci", False), # 110
("utf16", "utf16_czech_ci", False), # 111
("utf16", "utf16_danish_ci", False), # 112
("utf16", "utf16_lithuanian_ci", False), # 113
("utf16", "utf16_slovak_ci", False), # 114
("utf16", "utf16_spanish2_ci", False), # 115
("utf16", "utf16_roman_ci", False), # 116
("utf16", "utf16_persian_ci", False), # 117
("utf16", "utf16_esperanto_ci", False), # 118
("utf16", "utf16_hungarian_ci", False), # 119
("utf16", "utf16_sinhala_ci", False), # 120
("utf16", "utf16_german2_ci", False), # 121
("utf16", "utf16_croatian_ci", False), # 122
("utf16", "utf16_unicode_520_ci", False), # 123
("utf16", "utf16_vietnamese_ci", False), # 124
None,
None,
None,
("ucs2", "ucs2_unicode_ci", False), # 128
("ucs2", "ucs2_icelandic_ci", False), # 129
("ucs2", "ucs2_latvian_ci", False), # 130
("ucs2", "ucs2_romanian_ci", False), # 131
("ucs2", "ucs2_slovenian_ci", False), # 132
("ucs2", "ucs2_polish_ci", False), # 133
("ucs2", "ucs2_estonian_ci", False), # 134
("ucs2", "ucs2_spanish_ci", False), # 135
("ucs2", "ucs2_swedish_ci", False), # 136
("ucs2", "ucs2_turkish_ci", False), # 137
("ucs2", "ucs2_czech_ci", False), # 138
("ucs2", "ucs2_danish_ci", False), # 139
("ucs2", "ucs2_lithuanian_ci", False), # 140
("ucs2", "ucs2_slovak_ci", False), # 141
("ucs2", "ucs2_spanish2_ci", False), # 142
("ucs2", "ucs2_roman_ci", False), # 143
("ucs2", "ucs2_persian_ci", False), # 144
("ucs2", "ucs2_esperanto_ci", False), # 145
("ucs2", "ucs2_hungarian_ci", False), # 146
("ucs2", "ucs2_sinhala_ci", False), # 147
("ucs2", "ucs2_german2_ci", False), # 148
("ucs2", "ucs2_croatian_ci", False), # 149
("ucs2", "ucs2_unicode_520_ci", False), # 150
("ucs2", "ucs2_vietnamese_ci", False), # 151
None,
None,
None,
None,
None,
None,
None,
("ucs2", "ucs2_general_mysql500_ci", False), # 159
("utf32", "utf32_unicode_ci", False), # 160
("utf32", "utf32_icelandic_ci", False), # 161
("utf32", "utf32_latvian_ci", False), # 162
("utf32", "utf32_romanian_ci", False), # 163
("utf32", "utf32_slovenian_ci", False), # 164
("utf32", "utf32_polish_ci", False), # 165
("utf32", "utf32_estonian_ci", False), # 166
("utf32", "utf32_spanish_ci", False), # 167
("utf32", "utf32_swedish_ci", False), # 168
("utf32", "utf32_turkish_ci", False), # 169
("utf32", "utf32_czech_ci", False), # 170
("utf32", "utf32_danish_ci", False), # 171
("utf32", "utf32_lithuanian_ci", False), # 172
("utf32", "utf32_slovak_ci", False), # 173
("utf32", "utf32_spanish2_ci", False), # 174
("utf32", "utf32_roman_ci", False), # 175
("utf32", "utf32_persian_ci", False), # 176
("utf32", "utf32_esperanto_ci", False), # 177
("utf32", "utf32_hungarian_ci", False), # 178
("utf32", "utf32_sinhala_ci", False), # 179
("utf32", "utf32_german2_ci", False), # 180
("utf32", "utf32_croatian_ci", False), # 181
("utf32", "utf32_unicode_520_ci", False), # 182
("utf32", "utf32_vietnamese_ci", False), # 183
None,
None,
None,
None,
None,
None,
None,
None,
("utf8", "utf8_unicode_ci", False), # 192
("utf8", "utf8_icelandic_ci", False), # 193
("utf8", "utf8_latvian_ci", False), # 194
("utf8", "utf8_romanian_ci", False), # 195
("utf8", "utf8_slovenian_ci", False), # 196
("utf8", "utf8_polish_ci", False), # 197
("utf8", "utf8_estonian_ci", False), # 198
("utf8", "utf8_spanish_ci", False), # 199
("utf8", "utf8_swedish_ci", False), # 200
("utf8", "utf8_turkish_ci", False), # 201
("utf8", "utf8_czech_ci", False), # 202
("utf8", "utf8_danish_ci", False), # 203
("utf8", "utf8_lithuanian_ci", False), # 204
("utf8", "utf8_slovak_ci", False), # 205
("utf8", "utf8_spanish2_ci", False), # 206
("utf8", "utf8_roman_ci", False), # 207
("utf8", "utf8_persian_ci", False), # 208
("utf8", "utf8_esperanto_ci", False), # 209
("utf8", "utf8_hungarian_ci", False), # 210
("utf8", "utf8_sinhala_ci", False), # 211
("utf8", "utf8_german2_ci", False), # 212
("utf8", "utf8_croatian_ci", False), # 213
("utf8", "utf8_unicode_520_ci", False), # 214
("utf8", "utf8_vietnamese_ci", False), # 215
None,
None,
None,
None,
None,
None,
None,
("utf8", "utf8_general_mysql500_ci", False), # 223
("utf8mb4", "utf8mb4_unicode_ci", False), # 224
("utf8mb4", "utf8mb4_icelandic_ci", False), # 225
("utf8mb4", "utf8mb4_latvian_ci", False), # 226
("utf8mb4", "utf8mb4_romanian_ci", False), # 227
("utf8mb4", "utf8mb4_slovenian_ci", False), # 228
("utf8mb4", "utf8mb4_polish_ci", False), # 229
("utf8mb4", "utf8mb4_estonian_ci", False), # 230
("utf8mb4", "utf8mb4_spanish_ci", False), # 231
("utf8mb4", "utf8mb4_swedish_ci", False), # 232
("utf8mb4", "utf8mb4_turkish_ci", False), # 233
("utf8mb4", "utf8mb4_czech_ci", False), # 234
("utf8mb4", "utf8mb4_danish_ci", False), # 235
("utf8mb4", "utf8mb4_lithuanian_ci", False), # 236
("utf8mb4", "utf8mb4_slovak_ci", False), # 237
("utf8mb4", "utf8mb4_spanish2_ci", False), # 238
("utf8mb4", "utf8mb4_roman_ci", False), # 239
("utf8mb4", "utf8mb4_persian_ci", False), # 240
("utf8mb4", "utf8mb4_esperanto_ci", False), # 241
("utf8mb4", "utf8mb4_hungarian_ci", False), # 242
("utf8mb4", "utf8mb4_sinhala_ci", False), # 243
("utf8mb4", "utf8mb4_german2_ci", False), # 244
("utf8mb4", "utf8mb4_croatian_ci", False), # 245
("utf8mb4", "utf8mb4_unicode_520_ci", False), # 246
("utf8mb4", "utf8mb4_vietnamese_ci", False), # 247
("gb18030", "gb18030_chinese_ci", True), # 248
("gb18030", "gb18030_bin", False), # 249
("gb18030", "gb18030_unicode_520_ci", False), # 250
]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,594 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Connection class using the C Extension
"""
# Detection of abstract methods in pylint is not working correctly
#pylint: disable=W0223
from . import errors
from .catch23 import INT_TYPES
from .constants import (
CharacterSet, FieldFlag, ServerFlag, ShutdownType, ClientFlag
)
from .abstracts import MySQLConnectionAbstract, MySQLCursorAbstract
from .protocol import MySQLProtocol
HAVE_CMYSQL = False
# pylint: disable=F0401,C0413
try:
import _mysql_connector
from .cursor_cext import (
CMySQLCursor, CMySQLCursorRaw,
CMySQLCursorBuffered, CMySQLCursorBufferedRaw, CMySQLCursorPrepared,
CMySQLCursorDict, CMySQLCursorBufferedDict, CMySQLCursorNamedTuple,
CMySQLCursorBufferedNamedTuple)
from _mysql_connector import MySQLInterfaceError # pylint: disable=F0401
except ImportError as exc:
raise ImportError(
"MySQL Connector/Python C Extension not available ({0})".format(
str(exc)
))
else:
HAVE_CMYSQL = True
# pylint: enable=F0401,C0413
class CMySQLConnection(MySQLConnectionAbstract):
"""Class initiating a MySQL Connection using Connector/C"""
def __init__(self, **kwargs):
"""Initialization"""
if not HAVE_CMYSQL:
raise RuntimeError(
"MySQL Connector/Python C Extension not available")
self._cmysql = None
self._connection_timeout = 2
self._columns = []
self.converter = None
super(CMySQLConnection, self).__init__(**kwargs)
if len(kwargs) > 0:
self.connect(**kwargs)
def _do_handshake(self):
"""Gather information of the MySQL server before authentication"""
self._handshake = {
'protocol': self._cmysql.get_proto_info(),
'server_version_original': self._cmysql.get_server_info(),
'server_threadid': self._cmysql.thread_id(),
'charset': None,
'server_status': None,
'auth_plugin': None,
'auth_data': None,
'capabilities': self._cmysql.st_server_capabilities(),
}
self._server_version = self._check_server_version(
self._handshake['server_version_original']
)
@property
def _server_status(self):
"""Returns the server status attribute of MYSQL structure"""
return self._cmysql.st_server_status()
def set_unicode(self, value=True):
"""Toggle unicode mode
Set whether we return string fields as unicode or not.
Default is True.
"""
self._use_unicode = value
if self._cmysql:
self._cmysql.use_unicode(value)
if self.converter:
self.converter.set_unicode(value)
@property
def autocommit(self):
"""Get whether autocommit is on or off"""
value = self.info_query("SELECT @@session.autocommit")[0]
return True if value == 1 else False
@autocommit.setter
def autocommit(self, value): # pylint: disable=W0221
"""Toggle autocommit"""
try:
self._cmysql.autocommit(value)
self._autocommit = value
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
@property
def database(self):
"""Get the current database"""
return self.info_query("SELECT DATABASE()")[0]
@database.setter
def database(self, value): # pylint: disable=W0221
"""Set the current database"""
self._cmysql.select_db(value)
@property
def in_transaction(self):
"""MySQL session has started a transaction"""
return self._server_status & ServerFlag.STATUS_IN_TRANS
def _open_connection(self):
charset_name = CharacterSet.get_info(self._charset_id)[0]
self._cmysql = _mysql_connector.MySQL(
buffered=self._buffered,
raw=self._raw,
charset_name=charset_name,
connection_timeout=int(self._connection_timeout or 10),
use_unicode=self._use_unicode,
auth_plugin=self._auth_plugin)
cnx_kwargs = {
'host': self._host,
'user': self._user,
'password': self._password,
'database': self._database,
'port': self._port,
'client_flags': self._client_flags,
'unix_socket': self._unix_socket,
'compress': self.isset_client_flag(ClientFlag.COMPRESS)
}
if self.isset_client_flag(ClientFlag.SSL):
cnx_kwargs.update({
'ssl_ca': self._ssl['ca'],
'ssl_cert': self._ssl['cert'],
'ssl_key': self._ssl['key'],
'ssl_verify_cert': self._ssl['verify_cert']
})
try:
self._cmysql.connect(**cnx_kwargs)
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
self._do_handshake()
def close(self):
"""Disconnect from the MySQL server"""
if self._cmysql:
try:
self._cmysql.close()
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
self._cmysql = None
disconnect = close
def is_connected(self):
"""Reports whether the connection to MySQL Server is available"""
if self._cmysql:
return self._cmysql.ping()
return False
def ping(self, reconnect=False, attempts=1, delay=0):
"""Check availability of the MySQL server
When reconnect is set to True, one or more attempts are made to try
to reconnect to the MySQL server using the reconnect()-method.
delay is the number of seconds to wait between each retry.
When the connection is not available, an InterfaceError is raised. Use
the is_connected()-method if you just want to check the connection
without raising an error.
Raises InterfaceError on errors.
"""
errmsg = "Connection to MySQL is not available"
try:
connected = self._cmysql.ping()
except AttributeError:
pass # Raise or reconnect later
else:
if connected:
return
if reconnect:
self.reconnect(attempts=attempts, delay=delay)
else:
raise errors.InterfaceError(errmsg)
def set_character_set_name(self, charset):
"""Sets the default character set name for current connection.
"""
self._cmysql.set_character_set(charset)
def info_query(self, query):
"""Send a query which only returns 1 row"""
self._cmysql.query(query)
first_row = ()
if self._cmysql.have_result_set:
first_row = self._cmysql.fetch_row()
if self._cmysql.fetch_row():
self._cmysql.free_result()
raise errors.InterfaceError(
"Query should not return more than 1 row")
self._cmysql.free_result()
return first_row
@property
def connection_id(self):
"""MySQL connection ID"""
try:
return self._cmysql.thread_id()
except MySQLInterfaceError:
pass # Just return None
return None
def get_rows(self, count=None, binary=False, columns=None):
"""Get all or a subset of rows returned by the MySQL server"""
if not (self._cmysql and self.unread_result):
raise errors.InternalError("No result set available")
rows = []
if count is not None and count <= 0:
raise AttributeError("count should be 1 or higher, or None")
counter = 0
try:
row = self._cmysql.fetch_row()
while row:
if self.converter:
row = list(row)
for i, _ in enumerate(row):
row[i] = self.converter.to_python(self._columns[i],
row[i])
row = tuple(row)
rows.append(row)
counter += 1
if count and counter == count:
break
row = self._cmysql.fetch_row()
except MySQLInterfaceError as exc:
self.free_result()
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
return rows
def get_row(self, binary=False, columns=None):
"""Get the next rows returned by the MySQL server"""
try:
return self.get_rows(count=1, binary=binary, columns=columns)[0]
except IndexError:
# No row available
return None
def next_result(self):
"""Reads the next result"""
if self._cmysql:
self._cmysql.consume_result()
return self._cmysql.next_result()
return None
def free_result(self):
"""Frees the result"""
if self._cmysql:
self._cmysql.free_result()
def commit(self):
"""Commit current transaction"""
if self._cmysql:
self._cmysql.commit()
def rollback(self):
"""Rollback current transaction"""
if self._cmysql:
self._cmysql.consume_result()
self._cmysql.rollback()
def cmd_init_db(self, database):
"""Change the current database"""
try:
self._cmysql.select_db(database)
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
def fetch_eof_columns(self):
"""Fetch EOF and column information"""
if not self._cmysql.have_result_set:
raise errors.InterfaceError("No result set")
fields = self._cmysql.fetch_fields()
self._columns = []
for col in fields:
self._columns.append((
col[4],
int(col[8]),
None,
None,
None,
None,
~int(col[9]) & FieldFlag.NOT_NULL,
int(col[9])
))
return {
'eof': {
'status_flag': self._server_status,
'warning_count': self._cmysql.st_warning_count(),
},
'columns': self._columns,
}
def fetch_eof_status(self):
"""Fetch EOF and status information"""
if self._cmysql:
return {
'warning_count': self._cmysql.st_warning_count(),
'field_count': self._cmysql.st_field_count(),
'insert_id': self._cmysql.insert_id(),
'affected_rows': self._cmysql.affected_rows(),
'server_status': self._server_status,
}
return None
def cmd_query(self, query, raw=False, buffered=False, raw_as_string=False):
"""Send a query to the MySQL server"""
self.handle_unread_result()
try:
if not isinstance(query, bytes):
query = query.encode('utf-8')
self._cmysql.query(query,
raw=raw, buffered=buffered,
raw_as_string=raw_as_string)
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(exc.errno, msg=exc.msg,
sqlstate=exc.sqlstate)
except AttributeError:
if self._unix_socket:
addr = self._unix_socket
else:
addr = self._host + ':' + str(self._port)
raise errors.OperationalError(
errno=2055, values=(addr, 'Connection not available.'))
self._columns = []
if not self._cmysql.have_result_set:
# No result
return self.fetch_eof_status()
return self.fetch_eof_columns()
_execute_query = cmd_query
def cursor(self, buffered=None, raw=None, prepared=None, cursor_class=None,
dictionary=None, named_tuple=None):
"""Instantiates and returns a cursor using C Extension
By default, CMySQLCursor is returned. Depending on the options
while connecting, a buffered and/or raw cursor is instantiated
instead. Also depending upon the cursor options, rows can be
returned as dictionary or named tuple.
Dictionary and namedtuple based cursors are available with buffered
output but not raw.
It is possible to also give a custom cursor through the
cursor_class parameter, but it needs to be a subclass of
mysql.connector.cursor_cext.CMySQLCursor.
Raises ProgrammingError when cursor_class is not a subclass of
CursorBase. Raises ValueError when cursor is not available.
Returns instance of CMySQLCursor or subclass.
:param buffered: Return a buffering cursor
:param raw: Return a raw cursor
:param prepared: Return a cursor which uses prepared statements
:param cursor_class: Use a custom cursor class
:param dictionary: Rows are returned as dictionary
:param named_tuple: Rows are returned as named tuple
:return: Subclass of CMySQLCursor
:rtype: CMySQLCursor or subclass
"""
self.handle_unread_result()
if not self.is_connected():
raise errors.OperationalError("MySQL Connection not available.")
if cursor_class is not None:
if not issubclass(cursor_class, MySQLCursorAbstract):
raise errors.ProgrammingError(
"Cursor class needs be to subclass"
" of cursor_cext.CMySQLCursor")
return (cursor_class)(self)
buffered = buffered or self._buffered
raw = raw or self._raw
cursor_type = 0
if buffered is True:
cursor_type |= 1
if raw is True:
cursor_type |= 2
if dictionary is True:
cursor_type |= 4
if named_tuple is True:
cursor_type |= 8
if prepared is True:
cursor_type |= 16
types = {
0: CMySQLCursor, # 0
1: CMySQLCursorBuffered,
2: CMySQLCursorRaw,
3: CMySQLCursorBufferedRaw,
4: CMySQLCursorDict,
5: CMySQLCursorBufferedDict,
8: CMySQLCursorNamedTuple,
9: CMySQLCursorBufferedNamedTuple,
16: CMySQLCursorPrepared
}
try:
return (types[cursor_type])(self)
except KeyError:
args = ('buffered', 'raw', 'dictionary', 'named_tuple', 'prepared')
raise ValueError('Cursor not available with given criteria: ' +
', '.join([args[i] for i in range(5)
if cursor_type & (1 << i) != 0]))
@property
def num_rows(self):
"""Returns number of rows of current result set"""
if not self._cmysql.have_result_set:
raise errors.InterfaceError("No result set")
return self._cmysql.num_rows()
@property
def warning_count(self):
"""Returns number of warnings"""
if not self._cmysql:
return 0
return self._cmysql.warning_count()
@property
def result_set_available(self):
"""Check if a result set is available"""
if not self._cmysql:
return False
return self._cmysql.have_result_set
@property
def unread_result(self):
"""Check if there are unread results or rows"""
return self.result_set_available
@property
def more_results(self):
"""Check if there are more results"""
return self._cmysql.more_results()
def prepare_for_mysql(self, params):
"""Prepare parameters for statements
This method is use by cursors to prepared parameters found in the
list (or tuple) params.
Returns dict.
"""
if isinstance(params, (list, tuple)):
result = self._cmysql.convert_to_mysql(*params)
elif isinstance(params, dict):
result = {}
for key, value in params.items():
result[key] = self._cmysql.convert_to_mysql(value)[0]
else:
raise ValueError("Could not process parameters")
return result
def consume_results(self):
"""Consume the current result
This method consume the result by reading (consuming) all rows.
"""
self._cmysql.consume_result()
def cmd_change_user(self, username='', password='', database='',
charset=33):
"""Change the current logged in user"""
try:
self._cmysql.change_user(username, password, database)
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
self._charset_id = charset
self._post_connection()
def cmd_refresh(self, options):
"""Send the Refresh command to the MySQL server"""
try:
self._cmysql.refresh(options)
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
return self.fetch_eof_status()
def cmd_quit(self):
"""Close the current connection with the server"""
self.close()
def cmd_shutdown(self, shutdown_type=None):
"""Shut down the MySQL Server"""
if not self._cmysql:
raise errors.OperationalError("MySQL Connection not available")
if shutdown_type:
if not ShutdownType.get_info(shutdown_type):
raise errors.InterfaceError("Invalid shutdown type")
level = shutdown_type
else:
level = ShutdownType.SHUTDOWN_DEFAULT
try:
self._cmysql.shutdown(level)
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
self.close()
def cmd_statistics(self):
"""Return statistics from the MySQL server"""
self.handle_unread_result()
try:
stat = self._cmysql.stat()
return MySQLProtocol().parse_statistics(stat, with_header=False)
except (MySQLInterfaceError, errors.InterfaceError) as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
def cmd_process_kill(self, mysql_pid):
"""Kill a MySQL process"""
if not isinstance(mysql_pid, INT_TYPES):
raise ValueError("MySQL PID must be int")
self.info_query("KILL {0}".format(mysql_pid))
def handle_unread_result(self):
"""Check whether there is an unread result"""
if self.can_consume_results:
self.consume_results()
elif self.unread_result:
raise errors.InternalError("Unread result found")

View file

@ -0,0 +1,754 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Various MySQL constants and character sets
"""
from .errors import ProgrammingError
from .charsets import MYSQL_CHARACTER_SETS
MAX_PACKET_LENGTH = 16777215
NET_BUFFER_LENGTH = 8192
MAX_MYSQL_TABLE_COLUMNS = 4096
DEFAULT_CONFIGURATION = {
'database': None,
'user': '',
'password': '',
'host': '127.0.0.1',
'port': 3306,
'unix_socket': None,
'use_unicode': True,
'charset': 'utf8',
'collation': None,
'converter_class': None,
'autocommit': False,
'time_zone': None,
'sql_mode': None,
'get_warnings': False,
'raise_on_warnings': False,
'connection_timeout': None,
'client_flags': 0,
'compress': False,
'buffered': False,
'raw': False,
'ssl_ca': None,
'ssl_cert': None,
'ssl_key': None,
'ssl_verify_cert': False,
'ssl_cipher': None,
'passwd': None,
'db': None,
'connect_timeout': None,
'dsn': None,
'force_ipv6': False,
'auth_plugin': None,
'allow_local_infile': True,
'consume_results': False,
}
CNX_POOL_ARGS = ('pool_name', 'pool_size', 'pool_reset_session')
CNX_FABRIC_ARGS = ['fabric_host', 'fabric_username', 'fabric_password',
'fabric_port', 'fabric_connect_attempts',
'fabric_connect_delay', 'fabric_report_errors',
'fabric_ssl_ca', 'fabric_ssl_key', 'fabric_ssl_cert',
'fabric_user']
def flag_is_set(flag, flags):
"""Checks if the flag is set
Returns boolean"""
if (flags & flag) > 0:
return True
return False
class _Constants(object):
"""
Base class for constants
"""
prefix = ''
desc = {}
def __new__(cls):
raise TypeError("Can not instanciate from %s" % cls.__name__)
@classmethod
def get_desc(cls, name):
"""Get description of given constant"""
try:
return cls.desc[name][1]
except:
return None
@classmethod
def get_info(cls, num):
"""Get information about given constant"""
for name, info in cls.desc.items():
if info[0] == num:
return name
return None
@classmethod
def get_full_info(cls):
"""get full information about given constant"""
res = ()
try:
res = ["%s : %s" % (k, v[1]) for k, v in cls.desc.items()]
except Exception as err: # pylint: disable=W0703
res = ('No information found in constant class.%s' % err)
return res
class _Flags(_Constants):
"""Base class for classes describing flags
"""
@classmethod
def get_bit_info(cls, value):
"""Get the name of all bits set
Returns a list of strings."""
res = []
for name, info in cls.desc.items():
if value & info[0]:
res.append(name)
return res
class FieldType(_Constants):
"""MySQL Field Types
"""
prefix = 'FIELD_TYPE_'
DECIMAL = 0x00
TINY = 0x01
SHORT = 0x02
LONG = 0x03
FLOAT = 0x04
DOUBLE = 0x05
NULL = 0x06
TIMESTAMP = 0x07
LONGLONG = 0x08
INT24 = 0x09
DATE = 0x0a
TIME = 0x0b
DATETIME = 0x0c
YEAR = 0x0d
NEWDATE = 0x0e
VARCHAR = 0x0f
BIT = 0x10
NEWDECIMAL = 0xf6
ENUM = 0xf7
SET = 0xf8
TINY_BLOB = 0xf9
MEDIUM_BLOB = 0xfa
LONG_BLOB = 0xfb
BLOB = 0xfc
VAR_STRING = 0xfd
STRING = 0xfe
GEOMETRY = 0xff
desc = {
'DECIMAL': (0x00, 'DECIMAL'),
'TINY': (0x01, 'TINY'),
'SHORT': (0x02, 'SHORT'),
'LONG': (0x03, 'LONG'),
'FLOAT': (0x04, 'FLOAT'),
'DOUBLE': (0x05, 'DOUBLE'),
'NULL': (0x06, 'NULL'),
'TIMESTAMP': (0x07, 'TIMESTAMP'),
'LONGLONG': (0x08, 'LONGLONG'),
'INT24': (0x09, 'INT24'),
'DATE': (0x0a, 'DATE'),
'TIME': (0x0b, 'TIME'),
'DATETIME': (0x0c, 'DATETIME'),
'YEAR': (0x0d, 'YEAR'),
'NEWDATE': (0x0e, 'NEWDATE'),
'VARCHAR': (0x0f, 'VARCHAR'),
'BIT': (0x10, 'BIT'),
'NEWDECIMAL': (0xf6, 'NEWDECIMAL'),
'ENUM': (0xf7, 'ENUM'),
'SET': (0xf8, 'SET'),
'TINY_BLOB': (0xf9, 'TINY_BLOB'),
'MEDIUM_BLOB': (0xfa, 'MEDIUM_BLOB'),
'LONG_BLOB': (0xfb, 'LONG_BLOB'),
'BLOB': (0xfc, 'BLOB'),
'VAR_STRING': (0xfd, 'VAR_STRING'),
'STRING': (0xfe, 'STRING'),
'GEOMETRY': (0xff, 'GEOMETRY'),
}
@classmethod
def get_string_types(cls):
"""Get the list of all string types"""
return [
cls.VARCHAR,
cls.ENUM,
cls.VAR_STRING, cls.STRING,
]
@classmethod
def get_binary_types(cls):
"""Get the list of all binary types"""
return [
cls.TINY_BLOB, cls.MEDIUM_BLOB,
cls.LONG_BLOB, cls.BLOB,
]
@classmethod
def get_number_types(cls):
"""Get the list of all number types"""
return [
cls.DECIMAL, cls.NEWDECIMAL,
cls.TINY, cls.SHORT, cls.LONG,
cls.FLOAT, cls.DOUBLE,
cls.LONGLONG, cls.INT24,
cls.BIT,
cls.YEAR,
]
@classmethod
def get_timestamp_types(cls):
"""Get the list of all timestamp types"""
return [
cls.DATETIME, cls.TIMESTAMP,
]
class FieldFlag(_Flags):
"""MySQL Field Flags
Field flags as found in MySQL sources mysql-src/include/mysql_com.h
"""
_prefix = ''
NOT_NULL = 1 << 0
PRI_KEY = 1 << 1
UNIQUE_KEY = 1 << 2
MULTIPLE_KEY = 1 << 3
BLOB = 1 << 4
UNSIGNED = 1 << 5
ZEROFILL = 1 << 6
BINARY = 1 << 7
ENUM = 1 << 8
AUTO_INCREMENT = 1 << 9
TIMESTAMP = 1 << 10
SET = 1 << 11
NO_DEFAULT_VALUE = 1 << 12
ON_UPDATE_NOW = 1 << 13
NUM = 1 << 14
PART_KEY = 1 << 15
GROUP = 1 << 14 # SAME AS NUM !!!!!!!????
UNIQUE = 1 << 16
BINCMP = 1 << 17
GET_FIXED_FIELDS = 1 << 18
FIELD_IN_PART_FUNC = 1 << 19
FIELD_IN_ADD_INDEX = 1 << 20
FIELD_IS_RENAMED = 1 << 21
desc = {
'NOT_NULL': (1 << 0, "Field can't be NULL"),
'PRI_KEY': (1 << 1, "Field is part of a primary key"),
'UNIQUE_KEY': (1 << 2, "Field is part of a unique key"),
'MULTIPLE_KEY': (1 << 3, "Field is part of a key"),
'BLOB': (1 << 4, "Field is a blob"),
'UNSIGNED': (1 << 5, "Field is unsigned"),
'ZEROFILL': (1 << 6, "Field is zerofill"),
'BINARY': (1 << 7, "Field is binary "),
'ENUM': (1 << 8, "field is an enum"),
'AUTO_INCREMENT': (1 << 9, "field is a autoincrement field"),
'TIMESTAMP': (1 << 10, "Field is a timestamp"),
'SET': (1 << 11, "field is a set"),
'NO_DEFAULT_VALUE': (1 << 12, "Field doesn't have default value"),
'ON_UPDATE_NOW': (1 << 13, "Field is set to NOW on UPDATE"),
'NUM': (1 << 14, "Field is num (for clients)"),
'PART_KEY': (1 << 15, "Intern; Part of some key"),
'GROUP': (1 << 14, "Intern: Group field"), # Same as NUM
'UNIQUE': (1 << 16, "Intern: Used by sql_yacc"),
'BINCMP': (1 << 17, "Intern: Used by sql_yacc"),
'GET_FIXED_FIELDS': (1 << 18, "Used to get fields in item tree"),
'FIELD_IN_PART_FUNC': (1 << 19, "Field part of partition func"),
'FIELD_IN_ADD_INDEX': (1 << 20, "Intern: Field used in ADD INDEX"),
'FIELD_IS_RENAMED': (1 << 21, "Intern: Field is being renamed"),
}
class ServerCmd(_Constants):
"""MySQL Server Commands
"""
_prefix = 'COM_'
SLEEP = 0
QUIT = 1
INIT_DB = 2
QUERY = 3
FIELD_LIST = 4
CREATE_DB = 5
DROP_DB = 6
REFRESH = 7
SHUTDOWN = 8
STATISTICS = 9
PROCESS_INFO = 10
CONNECT = 11
PROCESS_KILL = 12
DEBUG = 13
PING = 14
TIME = 15
DELAYED_INSERT = 16
CHANGE_USER = 17
BINLOG_DUMP = 18
TABLE_DUMP = 19
CONNECT_OUT = 20
REGISTER_SLAVE = 21
STMT_PREPARE = 22
STMT_EXECUTE = 23
STMT_SEND_LONG_DATA = 24
STMT_CLOSE = 25
STMT_RESET = 26
SET_OPTION = 27
STMT_FETCH = 28
DAEMON = 29
BINLOG_DUMP_GTID = 30
RESET_CONNECTION = 31
desc = {
'SLEEP': (0, 'SLEEP'),
'QUIT': (1, 'QUIT'),
'INIT_DB': (2, 'INIT_DB'),
'QUERY': (3, 'QUERY'),
'FIELD_LIST': (4, 'FIELD_LIST'),
'CREATE_DB': (5, 'CREATE_DB'),
'DROP_DB': (6, 'DROP_DB'),
'REFRESH': (7, 'REFRESH'),
'SHUTDOWN': (8, 'SHUTDOWN'),
'STATISTICS': (9, 'STATISTICS'),
'PROCESS_INFO': (10, 'PROCESS_INFO'),
'CONNECT': (11, 'CONNECT'),
'PROCESS_KILL': (12, 'PROCESS_KILL'),
'DEBUG': (13, 'DEBUG'),
'PING': (14, 'PING'),
'TIME': (15, 'TIME'),
'DELAYED_INSERT': (16, 'DELAYED_INSERT'),
'CHANGE_USER': (17, 'CHANGE_USER'),
'BINLOG_DUMP': (18, 'BINLOG_DUMP'),
'TABLE_DUMP': (19, 'TABLE_DUMP'),
'CONNECT_OUT': (20, 'CONNECT_OUT'),
'REGISTER_SLAVE': (21, 'REGISTER_SLAVE'),
'STMT_PREPARE': (22, 'STMT_PREPARE'),
'STMT_EXECUTE': (23, 'STMT_EXECUTE'),
'STMT_SEND_LONG_DATA': (24, 'STMT_SEND_LONG_DATA'),
'STMT_CLOSE': (25, 'STMT_CLOSE'),
'STMT_RESET': (26, 'STMT_RESET'),
'SET_OPTION': (27, 'SET_OPTION'),
'STMT_FETCH': (28, 'STMT_FETCH'),
'DAEMON': (29, 'DAEMON'),
'BINLOG_DUMP_GTID': (30, 'BINLOG_DUMP_GTID'),
'RESET_CONNECTION': (31, 'RESET_CONNECTION'),
}
class ClientFlag(_Flags):
"""MySQL Client Flags
Client options as found in the MySQL sources mysql-src/include/mysql_com.h
"""
LONG_PASSWD = 1 << 0
FOUND_ROWS = 1 << 1
LONG_FLAG = 1 << 2
CONNECT_WITH_DB = 1 << 3
NO_SCHEMA = 1 << 4
COMPRESS = 1 << 5
ODBC = 1 << 6
LOCAL_FILES = 1 << 7
IGNORE_SPACE = 1 << 8
PROTOCOL_41 = 1 << 9
INTERACTIVE = 1 << 10
SSL = 1 << 11
IGNORE_SIGPIPE = 1 << 12
TRANSACTIONS = 1 << 13
RESERVED = 1 << 14
SECURE_CONNECTION = 1 << 15
MULTI_STATEMENTS = 1 << 16
MULTI_RESULTS = 1 << 17
PS_MULTI_RESULTS = 1 << 18
PLUGIN_AUTH = 1 << 19
CONNECT_ARGS = 1 << 20
PLUGIN_AUTH_LENENC_CLIENT_DATA = 1 << 21
CAN_HANDLE_EXPIRED_PASSWORDS = 1 << 22
SESION_TRACK = 1 << 23
DEPRECATE_EOF = 1 << 24
SSL_VERIFY_SERVER_CERT = 1 << 30
REMEMBER_OPTIONS = 1 << 31
desc = {
'LONG_PASSWD': (1 << 0, 'New more secure passwords'),
'FOUND_ROWS': (1 << 1, 'Found instead of affected rows'),
'LONG_FLAG': (1 << 2, 'Get all column flags'),
'CONNECT_WITH_DB': (1 << 3, 'One can specify db on connect'),
'NO_SCHEMA': (1 << 4, "Don't allow database.table.column"),
'COMPRESS': (1 << 5, 'Can use compression protocol'),
'ODBC': (1 << 6, 'ODBC client'),
'LOCAL_FILES': (1 << 7, 'Can use LOAD DATA LOCAL'),
'IGNORE_SPACE': (1 << 8, "Ignore spaces before ''"),
'PROTOCOL_41': (1 << 9, 'New 4.1 protocol'),
'INTERACTIVE': (1 << 10, 'This is an interactive client'),
'SSL': (1 << 11, 'Switch to SSL after handshake'),
'IGNORE_SIGPIPE': (1 << 12, 'IGNORE sigpipes'),
'TRANSACTIONS': (1 << 13, 'Client knows about transactions'),
'RESERVED': (1 << 14, 'Old flag for 4.1 protocol'),
'SECURE_CONNECTION': (1 << 15, 'New 4.1 authentication'),
'MULTI_STATEMENTS': (1 << 16, 'Enable/disable multi-stmt support'),
'MULTI_RESULTS': (1 << 17, 'Enable/disable multi-results'),
'PS_MULTI_RESULTS': (1 << 18, 'Multi-results in PS-protocol'),
'PLUGIN_AUTH': (1 << 19, 'Client supports plugin authentication'),
'CONNECT_ARGS': (1 << 20, 'Client supports connection attributes'),
'PLUGIN_AUTH_LENENC_CLIENT_DATA': (1 << 21,
'Enable authentication response packet to be larger than 255 bytes'),
'CAN_HANDLE_EXPIRED_PASSWORDS': (1 << 22, "Don't close the connection for a connection with expired password"),
'SESION_TRACK': (1 << 23, 'Capable of handling server state change information'),
'DEPRECATE_EOF': (1 << 24, 'Client no longer needs EOF packet'),
'SSL_VERIFY_SERVER_CERT': (1 << 30, ''),
'REMEMBER_OPTIONS': (1 << 31, ''),
}
default = [
LONG_PASSWD,
LONG_FLAG,
CONNECT_WITH_DB,
PROTOCOL_41,
TRANSACTIONS,
SECURE_CONNECTION,
MULTI_STATEMENTS,
MULTI_RESULTS,
LOCAL_FILES,
]
@classmethod
def get_default(cls):
"""Get the default client options set
Returns a flag with all the default client options set"""
flags = 0
for option in cls.default:
flags |= option
return flags
class ServerFlag(_Flags):
"""MySQL Server Flags
Server flags as found in the MySQL sources mysql-src/include/mysql_com.h
"""
_prefix = 'SERVER_'
STATUS_IN_TRANS = 1 << 0
STATUS_AUTOCOMMIT = 1 << 1
MORE_RESULTS_EXISTS = 1 << 3
QUERY_NO_GOOD_INDEX_USED = 1 << 4
QUERY_NO_INDEX_USED = 1 << 5
STATUS_CURSOR_EXISTS = 1 << 6
STATUS_LAST_ROW_SENT = 1 << 7
STATUS_DB_DROPPED = 1 << 8
STATUS_NO_BACKSLASH_ESCAPES = 1 << 9
desc = {
'SERVER_STATUS_IN_TRANS': (1 << 0,
'Transaction has started'),
'SERVER_STATUS_AUTOCOMMIT': (1 << 1,
'Server in auto_commit mode'),
'SERVER_MORE_RESULTS_EXISTS': (1 << 3,
'Multi query - '
'next query exists'),
'SERVER_QUERY_NO_GOOD_INDEX_USED': (1 << 4, ''),
'SERVER_QUERY_NO_INDEX_USED': (1 << 5, ''),
'SERVER_STATUS_CURSOR_EXISTS': (1 << 6, ''),
'SERVER_STATUS_LAST_ROW_SENT': (1 << 7, ''),
'SERVER_STATUS_DB_DROPPED': (1 << 8, 'A database was dropped'),
'SERVER_STATUS_NO_BACKSLASH_ESCAPES': (1 << 9, ''),
}
class RefreshOption(_Constants):
"""MySQL Refresh command options
Options used when sending the COM_REFRESH server command.
"""
_prefix = 'REFRESH_'
GRANT = 1 << 0
LOG = 1 << 1
TABLES = 1 << 2
HOST = 1 << 3
STATUS = 1 << 4
THREADS = 1 << 5
SLAVE = 1 << 6
desc = {
'GRANT': (1 << 0, 'Refresh grant tables'),
'LOG': (1 << 1, 'Start on new log file'),
'TABLES': (1 << 2, 'close all tables'),
'HOSTS': (1 << 3, 'Flush host cache'),
'STATUS': (1 << 4, 'Flush status variables'),
'THREADS': (1 << 5, 'Flush thread cache'),
'SLAVE': (1 << 6, 'Reset master info and restart slave thread'),
}
class ShutdownType(_Constants):
"""MySQL Shutdown types
Shutdown types used by the COM_SHUTDOWN server command.
"""
_prefix = ''
SHUTDOWN_DEFAULT = 0
SHUTDOWN_WAIT_CONNECTIONS = 1
SHUTDOWN_WAIT_TRANSACTIONS = 2
SHUTDOWN_WAIT_UPDATES = 8
SHUTDOWN_WAIT_ALL_BUFFERS = 16
SHUTDOWN_WAIT_CRITICAL_BUFFERS = 17
KILL_QUERY = 254
KILL_CONNECTION = 255
desc = {
'SHUTDOWN_DEFAULT': (
SHUTDOWN_DEFAULT,
"defaults to SHUTDOWN_WAIT_ALL_BUFFERS"),
'SHUTDOWN_WAIT_CONNECTIONS': (
SHUTDOWN_WAIT_CONNECTIONS,
"wait for existing connections to finish"),
'SHUTDOWN_WAIT_TRANSACTIONS': (
SHUTDOWN_WAIT_TRANSACTIONS,
"wait for existing trans to finish"),
'SHUTDOWN_WAIT_UPDATES': (
SHUTDOWN_WAIT_UPDATES,
"wait for existing updates to finish"),
'SHUTDOWN_WAIT_ALL_BUFFERS': (
SHUTDOWN_WAIT_ALL_BUFFERS,
"flush InnoDB and other storage engine buffers"),
'SHUTDOWN_WAIT_CRITICAL_BUFFERS': (
SHUTDOWN_WAIT_CRITICAL_BUFFERS,
"don't flush InnoDB buffers, "
"flush other storage engines' buffers"),
'KILL_QUERY': (
KILL_QUERY,
"(no description)"),
'KILL_CONNECTION': (
KILL_CONNECTION,
"(no description)"),
}
class CharacterSet(_Constants):
"""MySQL supported character sets and collations
List of character sets with their collations supported by MySQL. This
maps to the character set we get from the server within the handshake
packet.
The list is hardcode so we avoid a database query when getting the
name of the used character set or collation.
"""
desc = MYSQL_CHARACTER_SETS
# Multi-byte character sets which use 5c (backslash) in characters
slash_charsets = (1, 13, 28, 84, 87, 88)
@classmethod
def get_info(cls, setid):
"""Retrieves character set information as tuple using an ID
Retrieves character set and collation information based on the
given MySQL ID.
Raises ProgrammingError when character set is not supported.
Returns a tuple.
"""
try:
return cls.desc[setid][0:2]
except IndexError:
raise ProgrammingError(
"Character set '{0}' unsupported".format(setid))
@classmethod
def get_desc(cls, setid):
"""Retrieves character set information as string using an ID
Retrieves character set and collation information based on the
given MySQL ID.
Returns a tuple.
"""
try:
return "%s/%s" % cls.get_info(setid)
except:
raise
@classmethod
def get_default_collation(cls, charset):
"""Retrieves the default collation for given character set
Raises ProgrammingError when character set is not supported.
Returns list (collation, charset, index)
"""
if isinstance(charset, int):
try:
info = cls.desc[charset]
return info[1], info[0], charset
except:
ProgrammingError("Character set ID '%s' unsupported." % (
charset))
for cid, info in enumerate(cls.desc):
if info is None:
continue
if info[0] == charset and info[2] is True:
return info[1], info[0], cid
raise ProgrammingError("Character set '%s' unsupported." % (charset))
@classmethod
def get_charset_info(cls, charset=None, collation=None):
"""Get character set information using charset name and/or collation
Retrieves character set and collation information given character
set name and/or a collation name.
If charset is an integer, it will look up the character set based
on the MySQL's ID.
For example:
get_charset_info('utf8',None)
get_charset_info(collation='utf8_general_ci')
get_charset_info(47)
Raises ProgrammingError when character set is not supported.
Returns a tuple with (id, characterset name, collation)
"""
if isinstance(charset, int):
try:
info = cls.desc[charset]
return (charset, info[0], info[1])
except IndexError:
ProgrammingError("Character set ID {0} unknown.".format(
charset))
if charset is not None and collation is None:
info = cls.get_default_collation(charset)
return (info[2], info[1], info[0])
elif charset is None and collation is not None:
for cid, info in enumerate(cls.desc):
if info is None:
continue
if collation == info[1]:
return (cid, info[0], info[1])
raise ProgrammingError("Collation '{0}' unknown.".format(collation))
else:
for cid, info in enumerate(cls.desc):
if info is None:
continue
if info[0] == charset and info[1] == collation:
return (cid, info[0], info[1])
raise ProgrammingError("Character set '{0}' unknown.".format(
charset))
@classmethod
def get_supported(cls):
"""Retrieves a list with names of all supproted character sets
Returns a tuple.
"""
res = []
for info in cls.desc:
if info and info[0] not in res:
res.append(info[0])
return tuple(res)
class SQLMode(_Constants): # pylint: disable=R0921
"""MySQL SQL Modes
The numeric values of SQL Modes are not interesting, only the names
are used when setting the SQL_MODE system variable using the MySQL
SET command.
See http://dev.mysql.com/doc/refman/5.6/en/server-sql-mode.html
"""
_prefix = 'MODE_'
REAL_AS_FLOAT = 'REAL_AS_FLOAT'
PIPES_AS_CONCAT = 'PIPES_AS_CONCAT'
ANSI_QUOTES = 'ANSI_QUOTES'
IGNORE_SPACE = 'IGNORE_SPACE'
NOT_USED = 'NOT_USED'
ONLY_FULL_GROUP_BY = 'ONLY_FULL_GROUP_BY'
NO_UNSIGNED_SUBTRACTION = 'NO_UNSIGNED_SUBTRACTION'
NO_DIR_IN_CREATE = 'NO_DIR_IN_CREATE'
POSTGRESQL = 'POSTGRESQL'
ORACLE = 'ORACLE'
MSSQL = 'MSSQL'
DB2 = 'DB2'
MAXDB = 'MAXDB'
NO_KEY_OPTIONS = 'NO_KEY_OPTIONS'
NO_TABLE_OPTIONS = 'NO_TABLE_OPTIONS'
NO_FIELD_OPTIONS = 'NO_FIELD_OPTIONS'
MYSQL323 = 'MYSQL323'
MYSQL40 = 'MYSQL40'
ANSI = 'ANSI'
NO_AUTO_VALUE_ON_ZERO = 'NO_AUTO_VALUE_ON_ZERO'
NO_BACKSLASH_ESCAPES = 'NO_BACKSLASH_ESCAPES'
STRICT_TRANS_TABLES = 'STRICT_TRANS_TABLES'
STRICT_ALL_TABLES = 'STRICT_ALL_TABLES'
NO_ZERO_IN_DATE = 'NO_ZERO_IN_DATE'
NO_ZERO_DATE = 'NO_ZERO_DATE'
INVALID_DATES = 'INVALID_DATES'
ERROR_FOR_DIVISION_BY_ZERO = 'ERROR_FOR_DIVISION_BY_ZERO'
TRADITIONAL = 'TRADITIONAL'
NO_AUTO_CREATE_USER = 'NO_AUTO_CREATE_USER'
HIGH_NOT_PRECEDENCE = 'HIGH_NOT_PRECEDENCE'
NO_ENGINE_SUBSTITUTION = 'NO_ENGINE_SUBSTITUTION'
PAD_CHAR_TO_FULL_LENGTH = 'PAD_CHAR_TO_FULL_LENGTH'
@classmethod
def get_desc(cls, name):
raise NotImplementedError
@classmethod
def get_info(cls, number):
raise NotImplementedError
@classmethod
def get_full_info(cls):
"""Returns a sequence of all available SQL Modes
This class method returns a tuple containing all SQL Mode names. The
names will be alphabetically sorted.
Returns a tuple.
"""
res = []
for key in vars(cls).keys():
if not key.startswith('_') \
and not hasattr(getattr(cls, key), '__call__'):
res.append(key)
return tuple(sorted(res))

View file

@ -0,0 +1,586 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Converting MySQL and Python types
"""
import datetime
import time
from decimal import Decimal
from .constants import FieldType, FieldFlag, CharacterSet
from .catch23 import PY2, NUMERIC_TYPES, struct_unpack
from .custom_types import HexLiteral
class MySQLConverterBase(object):
"""Base class for conversion classes
All class dealing with converting to and from MySQL data types must
be a subclass of this class.
"""
def __init__(self, charset='utf8', use_unicode=True):
self.python_types = None
self.mysql_types = None
self.charset = None
self.charset_id = 0
self.use_unicode = None
self.set_charset(charset)
self.set_unicode(use_unicode)
self._cache_field_types = {}
def set_charset(self, charset):
"""Set character set"""
if charset == 'utf8mb4':
charset = 'utf8'
if charset is not None:
self.charset = charset
else:
# default to utf8
self.charset = 'utf8'
self.charset_id = CharacterSet.get_charset_info(self.charset)[0]
def set_unicode(self, value=True):
"""Set whether to use Unicode"""
self.use_unicode = value
def to_mysql(self, value):
"""Convert Python data type to MySQL"""
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"""
if (value == b'\x00' or value is None) and vtype[1] != FieldType.BIT:
# Don't go further when we hit a NULL value
return None
if not self._cache_field_types:
self._cache_field_types = {}
for name, info in FieldType.desc.items():
try:
self._cache_field_types[info[0]] = getattr(
self, '_{0}_to_python'.format(name))
except AttributeError:
# We ignore field types which has no method
pass
try:
return self._cache_field_types[vtype[1]](value, vtype)
except KeyError:
return value
def escape(self, buf):
"""Escape buffer for sending to MySQL"""
return buf
def quote(self, buf):
"""Quote buffer for sending to MySQL"""
return str(buf)
class MySQLConverter(MySQLConverterBase):
"""Default conversion class for MySQL Connector/Python.
o escape method: for escaping values send to MySQL
o quoting method: for quoting values send to MySQL in statements
o conversion mapping: maps Python and MySQL data types to
function for converting them.
Whenever one needs to convert values differently, a converter_class
argument can be given while instantiating a new connection like
cnx.connect(converter_class=CustomMySQLConverterClass).
"""
def __init__(self, charset=None, use_unicode=True):
MySQLConverterBase.__init__(self, charset, use_unicode)
self._cache_field_types = {}
def escape(self, value):
"""
Escapes special characters as they are expected to by when MySQL
receives them.
As found in MySQL source mysys/charset.c
Returns the value if not a string, or the escaped string.
"""
if value is None:
return value
elif isinstance(value, NUMERIC_TYPES):
return value
if isinstance(value, (bytes, bytearray)):
value = value.replace(b'\\', b'\\\\')
value = value.replace(b'\n', b'\\n')
value = value.replace(b'\r', b'\\r')
value = value.replace(b'\047', b'\134\047') # single quotes
value = value.replace(b'\042', b'\134\042') # double quotes
value = value.replace(b'\032', b'\134\032') # for Win32
else:
value = value.replace('\\', '\\\\')
value = value.replace('\n', '\\n')
value = value.replace('\r', '\\r')
value = value.replace('\047', '\134\047') # single quotes
value = value.replace('\042', '\134\042') # double quotes
value = value.replace('\032', '\134\032') # for Win32
return value
def quote(self, buf):
"""
Quote the parameters for commands. General rules:
o numbers are returns as bytes using ascii codec
o None is returned as bytearray(b'NULL')
o Everything else is single quoted '<buf>'
Returns a bytearray object.
"""
if isinstance(buf, NUMERIC_TYPES):
if PY2:
if isinstance(buf, float):
return repr(buf)
else:
return str(buf)
else:
return str(buf).encode('ascii')
elif isinstance(buf, type(None)):
return bytearray(b"NULL")
else:
return bytearray(b"'" + buf + b"'")
def to_mysql(self, value):
"""Convert Python data type to MySQL"""
type_name = value.__class__.__name__.lower()
try:
return getattr(self, "_{0}_to_mysql".format(type_name))(value)
except AttributeError:
raise TypeError("Python '{0}' cannot be converted to a "
"MySQL type".format(type_name))
def to_python(self, vtype, value):
"""Convert MySQL data type to Python"""
if value == 0 and vtype[1] != FieldType.BIT: # \x00
# Don't go further when we hit a NULL value
return None
if value is None:
return None
if not self._cache_field_types:
self._cache_field_types = {}
for name, info in FieldType.desc.items():
try:
self._cache_field_types[info[0]] = getattr(
self, '_{0}_to_python'.format(name))
except AttributeError:
# We ignore field types which has no method
pass
try:
return self._cache_field_types[vtype[1]](value, vtype)
except KeyError:
# If one type is not defined, we just return the value as str
try:
return value.decode('utf-8')
except UnicodeDecodeError:
return value
except ValueError as err:
raise ValueError("%s (field %s)" % (err, vtype[0]))
except TypeError as err:
raise TypeError("%s (field %s)" % (err, vtype[0]))
except:
raise
def _int_to_mysql(self, value):
"""Convert value to int"""
return int(value)
def _long_to_mysql(self, value):
"""Convert value to int"""
return int(value)
def _float_to_mysql(self, value):
"""Convert value to float"""
return float(value)
def _str_to_mysql(self, value):
"""Convert value to string"""
if PY2:
return str(value)
return self._unicode_to_mysql(value)
def _unicode_to_mysql(self, value):
"""Convert unicode"""
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, charset)
return encoded
def _bytes_to_mysql(self, value):
"""Convert value to bytes"""
return value
def _bytearray_to_mysql(self, value):
"""Convert value to bytes"""
return bytes(value)
def _bool_to_mysql(self, value):
"""Convert value to boolean"""
if value:
return 1
else:
return 0
def _nonetype_to_mysql(self, value):
"""
This would return what None would be in MySQL, but instead we
leave it None and return it right away. The actual conversion
from None to NULL happens in the quoting functionality.
Return None.
"""
return None
def _datetime_to_mysql(self, value):
"""
Converts a datetime instance to a string suitable for MySQL.
The returned string has format: %Y-%m-%d %H:%M:%S[.%f]
If the instance isn't a datetime.datetime type, it return None.
Returns a bytes.
"""
if value.microsecond:
fmt = '{0:d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}'
return fmt.format(
value.year, value.month, value.day,
value.hour, value.minute, value.second,
value.microsecond).encode('ascii')
fmt = '{0:d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'
return fmt.format(
value.year, value.month, value.day,
value.hour, value.minute, value.second).encode('ascii')
def _date_to_mysql(self, value):
"""
Converts a date instance to a string suitable for MySQL.
The returned string has format: %Y-%m-%d
If the instance isn't a datetime.date type, it return None.
Returns a bytes.
"""
return '{0:d}-{1:02d}-{2:02d}'.format(value.year, value.month,
value.day).encode('ascii')
def _time_to_mysql(self, value):
"""
Converts a time instance to a string suitable for MySQL.
The returned string has format: %H:%M:%S[.%f]
If the instance isn't a datetime.time type, it return None.
Returns a bytes.
"""
if value.microsecond:
return value.strftime('%H:%M:%S.%f').encode('ascii')
return value.strftime('%H:%M:%S').encode('ascii')
def _struct_time_to_mysql(self, value):
"""
Converts a time.struct_time sequence to a string suitable
for MySQL.
The returned string has format: %Y-%m-%d %H:%M:%S
Returns a bytes or None when not valid.
"""
return time.strftime('%Y-%m-%d %H:%M:%S', value).encode('ascii')
def _timedelta_to_mysql(self, value):
"""
Converts a timedelta instance to a string suitable for MySQL.
The returned string has format: %H:%M:%S
Returns a bytes.
"""
seconds = abs(value.days * 86400 + value.seconds)
if value.microseconds:
fmt = '{0:02d}:{1:02d}:{2:02d}.{3:06d}'
if value.days < 0:
mcs = 1000000 - value.microseconds
seconds -= 1
else:
mcs = value.microseconds
else:
fmt = '{0:02d}:{1:02d}:{2:02d}'
if value.days < 0:
fmt = '-' + fmt
(hours, remainder) = divmod(seconds, 3600)
(mins, secs) = divmod(remainder, 60)
if value.microseconds:
result = fmt.format(hours, mins, secs, mcs)
else:
result = fmt.format(hours, mins, secs)
if PY2:
return result
else:
return result.encode('ascii')
def _decimal_to_mysql(self, value):
"""
Converts a decimal.Decimal instance to a string suitable for
MySQL.
Returns a bytes or None when not valid.
"""
if isinstance(value, Decimal):
return str(value).encode('ascii')
return None
def row_to_python(self, row, fields):
"""Convert a MySQL text result row to Python types
The row argument is a sequence containing text result returned
by a MySQL server. Each value of the row is converted to the
using the field type information in the fields argument.
Returns a tuple.
"""
i = 0
result = [None]*len(fields)
if not self._cache_field_types:
self._cache_field_types = {}
for name, info in FieldType.desc.items():
try:
self._cache_field_types[info[0]] = getattr(
self, '_{0}_to_python'.format(name))
except AttributeError:
# We ignore field types which has no method
pass
for field in fields:
field_type = field[1]
if (row[i] == 0 and field_type != FieldType.BIT) or row[i] is None:
# Don't convert NULL value
i += 1
continue
try:
result[i] = self._cache_field_types[field_type](row[i], field)
except KeyError:
# If one type is not defined, we just return the value as str
try:
result[i] = row[i].decode('utf-8')
except UnicodeDecodeError:
result[i] = row[i]
except (ValueError, TypeError) as err:
err.message = "{0} (field {1})".format(str(err), field[0])
raise
i += 1
return tuple(result)
def _FLOAT_to_python(self, value, desc=None): # pylint: disable=C0103
"""
Returns value as float type.
"""
return float(value)
_DOUBLE_to_python = _FLOAT_to_python
def _INT_to_python(self, value, desc=None): # pylint: disable=C0103
"""
Returns value as int type.
"""
return int(value)
_TINY_to_python = _INT_to_python
_SHORT_to_python = _INT_to_python
_INT24_to_python = _INT_to_python
_LONG_to_python = _INT_to_python
_LONGLONG_to_python = _INT_to_python
def _DECIMAL_to_python(self, value, desc=None): # pylint: disable=C0103
"""
Returns value as a decimal.Decimal.
"""
val = value.decode(self.charset)
return Decimal(val)
_NEWDECIMAL_to_python = _DECIMAL_to_python
def _str(self, value, desc=None):
"""
Returns value as str type.
"""
return str(value)
def _BIT_to_python(self, value, dsc=None): # pylint: disable=C0103
"""Returns BIT columntype as integer"""
int_val = value
if len(int_val) < 8:
int_val = b'\x00' * (8 - len(int_val)) + int_val
return struct_unpack('>Q', int_val)[0]
def _DATE_to_python(self, value, dsc=None): # pylint: disable=C0103
"""
Returns DATE column type as datetime.date type.
"""
try:
parts = value.split(b'-')
return datetime.date(int(parts[0]), int(parts[1]), int(parts[2]))
except ValueError:
return None
_NEWDATE_to_python = _DATE_to_python
def _TIME_to_python(self, value, dsc=None): # pylint: disable=C0103
"""
Returns TIME column type as datetime.time type.
"""
time_val = None
try:
(hms, mcs) = value.split(b'.')
mcs = int(mcs.ljust(6, b'0'))
except ValueError:
hms = value
mcs = 0
try:
(hours, mins, secs) = [int(d) for d in hms.split(b':')]
if value[0] == 45 or value[0] == '-': # if PY3 or PY2
mins, secs, mcs = -mins, -secs, -mcs
time_val = datetime.timedelta(hours=hours, minutes=mins,
seconds=secs, microseconds=mcs)
except ValueError:
raise ValueError(
"Could not convert {0} to python datetime.timedelta".format(
value))
else:
return time_val
def _DATETIME_to_python(self, value, dsc=None): # pylint: disable=C0103
"""
Returns DATETIME column type as datetime.datetime type.
"""
datetime_val = None
try:
(date_, time_) = value.split(b' ')
if len(time_) > 8:
(hms, mcs) = time_.split(b'.')
mcs = int(mcs.ljust(6, b'0'))
else:
hms = time_
mcs = 0
dtval = [int(i) for i in date_.split(b'-')] + \
[int(i) for i in hms.split(b':')] + [mcs, ]
datetime_val = datetime.datetime(*dtval)
except ValueError:
datetime_val = None
return datetime_val
_TIMESTAMP_to_python = _DATETIME_to_python
def _YEAR_to_python(self, value, desc=None): # pylint: disable=C0103
"""Returns YEAR column type as integer"""
try:
year = int(value)
except ValueError:
raise ValueError("Failed converting YEAR to int (%s)" % value)
return year
def _SET_to_python(self, value, dsc=None): # pylint: disable=C0103
"""Returns SET column type as set
Actually, MySQL protocol sees a SET as a string type field. So this
code isn't called directly, but used by STRING_to_python() method.
Returns SET column type as a set.
"""
set_type = None
val = value.decode(self.charset)
if not val:
return set()
try:
set_type = set(val.split(','))
except ValueError:
raise ValueError("Could not convert set %s to a sequence." % value)
return set_type
def _STRING_to_python(self, value, dsc=None): # pylint: disable=C0103
"""
Note that a SET is a string too, but using the FieldFlag we can see
whether we have to split it.
Returns string typed columns as string type.
"""
if dsc is not None:
# Check if we deal with a SET
if dsc[7] & FieldFlag.SET:
return self._SET_to_python(value, dsc)
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)
return value
_VAR_STRING_to_python = _STRING_to_python
def _BLOB_to_python(self, value, dsc=None): # pylint: disable=C0103
"""Convert BLOB data type to Python"""
if dsc is not None:
if dsc[7] & FieldFlag.BINARY:
if PY2:
return value
else:
return bytes(value)
return self._STRING_to_python(value, dsc)
_LONG_BLOB_to_python = _BLOB_to_python
_MEDIUM_BLOB_to_python = _BLOB_to_python
_TINY_BLOB_to_python = _BLOB_to_python

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,810 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Cursor classes using the C Extension
"""
from collections import namedtuple
import re
import weakref
from .abstracts import MySQLConnectionAbstract, MySQLCursorAbstract
from .catch23 import PY2, isunicode
from . import errors
from .errorcode import CR_NO_RESULT_SET
from .cursor import (
RE_PY_PARAM, RE_SQL_INSERT_STMT,
RE_SQL_ON_DUPLICATE, RE_SQL_COMMENT, RE_SQL_INSERT_VALUES,
RE_SQL_SPLIT_STMTS
)
from _mysql_connector import MySQLInterfaceError # pylint: disable=F0401
class _ParamSubstitutor(object):
"""
Substitutes parameters into SQL statement.
"""
def __init__(self, params):
self.params = params
self.index = 0
def __call__(self, matchobj):
index = self.index
self.index += 1
try:
return self.params[index]
except IndexError:
raise errors.ProgrammingError(
"Not enough parameters for the SQL statement")
@property
def remaining(self):
"""Returns number of parameters remaining to be substituted"""
return len(self.params) - self.index
class CMySQLCursor(MySQLCursorAbstract):
"""Default cursor for interacting with MySQL using C Extension"""
_raw = False
_buffered = False
_raw_as_string = False
def __init__(self, connection):
"""Initialize"""
MySQLCursorAbstract.__init__(self)
self._insert_id = 0
self._warning_count = 0
self._warnings = None
self._affected_rows = -1
self._rowcount = -1
self._nextrow = None
self._executed = None
self._executed_list = []
self._stored_results = []
if not isinstance(connection, MySQLConnectionAbstract):
raise errors.InterfaceError(errno=2048)
self._cnx = weakref.proxy(connection)
def reset(self, free=True):
"""Reset the cursor
When free is True (default) the result will be freed.
"""
self._rowcount = -1
self._nextrow = None
self._affected_rows = -1
self._insert_id = 0
self._warning_count = 0
self._warnings = None
self._warnings = None
self._warning_count = 0
self._description = None
self._executed = None
self._executed_list = []
if free and self._cnx:
self._cnx.free_result()
super(CMySQLCursor, self).reset()
def _fetch_warnings(self):
"""Fetch warnings
Fetch warnings doing a SHOW WARNINGS. Can be called after getting
the result.
Returns a result set or None when there were no warnings.
Raises errors.Error (or subclass) on errors.
Returns list of tuples or None.
"""
warnings = []
try:
# force freeing result
self._cnx.consume_results()
_ = self._cnx.cmd_query("SHOW WARNINGS")
warnings = self._cnx.get_rows()
self._cnx.consume_results()
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
except Exception as err:
raise errors.InterfaceError(
"Failed getting warnings; {0}".format(str(err)))
if warnings:
return warnings
return None
def _handle_warnings(self):
"""Handle possible warnings after all results are consumed"""
if self._cnx.get_warnings is True and self._warning_count:
self._warnings = self._fetch_warnings()
def _handle_result(self, result):
"""Handles the result after statement execution"""
if 'columns' in result:
self._description = result['columns']
self._rowcount = 0
self._handle_resultset()
else:
self._insert_id = result['insert_id']
self._warning_count = result['warning_count']
self._affected_rows = result['affected_rows']
self._rowcount = -1
self._handle_warnings()
if self._cnx.raise_on_warnings is True and self._warnings:
raise errors.get_mysql_exception(*self._warnings[0][1:3])
def _handle_resultset(self):
"""Handle a result set"""
pass
def _handle_eof(self):
"""Handle end of reading the result
Raises an errors.Error on errors.
"""
self._warning_count = self._cnx.warning_count
self._handle_warnings()
if self._cnx.raise_on_warnings is True and self._warnings:
raise errors.get_mysql_exception(*self._warnings[0][1:3])
if not self._cnx.more_results:
self._cnx.free_result()
def _execute_iter(self):
"""Generator returns MySQLCursor objects for multiple statements
Deprecated: use nextset() method directly.
This method is only used when multiple statements are executed
by the execute() method. It uses zip() to make an iterator from the
given query_iter (result of MySQLConnection.cmd_query_iter()) and
the list of statements that were executed.
"""
executed_list = RE_SQL_SPLIT_STMTS.split(self._executed)
i = 0
self._executed = executed_list[i]
yield self
while True:
try:
if not self.nextset():
raise StopIteration
except errors.InterfaceError as exc:
# Result without result set
if exc.errno != CR_NO_RESULT_SET:
raise
i += 1
self._executed = executed_list[i].strip()
yield self
return
def execute(self, operation, params=(), multi=False):
"""Execute given statement using given parameters
Deprecated: The multi argument is not needed and nextset() should
be used to handle multiple result sets.
"""
if not operation:
return None
if not self._cnx:
raise errors.ProgrammingError("Cursor is not connected")
self._cnx.handle_unread_result()
stmt = ''
self.reset()
try:
if isunicode(operation):
stmt = operation.encode(self._cnx.python_charset)
else:
stmt = operation
except (UnicodeDecodeError, UnicodeEncodeError) as err:
raise errors.ProgrammingError(str(err))
if params:
prepared = self._cnx.prepare_for_mysql(params)
if isinstance(prepared, dict):
for key, value in prepared.items():
if PY2:
stmt = stmt.replace("%({0})s".format(key), value)
else:
stmt = stmt.replace("%({0})s".format(key).encode(),
value)
elif isinstance(prepared, (list, tuple)):
psub = _ParamSubstitutor(prepared)
stmt = RE_PY_PARAM.sub(psub, stmt)
if psub.remaining != 0:
raise errors.ProgrammingError(
"Not all parameters were used in the SQL statement")
try:
result = self._cnx.cmd_query(stmt, raw=self._raw,
buffered=self._buffered,
raw_as_string=self._raw_as_string)
except MySQLInterfaceError as exc:
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
sqlstate=exc.sqlstate)
self._executed = stmt
self._handle_result(result)
if multi:
return self._execute_iter()
return None
def _batch_insert(self, operation, seq_params):
"""Implements multi row insert"""
def remove_comments(match):
"""Remove comments from INSERT statements.
This function is used while removing comments from INSERT
statements. If the matched string is a comment not enclosed
by quotes, it returns an empty string, else the string itself.
"""
if match.group(1):
return ""
else:
return match.group(2)
tmp = re.sub(RE_SQL_ON_DUPLICATE, '',
re.sub(RE_SQL_COMMENT, remove_comments, operation))
matches = re.search(RE_SQL_INSERT_VALUES, tmp)
if not matches:
raise errors.InterfaceError(
"Failed rewriting statement for multi-row INSERT. "
"Check SQL syntax."
)
fmt = matches.group(1).encode(self._cnx.charset)
values = []
try:
stmt = operation.encode(self._cnx.charset)
for params in seq_params:
tmp = fmt
prepared = self._cnx.prepare_for_mysql(params)
if isinstance(prepared, dict):
for key, value in prepared.items():
tmp = tmp.replace("%({0})s".format(key).encode(), value)
elif isinstance(prepared, (list, tuple)):
psub = _ParamSubstitutor(prepared)
tmp = RE_PY_PARAM.sub(psub, tmp)
if psub.remaining != 0:
raise errors.ProgrammingError(
"Not all parameters were used in the SQL statement")
values.append(tmp)
if fmt in stmt:
stmt = stmt.replace(fmt, b','.join(values), 1)
self._executed = stmt
return stmt
else:
return None
except (UnicodeDecodeError, UnicodeEncodeError) as err:
raise errors.ProgrammingError(str(err))
except Exception as err:
raise errors.InterfaceError(
"Failed executing the operation; %s" % err)
def executemany(self, operation, seq_params):
"""Execute the given operation multiple times"""
if not operation or not seq_params:
return None
if not self._cnx:
raise errors.ProgrammingError("Cursor is not connected")
self._cnx.handle_unread_result()
if not isinstance(seq_params, (list, tuple)):
raise errors.ProgrammingError(
"Parameters for query must be list or tuple.")
# Optimize INSERTs by batching them
if re.match(RE_SQL_INSERT_STMT, operation):
if not seq_params:
self._rowcount = 0
return
stmt = self._batch_insert(operation, seq_params)
if stmt is not None:
return self.execute(stmt)
rowcnt = 0
try:
for params in seq_params:
self.execute(operation, params)
try:
while True:
if self._description:
rowcnt += len(self._cnx.get_rows())
else:
rowcnt += self._affected_rows
if not self.nextset():
break
except StopIteration:
# No more results
pass
except (ValueError, TypeError) as err:
raise errors.ProgrammingError(
"Failed executing the operation; {0}".format(err))
self._rowcount = rowcnt
@property
def description(self):
"""Returns description of columns in a result"""
return self._description
@property
def rowcount(self):
"""Returns the number of rows produced or affected"""
if self._rowcount == -1:
return self._affected_rows
else:
return self._rowcount
@property
def lastrowid(self):
"""Returns the value generated for an AUTO_INCREMENT column"""
return self._insert_id
def close(self):
"""Close the cursor
The result will be freed.
"""
if not self._cnx:
return False
self._cnx.handle_unread_result()
self._warnings = None
self._cnx = None
return True
def callproc(self, procname, args=()):
"""Calls a stored procedure with the given arguments"""
if not procname or not isinstance(procname, str):
raise ValueError("procname must be a string")
if not isinstance(args, (tuple, list)):
raise ValueError("args must be a sequence")
argfmt = "@_{name}_arg{index}"
self._stored_results = []
results = []
try:
argnames = []
argtypes = []
if args:
for idx, arg in enumerate(args):
argname = argfmt.format(name=procname, index=idx + 1)
argnames.append(argname)
if isinstance(arg, tuple):
argtypes.append(" CAST({0} AS {1})".format(argname,
arg[1]))
self.execute("SET {0}=%s".format(argname), (arg[0],))
else:
argtypes.append(argname)
self.execute("SET {0}=%s".format(argname), (arg,))
call = "CALL {0}({1})".format(procname, ','.join(argnames))
result = self._cnx.cmd_query(call, raw=self._raw,
raw_as_string=self._raw_as_string)
results = []
while self._cnx.result_set_available:
result = self._cnx.fetch_eof_columns()
# pylint: disable=W0212
if self._raw:
cur = CMySQLCursorBufferedRaw(self._cnx._get_self())
else:
cur = CMySQLCursorBuffered(self._cnx._get_self())
cur._executed = "(a result of {0})".format(call)
cur._handle_result(result)
# pylint: enable=W0212
results.append(cur)
self._cnx.next_result()
self._stored_results = results
self._handle_eof()
if argnames:
self.reset()
select = "SELECT {0}".format(','.join(argtypes))
self.execute(select)
return self.fetchone()
else:
return tuple()
except errors.Error:
raise
except Exception as err:
raise errors.InterfaceError(
"Failed calling stored routine; {0}".format(err))
def nextset(self):
"""Skip to the next available result set"""
if not self._cnx.next_result():
self.reset(free=True)
return None
self.reset(free=False)
if not self._cnx.result_set_available:
eof = self._cnx.fetch_eof_status()
self._handle_result(eof)
raise errors.InterfaceError(errno=CR_NO_RESULT_SET)
self._handle_result(self._cnx.fetch_eof_columns())
return True
def fetchall(self):
"""Returns all rows of a query result set
Returns a list of tuples.
"""
if not self._cnx.unread_result:
raise errors.InterfaceError("No result set to fetch from.")
rows = self._cnx.get_rows()
if self._nextrow:
rows.insert(0, self._nextrow)
if not rows:
self._handle_eof()
return []
self._rowcount += len(rows)
self._handle_eof()
return rows
def fetchmany(self, size=1):
"""Returns the next set of rows of a result set"""
if self._nextrow:
rows = [self._nextrow]
size -= 1
else:
rows = []
if size and self._cnx.unread_result:
rows.extend(self._cnx.get_rows(size))
if rows:
self._nextrow = self._cnx.get_row()
if not self._nextrow and not self._cnx.more_results:
self._cnx.free_result()
if not rows:
self._handle_eof()
return []
self._rowcount += len(rows)
return rows
def fetchone(self):
"""Returns next row of a query result set"""
row = self._nextrow
if not row and self._cnx.unread_result:
row = self._cnx.get_row()
if row:
self._nextrow = self._cnx.get_row()
if not self._nextrow and not self._cnx.more_results:
self._cnx.free_result()
else:
self._handle_eof()
return None
self._rowcount += 1
return row
def __iter__(self):
"""Iteration over the result set
Iteration over the result set which calls self.fetchone()
and returns the next row.
"""
return iter(self.fetchone, None)
def stored_results(self):
"""Returns an iterator for stored results
This method returns an iterator over results which are stored when
callproc() is called. The iterator will provide MySQLCursorBuffered
instances.
Returns a iterator.
"""
for i in range(len(self._stored_results)):
yield self._stored_results[i]
self._stored_results = []
if PY2:
def next(self):
"""Used for iterating over the result set."""
return self.__next__()
def __next__(self):
"""Iteration over the result set
Used for iterating over the result set. Calls self.fetchone()
to get the next row.
Raises StopIteration when no more rows are available.
"""
try:
row = self.fetchone()
except errors.InterfaceError:
raise StopIteration
if not row:
raise StopIteration
return row
@property
def column_names(self):
"""Returns column names
This property returns the columns names as a tuple.
Returns a tuple.
"""
if not self.description:
return ()
return tuple([d[0] for d in self.description])
@property
def statement(self):
"""Returns the executed statement
This property returns the executed statement. When multiple
statements were executed, the current statement in the iterator
will be returned.
"""
try:
return self._executed.strip().decode('utf8')
except AttributeError:
return self._executed.strip()
@property
def with_rows(self):
"""Returns whether the cursor could have rows returned
This property returns True when column descriptions are available
and possibly also rows, which will need to be fetched.
Returns True or False.
"""
if self.description:
return True
return False
def __str__(self):
fmt = "{class_name}: {stmt}"
if self._executed:
try:
executed = self._executed.decode('utf-8')
except AttributeError:
executed = self._executed
if len(executed) > 40:
executed = executed[:40] + '..'
else:
executed = '(Nothing executed yet)'
return fmt.format(class_name=self.__class__.__name__, stmt=executed)
class CMySQLCursorBuffered(CMySQLCursor):
"""Cursor using C Extension buffering results"""
def __init__(self, connection):
"""Initialize"""
super(CMySQLCursorBuffered, self).__init__(connection)
self._rows = None
self._next_row = 0
def _handle_resultset(self):
"""Handle a result set"""
self._rows = self._cnx.get_rows()
self._next_row = 0
self._rowcount = len(self._rows)
self._handle_eof()
def reset(self, free=True):
"""Reset the cursor to default"""
self._rows = None
self._next_row = 0
super(CMySQLCursorBuffered, self).reset(free=free)
def _fetch_row(self):
"""Returns the next row in the result set
Returns a tuple or None.
"""
row = None
try:
row = self._rows[self._next_row]
except IndexError:
return None
else:
self._next_row += 1
return row
def fetchall(self):
if self._rows is None:
raise errors.InterfaceError("No result set to fetch from.")
res = self._rows[self._next_row:]
self._next_row = len(self._rows)
return res
def fetchmany(self, size=1):
res = []
cnt = size or self.arraysize
while cnt > 0:
cnt -= 1
row = self._fetch_row()
if row:
res.append(row)
else:
break
return res
def fetchone(self):
return self._fetch_row()
class CMySQLCursorRaw(CMySQLCursor):
"""Cursor using C Extension return raw results"""
_raw = True
class CMySQLCursorBufferedRaw(CMySQLCursorBuffered):
"""Cursor using C Extension buffering raw results"""
_raw = True
class CMySQLCursorDict(CMySQLCursor):
"""Cursor using C Extension returning rows as dictionaries"""
_raw = False
def fetchone(self):
"""Returns all rows of a query result set
"""
row = super(CMySQLCursorDict, self).fetchone()
if row:
return dict(zip(self.column_names, row))
else:
return None
def fetchmany(self, size=1):
"""Returns next set of rows as list of dictionaries"""
res = super(CMySQLCursorDict, self).fetchmany(size=size)
return [dict(zip(self.column_names, row)) for row in res]
def fetchall(self):
"""Returns all rows of a query result set as list of dictionaries"""
res = super(CMySQLCursorDict, self).fetchall()
return [dict(zip(self.column_names, row)) for row in res]
class CMySQLCursorBufferedDict(CMySQLCursorBuffered):
"""Cursor using C Extension buffering and returning rows as dictionaries"""
_raw = False
def _fetch_row(self):
row = super(CMySQLCursorBufferedDict, self)._fetch_row()
if row:
return dict(zip(self.column_names, row))
else:
return None
def fetchall(self):
res = super(CMySQLCursorBufferedDict, self).fetchall()
return [dict(zip(self.column_names, row)) for row in res]
class CMySQLCursorNamedTuple(CMySQLCursor):
"""Cursor using C Extension returning rows as named tuples"""
def _handle_resultset(self):
"""Handle a result set"""
super(CMySQLCursorNamedTuple, self)._handle_resultset()
# pylint: disable=W0201
self.named_tuple = namedtuple('Row', self.column_names)
# pylint: enable=W0201
def fetchone(self):
"""Returns all rows of a query result set
"""
row = super(CMySQLCursorNamedTuple, self).fetchone()
if row:
return self.named_tuple(*row)
else:
return None
def fetchmany(self, size=1):
"""Returns next set of rows as list of named tuples"""
res = super(CMySQLCursorNamedTuple, self).fetchmany(size=size)
return [self.named_tuple(*row) for row in res]
def fetchall(self):
"""Returns all rows of a query result set as list of named tuples"""
res = super(CMySQLCursorNamedTuple, self).fetchall()
return [self.named_tuple(*row) for row in res]
class CMySQLCursorBufferedNamedTuple(CMySQLCursorBuffered):
"""Cursor using C Extension buffering and returning rows as named tuples"""
def _handle_resultset(self):
super(CMySQLCursorBufferedNamedTuple, self)._handle_resultset()
# pylint: disable=W0201
self.named_tuple = namedtuple('Row', self.column_names)
# pylint: enable=W0201
def _fetch_row(self):
row = super(CMySQLCursorBufferedNamedTuple, self)._fetch_row()
if row:
return self.named_tuple(*row)
else:
return None
def fetchall(self):
res = super(CMySQLCursorBufferedNamedTuple, self).fetchall()
return [self.named_tuple(*row) for row in res]
class CMySQLCursorPrepared(CMySQLCursor):
"""Cursor using Prepare Statement
"""
def __init__(self, connection):
super(CMySQLCursorPrepared, self).__init__(connection)
raise NotImplementedError(
"Alternative: Use connection.MySQLCursorPrepared")

View file

@ -0,0 +1,45 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Custom Python types used by MySQL Connector/Python"""
import sys
class HexLiteral(str):
"""Class holding MySQL hex literals"""
def __new__(cls, str_, charset='utf8'):
if sys.version_info[0] == 2:
hexed = ["%02x" % ord(i) for i in str_.encode(charset)]
else:
hexed = ["%02x" % i for i in str_.encode(charset)]
obj = str.__new__(cls, ''.join(hexed))
obj.charset = charset
obj.original = str_
return obj
def __str__(self):
return '0x' + self

View file

@ -0,0 +1,75 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
This module implements some constructors and singletons as required by the
DB API v2.0 (PEP-249).
"""
# Python Db API v2
apilevel = '2.0'
threadsafety = 1
paramstyle = 'pyformat'
import time
import datetime
from . import constants
class _DBAPITypeObject(object):
def __init__(self, *values):
self.values = values
def __eq__(self, other):
if other in self.values:
return True
else:
return False
def __ne__(self, other):
if other in self.values:
return False
else:
return True
Date = datetime.date
Time = datetime.time
Timestamp = datetime.datetime
def DateFromTicks(ticks):
return Date(*time.localtime(ticks)[:3])
def TimeFromTicks(ticks):
return Time(*time.localtime(ticks)[3:6])
def TimestampFromTicks(ticks):
return Timestamp(*time.localtime(ticks)[:6])
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())
ROWID = _DBAPITypeObject()

View file

@ -0,0 +1,567 @@
# MySQL Connector/Python - MySQL driver written in Python.
"""Django database Backend using MySQL Connector/Python
This Django database backend is heavily based on the MySQL backend coming
with Django.
Changes include:
* Support for microseconds (MySQL 5.6.3 and later)
* Using INFORMATION_SCHEMA where possible
* Using new defaults for, for example SQL_AUTO_IS_NULL
Requires and comes with MySQL Connector/Python v1.1 and later:
http://dev.mysql.com/downloads/connector/python/
"""
from __future__ import unicode_literals
from datetime import datetime
import sys
import warnings
import django
from django.utils.functional import cached_property
try:
import mysql.connector
from mysql.connector.conversion import MySQLConverter, MySQLConverterBase
from mysql.connector.catch23 import PY2
except ImportError as err:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured(
"Error loading mysql.connector module: {0}".format(err))
try:
version = mysql.connector.__version_info__[0:3]
except AttributeError:
from mysql.connector.version import VERSION
version = VERSION[0:3]
try:
from _mysql_connector import datetime_to_mysql, time_to_mysql
except ImportError:
HAVE_CEXT = False
else:
HAVE_CEXT = True
if version < (1, 1):
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured(
"MySQL Connector/Python v1.1.0 or newer "
"is required; you have %s" % mysql.connector.__version__)
from django.db import utils
if django.VERSION < (1, 7):
from django.db.backends import util
else:
from django.db.backends import utils as backend_utils
if django.VERSION >= (1, 8):
from django.db.backends.base.base import BaseDatabaseWrapper
else:
from django.db.backends import BaseDatabaseWrapper
from django.db.backends.signals import connection_created
from django.utils import (six, timezone, dateparse)
from django.conf import settings
from mysql.connector.django.client import DatabaseClient
from mysql.connector.django.creation import DatabaseCreation
from mysql.connector.django.introspection import DatabaseIntrospection
from mysql.connector.django.validation import DatabaseValidation
from mysql.connector.django.features import DatabaseFeatures
from mysql.connector.django.operations import DatabaseOperations
if django.VERSION >= (1, 7):
from mysql.connector.django.schema import DatabaseSchemaEditor
DatabaseError = mysql.connector.DatabaseError
IntegrityError = mysql.connector.IntegrityError
NotSupportedError = mysql.connector.NotSupportedError
def adapt_datetime_with_timezone_support(value):
# Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
if settings.USE_TZ:
if timezone.is_naive(value):
warnings.warn("MySQL received a naive datetime (%s)"
" while time zone support is active." % value,
RuntimeWarning)
default_timezone = timezone.get_default_timezone()
value = timezone.make_aware(value, default_timezone)
value = value.astimezone(timezone.utc).replace(tzinfo=None)
if HAVE_CEXT:
return datetime_to_mysql(value)
else:
return value.strftime("%Y-%m-%d %H:%M:%S.%f")
class DjangoMySQLConverter(MySQLConverter):
"""Custom converter for Django for MySQLConnection"""
def _TIME_to_python(self, value, dsc=None):
"""Return MySQL TIME data type as datetime.time()
Returns datetime.time()
"""
return dateparse.parse_time(value.decode('utf-8'))
def _DATETIME_to_python(self, value, dsc=None):
"""Connector/Python always returns naive datetime.datetime
Connector/Python always returns naive timestamps since MySQL has
no time zone support. Since Django needs non-naive, we need to add
the UTC time zone.
Returns datetime.datetime()
"""
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):
if PY2:
return self._unicode_to_mysql(value)
else:
return self._str_to_mysql(value)
def _safebytes_to_mysql(self, value):
return self._bytes_to_mysql(value)
class DjangoCMySQLConverter(MySQLConverterBase):
"""Custom converter for Django for CMySQLConnection"""
def _TIME_to_python(self, value, dsc=None):
"""Return MySQL TIME data type as datetime.time()
Returns datetime.time()
"""
return dateparse.parse_time(str(value))
def _DATETIME_to_python(self, value, dsc=None):
"""Connector/Python always returns naive datetime.datetime
Connector/Python always returns naive timestamps since MySQL has
no time zone support. Since Django needs non-naive, we need to add
the UTC time zone.
Returns datetime.datetime()
"""
if not value:
return None
if settings.USE_TZ and timezone.is_naive(value):
value = value.replace(tzinfo=timezone.utc)
return value
class CursorWrapper(object):
"""Wrapper around MySQL Connector/Python's cursor class.
The cursor class is defined by the options passed to MySQL
Connector/Python. If buffered option is True in those options,
MySQLCursorBuffered will be used.
"""
codes_for_integrityerror = (1048,)
def __init__(self, cursor):
self.cursor = cursor
def _execute_wrapper(self, method, query, args):
"""Wrapper around execute() and executemany()"""
try:
return method(query, args)
except (mysql.connector.ProgrammingError) as err:
six.reraise(utils.ProgrammingError,
utils.ProgrammingError(err.msg), sys.exc_info()[2])
except (mysql.connector.IntegrityError) as err:
six.reraise(utils.IntegrityError,
utils.IntegrityError(err.msg), sys.exc_info()[2])
except mysql.connector.OperationalError as err:
# Map some error codes to IntegrityError, since they seem to be
# misclassified and Django would prefer the more logical place.
if err.args[0] in self.codes_for_integrityerror:
six.reraise(utils.IntegrityError,
utils.IntegrityError(err.msg), sys.exc_info()[2])
else:
six.reraise(utils.DatabaseError,
utils.DatabaseError(err.msg), sys.exc_info()[2])
except mysql.connector.DatabaseError as err:
six.reraise(utils.DatabaseError,
utils.DatabaseError(err.msg), sys.exc_info()[2])
def _adapt_execute_args_dict(self, args):
if not args:
return args
new_args = dict(args)
for key, value in args.items():
if isinstance(value, datetime):
new_args[key] = adapt_datetime_with_timezone_support(value)
return new_args
def _adapt_execute_args(self, args):
if not args:
return args
new_args = list(args)
for i, arg in enumerate(args):
if isinstance(arg, datetime):
new_args[i] = adapt_datetime_with_timezone_support(arg)
return tuple(new_args)
def execute(self, query, args=None):
"""Executes the given operation
This wrapper method around the execute()-method of the cursor is
mainly needed to re-raise using different exceptions.
"""
if isinstance(args, dict):
new_args = self._adapt_execute_args_dict(args)
else:
new_args = self._adapt_execute_args(args)
return self._execute_wrapper(self.cursor.execute, query, new_args)
def executemany(self, query, args):
"""Executes the given operation
This wrapper method around the executemany()-method of the cursor is
mainly needed to re-raise using different exceptions.
"""
return self._execute_wrapper(self.cursor.executemany, query, args)
def __getattr__(self, attr):
"""Return attribute of wrapped cursor"""
return getattr(self.cursor, attr)
def __iter__(self):
"""Returns iterator over wrapped cursor"""
return iter(self.cursor)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
self.close()
class DatabaseWrapper(BaseDatabaseWrapper):
vendor = 'mysql'
# This dictionary maps Field objects to their associated MySQL column
# types, as strings. Column-type strings can contain format strings; they'll
# be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
# Moved from DatabaseCreation class in Django v1.8
_data_types = {
'AutoField': 'integer AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
}
@cached_property
def data_types(self):
if self.features.supports_microsecond_precision:
return dict(self._data_types, DateTimeField='datetime(6)',
TimeField='time(6)')
else:
return self._data_types
operators = {
'exact': '= %s',
'iexact': 'LIKE %s',
'contains': 'LIKE BINARY %s',
'icontains': 'LIKE %s',
'regex': 'REGEXP BINARY %s',
'iregex': 'REGEXP %s',
'gt': '> %s',
'gte': '>= %s',
'lt': '< %s',
'lte': '<= %s',
'startswith': 'LIKE BINARY %s',
'endswith': 'LIKE BINARY %s',
'istartswith': 'LIKE %s',
'iendswith': 'LIKE %s',
}
# The patterns below are used to generate SQL pattern lookup clauses when
# the right-hand side of the lookup isn't a raw string (it might be an
# expression or the result of a bilateral transformation).
# In those cases, special characters for LIKE operators (e.g. \, *, _)
# should be escaped on database side.
#
# Note: we use str.format() here for readability as '%' is used as a
# wildcard for the LIKE operator.
pattern_esc = (r"REPLACE(REPLACE(REPLACE({}, '\\', '\\\\'),"
r" '%%', '\%%'), '_', '\_')")
pattern_ops = {
'contains': "LIKE BINARY CONCAT('%%', {}, '%%')",
'icontains': "LIKE CONCAT('%%', {}, '%%')",
'startswith': "LIKE BINARY CONCAT({}, '%%')",
'istartswith': "LIKE CONCAT({}, '%%')",
'endswith': "LIKE BINARY CONCAT('%%', {})",
'iendswith': "LIKE CONCAT('%%', {})",
}
SchemaEditorClass = DatabaseSchemaEditor
Database = mysql.connector
def __init__(self, *args, **kwargs):
super(DatabaseWrapper, self).__init__(*args, **kwargs)
try:
self._use_pure = self.settings_dict['OPTIONS']['use_pure']
except KeyError:
self._use_pure = True
if not self.use_pure:
self.converter = DjangoCMySQLConverter()
else:
self.converter = DjangoMySQLConverter()
self.ops = DatabaseOperations(self)
self.features = DatabaseFeatures(self)
self.client = DatabaseClient(self)
self.creation = DatabaseCreation(self)
self.introspection = DatabaseIntrospection(self)
self.validation = DatabaseValidation(self)
def _valid_connection(self):
if self.connection:
return self.connection.is_connected()
return False
def get_connection_params(self):
# Django 1.6
kwargs = {
'charset': 'utf8',
'use_unicode': True,
'buffered': False,
'consume_results': True,
}
settings_dict = self.settings_dict
if settings_dict['USER']:
kwargs['user'] = settings_dict['USER']
if settings_dict['NAME']:
kwargs['database'] = settings_dict['NAME']
if settings_dict['PASSWORD']:
kwargs['passwd'] = settings_dict['PASSWORD']
if settings_dict['HOST'].startswith('/'):
kwargs['unix_socket'] = settings_dict['HOST']
elif settings_dict['HOST']:
kwargs['host'] = settings_dict['HOST']
if settings_dict['PORT']:
kwargs['port'] = int(settings_dict['PORT'])
# Raise exceptions for database warnings if DEBUG is on
kwargs['raise_on_warnings'] = settings.DEBUG
kwargs['client_flags'] = [
# Need potentially affected rows on UPDATE
mysql.connector.constants.ClientFlag.FOUND_ROWS,
]
try:
kwargs.update(settings_dict['OPTIONS'])
except KeyError:
# OPTIONS missing is OK
pass
return kwargs
def get_new_connection(self, conn_params):
# Django 1.6
if not self.use_pure:
conn_params['converter_class'] = DjangoCMySQLConverter
else:
conn_params['converter_class'] = DjangoMySQLConverter
cnx = mysql.connector.connect(**conn_params)
return cnx
def init_connection_state(self):
# Django 1.6
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")
if 'AUTOCOMMIT' in self.settings_dict:
try:
# Django 1.6
self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
except AttributeError:
self._set_autocommit(self.settings_dict['AUTOCOMMIT'])
def create_cursor(self):
# Django 1.6
cursor = self.connection.cursor()
return CursorWrapper(cursor)
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):
"""Return a CursorWrapper object
Returns a CursorWrapper
"""
try:
# Django 1.6
return super(DatabaseWrapper, self)._cursor()
except AttributeError:
if not self.connection:
self._connect()
return self.create_cursor()
def get_server_version(self):
"""Returns the MySQL server version of current connection
Returns a tuple
"""
try:
# Django 1.6
self.ensure_connection()
except AttributeError:
if not self.connection:
self._connect()
return self.connection.get_server_version()
def disable_constraint_checking(self):
"""Disables foreign key checks
Disables foreign key checks, primarily for use in adding rows with
forward references. Always returns True,
to indicate constraint checks need to be re-enabled.
Returns True
"""
self.cursor().execute('SET @@session.foreign_key_checks = 0')
return True
def enable_constraint_checking(self):
"""Re-enable foreign key checks
Re-enable foreign key checks after they have been disabled.
"""
# Override needs_rollback in case constraint_checks_disabled is
# nested inside transaction.atomic.
if django.VERSION >= (1, 6):
self.needs_rollback, needs_rollback = False, self.needs_rollback
try:
self.cursor().execute('SET @@session.foreign_key_checks = 1')
finally:
if django.VERSION >= (1, 6):
self.needs_rollback = needs_rollback
def check_constraints(self, table_names=None):
"""Check rows in tables for invalid foreign key references
Checks each table name in `table_names` for rows with invalid foreign
key references. This method is intended to be used in conjunction with
`disable_constraint_checking()` and `enable_constraint_checking()`, to
determine if rows with invalid references were entered while
constraint checks were off.
Raises an IntegrityError on the first invalid foreign key reference
encountered (if any) and provides detailed information about the
invalid reference in the error message.
Backends can override this method if they can more directly apply
constraint checking (e.g. via "SET CONSTRAINTS ALL IMMEDIATE")
"""
ref_query = """
SELECT REFERRING.`{0}`, REFERRING.`{1}` FROM `{2}` as REFERRING
LEFT JOIN `{3}` as REFERRED
ON (REFERRING.`{4}` = REFERRED.`{5}`)
WHERE REFERRING.`{6}` IS NOT NULL AND REFERRED.`{7}` IS NULL"""
cursor = self.cursor()
if table_names is None:
table_names = self.introspection.table_names(cursor)
for table_name in table_names:
primary_key_column_name = \
self.introspection.get_primary_key_column(cursor, table_name)
if not primary_key_column_name:
continue
key_columns = self.introspection.get_key_columns(cursor,
table_name)
for column_name, referenced_table_name, referenced_column_name \
in key_columns:
cursor.execute(ref_query.format(primary_key_column_name,
column_name, table_name,
referenced_table_name,
column_name,
referenced_column_name,
column_name,
referenced_column_name))
for bad_row in cursor.fetchall():
msg = ("The row in table '{0}' with primary key '{1}' has "
"an invalid foreign key: {2}.{3} contains a value "
"'{4}' that does not have a corresponding value in "
"{5}.{6}.".format(table_name, bad_row[0],
table_name, column_name,
bad_row[1], referenced_table_name,
referenced_column_name))
raise utils.IntegrityError(msg)
def _rollback(self):
try:
BaseDatabaseWrapper._rollback(self)
except NotSupportedError:
pass
def _set_autocommit(self, autocommit):
# Django 1.6
with self.wrap_database_errors:
self.connection.autocommit = autocommit
def schema_editor(self, *args, **kwargs):
"""Returns a new instance of this backend's SchemaEditor"""
# Django 1.7
return DatabaseSchemaEditor(self, *args, **kwargs)
def is_usable(self):
# Django 1.6
return self.connection.is_connected()
@cached_property
def mysql_version(self):
config = self.get_connection_params()
temp_conn = mysql.connector.connect(**config)
server_version = temp_conn.get_server_version()
temp_conn.close()
return server_version
@property
def use_pure(self):
return not HAVE_CEXT or self._use_pure

View file

@ -0,0 +1,57 @@
# MySQL Connector/Python - MySQL driver written in Python.
import django
import subprocess
if django.VERSION >= (1, 8):
from django.db.backends.base.client import BaseDatabaseClient
else:
from django.db.backends import BaseDatabaseClient
class DatabaseClient(BaseDatabaseClient):
executable_name = 'mysql'
@classmethod
def settings_to_cmd_args(cls, settings_dict):
args = [cls.executable_name]
db = settings_dict['OPTIONS'].get('database', settings_dict['NAME'])
user = settings_dict['OPTIONS'].get('user',
settings_dict['USER'])
passwd = settings_dict['OPTIONS'].get('password',
settings_dict['PASSWORD'])
host = settings_dict['OPTIONS'].get('host', settings_dict['HOST'])
port = settings_dict['OPTIONS'].get('port', settings_dict['PORT'])
defaults_file = settings_dict['OPTIONS'].get('read_default_file')
# --defaults-file should always be the first option
if defaults_file:
args.append("--defaults-file={0}".format(defaults_file))
# We force SQL_MODE to TRADITIONAL
args.append("--init-command=SET @@session.SQL_MODE=TRADITIONAL")
if user:
args.append("--user={0}".format(user))
if passwd:
args.append("--password={0}".format(passwd))
if host:
if '/' in host:
args.append("--socket={0}".format(host))
else:
args.append("--host={0}".format(host))
if port:
args.append("--port={0}".format(port))
if db:
args.append("--database={0}".format(db))
return args
def runshell(self):
args = DatabaseClient.settings_to_cmd_args(
self.connection.settings_dict)
subprocess.call(args)

View file

@ -0,0 +1,59 @@
# MySQL Connector/Python - MySQL driver written in Python.
import django
from django.db.models.sql import compiler
from django.utils.six.moves import zip_longest
class SQLCompiler(compiler.SQLCompiler):
def resolve_columns(self, row, fields=()):
values = []
index_extra_select = len(self.query.extra_select)
bool_fields = ("BooleanField", "NullBooleanField")
for value, field in zip_longest(row[index_extra_select:], fields):
if (field and field.get_internal_type() in bool_fields and
value in (0, 1)):
value = bool(value)
values.append(value)
return row[:index_extra_select] + tuple(values)
if django.VERSION >= (1, 8):
def as_subquery_condition(self, alias, columns, compiler):
qn = compiler.quote_name_unless_alias
qn2 = self.connection.ops.quote_name
sql, params = self.as_sql()
return '(%s) IN (%s)' % (', '.join('%s.%s' % (qn(alias), qn2(column)) for column in columns), sql), params
else:
def as_subquery_condition(self, alias, columns, qn):
# Django 1.6
qn2 = self.connection.ops.quote_name
sql, params = self.as_sql()
column_list = ', '.join(
['%s.%s' % (qn(alias), qn2(column)) for column in columns])
return '({0}) IN ({1})'.format(column_list, sql), params
class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
pass
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
pass
class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
pass
class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler):
pass
if django.VERSION < (1, 8):
class SQLDateCompiler(compiler.SQLDateCompiler, SQLCompiler):
pass
if django.VERSION >= (1, 6):
class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, SQLCompiler):
# Django 1.6
pass

View file

@ -0,0 +1,141 @@
# MySQL Connector/Python - MySQL driver written in Python.
import django
from django.db import models
if django.VERSION >= (1, 8):
from django.db.backends.base.creation import BaseDatabaseCreation
else:
from django.db.backends.creation import BaseDatabaseCreation
if django.VERSION < (1, 7):
from django.db.backends.util import truncate_name
else:
from django.db.backends.utils import truncate_name
class DatabaseCreation(BaseDatabaseCreation):
"""Maps Django Field object with MySQL data types
"""
def __init__(self, connection):
super(DatabaseCreation, self).__init__(connection)
if django.VERSION < (1, 8):
self.data_types = {
'AutoField': 'integer AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime', # ms support set later
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time', # ms support set later
}
# Support for microseconds
if self.connection.mysql_version >= (5, 6, 4):
self.data_types.update({
'DateTimeField': 'datetime(6)',
'TimeField': 'time(6)',
})
def sql_table_creation_suffix(self):
suffix = []
if django.VERSION < (1, 7):
if self.connection.settings_dict['TEST_CHARSET']:
suffix.append('CHARACTER SET {0}'.format(
self.connection.settings_dict['TEST_CHARSET']))
if self.connection.settings_dict['TEST_COLLATION']:
suffix.append('COLLATE {0}'.format(
self.connection.settings_dict['TEST_COLLATION']))
else:
test_settings = self.connection.settings_dict['TEST']
if test_settings['CHARSET']:
suffix.append('CHARACTER SET %s' % test_settings['CHARSET'])
if test_settings['COLLATION']:
suffix.append('COLLATE %s' % test_settings['COLLATION'])
return ' '.join(suffix)
if django.VERSION < (1, 6):
def sql_for_inline_foreign_key_references(self, field, known_models,
style):
"All inline references are pending under MySQL"
return [], True
else:
def sql_for_inline_foreign_key_references(self, model, field,
known_models, style):
"All inline references are pending under MySQL"
return [], True
def sql_for_inline_many_to_many_references(self, model, field, style):
opts = model._meta
qn = self.connection.ops.quote_name
columndef = ' {column} {type} {options},'
table_output = [
columndef.format(
column=style.SQL_FIELD(qn(field.m2m_column_name())),
type=style.SQL_COLTYPE(models.ForeignKey(model).db_type(
connection=self.connection)),
options=style.SQL_KEYWORD('NOT NULL')
),
columndef.format(
column=style.SQL_FIELD(qn(field.m2m_reverse_name())),
type=style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type(
connection=self.connection)),
options=style.SQL_KEYWORD('NOT NULL')
),
]
deferred = [
(field.m2m_db_table(), field.m2m_column_name(), opts.db_table,
opts.pk.column),
(field.m2m_db_table(), field.m2m_reverse_name(),
field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
]
return table_output, deferred
def sql_destroy_indexes_for_fields(self, model, fields, style):
# Django 1.6
if len(fields) == 1 and fields[0].db_tablespace:
tablespace_sql = self.connection.ops.tablespace_sql(
fields[0].db_tablespace)
elif model._meta.db_tablespace:
tablespace_sql = self.connection.ops.tablespace_sql(
model._meta.db_tablespace)
else:
tablespace_sql = ""
if tablespace_sql:
tablespace_sql = " " + tablespace_sql
field_names = []
qn = self.connection.ops.quote_name
for f in fields:
field_names.append(style.SQL_FIELD(qn(f.column)))
index_name = "{0}_{1}".format(model._meta.db_table,
self._digest([f.name for f in fields]))
return [
style.SQL_KEYWORD("DROP INDEX") + " " +
style.SQL_TABLE(qn(truncate_name(index_name,
self.connection.ops.max_name_length()))) + " " +
style.SQL_KEYWORD("ON") + " " +
style.SQL_TABLE(qn(model._meta.db_table)) + ";",
]

View file

@ -0,0 +1,127 @@
# MySQL Connector/Python - MySQL driver written in Python.
# New file added for Django 1.8
import django
if django.VERSION >= (1, 8):
from django.db.backends.base.features import BaseDatabaseFeatures
else:
from django.db.backends import BaseDatabaseFeatures
from django.utils.functional import cached_property
from django.utils import six
try:
import pytz
HAVE_PYTZ = True
except ImportError:
HAVE_PYTZ = False
class DatabaseFeatures(BaseDatabaseFeatures):
"""Features specific to MySQL
Microsecond precision is supported since MySQL 5.6.3 and turned on
by default if this MySQL version is used.
"""
empty_fetchmany_value = []
update_can_self_select = False
allows_group_by_pk = True
related_fields_match_type = True
allow_sliced_subqueries = False
has_bulk_insert = True
has_select_for_update = True
has_select_for_update_nowait = False
supports_forward_references = False
supports_regex_backreferencing = False
supports_date_lookup_using_string = False
can_introspect_autofield = True
can_introspect_binary_field = False
can_introspect_small_integer_field = True
supports_timezones = False
requires_explicit_null_ordering_when_grouping = True
allows_auto_pk_0 = False
allows_primary_key_0 = False
uses_savepoints = True
atomic_transactions = False
supports_column_check_constraints = False
if django.VERSION < (1, 8):
supports_long_model_names = False
supports_binary_field = six.PY2
can_introspect_boolean_field = False
def __init__(self, connection):
super(DatabaseFeatures, self).__init__(connection)
@cached_property
def supports_microsecond_precision(self):
if self.connection.mysql_version >= (5, 6, 3):
return True
return False
@cached_property
def mysql_storage_engine(self):
"""Get default storage engine of MySQL
This method creates a table without ENGINE table option and inspects
which engine was used.
Used by Django tests.
"""
tblname = 'INTROSPECT_TEST'
droptable = 'DROP TABLE IF EXISTS {table}'.format(table=tblname)
with self.connection.cursor() as cursor:
cursor.execute(droptable)
cursor.execute('CREATE TABLE {table} (X INT)'.format(table=tblname))
if self.connection.mysql_version >= (5, 0, 0):
cursor.execute(
"SELECT ENGINE FROM INFORMATION_SCHEMA.TABLES "
"WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s",
(self.connection.settings_dict['NAME'], tblname))
engine = cursor.fetchone()[0]
else:
# Very old MySQL servers..
cursor.execute("SHOW TABLE STATUS WHERE Name='{table}'".format(
table=tblname))
engine = cursor.fetchone()[1]
cursor.execute(droptable)
self._cached_storage_engine = engine
return engine
@cached_property
def _disabled_supports_transactions(self):
return self.mysql_storage_engine == 'InnoDB'
@cached_property
def can_introspect_foreign_keys(self):
"""Confirm support for introspected foreign keys
Only the InnoDB storage engine supports Foreigen Key (not taking
into account MySQL Cluster here).
"""
return self.mysql_storage_engine == 'InnoDB'
@cached_property
def has_zoneinfo_database(self):
"""Tests if the time zone definitions are installed
MySQL accepts full time zones names (eg. Africa/Nairobi) but rejects
abbreviations (eg. EAT). When pytz isn't installed and the current
time zone is LocalTimezone (the only sensible value in this context),
the current time zone name will be an abbreviation. As a consequence,
MySQL cannot perform time zone conversions reliably.
"""
# Django 1.6
if not HAVE_PYTZ:
return False
with self.connection.cursor() as cursor:
cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1")
return cursor.fetchall() != []
def introspected_boolean_field_type(self, *args, **kwargs):
# New in Django 1.8
return 'IntegerField'

View file

@ -0,0 +1,322 @@
# MySQL Connector/Python - MySQL driver written in Python.
import re
from collections import namedtuple
import django
if django.VERSION >= (1, 8):
from django.db.backends.base.introspection import (
BaseDatabaseIntrospection, FieldInfo, TableInfo
)
else:
from django.db.backends import BaseDatabaseIntrospection
if django.VERSION >= (1, 6):
if django.VERSION < (1, 8):
from django.db.backends import FieldInfo
from django.utils.encoding import force_text
if django.VERSION >= (1, 7):
from django.utils.datastructures import OrderedSet
from mysql.connector.constants import FieldType
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) "
r"REFERENCES `([^`]*)` \(`([^`]*)`\)")
if django.VERSION >= (1, 8):
FieldInfo = namedtuple('FieldInfo', FieldInfo._fields + ('extra',))
class DatabaseIntrospection(BaseDatabaseIntrospection):
data_types_reverse = {
FieldType.BLOB: 'TextField',
FieldType.DECIMAL: 'DecimalField',
FieldType.NEWDECIMAL: 'DecimalField',
FieldType.DATE: 'DateField',
FieldType.DATETIME: 'DateTimeField',
FieldType.DOUBLE: 'FloatField',
FieldType.FLOAT: 'FloatField',
FieldType.INT24: 'IntegerField',
FieldType.LONG: 'IntegerField',
FieldType.LONGLONG: 'BigIntegerField',
FieldType.SHORT: (
'IntegerField' if django.VERSION < (1, 8) else 'SmallIntegerField'
),
FieldType.STRING: 'CharField',
FieldType.TIME: 'TimeField',
FieldType.TIMESTAMP: 'DateTimeField',
FieldType.TINY: 'IntegerField',
FieldType.TINY_BLOB: 'TextField',
FieldType.MEDIUM_BLOB: 'TextField',
FieldType.LONG_BLOB: 'TextField',
FieldType.VAR_STRING: 'CharField',
}
def get_field_type(self, data_type, description):
field_type = super(DatabaseIntrospection, self).get_field_type(
data_type, description)
if (field_type == 'IntegerField'
and 'auto_increment' in description.extra):
return 'AutoField'
return field_type
def get_table_list(self, cursor):
"""Returns a list of table names in the current database."""
cursor.execute("SHOW FULL TABLES")
if django.VERSION >= (1, 8):
return [
TableInfo(row[0], {'BASE TABLE': 't', 'VIEW': 'v'}.get(row[1]))
for row in cursor.fetchall()
]
else:
return [row[0] for row in cursor.fetchall()]
if django.VERSION >= (1, 8):
def get_table_description(self, cursor, table_name):
"""
Returns a description of the table, with the DB-API
cursor.description interface."
"""
# - information_schema database gives more accurate results for
# some figures:
# - varchar length returned by cursor.description is an internal
# length, not visible length (#5725)
# - precision and scale (for decimal fields) (#5014)
# - auto_increment is not available in cursor.description
InfoLine = namedtuple(
'InfoLine',
'col_name data_type max_len num_prec num_scale extra'
)
cursor.execute("""
SELECT column_name, data_type, character_maximum_length,
numeric_precision, numeric_scale, extra
FROM information_schema.columns
WHERE table_name = %s AND table_schema = DATABASE()""",
[table_name])
field_info = dict(
(line[0], InfoLine(*line)) for line in cursor.fetchall()
)
cursor.execute("SELECT * FROM %s LIMIT 1"
% self.connection.ops.quote_name(table_name))
to_int = lambda i: int(i) if i is not None else i
fields = []
for line in cursor.description:
col_name = force_text(line[0])
fields.append(
FieldInfo(*((col_name,)
+ line[1:3]
+ (to_int(field_info[col_name].max_len)
or line[3],
to_int(field_info[col_name].num_prec)
or line[4],
to_int(field_info[col_name].num_scale)
or line[5])
+ (line[6],)
+ (field_info[col_name].extra,)))
)
return fields
else:
def get_table_description(self, cursor, table_name):
"""
Returns a description of the table, with the DB-API
cursor.description interface.
"""
# varchar length returned by cursor.description is an internal
# length not visible length (#5725), use information_schema database
# to fix this
cursor.execute(
"SELECT column_name, character_maximum_length "
"FROM INFORMATION_SCHEMA.COLUMNS "
"WHERE table_name = %s AND table_schema = DATABASE() "
"AND character_maximum_length IS NOT NULL", [table_name])
length_map = dict(cursor.fetchall())
# Also getting precision and scale from
# information_schema (see #5014)
cursor.execute(
"SELECT column_name, numeric_precision, numeric_scale FROM "
"INFORMATION_SCHEMA.COLUMNS WHERE table_name = %s AND "
"table_schema = DATABASE() AND data_type='decimal'",
[table_name])
numeric_map = dict((line[0], tuple([int(n) for n in line[1:]]))
for line in cursor.fetchall())
cursor.execute("SELECT * FROM {0} LIMIT 1".format(
self.connection.ops.quote_name(table_name)))
if django.VERSION >= (1, 6):
return [FieldInfo(*((force_text(line[0]),)
+ line[1:3]
+ (length_map.get(line[0], line[3]),)
+ numeric_map.get(line[0], line[4:6])
+ (line[6],)))
for line in cursor.description]
else:
return [
line[:3] + (length_map.get(line[0], line[3]),) + line[4:]
for line in cursor.description
]
def _name_to_index(self, cursor, table_name):
"""
Returns a dictionary of {field_name: field_index} for the given table.
Indexes are 0-based.
"""
return dict((d[0], i) for i, d in enumerate(
self.get_table_description(cursor, table_name)))
def get_relations(self, cursor, table_name):
"""
Returns a dictionary of {field_index: (field_index_other_table,
other_table)}
representing all relationships to the given table. Indexes are 0-based.
"""
constraints = self.get_key_columns(cursor, table_name)
relations = {}
if django.VERSION >= (1, 8):
for my_fieldname, other_table, other_field in constraints:
relations[my_fieldname] = (other_field, other_table)
return relations
else:
my_field_dict = self._name_to_index(cursor, table_name)
for my_fieldname, other_table, other_field in constraints:
other_field_index = self._name_to_index(
cursor, other_table)[other_field]
my_field_index = my_field_dict[my_fieldname]
relations[my_field_index] = (other_field_index, other_table)
return relations
def get_key_columns(self, cursor, table_name):
"""
Returns a list of (column_name, referenced_table_name,
referenced_column_name) for all key columns in given table.
"""
key_columns = []
cursor.execute(
"SELECT column_name, referenced_table_name, referenced_column_name "
"FROM information_schema.key_column_usage "
"WHERE table_name = %s "
"AND table_schema = DATABASE() "
"AND referenced_table_name IS NOT NULL "
"AND referenced_column_name IS NOT NULL", [table_name])
key_columns.extend(cursor.fetchall())
return key_columns
def get_indexes(self, cursor, table_name):
cursor.execute("SHOW INDEX FROM {0}"
"".format(self.connection.ops.quote_name(table_name)))
# Do a two-pass search for indexes: on first pass check which indexes
# are multicolumn, on second pass check which single-column indexes
# are present.
rows = list(cursor.fetchall())
multicol_indexes = set()
for row in rows:
if row[3] > 1:
multicol_indexes.add(row[2])
indexes = {}
for row in rows:
if row[2] in multicol_indexes:
continue
if row[4] not in indexes:
indexes[row[4]] = {'primary_key': False, 'unique': False}
# It's possible to have the unique and PK constraints in
# separate indexes.
if row[2] == 'PRIMARY':
indexes[row[4]]['primary_key'] = True
if not row[1]:
indexes[row[4]]['unique'] = True
return indexes
def get_primary_key_column(self, cursor, table_name):
"""
Returns the name of the primary key column for the given table
"""
# Django 1.6
for column in self.get_indexes(cursor, table_name).items():
if column[1]['primary_key']:
return column[0]
return None
def get_storage_engine(self, cursor, table_name):
"""
Retrieves the storage engine for a given table. Returns the default
storage engine if the table doesn't exist.
"""
cursor.execute(
"SELECT engine "
"FROM information_schema.tables "
"WHERE table_name = %s", [table_name])
result = cursor.fetchone()
if not result:
return self.connection.features.mysql_storage_engine
return result[0]
def get_constraints(self, cursor, table_name):
"""
Retrieves any constraints or keys (unique, pk, fk, check, index) across
one or more columns.
"""
# Django 1.7
constraints = {}
# Get the actual constraint names and columns
name_query = (
"SELECT kc.`constraint_name`, kc.`column_name`, "
"kc.`referenced_table_name`, kc.`referenced_column_name` "
"FROM information_schema.key_column_usage AS kc "
"WHERE "
"kc.table_schema = %s AND "
"kc.table_name = %s"
)
cursor.execute(name_query, [self.connection.settings_dict['NAME'],
table_name])
for constraint, column, ref_table, ref_column in cursor.fetchall():
if constraint not in constraints:
constraints[constraint] = {
'columns': OrderedSet(),
'primary_key': False,
'unique': False,
'index': False,
'check': False,
'foreign_key': (
(ref_table, ref_column) if ref_column else None,
)
}
constraints[constraint]['columns'].add(column)
# Now get the constraint types
type_query = """
SELECT c.constraint_name, c.constraint_type
FROM information_schema.table_constraints AS c
WHERE
c.table_schema = %s AND
c.table_name = %s
"""
cursor.execute(type_query, [self.connection.settings_dict['NAME'],
table_name])
for constraint, kind in cursor.fetchall():
if kind.lower() == "primary key":
constraints[constraint]['primary_key'] = True
constraints[constraint]['unique'] = True
elif kind.lower() == "unique":
constraints[constraint]['unique'] = True
# Now add in the indexes
cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name(
table_name))
for table, non_unique, index, colseq, column in [x[:5] for x in
cursor.fetchall()]:
if index not in constraints:
constraints[index] = {
'columns': OrderedSet(),
'primary_key': False,
'unique': False,
'index': True,
'check': False,
'foreign_key': None,
}
constraints[index]['index'] = True
constraints[index]['columns'].add(column)
# Convert the sorted sets to lists
for constraint in constraints.values():
constraint['columns'] = list(constraint['columns'])
return constraints

View file

@ -0,0 +1,308 @@
# MySQL Connector/Python - MySQL driver written in Python.
# New file added for Django 1.8
from __future__ import unicode_literals
import uuid
import django
from django.conf import settings
if django.VERSION >= (1, 8):
from django.db.backends.base.operations import BaseDatabaseOperations
else:
from django.db.backends import BaseDatabaseOperations
from django.utils import six, timezone
from django.utils.encoding import force_text
try:
from _mysql_connector import datetime_to_mysql, time_to_mysql
except ImportError:
HAVE_CEXT = False
else:
HAVE_CEXT = True
class DatabaseOperations(BaseDatabaseOperations):
compiler_module = "mysql.connector.django.compiler"
# MySQL stores positive fields as UNSIGNED ints.
if django.VERSION >= (1, 7):
integer_field_ranges = dict(BaseDatabaseOperations.integer_field_ranges,
PositiveSmallIntegerField=(0, 4294967295),
PositiveIntegerField=(
0, 18446744073709551615),)
def date_extract_sql(self, lookup_type, field_name):
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
if lookup_type == 'week_day':
# DAYOFWEEK() returns an integer, 1-7, Sunday=1.
# Note: WEEKDAY() returns 0-6, Monday=0.
return "DAYOFWEEK({0})".format(field_name)
else:
return "EXTRACT({0} FROM {1})".format(
lookup_type.upper(), field_name)
def date_trunc_sql(self, lookup_type, field_name):
"""Returns SQL simulating DATE_TRUNC
This function uses MySQL functions DATE_FORMAT and CAST to
simulate DATE_TRUNC.
The field_name is returned when lookup_type is not supported.
"""
fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
format = ('%Y-', '%m', '-%d', ' %H:', '%i', ':%S')
format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
try:
i = fields.index(lookup_type) + 1
except ValueError:
# Wrong lookup type, just return the value from MySQL as-is
sql = field_name
else:
format_str = ''.join([f for f in format[:i]] +
[f for f in format_def[i:]])
sql = "CAST(DATE_FORMAT({0}, '{1}') AS DATETIME)".format(
field_name, format_str)
return sql
def datetime_extract_sql(self, lookup_type, field_name, tzname):
# Django 1.6
if settings.USE_TZ:
field_name = "CONVERT_TZ({0}, 'UTC', %s)".format(field_name)
params = [tzname]
else:
params = []
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
if lookup_type == 'week_day':
# DAYOFWEEK() returns an integer, 1-7, Sunday=1.
# Note: WEEKDAY() returns 0-6, Monday=0.
sql = "DAYOFWEEK({0})".format(field_name)
else:
sql = "EXTRACT({0} FROM {1})".format(lookup_type.upper(),
field_name)
return sql, params
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
# Django 1.6
if settings.USE_TZ:
field_name = "CONVERT_TZ({0}, 'UTC', %s)".format(field_name)
params = [tzname]
else:
params = []
fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
format_ = ('%Y-', '%m', '-%d', ' %H:', '%i', ':%S')
format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
try:
i = fields.index(lookup_type) + 1
except ValueError:
sql = field_name
else:
format_str = ''.join([f for f in format_[:i]] +
[f for f in format_def[i:]])
sql = "CAST(DATE_FORMAT({0}, '{1}') AS DATETIME)".format(
field_name, format_str)
return sql, params
if django.VERSION >= (1, 8):
def date_interval_sql(self, timedelta):
"""Returns SQL for calculating date/time intervals
"""
return "INTERVAL '%d 0:0:%d:%d' DAY_MICROSECOND" % (
timedelta.days, timedelta.seconds, timedelta.microseconds), []
else:
def date_interval_sql(self, sql, connector, timedelta):
"""Returns SQL for calculating date/time intervals
"""
fmt = (
"({sql} {connector} INTERVAL '{days} "
"0:0:{secs}:{msecs}' DAY_MICROSECOND)"
)
return fmt.format(
sql=sql,
connector=connector,
days=timedelta.days,
secs=timedelta.seconds,
msecs=timedelta.microseconds
)
def format_for_duration_arithmetic(self, sql):
if self.connection.features.supports_microsecond_precision:
return 'INTERVAL %s MICROSECOND' % sql
else:
return 'INTERVAL FLOOR(%s / 1000000) SECOND' % sql
def drop_foreignkey_sql(self):
return "DROP FOREIGN KEY"
def force_no_ordering(self):
"""
"ORDER BY NULL" prevents MySQL from implicitly ordering by grouped
columns. If no ordering would otherwise be applied, we don't want any
implicit sorting going on.
"""
if django.VERSION >= (1, 8):
return [(None, ("NULL", [], False))]
else:
return ["NULL"]
def fulltext_search_sql(self, field_name):
return 'MATCH ({0}) AGAINST (%s IN BOOLEAN MODE)'.format(field_name)
def last_executed_query(self, cursor, sql, params):
return force_text(cursor.statement, errors='replace')
def no_limit_value(self):
# 2**64 - 1, as recommended by the MySQL documentation
return 18446744073709551615
def quote_name(self, name):
if name.startswith("`") and name.endswith("`"):
return name # Quoting once is enough.
return "`{0}`".format(name)
def random_function_sql(self):
return 'RAND()'
def sql_flush(self, style, tables, sequences, allow_cascade=False):
if tables:
sql = ['SET FOREIGN_KEY_CHECKS = 0;']
for table in tables:
sql.append('{keyword} {table};'.format(
keyword=style.SQL_KEYWORD('TRUNCATE'),
table=style.SQL_FIELD(self.quote_name(table))))
sql.append('SET FOREIGN_KEY_CHECKS = 1;')
sql.extend(self.sequence_reset_by_name_sql(style, sequences))
return sql
else:
return []
def validate_autopk_value(self, value):
# MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653.
if value == 0:
raise ValueError('The database backend does not accept 0 as a '
'value for AutoField.')
return value
def value_to_db_datetime(self, value):
if value is None:
return None
# MySQL doesn't support tz-aware times
if timezone.is_aware(value):
if settings.USE_TZ:
value = value.astimezone(timezone.utc).replace(tzinfo=None)
else:
raise ValueError(
"MySQL backend does not support timezone-aware times."
)
if not self.connection.features.supports_microsecond_precision:
value = value.replace(microsecond=0)
if not self.connection.use_pure:
return datetime_to_mysql(value)
return self.connection.converter.to_mysql(value)
def value_to_db_time(self, value):
if value is None:
return None
# MySQL doesn't support tz-aware times
if timezone.is_aware(value):
raise ValueError("MySQL backend does not support timezone-aware "
"times.")
if not self.connection.use_pure:
return time_to_mysql(value)
return self.connection.converter.to_mysql(value)
def max_name_length(self):
return 64
def bulk_insert_sql(self, fields, num_values):
items_sql = "({0})".format(", ".join(["%s"] * len(fields)))
return "VALUES " + ", ".join([items_sql] * num_values)
if django.VERSION < (1, 8):
def year_lookup_bounds(self, value):
# Again, no microseconds
first = '{0}-01-01 00:00:00'
second = '{0}-12-31 23:59:59.999999'
return [first.format(value), second.format(value)]
def year_lookup_bounds_for_datetime_field(self, value):
# Django 1.6
# Again, no microseconds
first, second = super(DatabaseOperations,
self).year_lookup_bounds_for_datetime_field(value)
if self.connection.mysql_version >= (5, 6, 4):
return [first.replace(microsecond=0), second]
else:
return [first.replace(microsecond=0),
second.replace(microsecond=0)]
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.mysql_version < (5, 0, 13):
fmt = "{alter} {table} {{tablename}} {auto_inc} {field};".format(
alter=style.SQL_KEYWORD('ALTER'),
table=style.SQL_KEYWORD('TABLE'),
auto_inc=style.SQL_KEYWORD('AUTO_INCREMENT'),
field=style.SQL_FIELD('= 1')
)
for sequence in sequences:
tablename = style.SQL_TABLE(self.quote_name(sequence['table']))
res.append(fmt.format(tablename=tablename))
return res
return res
def savepoint_create_sql(self, sid):
return "SAVEPOINT {0}".format(sid)
def savepoint_commit_sql(self, sid):
return "RELEASE SAVEPOINT {0}".format(sid)
def savepoint_rollback_sql(self, sid):
return "ROLLBACK TO SAVEPOINT {0}".format(sid)
def combine_expression(self, connector, sub_expressions):
"""
MySQL requires special cases for ^ operators in query expressions
"""
if connector == '^':
return 'POW(%s)' % ','.join(sub_expressions)
return super(DatabaseOperations, self).combine_expression(
connector, sub_expressions)
def get_db_converters(self, expression):
# New in Django 1.8
converters = super(DatabaseOperations, self).get_db_converters(
expression)
internal_type = expression.output_field.get_internal_type()
if internal_type in ['BooleanField', 'NullBooleanField']:
converters.append(self.convert_booleanfield_value)
if internal_type == 'UUIDField':
converters.append(self.convert_uuidfield_value)
if internal_type == 'TextField':
converters.append(self.convert_textfield_value)
return converters
def convert_booleanfield_value(self, value,
expression, connection, context):
# New in Django 1.8
if value in (0, 1):
value = bool(value)
return value
def convert_uuidfield_value(self, value, expression, connection, context):
# New in Django 1.8
if value is not None:
value = uuid.UUID(value)
return value
def convert_textfield_value(self, value, expression, connection, context):
# New in Django 1.8
if value is not None:
value = force_text(value)
return value

View file

@ -0,0 +1,86 @@
# MySQL Connector/Python - MySQL driver written in Python.
# New file added for Django 1.7
import django
if django.VERSION >= (1, 8):
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
else:
from django.db.backends.schema import BaseDatabaseSchemaEditor
from django.db.models import NOT_PROVIDED
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
sql_rename_table = "RENAME TABLE %(old_table)s TO %(new_table)s"
sql_alter_column_null = "MODIFY %(column)s %(type)s NULL"
sql_alter_column_not_null = "MODIFY %(column)s %(type)s NOT NULL"
sql_alter_column_type = "MODIFY %(column)s %(type)s"
sql_rename_column = "ALTER TABLE %(table)s CHANGE %(old_column)s " \
"%(new_column)s %(type)s"
sql_delete_unique = "ALTER TABLE %(table)s DROP INDEX %(name)s"
sql_create_fk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s FOREIGN " \
"KEY (%(column)s) REFERENCES %(to_table)s (%(to_column)s)"
sql_delete_fk = "ALTER TABLE %(table)s DROP FOREIGN KEY %(name)s"
sql_delete_index = "DROP INDEX %(name)s ON %(table)s"
alter_string_set_null = 'MODIFY %(column)s %(type)s NULL;'
alter_string_drop_null = 'MODIFY %(column)s %(type)s NOT NULL;'
sql_create_pk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s " \
"PRIMARY KEY (%(columns)s)"
sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY"
def quote_value(self, value):
# Inner import to allow module to fail to load gracefully
from mysql.connector.conversion import MySQLConverter
return MySQLConverter.quote(MySQLConverter.escape(value))
def skip_default(self, field):
"""
MySQL doesn't accept default values for longtext and longblob
and implicitly treats these columns as nullable.
"""
return field.db_type(self.connection) in ('longtext', 'longblob')
def add_field(self, model, field):
super(DatabaseSchemaEditor, self).add_field(model, field)
# Simulate the effect of a one-off default.
if (self.skip_default(field)
and field.default not in (None, NOT_PROVIDED)):
effective_default = self.effective_default(field)
self.execute('UPDATE %(table)s SET %(column)s = %%s' % {
'table': self.quote_name(model._meta.db_table),
'column': self.quote_name(field.column),
}, [effective_default])
def _model_indexes_sql(self, model):
# New in Django 1.8
storage = self.connection.introspection.get_storage_engine(
self.connection.cursor(), model._meta.db_table
)
if storage == "InnoDB":
for field in model._meta.local_fields:
if (field.db_index and not field.unique
and field.get_internal_type() == "ForeignKey"):
# Temporary setting db_index to False (in memory) to
# disable index creation for FKs (index automatically
# created by MySQL)
field.db_index = False
return super(DatabaseSchemaEditor, self)._model_indexes_sql(model)
def _alter_column_type_sql(self, table, old_field, new_field, new_type):
# New in Django 1.8
# Keep null property of old field, if it has changed, it will be
# handled separately
if old_field.null:
new_type += " NULL"
else:
new_type += " NOT NULL"
return super(DatabaseSchemaEditor, self)._alter_column_type_sql(
table, old_field, new_field, new_type)

View file

@ -0,0 +1,65 @@
# MySQL Connector/Python - MySQL driver written in Python.
import django
if django.VERSION >= (1, 8):
from django.db.backends.base.validation import BaseDatabaseValidation
else:
from django.db.backends import BaseDatabaseValidation
if django.VERSION < (1, 7):
from django.db import models
else:
from django.core import checks
from django.db import connection
class DatabaseValidation(BaseDatabaseValidation):
if django.VERSION < (1, 7):
def validate_field(self, errors, opts, f):
"""
MySQL has the following field length restriction:
No character (varchar) fields can have a length exceeding 255
characters if they have a unique index on them.
"""
varchar_fields = (models.CharField,
models.CommaSeparatedIntegerField,
models.SlugField)
if isinstance(f, varchar_fields) and f.max_length > 255 and f.unique:
msg = ('"%(name)s": %(cls)s cannot have a "max_length" greater '
'than 255 when using "unique=True".')
errors.add(opts, msg % {'name': f.name,
'cls': f.__class__.__name__})
else:
def check_field(self, field, **kwargs):
"""
MySQL has the following field length restriction:
No character (varchar) fields can have a length exceeding 255
characters if they have a unique index on them.
"""
# Django 1.7
errors = super(DatabaseValidation, self).check_field(field,
**kwargs)
# Ignore any related fields.
if getattr(field, 'rel', None) is None:
field_type = field.db_type(connection)
if field_type is None:
return errors
if (field_type.startswith('varchar') # Look for CharFields...
and field.unique # ... that are unique
and (field.max_length is None or
int(field.max_length) > 255)):
errors.append(
checks.Error(
('MySQL does not allow unique CharFields to have a '
'max_length > 255.'),
hint=None,
obj=field,
id='mysql.E001',
)
)
return errors

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,304 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Python exceptions
"""
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)
# is similar, but hardcoded exceptions.
_CUSTOM_ERROR_EXCEPTIONS = {}
def custom_error_exception(error=None, exception=None):
"""Define custom exceptions for MySQL server errors
This function defines custom exceptions for MySQL server errors and
returns the current set customizations.
If error is a MySQL Server error number, then you have to pass also the
exception class.
The error argument can also be a dictionary in which case the key is
the server error number, and value the exception to be raised.
If none of the arguments are given, then custom_error_exception() will
simply return the current set customizations.
To reset the customizations, simply supply an empty dictionary.
Examples:
import mysql.connector
from mysql.connector import errorcode
# Server error 1028 should raise a DatabaseError
mysql.connector.custom_error_exception(
1028, mysql.connector.DatabaseError)
# Or using a dictionary:
mysql.connector.custom_error_exception({
1028: mysql.connector.DatabaseError,
1029: mysql.connector.OperationalError,
})
# Reset
mysql.connector.custom_error_exception({})
Returns a dictionary.
"""
global _CUSTOM_ERROR_EXCEPTIONS # pylint: disable=W0603
if isinstance(error, dict) and not len(error):
_CUSTOM_ERROR_EXCEPTIONS = {}
return _CUSTOM_ERROR_EXCEPTIONS
if not error and not exception:
return _CUSTOM_ERROR_EXCEPTIONS
if not isinstance(error, (int, dict)):
raise ValueError(
"The error argument should be either an integer or dictionary")
if isinstance(error, int):
error = {error: exception}
for errno, exception in error.items():
if not isinstance(errno, int):
raise ValueError("error number should be an integer")
try:
if not issubclass(exception, Exception):
raise TypeError
except TypeError:
raise ValueError("exception should be subclass of Exception")
_CUSTOM_ERROR_EXCEPTIONS[errno] = exception
return _CUSTOM_ERROR_EXCEPTIONS
def get_mysql_exception(errno, msg=None, sqlstate=None):
"""Get the exception matching the MySQL error
This function will return an exception based on the SQLState. The given
message will be passed on in the returned exception.
The exception returned can be customized using the
mysql.connector.custom_error_exception() function.
Returns an Exception
"""
try:
return _CUSTOM_ERROR_EXCEPTIONS[errno](
msg=msg, errno=errno, sqlstate=sqlstate)
except KeyError:
# Error was not mapped to particular exception
pass
try:
return _ERROR_EXCEPTIONS[errno](
msg=msg, errno=errno, sqlstate=sqlstate)
except KeyError:
# Error was not mapped to particular exception
pass
if not sqlstate:
return DatabaseError(msg=msg, errno=errno)
try:
return _SQLSTATE_CLASS_EXCEPTION[sqlstate[0:2]](
msg=msg, errno=errno, sqlstate=sqlstate)
except KeyError:
# Return default InterfaceError
return DatabaseError(msg=msg, errno=errno, sqlstate=sqlstate)
def get_exception(packet):
"""Returns an exception object based on the MySQL error
Returns an exception object based on the MySQL error in the given
packet.
Returns an Error-Object.
"""
errno = errmsg = None
try:
if packet[4] != 255:
raise ValueError("Packet is not an error packet")
except IndexError as err:
return InterfaceError("Failed getting Error information (%r)" % err)
sqlstate = None
try:
packet = packet[5:]
(packet, errno) = utils.read_int(packet, 2)
if packet[0] != 35:
# Error without SQLState
if isinstance(packet, (bytes, bytearray)):
errmsg = packet.decode('utf8')
else:
errmsg = packet
else:
(packet, sqlstate) = utils.read_bytes(packet[1:], 5)
sqlstate = sqlstate.decode('utf8')
errmsg = packet.decode('utf8')
except Exception as err: # pylint: disable=W0703
return InterfaceError("Failed getting Error information (%r)" % err)
else:
return get_mysql_exception(errno, errmsg, sqlstate)
class Error(Exception):
"""Exception that is base class for all other error exceptions"""
def __init__(self, msg=None, errno=None, values=None, sqlstate=None):
super(Error, self).__init__()
self.msg = msg
self._full_msg = self.msg
self.errno = errno or -1
self.sqlstate = sqlstate
if not self.msg and (2000 <= self.errno < 3000):
self.msg = get_client_error(self.errno)
if values is not None:
try:
self.msg = self.msg % values
except TypeError as err:
self.msg = "{0} (Warning: {1})".format(self.msg, str(err))
elif not self.msg:
self._full_msg = self.msg = 'Unknown error'
if self.msg and self.errno != -1:
fields = {
'errno': self.errno,
'msg': self.msg.encode('utf8') if PY2 else self.msg
}
if self.sqlstate:
fmt = '{errno} ({state}): {msg}'
fields['state'] = self.sqlstate
else:
fmt = '{errno}: {msg}'
self._full_msg = fmt.format(**fields)
self.args = (self.errno, self._full_msg, self.sqlstate)
def __str__(self):
return self._full_msg
class Warning(Exception): # pylint: disable=W0622
"""Exception for important warnings"""
pass
class InterfaceError(Error):
"""Exception for errors related to the interface"""
pass
class DatabaseError(Error):
"""Exception for errors related to the database"""
pass
class InternalError(DatabaseError):
"""Exception for errors internal database errors"""
pass
class OperationalError(DatabaseError):
"""Exception for errors related to the database's operation"""
pass
class ProgrammingError(DatabaseError):
"""Exception for errors programming errors"""
pass
class IntegrityError(DatabaseError):
"""Exception for errors regarding relational integrity"""
pass
class DataError(DatabaseError):
"""Exception for errors reporting problems with processed data"""
pass
class NotSupportedError(DatabaseError):
"""Exception for errors when an unsupported database feature was used"""
pass
class PoolError(Error):
"""Exception for errors relating to connection pooling"""
pass
class MySQLFabricError(Error):
"""Exception for errors relating to MySQL Fabric"""
_SQLSTATE_CLASS_EXCEPTION = {
'02': DataError, # no data
'07': DatabaseError, # dynamic SQL error
'08': OperationalError, # connection exception
'0A': NotSupportedError, # feature not supported
'21': DataError, # cardinality violation
'22': DataError, # data exception
'23': IntegrityError, # integrity constraint violation
'24': ProgrammingError, # invalid cursor state
'25': ProgrammingError, # invalid transaction state
'26': ProgrammingError, # invalid SQL statement name
'27': ProgrammingError, # triggered data change violation
'28': ProgrammingError, # invalid authorization specification
'2A': ProgrammingError, # direct SQL syntax error or access rule violation
'2B': DatabaseError, # dependent privilege descriptors still exist
'2C': ProgrammingError, # invalid character set name
'2D': DatabaseError, # invalid transaction termination
'2E': DatabaseError, # invalid connection name
'33': DatabaseError, # invalid SQL descriptor name
'34': ProgrammingError, # invalid cursor name
'35': ProgrammingError, # invalid condition number
'37': ProgrammingError, # dynamic SQL syntax error or access rule violation
'3C': ProgrammingError, # ambiguous cursor name
'3D': ProgrammingError, # invalid catalog name
'3F': ProgrammingError, # invalid schema name
'40': InternalError, # transaction rollback
'42': ProgrammingError, # syntax error or access rule violation
'44': InternalError, # with check option violation
'HZ': OperationalError, # remote database access
'XA': IntegrityError,
'0K': OperationalError,
'HY': DatabaseError, # default when no SQLState provided by MySQL server
}
_ERROR_EXCEPTIONS = {
1243: ProgrammingError,
1210: ProgrammingError,
2002: InterfaceError,
2013: OperationalError,
2049: NotSupportedError,
2055: OperationalError,
2061: InterfaceError,
}

View file

@ -0,0 +1,70 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""MySQL Fabric support"""
from collections import namedtuple
# Order of field_names must match how Fabric is returning the data
FabricMySQLServer = namedtuple(
'FabricMySQLServer',
['uuid', 'group', 'host', 'port', 'mode', 'status', 'weight']
)
# Order of field_names must match how Fabric is returning the data
FabricShard = namedtuple(
'FabricShard',
['database', 'table', 'column', 'key',
'shard', 'shard_type', 'group', 'global_group']
)
from .connection import (
MODE_READONLY, MODE_READWRITE,
STATUS_PRIMARY, STATUS_SECONDARY,
SCOPE_GLOBAL, SCOPE_LOCAL,
Fabric, FabricConnection,
MySQLFabricConnection,
FabricSet,
)
def connect(**kwargs):
"""Create a MySQLFabricConnection object"""
return MySQLFabricConnection(**kwargs)
__all__ = [
'MODE_READWRITE',
'MODE_READONLY',
'STATUS_PRIMARY',
'STATUS_SECONDARY',
'SCOPE_GLOBAL',
'SCOPE_LOCAL',
'FabricMySQLServer',
'FabricShard',
'connect',
'Fabric',
'FabricConnection',
'MySQLFabricConnection',
'FabricSet',
]

View file

@ -0,0 +1,159 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Implementing load balancing"""
import decimal
def _calc_ratio(part, whole):
"""Calculate ratio
Returns int
"""
return int((part/whole*100).quantize(
decimal.Decimal('1'), rounding=decimal.ROUND_HALF_DOWN))
class BaseScheduling(object):
"""Base class for all scheduling classes dealing with load balancing"""
def __init__(self):
"""Initialize"""
self._members = []
self._ratios = []
def set_members(self, *args):
"""Set members and ratios
This methods sets the members using the arguments passed. Each
argument must be a sequence where the second item is the weight.
The first element is an identifier. For example:
('server1', 0.6), ('server2', 0.8)
Setting members means that the load will be reset. If the members
are the same as previously set, nothing will be reset or set.
If no arguments were given the members will be set to an empty
list.
Raises ValueError when weight can't be converted to a Decimal.
"""
raise NotImplementedError
def get_next(self):
"""Returns the next member"""
raise NotImplementedError
@property
def members(self):
"""Returns the members of this loadbalancer"""
return self._members
@property
def ratios(self):
"""Returns the ratios for all members"""
return self._ratios
class WeightedRoundRobin(BaseScheduling):
"""Class for doing Weighted Round Robin balancing"""
def __init__(self, *args):
"""Initializing"""
super(WeightedRoundRobin, self).__init__()
self._load = []
self._next_member = 0
self._nr_members = 0
if args:
self.set_members(*args)
@property
def load(self):
"""Returns the current load"""
return self._load
def set_members(self, *args):
if not args:
# Reset members if nothing was given
self._members = []
return
new_members = []
for member in args:
member = list(member)
try:
member[1] = decimal.Decimal(str(member[1]))
except decimal.InvalidOperation:
raise ValueError("Member '{member}' is invalid".format(
member=member))
new_members.append(tuple(member))
new_members.sort(key=lambda x: x[1], reverse=True)
if self._members == new_members:
return
self._members = new_members
self._nr_members = len(new_members)
min_weight = min(i[1] for i in self._members)
self._ratios = []
for _, weight in self._members:
self._ratios.append(int(weight/min_weight * 100))
self.reset()
def reset(self):
"""Reset the load"""
self._next_member = 0
self._load = [0] * self._nr_members
def get_next(self):
"""Returns the next member"""
if self._ratios == self._load:
self.reset()
# Figure out the member to return
current = self._next_member
while self._load[current] == self._ratios[current]:
current = (current + 1) % self._nr_members
# Update the load and set next member
self._load[current] += 1
self._next_member = (current + 1) % self._nr_members
# Return current
return self._members[current]
def __repr__(self):
return "{class_}(load={load}, ratios={ratios})".format(
class_=self.__class__,
load=self.load,
ratios=self.ratios
)
def __eq__(self, other):
return self._members == other.members

View file

@ -0,0 +1,281 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Implementing caching mechanisms for MySQL Fabric"""
import bisect
from datetime import datetime, timedelta
from hashlib import sha1
import logging
import threading
from . import FabricShard
_LOGGER = logging.getLogger('myconnpy-fabric')
_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"""
def __init__(self, version=None, fabric_uuid=None, ttl=_CACHE_TTL):
self.version = version
self.fabric_uuid = fabric_uuid
self.last_updated = datetime.utcnow()
self._ttl = ttl
@classmethod
def hash_index(cls, part1, part2=None):
"""Create hash for indexing"""
raise NotImplementedError
@property
def invalid(self):
"""Returns True if entry is not valid any longer
This property returns True when the entry is not valid any longer.
The entry is valid when now > (last updated + ttl), where ttl is
in seconds.
"""
if not self.last_updated:
return False
atime = self.last_updated + timedelta(seconds=self._ttl)
return datetime.utcnow() > atime
def reset_ttl(self):
"""Reset the Time to Live"""
self.last_updated = datetime.utcnow()
def invalidate(self):
"""Invalidates the cache entry"""
self.last_updated = None
class CacheShardTable(CacheEntry):
"""Cache entry for a Fabric sharded table"""
def __init__(self, shard, version=None, fabric_uuid=None):
if not isinstance(shard, FabricShard):
raise ValueError("shard argument must be a FabricShard instance")
super(CacheShardTable, self).__init__(version=version,
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)
def __getattr__(self, attr):
return getattr(self._shard, attr)
def add_partition(self, key, group):
"""Add sharding information for a group"""
if self.shard_type == 'RANGE':
key = int(key)
elif self.shard_type == 'RANGE_DATETIME':
try:
if ':' in key:
key = datetime.strptime(key, "%Y-%m-%d %H:%M:%S")
else:
key = datetime.strptime(key, "%Y-%m-%d").date()
except:
raise ValueError(
"RANGE_DATETIME key could not be parsed, was: {0}".format(
key
))
elif self.shard_type == 'RANGE_STRING':
pass
elif self.shard_type == "HASH":
pass
else:
raise ValueError("Unsupported sharding type {0}".format(
self.shard_type
))
self.partitioning[key] = {
'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):
"""Create hash for indexing"""
return sha1(part1.encode('utf-8') + part2.encode('utf-8')).hexdigest()
def __repr__(self):
return "{class_}({database}.{table}.{column})".format(
class_=self.__class__,
database=self.database,
table=self.table,
column=self.column
)
class CacheGroup(CacheEntry):
"""Cache entry for a Fabric group"""
def __init__(self, group_name, servers):
super(CacheGroup, self).__init__(version=None, fabric_uuid=None)
self.group_name = group_name
self.servers = servers
@classmethod
def hash_index(cls, part1, part2=None):
"""Create hash for indexing"""
return sha1(part1.encode('utf-8')).hexdigest()
def __repr__(self):
return "{class_}({group})".format(
class_=self.__class__,
group=self.group_name,
)
class FabricCache(object):
"""Singleton class for caching Fabric data
Only one instance of this class can exists globally.
"""
def __init__(self, ttl=_CACHE_TTL):
self._ttl = ttl
self._sharding = {}
self._groups = {}
self.__sharding_lock = threading.Lock()
self.__groups_lock = threading.Lock()
def remove_group(self, entry_hash):
"""Remove cache entry for group"""
with self.__groups_lock:
try:
del self._groups[entry_hash]
except KeyError:
# not cached, that's OK
pass
else:
_LOGGER.debug("Group removed from cache")
def remove_shardtable(self, entry_hash):
"""Remove cache entry for shard"""
with self.__sharding_lock:
try:
del self._sharding[entry_hash]
except KeyError:
# not cached, that's OK
pass
def sharding_cache_table(self, shard, version=None, fabric_uuid=None):
"""Cache information about a shard"""
entry_hash = CacheShardTable.hash_index(shard.database, shard.table)
with self.__sharding_lock:
try:
entry = self._sharding[entry_hash]
entry.add_partition(shard.key, shard.group)
except KeyError:
# New cache entry
entry = CacheShardTable(shard, version=version,
fabric_uuid=fabric_uuid)
self._sharding[entry_hash] = entry
def cache_group(self, group_name, servers):
"""Cache information about a group"""
entry_hash = CacheGroup.hash_index(group_name)
with self.__groups_lock:
try:
entry = self._groups[entry_hash]
entry.servers = servers
entry.reset_ttl()
_LOGGER.debug("Recaching group {0} with {1}".format(
group_name, servers))
except KeyError:
# New cache entry
entry = CacheGroup(group_name, servers)
self._groups[entry_hash] = entry
_LOGGER.debug("Caching group {0} with {1}".format(
group_name, servers))
def sharding_search(self, database, table):
"""Search cache for a shard based on database and table"""
entry_hash = CacheShardTable.hash_index(database, table)
entry = None
try:
entry = self._sharding[entry_hash]
if entry.invalid:
_LOGGER.debug("{0} invalidated".format(entry))
self.remove_shardtable(entry_hash)
return None
except KeyError:
# Nothing in cache
return None
return entry
def group_search(self, group_name):
"""Search cache for a group based on its name"""
entry_hash = CacheGroup.hash_index(group_name)
entry = None
try:
entry = self._groups[entry_hash]
if entry.invalid:
_LOGGER.debug("{0} invalidated".format(entry))
self.remove_group(entry_hash)
return None
except KeyError:
# Nothing in cache
return None
return entry
def __repr__(self):
return "{class_}(groups={nrgroups},shards={nrshards})".format(
class_=self.__class__,
nrgroups=len(self._groups),
nrshards=len(self._sharding)
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,71 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Translations
"""
__all__ = [
'get_client_error'
]
from .. import errorcode
def get_client_error(error, language='eng'):
"""Lookup client error
This function will lookup the client error message based on the given
error and return the error message. If the error was not found,
None will be returned.
Error can be either an integer or a string. For example:
error: 2000
error: CR_UNKNOWN_ERROR
The language attribute can be used to retrieve a localized message, when
available.
Returns a string or None.
"""
try:
tmp = __import__('mysql.connector.locales.{0}'.format(language),
globals(), locals(), ['client_error'])
except ImportError:
raise ImportError("No localization support for language '{0}'".format(
language))
client_error = tmp.client_error
if isinstance(error, int):
errno = error
for key, value in errorcode.__dict__.items():
if value == errno:
error = key
break
if isinstance(error, (str)):
try:
return getattr(client_error, error)
except AttributeError:
return None
raise ValueError("error argument needs to be either an integer or string")

View file

@ -0,0 +1,25 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""English Content
"""

View file

@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# This file was auto-generated.
_GENERATED_ON = '2015-12-13'
_MYSQL_VERSION = (5, 7, 10)
# Start MySQL Error messages
CR_UNKNOWN_ERROR = u"Unknown MySQL error"
CR_SOCKET_CREATE_ERROR = u"Can't create UNIX socket (%s)"
CR_CONNECTION_ERROR = u"Can't connect to local MySQL server through socket '%-.100s' (%s)"
CR_CONN_HOST_ERROR = u"Can't connect to MySQL server on '%-.100s' (%s)"
CR_IPSOCK_ERROR = u"Can't create TCP/IP socket (%s)"
CR_UNKNOWN_HOST = u"Unknown MySQL server host '%-.100s' (%s)"
CR_SERVER_GONE_ERROR = u"MySQL server has gone away"
CR_VERSION_ERROR = u"Protocol mismatch; server version = %s, client version = %s"
CR_OUT_OF_MEMORY = u"MySQL client ran out of memory"
CR_WRONG_HOST_INFO = u"Wrong host info"
CR_LOCALHOST_CONNECTION = u"Localhost via UNIX socket"
CR_TCP_CONNECTION = u"%-.100s via TCP/IP"
CR_SERVER_HANDSHAKE_ERR = u"Error in server handshake"
CR_SERVER_LOST = u"Lost connection to MySQL server during query"
CR_COMMANDS_OUT_OF_SYNC = u"Commands out of sync; you can't run this command now"
CR_NAMEDPIPE_CONNECTION = u"Named pipe: %-.32s"
CR_NAMEDPIPEWAIT_ERROR = u"Can't wait for named pipe to host: %-.64s pipe: %-.32s (%s)"
CR_NAMEDPIPEOPEN_ERROR = u"Can't open named pipe to host: %-.64s pipe: %-.32s (%s)"
CR_NAMEDPIPESETSTATE_ERROR = u"Can't set state of named pipe to host: %-.64s pipe: %-.32s (%s)"
CR_CANT_READ_CHARSET = u"Can't initialize character set %-.32s (path: %-.100s)"
CR_NET_PACKET_TOO_LARGE = u"Got packet bigger than 'max_allowed_packet' bytes"
CR_EMBEDDED_CONNECTION = u"Embedded server"
CR_PROBE_SLAVE_STATUS = u"Error on SHOW SLAVE STATUS:"
CR_PROBE_SLAVE_HOSTS = u"Error on SHOW SLAVE HOSTS:"
CR_PROBE_SLAVE_CONNECT = u"Error connecting to slave:"
CR_PROBE_MASTER_CONNECT = u"Error connecting to master:"
CR_SSL_CONNECTION_ERROR = u"SSL connection error: %-.100s"
CR_MALFORMED_PACKET = u"Malformed packet"
CR_WRONG_LICENSE = u"This client library is licensed only for use with MySQL servers having '%s' license"
CR_NULL_POINTER = u"Invalid use of null pointer"
CR_NO_PREPARE_STMT = u"Statement not prepared"
CR_PARAMS_NOT_BOUND = u"No data supplied for parameters in prepared statement"
CR_DATA_TRUNCATED = u"Data truncated"
CR_NO_PARAMETERS_EXISTS = u"No parameters exist in the statement"
CR_INVALID_PARAMETER_NO = u"Invalid parameter number"
CR_INVALID_BUFFER_USE = u"Can't send long data for non-string/non-binary data types (parameter: %s)"
CR_UNSUPPORTED_PARAM_TYPE = u"Using unsupported buffer type: %s (parameter: %s)"
CR_SHARED_MEMORY_CONNECTION = u"Shared memory: %-.100s"
CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = u"Can't open shared memory; client could not create request event (%s)"
CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = u"Can't open shared memory; no answer event received from server (%s)"
CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = u"Can't open shared memory; server could not allocate file mapping (%s)"
CR_SHARED_MEMORY_CONNECT_MAP_ERROR = u"Can't open shared memory; server could not get pointer to file mapping (%s)"
CR_SHARED_MEMORY_FILE_MAP_ERROR = u"Can't open shared memory; client could not allocate file mapping (%s)"
CR_SHARED_MEMORY_MAP_ERROR = u"Can't open shared memory; client could not get pointer to file mapping (%s)"
CR_SHARED_MEMORY_EVENT_ERROR = u"Can't open shared memory; client could not create %s event (%s)"
CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = u"Can't open shared memory; no answer from server (%s)"
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_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"
CR_NO_RESULT_SET = u"Attempt to read a row while there is no result set associated with the statement"
CR_NOT_IMPLEMENTED = u"This feature is not implemented yet"
CR_SERVER_LOST_EXTENDED = u"Lost connection to MySQL server at '%s', system error: %s"
CR_STMT_CLOSED = u"Statement closed indirectly because of a preceding %s() call"
CR_NEW_STMT_METADATA = u"The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again"
CR_ALREADY_CONNECTED = u"This handle is already connected. Use a separate handle for each connection."
CR_AUTH_PLUGIN_CANNOT_LOAD = u"Authentication plugin '%s' cannot be loaded: %s"
CR_DUPLICATE_CONNECTION_ATTR = u"There is an attribute with the same name already"
CR_AUTH_PLUGIN_ERR = u"Authentication plugin '%s' reported error: %s"
CR_INSECURE_API_ERR = u"Insecure API function call: '%s' Use instead: '%s'"
# End MySQL Error messages

View file

@ -0,0 +1,514 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Module implementing low-level socket communication with MySQL servers.
"""
from collections import deque
import socket
import struct
import sys
import zlib
try:
import ssl
except:
# If import fails, we don't have SSL support.
pass
from . import constants, errors
from .catch23 import PY2, init_bytearray, struct_unpack
def _strioerror(err):
"""Reformat the IOError error message
This function reformats the IOError error message.
"""
if not err.errno:
return str(err)
return '{errno} {strerr}'.format(errno=err.errno, strerr=err.strerror)
def _prepare_packets(buf, pktnr):
"""Prepare a packet for sending to the MySQL server"""
pkts = []
pllen = len(buf)
maxpktlen = constants.MAX_PACKET_LENGTH
while pllen > maxpktlen:
pkts.append(b'\xff\xff\xff' + struct.pack('<B', pktnr)
+ buf[:maxpktlen])
buf = buf[maxpktlen:]
pllen = len(buf)
pktnr = pktnr + 1
pkts.append(struct.pack('<I', pllen)[0:3]
+ struct.pack('<B', pktnr) + buf)
return pkts
class BaseMySQLSocket(object):
"""Base class for MySQL socket communication
This class should not be used directly but overloaded, changing the
at least the open_connection()-method. Examples of subclasses are
mysql.connector.network.MySQLTCPSocket
mysql.connector.network.MySQLUnixSocket
"""
def __init__(self):
self.sock = None # holds the socket connection
self._connection_timeout = None
self._packet_number = -1
self._compressed_packet_number = -1
self._packet_queue = deque()
self.recvsize = 8192
@property
def next_packet_number(self):
"""Increments the packet number"""
self._packet_number = self._packet_number + 1
if self._packet_number > 255:
self._packet_number = 0
return self._packet_number
@property
def next_compressed_packet_number(self):
"""Increments the compressed packet number"""
self._compressed_packet_number = self._compressed_packet_number + 1
if self._compressed_packet_number > 255:
self._compressed_packet_number = 0
return self._compressed_packet_number
def open_connection(self):
"""Open the socket"""
raise NotImplementedError
def get_address(self):
"""Get the location of the socket"""
raise NotImplementedError
def shutdown(self):
"""Shut down the socket before closing it"""
try:
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
del self._packet_queue
except (socket.error, AttributeError):
pass
def close_connection(self):
"""Close the socket"""
try:
self.sock.close()
del self._packet_queue
except (socket.error, AttributeError):
pass
def send_plain(self, buf, packet_number=None,
compressed_packet_number=None):
"""Send packets to the MySQL server"""
if packet_number is None:
self.next_packet_number # pylint: disable=W0104
else:
self._packet_number = packet_number
packets = _prepare_packets(buf, self._packet_number)
for packet in packets:
try:
if PY2:
self.sock.sendall(buffer(packet)) # pylint: disable=E0602
else:
self.sock.sendall(packet)
except IOError as err:
raise errors.OperationalError(
errno=2055, values=(self.get_address(), _strioerror(err)))
except AttributeError:
raise errors.OperationalError(errno=2006)
send = send_plain
def send_compressed(self, buf, packet_number=None,
compressed_packet_number=None):
"""Send compressed packets to the MySQL server"""
if packet_number is None:
self.next_packet_number # pylint: disable=W0104
else:
self._packet_number = packet_number
if compressed_packet_number is None:
self.next_compressed_packet_number # pylint: disable=W0104
else:
self._compressed_packet_number = compressed_packet_number
pktnr = self._packet_number
pllen = len(buf)
zpkts = []
maxpktlen = constants.MAX_PACKET_LENGTH
if pllen > maxpktlen:
pkts = _prepare_packets(buf, pktnr)
if PY2:
tmpbuf = bytearray()
for pkt in pkts:
tmpbuf += pkt
tmpbuf = buffer(tmpbuf) # pylint: disable=E0602
else:
tmpbuf = b''.join(pkts)
del pkts
zbuf = zlib.compress(tmpbuf[:16384])
header = (struct.pack('<I', len(zbuf))[0:3]
+ struct.pack('<B', self._compressed_packet_number)
+ b'\x00\x40\x00')
if PY2:
header = buffer(header) # pylint: disable=E0602
zpkts.append(header + zbuf)
tmpbuf = tmpbuf[16384:]
pllen = len(tmpbuf)
self.next_compressed_packet_number
while pllen > maxpktlen:
zbuf = zlib.compress(tmpbuf[:maxpktlen])
header = (struct.pack('<I', len(zbuf))[0:3]
+ struct.pack('<B', self._compressed_packet_number)
+ b'\xff\xff\xff')
if PY2:
header = buffer(header) # pylint: disable=E0602
zpkts.append(header + zbuf)
tmpbuf = tmpbuf[maxpktlen:]
pllen = len(tmpbuf)
self.next_compressed_packet_number
if tmpbuf:
zbuf = zlib.compress(tmpbuf)
header = (struct.pack('<I', len(zbuf))[0:3]
+ struct.pack('<B', self._compressed_packet_number)
+ struct.pack('<I', pllen)[0:3])
if PY2:
header = buffer(header) # pylint: disable=E0602
zpkts.append(header + zbuf)
del tmpbuf
else:
pkt = (struct.pack('<I', pllen)[0:3] +
struct.pack('<B', pktnr) + buf)
if PY2:
pkt = buffer(pkt) # pylint: disable=E0602
pllen = len(pkt)
if pllen > 50:
zbuf = zlib.compress(pkt)
zpkts.append(struct.pack('<I', len(zbuf))[0:3]
+ struct.pack('<B', self._compressed_packet_number)
+ struct.pack('<I', pllen)[0:3]
+ zbuf)
else:
header = (struct.pack('<I', pllen)[0:3]
+ struct.pack('<B', self._compressed_packet_number)
+ struct.pack('<I', 0)[0:3])
if PY2:
header = buffer(header) # pylint: disable=E0602
zpkts.append(header + pkt)
for zip_packet in zpkts:
try:
self.sock.sendall(zip_packet)
except IOError as err:
raise errors.OperationalError(
errno=2055, values=(self.get_address(), _strioerror(err)))
except AttributeError:
raise errors.OperationalError(errno=2006)
def recv_plain(self):
"""Receive packets from the MySQL server"""
try:
# Read the header of the MySQL packet, 4 bytes
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]
if PY2:
payload_len = struct.unpack_from(
"<I",
buffer(packet[0:3] + b'\x00'))[0] # pylint: disable=E0602
else:
payload_len = struct.unpack("<I", packet[0:3] + b'\x00')[0]
# Read the payload
rest = payload_len
packet.extend(bytearray(payload_len))
packet_view = memoryview(packet) # pylint: disable=E0602
packet_view = packet_view[4:]
while rest:
read = self.sock.recv_into(packet_view, rest)
if read == 0 and rest > 0:
raise errors.InterfaceError(errno=2013)
packet_view = packet_view[read:]
rest -= read
return packet
except IOError as err:
raise errors.OperationalError(
errno=2055, values=(self.get_address(), _strioerror(err)))
def recv_py26_plain(self):
"""Receive packets from the MySQL server"""
try:
# Read the header of the MySQL packet, 4 bytes
header = bytearray(b'')
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]
payload_len = struct_unpack("<I", header[0:3] + b'\x00')[0]
# Read the payload
rest = payload_len
payload = init_bytearray(b'')
while rest > 0:
chunk = self.sock.recv(rest)
if not chunk:
raise errors.InterfaceError(errno=2013)
payload += chunk
rest = payload_len - len(payload)
return header + payload
except IOError as err:
raise errors.OperationalError(
errno=2055, values=(self.get_address(), _strioerror(err)))
if sys.version_info[0:2] == (2, 6):
recv = recv_py26_plain
recv_plain = recv_py26_plain
else:
recv = recv_plain
def _split_zipped_payload(self, packet_bunch):
"""Split compressed payload"""
while packet_bunch:
if PY2:
payload_length = struct.unpack_from(
"<I",
packet_bunch[0:3] + b'\x00')[0] # pylint: disable=E0602
else:
payload_length = struct.unpack("<I", packet_bunch[0:3] + b'\x00')[0]
self._packet_queue.append(packet_bunch[0:payload_length + 4])
packet_bunch = packet_bunch[payload_length + 4:]
def recv_compressed(self):
"""Receive compressed packets from the MySQL server"""
try:
pkt = self._packet_queue.popleft()
self._packet_number = pkt[3]
return pkt
except IndexError:
pass
header = bytearray(b'')
packets = []
try:
abyte = self.sock.recv(1)
while abyte and len(header) < 7:
header += abyte
abyte = self.sock.recv(1)
while header:
if len(header) < 7:
raise errors.InterfaceError(errno=2013)
# Get length of compressed packet
zip_payload_length = struct_unpack("<I",
header[0:3] + b'\x00')[0]
self._compressed_packet_number = header[3]
# Get payload length before compression
payload_length = struct_unpack("<I", header[4:7] + b'\x00')[0]
zip_payload = init_bytearray(abyte)
while len(zip_payload) < zip_payload_length:
chunk = self.sock.recv(zip_payload_length
- len(zip_payload))
if len(chunk) == 0:
raise errors.InterfaceError(errno=2013)
zip_payload = zip_payload + chunk
# Payload was not compressed
if payload_length == 0:
self._split_zipped_payload(zip_payload)
pkt = self._packet_queue.popleft()
self._packet_number = pkt[3]
return pkt
packets.append((payload_length, zip_payload))
if zip_payload_length <= 16384:
# We received the full compressed packet
break
# Get next compressed packet
header = init_bytearray(b'')
abyte = self.sock.recv(1)
while abyte and len(header) < 7:
header += abyte
abyte = self.sock.recv(1)
except IOError as err:
raise errors.OperationalError(
errno=2055, values=(self.get_address(), _strioerror(err)))
# Compressed packet can contain more than 1 MySQL packets
# We decompress and make one so we can split it up
tmp = init_bytearray(b'')
for payload_length, payload in packets:
# payload_length can not be 0; this was previously handled
if PY2:
tmp += zlib.decompress(buffer(payload)) # pylint: disable=E0602
else:
tmp += zlib.decompress(payload)
self._split_zipped_payload(tmp)
del tmp
try:
pkt = self._packet_queue.popleft()
self._packet_number = pkt[3]
return pkt
except IndexError:
pass
def set_connection_timeout(self, timeout):
"""Set the connection timeout"""
self._connection_timeout = timeout
# pylint: disable=C0103
def switch_to_ssl(self, ca, cert, key, verify_cert=False, cipher=None):
"""Switch the socket to use SSL"""
if not self.sock:
raise errors.InterfaceError(errno=2048)
try:
if verify_cert:
cert_reqs = ssl.CERT_REQUIRED
else:
cert_reqs = ssl.CERT_NONE
self.sock = ssl.wrap_socket(
self.sock, keyfile=key, certfile=cert, ca_certs=ca,
cert_reqs=cert_reqs, do_handshake_on_connect=False,
ssl_version=ssl.PROTOCOL_TLSv1, ciphers=cipher)
self.sock.do_handshake()
except NameError:
raise errors.NotSupportedError(
"Python installation has no SSL support")
except (ssl.SSLError, IOError) as err:
raise errors.InterfaceError(
errno=2055, values=(self.get_address(), _strioerror(err)))
except NotImplementedError as err:
raise errors.InterfaceError(str(err))
# pylint: enable=C0103
class MySQLUnixSocket(BaseMySQLSocket):
"""MySQL socket class using UNIX sockets
Opens a connection through the UNIX socket of the MySQL Server.
"""
def __init__(self, unix_socket='/tmp/mysql.sock'):
super(MySQLUnixSocket, self).__init__()
self.unix_socket = unix_socket
def get_address(self):
return self.unix_socket
def open_connection(self):
try:
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.settimeout(self._connection_timeout)
self.sock.connect(self.unix_socket)
except IOError as err:
raise errors.InterfaceError(
errno=2002, values=(self.get_address(), _strioerror(err)))
except Exception as err:
raise errors.InterfaceError(str(err))
class MySQLTCPSocket(BaseMySQLSocket):
"""MySQL socket class using TCP/IP
Opens a TCP/IP connection to the MySQL Server.
"""
def __init__(self, host='127.0.0.1', port=3306, force_ipv6=False):
super(MySQLTCPSocket, self).__init__()
self.server_host = host
self.server_port = port
self.force_ipv6 = force_ipv6
self._family = 0
def get_address(self):
return "{0}:{1}".format(self.server_host, self.server_port)
def open_connection(self):
"""Open the TCP/IP connection to the MySQL server
"""
# Get address information
addrinfo = [None] * 5
try:
addrinfos = socket.getaddrinfo(self.server_host,
self.server_port,
0, socket.SOCK_STREAM,
socket.SOL_TCP)
# If multiple results we favor IPv4, unless IPv6 was forced.
for info in addrinfos:
if self.force_ipv6 and info[0] == socket.AF_INET6:
addrinfo = info
break
elif info[0] == socket.AF_INET:
addrinfo = info
break
if self.force_ipv6 and addrinfo[0] is None:
raise errors.InterfaceError(
"No IPv6 address found for {0}".format(self.server_host))
if addrinfo[0] is None:
addrinfo = addrinfos[0]
except IOError as err:
raise errors.InterfaceError(
errno=2003, values=(self.get_address(), _strioerror(err)))
else:
(self._family, socktype, proto, _, sockaddr) = addrinfo
# Instanciate the socket and connect
try:
self.sock = socket.socket(self._family, socktype, proto)
self.sock.settimeout(self._connection_timeout)
self.sock.connect(sockaddr)
except IOError as err:
raise errors.InterfaceError(
errno=2003, values=(self.get_address(), _strioerror(err)))
except Exception as err:
raise errors.OperationalError(str(err))

View file

@ -0,0 +1,360 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Implements parser to parse MySQL option files.
"""
import codecs
import io
import os
import re
from .catch23 import PY2
from .constants import DEFAULT_CONFIGURATION, CNX_POOL_ARGS, CNX_FABRIC_ARGS
# pylint: disable=F0401
if PY2:
from ConfigParser import SafeConfigParser, MissingSectionHeaderError
else:
from configparser import (ConfigParser as SafeConfigParser,
MissingSectionHeaderError)
# pylint: enable=F0401
DEFAULT_EXTENSIONS = {
'nt': ('ini', 'cnf'),
'posix': ('cnf',)
}
def read_option_files(**config):
"""
Read option files for connection parameters.
Checks if connection arguments contain option file arguments, and then
reads option files accordingly.
"""
if 'option_files' in config:
try:
if isinstance(config['option_groups'], str):
config['option_groups'] = [config['option_groups']]
groups = config['option_groups']
del config['option_groups']
except KeyError:
groups = ['client', 'connector_python']
if isinstance(config['option_files'], str):
config['option_files'] = [config['option_files']]
option_parser = MySQLOptionsParser(list(config['option_files']),
keep_dashes=False)
del config['option_files']
config_from_file = option_parser.get_groups_as_dict_with_priority(
*groups)
config_options = {}
fabric_options = {}
for group in groups:
try:
for option, value in config_from_file[group].items():
try:
if option == 'socket':
option = 'unix_socket'
if option in CNX_FABRIC_ARGS:
if (option not in fabric_options or
fabric_options[option][1] <= value[1]):
fabric_options[option] = value
continue
if (option not in CNX_POOL_ARGS and
option not in ['fabric', 'failover']):
# pylint: disable=W0104
DEFAULT_CONFIGURATION[option]
# pylint: enable=W0104
if (option not in config_options or
config_options[option][1] <= value[1]):
config_options[option] = value
except KeyError:
if group is 'connector_python':
raise AttributeError("Unsupported argument "
"'{0}'".format(option))
except KeyError:
continue
not_evaluate = ('password', 'passwd')
for option, value in config_options.items():
if option not in config:
try:
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]
if fabric_options:
config['fabric'] = {}
for option, value in fabric_options.items():
try:
# pylint: disable=W0123
config['fabric'][option.split('_', 1)[1]] = eval(value[0])
# pylint: enable=W0123
except (NameError, SyntaxError):
config['fabric'][option.split('_', 1)[1]] = value[0]
return config
class MySQLOptionsParser(SafeConfigParser): # pylint: disable=R0901
"""This class implements methods to parse MySQL option files"""
def __init__(self, files=None, keep_dashes=True): # pylint: disable=W0231
"""Initialize
If defaults is True, default option files are read first
Raises ValueError if defaults is set to True but defaults files
cannot be found.
"""
# Regular expression to allow options with no value(For Python v2.6)
self.OPTCRE = re.compile( # pylint: disable=C0103
r'(?P<option>[^:=\s][^:=]*)'
r'\s*(?:'
r'(?P<vi>[:=])\s*'
r'(?P<value>.*))?$'
)
self._options_dict = {}
if PY2:
SafeConfigParser.__init__(self)
else:
SafeConfigParser.__init__(self, strict=False)
self.default_extension = DEFAULT_EXTENSIONS[os.name]
self.keep_dashes = keep_dashes
if not files:
raise ValueError('files argument should be given')
if isinstance(files, str):
self.files = [files]
else:
self.files = files
self._parse_options(list(self.files))
self._sections = self.get_groups_as_dict()
def optionxform(self, optionstr):
"""Converts option strings
Converts option strings to lower case and replaces dashes(-) with
underscores(_) if keep_dashes variable is set.
"""
if not self.keep_dashes:
optionstr = optionstr.replace('-', '_')
return optionstr.lower()
def _parse_options(self, files):
"""Parse options from files given as arguments.
This method checks for !include or !inculdedir directives and if there
is any, those files included by these directives are also parsed
for options.
Raises ValueError if any of the included or file given in arguments
is not readable.
"""
index = 0
err_msg = "Option file '{0}' being included again in file '{1}'"
for file_ in files:
try:
if file_ in files[index+1:]:
raise ValueError("Same option file '{0}' occurring more "
"than once in the list".format(file_))
with open(file_, 'r') as op_file:
for line in op_file.readlines():
if line.startswith('!includedir'):
_, dir_path = line.split(None, 1)
dir_path = dir_path.strip()
for entry in os.listdir(dir_path):
entry = os.path.join(dir_path, entry)
if entry in files:
raise ValueError(err_msg.format(
entry, file_))
if (os.path.isfile(entry) and
entry.endswith(self.default_extension)):
files.insert(index+1, entry)
elif line.startswith('!include'):
_, filename = line.split(None, 1)
filename = filename.strip()
if filename in files:
raise ValueError(err_msg.format(
filename, file_))
files.insert(index+1, filename)
index += 1
except (IOError, OSError) as exc:
raise ValueError("Failed reading file '{0}': {1}".format(
file_, str(exc)))
read_files = self.read(files)
not_read_files = set(files) - set(read_files)
if not_read_files:
raise ValueError("File(s) {0} could not be read.".format(
', '.join(not_read_files)))
def read(self, filenames): # pylint: disable=W0221
"""Read and parse a filename or a list of filenames.
Overridden from ConfigParser and modified so as to allow options
which are not inside any section header
Return list of successfully read files.
"""
if isinstance(filenames, str):
filenames = [filenames]
read_ok = []
for priority, filename in enumerate(filenames):
try:
out_file = io.StringIO()
for line in codecs.open(filename, encoding='utf-8'):
line = line.strip()
match_obj = self.OPTCRE.match(line)
if not self.SECTCRE.match(line) and match_obj:
optname, delimiter, optval = match_obj.group('option',
'vi',
'value')
if optname and not optval and not delimiter:
out_file.write(line + "=\n")
else:
out_file.write(line + '\n')
else:
out_file.write(line + '\n')
out_file.seek(0)
except IOError:
continue
try:
self._read(out_file, filename)
for group in self._sections.keys():
try:
self._options_dict[group]
except KeyError:
self._options_dict[group] = {}
for option, value in self._sections[group].items():
self._options_dict[group][option] = (value, priority)
self._sections = self._dict()
except MissingSectionHeaderError:
self._read(out_file, filename)
out_file.close()
read_ok.append(filename)
return read_ok
def get_groups(self, *args):
"""Returns options as a dictionary.
Returns options from all the groups specified as arguments, returns
the options from all groups if no argument provided. Options are
overridden when they are found in the next group.
Returns a dictionary
"""
if len(args) == 0:
args = self._options_dict.keys()
options = {}
for group in args:
try:
for option, value in self._options_dict[group].items():
if option not in options or options[option][1] <= value[1]:
options[option] = value
except KeyError:
pass
for key in options.keys():
if key == '__name__' or key.startswith('!'):
del options[key]
else:
options[key] = options[key][0]
return options
def get_groups_as_dict_with_priority(self, *args): # pylint: disable=C0103
"""Returns options as dictionary of dictionaries.
Returns options from all the groups specified as arguments. For each
group the option are contained in a dictionary. The order in which
the groups are specified is unimportant. Also options are not
overridden in between the groups.
The value is a tuple with two elements, first being the actual value
and second is the priority of the value which is higher for a value
read from a higher priority file.
Returns an dictionary of dictionaries
"""
if len(args) == 0:
args = self._options_dict.keys()
options = dict()
for group in args:
try:
options[group] = dict(self._options_dict[group])
except KeyError:
pass
for group in options.keys():
for key in options[group].keys():
if key == '__name__' or key.startswith('!'):
del options[group][key]
return options
def get_groups_as_dict(self, *args):
"""Returns options as dictionary of dictionaries.
Returns options from all the groups specified as arguments. For each
group the option are contained in a dictionary. The order in which
the groups are specified is unimportant. Also options are not
overridden in between the groups.
Returns an dictionary of dictionaries
"""
if len(args) == 0:
args = self._options_dict.keys()
options = dict()
for group in args:
try:
options[group] = dict(self._options_dict[group])
except KeyError:
pass
for group in options.keys():
for key in options[group].keys():
if key == '__name__' or key.startswith('!'):
del options[group][key]
else:
options[group][key] = options[group][key][0]
return options

View file

@ -0,0 +1,353 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Implementing pooling of connections to MySQL servers.
"""
import re
from uuid import uuid4
# pylint: disable=F0401
try:
import queue
except ImportError:
# Python v2
import Queue as queue
# pylint: enable=F0401
import threading
from . import errors
from .connection import MySQLConnection
CONNECTION_POOL_LOCK = threading.RLock()
CNX_POOL_MAXSIZE = 32
CNX_POOL_MAXNAMESIZE = 64
CNX_POOL_NAMEREGEX = re.compile(r'[^a-zA-Z0-9._:\-*$#]')
def generate_pool_name(**kwargs):
"""Generate a pool name
This function takes keyword arguments, usually the connection
arguments for MySQLConnection, and tries to generate a name for
a pool.
Raises PoolError when no name can be generated.
Returns a string.
"""
parts = []
for key in ('host', 'port', 'user', 'database'):
try:
parts.append(str(kwargs[key]))
except KeyError:
pass
if not parts:
raise errors.PoolError(
"Failed generating pool name; specify pool_name")
return '_'.join(parts)
class PooledMySQLConnection(object):
"""Class holding a MySQL Connection in a pool
PooledMySQLConnection is used by MySQLConnectionPool to return an
instance holding a MySQL connection. It works like a MySQLConnection
except for methods like close() and config().
The close()-method will add the connection back to the pool rather
than disconnecting from the MySQL server.
Configuring the connection have to be done through the MySQLConnectionPool
method set_config(). Using config() on pooled connection will raise a
PoolError.
"""
def __init__(self, pool, cnx):
"""Initialize
The pool argument must be an instance of MySQLConnectionPoll. cnx
if an instance of MySQLConnection.
"""
if not isinstance(pool, MySQLConnectionPool):
raise AttributeError(
"pool should be a MySQLConnectionPool")
if not isinstance(cnx, MySQLConnection):
raise AttributeError(
"cnx should be a MySQLConnection")
self._cnx_pool = pool
self._cnx = cnx
def __getattr__(self, attr):
"""Calls attributes of the MySQLConnection instance"""
return getattr(self._cnx, attr)
def close(self):
"""Do not close, but add connection back to pool
The close() method does not close the connection with the
MySQL server. The connection is added back to the pool so it
can be reused.
When the pool is configured to reset the session, the session
state will be cleared by re-authenticating the user.
"""
cnx = self._cnx
if self._cnx_pool.reset_session:
cnx.reset_session()
self._cnx_pool.add_connection(cnx)
self._cnx = None
def config(self, **kwargs):
"""Configuration is done through the pool"""
raise errors.PoolError(
"Configuration for pooled connections should "
"be done through the pool itself."
)
@property
def pool_name(self):
"""Return the name of the connection pool"""
return self._cnx_pool.pool_name
class MySQLConnectionPool(object):
"""Class defining a pool of MySQL connections"""
def __init__(self, pool_size=5, pool_name=None, pool_reset_session=True,
**kwargs):
"""Initialize
Initialize a MySQL connection pool with a maximum number of
connections set to pool_size. The rest of the keywords
arguments, kwargs, are configuration arguments for MySQLConnection
instances.
"""
self._pool_size = None
self._pool_name = None
self._reset_session = pool_reset_session
self._set_pool_size(pool_size)
self._set_pool_name(pool_name or generate_pool_name(**kwargs))
self._cnx_config = {}
self._cnx_queue = queue.Queue(self._pool_size)
self._config_version = uuid4()
if kwargs:
self.set_config(**kwargs)
cnt = 0
while cnt < self._pool_size:
self.add_connection()
cnt += 1
@property
def pool_name(self):
"""Return the name of the connection pool"""
return self._pool_name
@property
def pool_size(self):
"""Return number of connections managed by the pool"""
return self._pool_size
@property
def reset_session(self):
"""Return whether to reset session"""
return self._reset_session
def set_config(self, **kwargs):
"""Set the connection configuration for MySQLConnection instances
This method sets the configuration used for creating MySQLConnection
instances. See MySQLConnection for valid connection arguments.
Raises PoolError when a connection argument is not valid, missing
or not supported by MySQLConnection.
"""
if not kwargs:
return
with CONNECTION_POOL_LOCK:
try:
test_cnx = MySQLConnection()
test_cnx.config(**kwargs)
self._cnx_config = kwargs
self._config_version = uuid4()
except AttributeError as err:
raise errors.PoolError(
"Connection configuration not valid: {0}".format(err))
def _set_pool_size(self, pool_size):
"""Set the size of the pool
This method sets the size of the pool but it will not resize the pool.
Raises an AttributeError when the pool_size is not valid. Invalid size
is 0, negative or higher than pooling.CNX_POOL_MAXSIZE.
"""
if pool_size <= 0 or pool_size > CNX_POOL_MAXSIZE:
raise AttributeError(
"Pool size should be higher than 0 and "
"lower or equal to {0}".format(CNX_POOL_MAXSIZE))
self._pool_size = pool_size
def _set_pool_name(self, pool_name):
r"""Set the name of the pool
This method checks the validity and sets the name of the pool.
Raises an AttributeError when pool_name contains illegal characters
([^a-zA-Z0-9._\-*$#]) or is longer than pooling.CNX_POOL_MAXNAMESIZE.
"""
if CNX_POOL_NAMEREGEX.search(pool_name):
raise AttributeError(
"Pool name '{0}' contains illegal characters".format(pool_name))
if len(pool_name) > CNX_POOL_MAXNAMESIZE:
raise AttributeError(
"Pool name '{0}' is too long".format(pool_name))
self._pool_name = pool_name
def _queue_connection(self, cnx):
"""Put connection back in the queue
This method is putting a connection back in the queue. It will not
acquire a lock as the methods using _queue_connection() will have it
set.
Raises PoolError on errors.
"""
if not isinstance(cnx, MySQLConnection):
raise errors.PoolError(
"Connection instance not subclass of MySQLConnection.")
try:
self._cnx_queue.put(cnx, block=False)
except queue.Full:
errors.PoolError("Failed adding connection; queue is full")
def add_connection(self, cnx=None):
"""Add a connection to the pool
This method instantiates a MySQLConnection using the configuration
passed when initializing the MySQLConnectionPool instance or using
the set_config() method.
If cnx is a MySQLConnection instance, it will be added to the
queue.
Raises PoolError when no configuration is set, when no more
connection can be added (maximum reached) or when the connection
can not be instantiated.
"""
with CONNECTION_POOL_LOCK:
if not self._cnx_config:
raise errors.PoolError(
"Connection configuration not available")
if self._cnx_queue.full():
raise errors.PoolError(
"Failed adding connection; queue is full")
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
else:
if not isinstance(cnx, MySQLConnection):
raise errors.PoolError(
"Connection instance not subclass of MySQLConnection.")
self._queue_connection(cnx)
def get_connection(self):
"""Get a connection from the pool
This method returns an PooledMySQLConnection instance which
has a reference to the pool that created it, and the next available
MySQL connection.
When the MySQL connection is not connect, a reconnect is attempted.
Raises PoolError on errors.
Returns a PooledMySQLConnection instance.
"""
with CONNECTION_POOL_LOCK:
try:
cnx = self._cnx_queue.get(block=False)
except queue.Empty:
raise errors.PoolError(
"Failed getting connection; pool exhausted")
# pylint: disable=W0201,W0212
if not cnx.is_connected() \
or self._config_version != cnx._pool_config_version:
cnx.config(**self._cnx_config)
try:
cnx.reconnect()
except errors.InterfaceError:
# Failed to reconnect, give connection back to pool
self._queue_connection(cnx)
raise
cnx._pool_config_version = self._config_version
# pylint: enable=W0201,W0212
return PooledMySQLConnection(self, cnx)
def _remove_connections(self):
"""Close all connections
This method closes all connections. It returns the number
of connections it closed.
Used mostly for tests.
Returns int.
"""
with CONNECTION_POOL_LOCK:
cnt = 0
cnxq = self._cnx_queue
while cnxq.qsize():
try:
cnx = cnxq.get(block=False)
cnx.disconnect()
cnt += 1
except queue.Empty:
return cnt
except errors.PoolError:
raise
except errors.Error:
# Any other error when closing means connection is closed
pass
return cnt

View file

@ -0,0 +1,732 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Implements the MySQL Client/Server protocol
"""
import struct
import datetime
from decimal import Decimal
from .constants import (
FieldFlag, ServerCmd, FieldType, ClientFlag, MAX_MYSQL_TABLE_COLUMNS)
from . import errors, utils
from .authentication import get_auth_plugin
from .catch23 import PY2, struct_unpack
from .errors import get_exception
class MySQLProtocol(object):
"""Implements MySQL client/server protocol
Create and parses MySQL packets.
"""
def _connect_with_db(self, client_flags, database):
"""Prepare database string for handshake response"""
if client_flags & ClientFlag.CONNECT_WITH_DB and database:
return database.encode('utf8') + b'\x00'
return b'\x00'
def _auth_response(self, client_flags, username, password, database,
auth_plugin, auth_data, ssl_enabled):
"""Prepare the authentication response"""
if not password:
return b'\x00'
try:
auth = get_auth_plugin(auth_plugin)(
auth_data,
username=username, password=password, database=database,
ssl_enabled=ssl_enabled)
plugin_auth_response = auth.auth_response()
except (TypeError, errors.InterfaceError) as exc:
raise errors.ProgrammingError(
"Failed authentication: {0}".format(str(exc)))
if client_flags & ClientFlag.SECURE_CONNECTION:
resplen = len(plugin_auth_response)
auth_response = struct.pack('<B', resplen) + plugin_auth_response
else:
auth_response = plugin_auth_response + b'\x00'
return auth_response
def make_auth(self, handshake, username=None, password=None, database=None,
charset=33, client_flags=0,
max_allowed_packet=1073741824, ssl_enabled=False,
auth_plugin=None):
"""Make a MySQL Authentication packet"""
try:
auth_data = handshake['auth_data']
auth_plugin = auth_plugin or handshake['auth_plugin']
except (TypeError, KeyError) as exc:
raise errors.ProgrammingError(
"Handshake misses authentication info ({0})".format(exc))
if not username:
username = b''
try:
username_bytes = username.encode('utf8') # pylint: disable=E1103
except AttributeError:
# Username is already bytes
username_bytes = username
packet = struct.pack('<IIB{filler}{usrlen}sx'.format(
filler='x' * 23, usrlen=len(username_bytes)),
client_flags, max_allowed_packet, charset,
username_bytes)
packet += self._auth_response(client_flags, username, password,
database,
auth_plugin,
auth_data, ssl_enabled)
packet += self._connect_with_db(client_flags, database)
if client_flags & ClientFlag.PLUGIN_AUTH:
packet += auth_plugin.encode('utf8') + b'\x00'
return packet
def make_auth_ssl(self, charset=33, client_flags=0,
max_allowed_packet=1073741824):
"""Make a SSL authentication packet"""
return utils.int4store(client_flags) + \
utils.int4store(max_allowed_packet) + \
utils.int1store(charset) + \
b'\x00' * 23
def make_command(self, command, argument=None):
"""Make a MySQL packet containing a command"""
data = utils.int1store(command)
if argument is not None:
data += argument
return data
def make_change_user(self, handshake, username=None, password=None,
database=None, charset=33, client_flags=0,
ssl_enabled=False, auth_plugin=None):
"""Make a MySQL packet with the Change User command"""
try:
auth_data = handshake['auth_data']
auth_plugin = auth_plugin or handshake['auth_plugin']
except (TypeError, KeyError) as exc:
raise errors.ProgrammingError(
"Handshake misses authentication info ({0})".format(exc))
if not username:
username = b''
try:
username_bytes = username.encode('utf8') # pylint: disable=E1103
except AttributeError:
# Username is already bytes
username_bytes = username
packet = struct.pack('<B{usrlen}sx'.format(usrlen=len(username_bytes)),
ServerCmd.CHANGE_USER, username_bytes)
packet += self._auth_response(client_flags, username, password,
database,
auth_plugin,
auth_data, ssl_enabled)
packet += self._connect_with_db(client_flags, database)
packet += struct.pack('<H', charset)
if client_flags & ClientFlag.PLUGIN_AUTH:
packet += auth_plugin.encode('utf8') + b'\x00'
return packet
def parse_handshake(self, packet):
"""Parse a MySQL Handshake-packet"""
res = {}
res['protocol'] = struct_unpack('<xxxxB', packet[0:5])[0]
(packet, res['server_version_original']) = utils.read_string(
packet[5:], end=b'\x00')
(res['server_threadid'],
auth_data1,
capabilities1,
res['charset'],
res['server_status'],
capabilities2,
auth_data_length
) = struct_unpack('<I8sx2sBH2sBxxxxxxxxxx', packet[0:31])
res['server_version_original'] = res['server_version_original'].decode()
packet = packet[31:]
capabilities = utils.intread(capabilities1 + capabilities2)
auth_data2 = b''
if capabilities & ClientFlag.SECURE_CONNECTION:
size = min(13, auth_data_length - 8) if auth_data_length else 13
auth_data2 = packet[0:size]
packet = packet[size:]
if auth_data2[-1] == 0:
auth_data2 = auth_data2[:-1]
if capabilities & ClientFlag.PLUGIN_AUTH:
if (b'\x00' not in packet
and res['server_version_original'].startswith("5.5.8")):
# MySQL server 5.5.8 has a bug where end byte is not send
(packet, res['auth_plugin']) = (b'', packet)
else:
(packet, res['auth_plugin']) = utils.read_string(
packet, end=b'\x00')
res['auth_plugin'] = res['auth_plugin'].decode('utf-8')
else:
res['auth_plugin'] = 'mysql_native_password'
res['auth_data'] = auth_data1 + auth_data2
res['capabilities'] = capabilities
return res
def parse_ok(self, packet):
"""Parse a MySQL OK-packet"""
if not packet[4] == 0:
raise errors.InterfaceError("Failed parsing OK packet (invalid).")
ok_packet = {}
try:
ok_packet['field_count'] = struct_unpack('<xxxxB', packet[0:5])[0]
(packet, ok_packet['affected_rows']) = utils.read_lc_int(packet[5:])
(packet, ok_packet['insert_id']) = utils.read_lc_int(packet)
(ok_packet['status_flag'],
ok_packet['warning_count']) = struct_unpack('<HH', packet[0:4])
packet = packet[4:]
if packet:
(packet, ok_packet['info_msg']) = utils.read_lc_string(packet)
ok_packet['info_msg'] = ok_packet['info_msg'].decode('utf-8')
except ValueError:
raise errors.InterfaceError("Failed parsing OK packet.")
return ok_packet
def parse_column_count(self, packet):
"""Parse a MySQL packet with the number of columns in result set"""
try:
count = utils.read_lc_int(packet[4:])[1]
if count > MAX_MYSQL_TABLE_COLUMNS:
return None
return count
except (struct.error, ValueError):
raise errors.InterfaceError("Failed parsing column count")
def parse_column(self, packet, charset='utf-8'):
"""Parse a MySQL column-packet"""
(packet, _) = utils.read_lc_string(packet[4:]) # catalog
(packet, _) = utils.read_lc_string(packet) # db
(packet, _) = utils.read_lc_string(packet) # table
(packet, _) = utils.read_lc_string(packet) # org_table
(packet, name) = utils.read_lc_string(packet) # name
(packet, _) = utils.read_lc_string(packet) # org_name
try:
(_, _, field_type,
flags, _) = struct_unpack('<xHIBHBxx', packet)
except struct.error:
raise errors.InterfaceError("Failed parsing column information")
return (
name.decode(charset),
field_type,
None, # display_size
None, # internal_size
None, # precision
None, # scale
~flags & FieldFlag.NOT_NULL, # null_ok
flags, # MySQL specific
)
def parse_eof(self, packet):
"""Parse a MySQL EOF-packet"""
if packet[4] == 0:
# EOF packet deprecation
return self.parse_ok(packet)
err_msg = "Failed parsing EOF packet."
res = {}
try:
unpacked = struct_unpack('<xxxBBHH', packet)
except struct.error:
raise errors.InterfaceError(err_msg)
if not (unpacked[1] == 254 and len(packet) <= 9):
raise errors.InterfaceError(err_msg)
res['warning_count'] = unpacked[2]
res['status_flag'] = unpacked[3]
return res
def parse_statistics(self, packet, with_header=True):
"""Parse the statistics packet"""
errmsg = "Failed getting COM_STATISTICS information"
res = {}
# Information is separated by 2 spaces
if with_header:
pairs = packet[4:].split(b'\x20\x20')
else:
pairs = packet.split(b'\x20\x20')
for pair in pairs:
try:
(lbl, val) = [v.strip() for v in pair.split(b':', 2)]
except:
raise errors.InterfaceError(errmsg)
# It's either an integer or a decimal
lbl = lbl.decode('utf-8')
try:
res[lbl] = int(val)
except:
try:
res[lbl] = Decimal(val.decode('utf-8'))
except:
raise errors.InterfaceError(
"{0} ({1}:{2}).".format(errmsg, lbl, val))
return res
def read_text_result(self, sock, version, count=1):
"""Read MySQL text result
Reads all or given number of rows from the socket.
Returns a tuple with 2 elements: a list with all rows and
the EOF packet.
"""
rows = []
eof = None
rowdata = None
i = 0
eof57 = version >= (5, 7, 5)
while True:
if eof or i == count:
break
packet = sock.recv()
if packet.startswith(b'\xff\xff\xff'):
datas = [packet[4:]]
packet = sock.recv()
while packet.startswith(b'\xff\xff\xff'):
datas.append(packet[4:])
packet = sock.recv()
datas.append(packet[4:])
rowdata = utils.read_lc_string_list(bytearray(b'').join(datas))
elif (packet[4] == 254 and packet[0] < 7):
eof = self.parse_eof(packet)
rowdata = None
elif eof57 and (packet[4] == 0 and packet[0] > 9):
# EOF deprecation: make sure we catch it whether flag is set or not
eof = self.parse_ok(packet)
rowdata = None
else:
eof = None
rowdata = utils.read_lc_string_list(packet[4:])
if eof is None and rowdata is not None:
rows.append(rowdata)
elif eof is None and rowdata is None:
raise get_exception(packet)
i += 1
return rows, eof
def _parse_binary_integer(self, packet, field):
"""Parse an integer from a binary packet"""
if field[1] == FieldType.TINY:
format_ = 'b'
length = 1
elif field[1] == FieldType.SHORT:
format_ = 'h'
length = 2
elif field[1] in (FieldType.INT24, FieldType.LONG):
format_ = 'i'
length = 4
elif field[1] == FieldType.LONGLONG:
format_ = 'q'
length = 8
if field[7] & FieldFlag.UNSIGNED:
format_ = format_.upper()
return (packet[length:], struct_unpack(format_, packet[0:length])[0])
def _parse_binary_float(self, packet, field):
"""Parse a float/double from a binary packet"""
if field[1] == FieldType.DOUBLE:
length = 8
format_ = 'd'
else:
length = 4
format_ = 'f'
return (packet[length:], struct_unpack(format_, packet[0:length])[0])
def _parse_binary_timestamp(self, packet, field):
"""Parse a timestamp from a binary packet"""
length = packet[0]
value = None
if length == 4:
value = datetime.date(
year=struct_unpack('H', packet[1:3])[0],
month=packet[3],
day=packet[4])
elif length >= 7:
mcs = 0
if length == 11:
mcs = struct_unpack('I', packet[8:length + 1])[0]
value = datetime.datetime(
year=struct_unpack('H', packet[1:3])[0],
month=packet[3],
day=packet[4],
hour=packet[5],
minute=packet[6],
second=packet[7],
microsecond=mcs)
return (packet[length + 1:], value)
def _parse_binary_time(self, packet, field):
"""Parse a time value from a binary packet"""
length = packet[0]
data = packet[1:length + 1]
mcs = 0
if length > 8:
mcs = struct_unpack('I', data[8:])[0]
days = struct_unpack('I', data[1:5])[0]
if data[0] == 1:
days *= -1
tmp = datetime.timedelta(days=days,
seconds=data[7],
microseconds=mcs,
minutes=data[6],
hours=data[5])
return (packet[length + 1:], tmp)
def _parse_binary_values(self, fields, packet):
"""Parse values from a binary result packet"""
null_bitmap_length = (len(fields) + 7 + 2) // 8
null_bitmap = [int(i) for i in packet[0:null_bitmap_length]]
packet = packet[null_bitmap_length:]
values = []
for pos, field in enumerate(fields):
if null_bitmap[int((pos+2)/8)] & (1 << (pos + 2) % 8):
values.append(None)
continue
elif field[1] in (FieldType.TINY, FieldType.SHORT,
FieldType.INT24,
FieldType.LONG, FieldType.LONGLONG):
(packet, value) = self._parse_binary_integer(packet, field)
values.append(value)
elif field[1] in (FieldType.DOUBLE, FieldType.FLOAT):
(packet, value) = self._parse_binary_float(packet, field)
values.append(value)
elif field[1] in (FieldType.DATETIME, FieldType.DATE,
FieldType.TIMESTAMP):
(packet, value) = self._parse_binary_timestamp(packet, field)
values.append(value)
elif field[1] == FieldType.TIME:
(packet, value) = self._parse_binary_time(packet, field)
values.append(value)
else:
(packet, value) = utils.read_lc_string(packet)
values.append(value)
return tuple(values)
def read_binary_result(self, sock, columns, count=1):
"""Read MySQL binary protocol result
Reads all or given number of binary resultset rows from the socket.
"""
rows = []
eof = None
values = None
i = 0
while True:
if eof is not None:
break
if i == count:
break
packet = sock.recv()
if packet[4] == 254:
eof = self.parse_eof(packet)
values = None
elif packet[4] == 0:
eof = None
values = self._parse_binary_values(columns, packet[5:])
if eof is None and values is not None:
rows.append(values)
elif eof is None and values is None:
raise get_exception(packet)
i += 1
return (rows, eof)
def parse_binary_prepare_ok(self, packet):
"""Parse a MySQL Binary Protocol OK packet"""
if not packet[4] == 0:
raise errors.InterfaceError("Failed parsing Binary OK packet")
ok_pkt = {}
try:
(packet, ok_pkt['statement_id']) = utils.read_int(packet[5:], 4)
(packet, ok_pkt['num_columns']) = utils.read_int(packet, 2)
(packet, ok_pkt['num_params']) = utils.read_int(packet, 2)
packet = packet[1:] # Filler 1 * \x00
(packet, ok_pkt['warning_count']) = utils.read_int(packet, 2)
except ValueError:
raise errors.InterfaceError("Failed parsing Binary OK packet")
return ok_pkt
def _prepare_binary_integer(self, value):
"""Prepare an integer for the MySQL binary protocol"""
field_type = None
flags = 0
if value < 0:
if value >= -128:
format_ = 'b'
field_type = FieldType.TINY
elif value >= -32768:
format_ = 'h'
field_type = FieldType.SHORT
elif value >= -2147483648:
format_ = 'i'
field_type = FieldType.LONG
else:
format_ = 'q'
field_type = FieldType.LONGLONG
else:
flags = 128
if value <= 255:
format_ = 'B'
field_type = FieldType.TINY
elif value <= 65535:
format_ = 'H'
field_type = FieldType.SHORT
elif value <= 4294967295:
format_ = 'I'
field_type = FieldType.LONG
else:
field_type = FieldType.LONGLONG
format_ = 'Q'
return (struct.pack(format_, value), field_type, flags)
def _prepare_binary_timestamp(self, value):
"""Prepare a timestamp object for the MySQL binary protocol
This method prepares a timestamp of type datetime.datetime or
datetime.date for sending over the MySQL binary protocol.
A tuple is returned with the prepared value and field type
as elements.
Raises ValueError when the argument value is of invalid type.
Returns a tuple.
"""
if isinstance(value, datetime.datetime):
field_type = FieldType.DATETIME
elif isinstance(value, datetime.date):
field_type = FieldType.DATE
else:
raise ValueError(
"Argument must a datetime.datetime or datetime.date")
packed = (utils.int2store(value.year) +
utils.int1store(value.month) +
utils.int1store(value.day))
if isinstance(value, datetime.datetime):
packed = (packed + utils.int1store(value.hour) +
utils.int1store(value.minute) +
utils.int1store(value.second))
if value.microsecond > 0:
packed += utils.int4store(value.microsecond)
packed = utils.int1store(len(packed)) + packed
return (packed, field_type)
def _prepare_binary_time(self, value):
"""Prepare a time object for the MySQL binary protocol
This method prepares a time object of type datetime.timedelta or
datetime.time for sending over the MySQL binary protocol.
A tuple is returned with the prepared value and field type
as elements.
Raises ValueError when the argument value is of invalid type.
Returns a tuple.
"""
if not isinstance(value, (datetime.timedelta, datetime.time)):
raise ValueError(
"Argument must a datetime.timedelta or datetime.time")
field_type = FieldType.TIME
negative = 0
mcs = None
packed = b''
if isinstance(value, datetime.timedelta):
if value.days < 0:
negative = 1
(hours, remainder) = divmod(value.seconds, 3600)
(mins, secs) = divmod(remainder, 60)
packed += (utils.int4store(abs(value.days)) +
utils.int1store(hours) +
utils.int1store(mins) +
utils.int1store(secs))
mcs = value.microseconds
else:
packed += (utils.int4store(0) +
utils.int1store(value.hour) +
utils.int1store(value.minute) +
utils.int1store(value.second))
mcs = value.microsecond
if mcs:
packed += utils.int4store(mcs)
packed = utils.int1store(negative) + packed
packed = utils.int1store(len(packed)) + packed
return (packed, field_type)
def _prepare_stmt_send_long_data(self, statement, param, data):
"""Prepare long data for prepared statements
Returns a string.
"""
packet = (
utils.int4store(statement) +
utils.int2store(param) +
data)
return packet
def make_stmt_execute(self, statement_id, data=(), parameters=(),
flags=0, long_data_used=None, charset='utf8'):
"""Make a MySQL packet with the Statement Execute command"""
iteration_count = 1
null_bitmap = [0] * ((len(data) + 7) // 8)
values = []
types = []
packed = b''
if long_data_used is None:
long_data_used = {}
if parameters and data:
if len(data) != len(parameters):
raise errors.InterfaceError(
"Failed executing prepared statement: data values does not"
" match number of parameters")
for pos, _ in enumerate(parameters):
value = data[pos]
flags = 0
if value is None:
null_bitmap[(pos // 8)] |= 1 << (pos % 8)
types.append(utils.int1store(FieldType.NULL) +
utils.int1store(flags))
continue
elif pos in long_data_used:
if long_data_used[pos][0]:
# We suppose binary data
field_type = FieldType.BLOB
else:
# We suppose text data
field_type = FieldType.STRING
elif isinstance(value, int):
(packed, field_type,
flags) = self._prepare_binary_integer(value)
values.append(packed)
elif isinstance(value, str):
if PY2:
values.append(utils.lc_int(len(value)) +
value)
else:
value = value.encode(charset)
values.append(
utils.lc_int(len(value)) + value)
field_type = FieldType.VARCHAR
elif isinstance(value, bytes):
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.lc_int(len(value)) + value)
field_type = FieldType.VARCHAR
elif isinstance(value, Decimal):
values.append(
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))
field_type = FieldType.DOUBLE
elif isinstance(value, (datetime.datetime, datetime.date)):
(packed, field_type) = self._prepare_binary_timestamp(
value)
values.append(packed)
elif isinstance(value, (datetime.timedelta, datetime.time)):
(packed, field_type) = self._prepare_binary_time(value)
values.append(packed)
else:
raise errors.ProgrammingError(
"MySQL binary protocol can not handle "
"'{classname}' objects".format(
classname=value.__class__.__name__))
types.append(utils.int1store(field_type) +
utils.int1store(flags))
packet = (
utils.int4store(statement_id) +
utils.int1store(flags) +
utils.int4store(iteration_count) +
b''.join([struct.pack('B', bit) for bit in null_bitmap]) +
utils.int1store(1)
)
for a_type in types:
packet += a_type
for a_value in values:
packet += a_value
return packet
def parse_auth_switch_request(self, packet):
"""Parse a MySQL AuthSwitchRequest-packet"""
if not packet[4] == 254:
raise errors.InterfaceError(
"Failed parsing AuthSwitchRequest packet")
(packet, plugin_name) = utils.read_string(packet[5:], end=b'\x00')
if packet and packet[-1] == 0:
packet = packet[:-1]
return plugin_name.decode('utf8'), packet
def parse_auth_more_data(self, packet):
"""Parse a MySQL AuthMoreData-packet"""
if not packet[4] == 1:
raise errors.InterfaceError(
"Failed parsing AuthMoreData packet")
return packet[5:]

View file

@ -0,0 +1,338 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Utilities
"""
from __future__ import print_function
__MYSQL_DEBUG__ = False
import struct
from .catch23 import struct_unpack
def intread(buf):
"""Unpacks the given buffer to an integer"""
try:
if isinstance(buf, int):
return buf
length = len(buf)
if length == 1:
return buf[0]
elif length <= 4:
tmp = buf + b'\x00'*(4-length)
return struct_unpack('<I', tmp)[0]
else:
tmp = buf + b'\x00'*(8-length)
return struct_unpack('<Q', tmp)[0]
except:
raise
def int1store(i):
"""
Takes an unsigned byte (1 byte) and packs it as a bytes-object.
Returns string.
"""
if i < 0 or i > 255:
raise ValueError('int1store requires 0 <= i <= 255')
else:
return bytearray(struct.pack('<B', i))
def int2store(i):
"""
Takes an unsigned short (2 bytes) and packs it as a bytes-object.
Returns string.
"""
if i < 0 or i > 65535:
raise ValueError('int2store requires 0 <= i <= 65535')
else:
return bytearray(struct.pack('<H', i))
def int3store(i):
"""
Takes an unsigned integer (3 bytes) and packs it as a bytes-object.
Returns string.
"""
if i < 0 or i > 16777215:
raise ValueError('int3store requires 0 <= i <= 16777215')
else:
return bytearray(struct.pack('<I', i)[0:3])
def int4store(i):
"""
Takes an unsigned integer (4 bytes) and packs it as a bytes-object.
Returns string.
"""
if i < 0 or i > 4294967295:
raise ValueError('int4store requires 0 <= i <= 4294967295')
else:
return bytearray(struct.pack('<I', i))
def int8store(i):
"""
Takes an unsigned integer (8 bytes) and packs it as string.
Returns string.
"""
if i < 0 or i > 18446744073709551616:
raise ValueError('int8store requires 0 <= i <= 2^64')
else:
return bytearray(struct.pack('<Q', i))
def intstore(i):
"""
Takes an unsigned integers and packs it as a bytes-object.
This function uses int1store, int2store, int3store,
int4store or int8store depending on the integer value.
returns string.
"""
if i < 0 or i > 18446744073709551616:
raise ValueError('intstore requires 0 <= i <= 2^64')
if i <= 255:
formed_string = int1store
elif i <= 65535:
formed_string = int2store
elif i <= 16777215:
formed_string = int3store
elif i <= 4294967295:
formed_string = int4store
else:
formed_string = int8store
return formed_string(i)
def lc_int(i):
"""
Takes an unsigned integer and packs it as bytes,
with the information of how much bytes the encoded int takes.
"""
if i < 0 or i > 18446744073709551616:
raise ValueError('Requires 0 <= i <= 2^64')
if i < 251:
return bytearray(struct.pack('<B', i))
elif i <= 65535:
return b'\xfc' + bytearray(struct.pack('<H', i))
elif i <= 16777215:
return b'\xfd' + bytearray(struct.pack('<I', i)[0:3])
else:
return b'\xfe' + bytearray(struct.pack('<Q', i))
def read_bytes(buf, size):
"""
Reads bytes from a buffer.
Returns a tuple with buffer less the read bytes, and the bytes.
"""
res = buf[0:size]
return (buf[size:], res)
def read_lc_string(buf):
"""
Takes a buffer and reads a length coded string from the start.
This is how Length coded strings work
If the string is 250 bytes long or smaller, then it looks like this:
<-- 1b -->
+----------+-------------------------
| length | a string goes here
+----------+-------------------------
If the string is bigger than 250, then it looks like this:
<- 1b -><- 2/3/8 ->
+------+-----------+-------------------------
| type | length | a string goes here
+------+-----------+-------------------------
if type == \xfc:
length is code in next 2 bytes
elif type == \xfd:
length is code in next 3 bytes
elif type == \xfe:
length is code in next 8 bytes
NULL has a special value. If the buffer starts with \xfb then
it's a NULL and we return None as value.
Returns a tuple (trucated buffer, bytes).
"""
if buf[0] == 251: # \xfb
# NULL value
return (buf[1:], None)
length = lsize = 0
fst = buf[0]
if fst <= 250: # \xFA
length = fst
return (buf[1 + length:], buf[1:length + 1])
elif fst == 252:
lsize = 2
elif fst == 253:
lsize = 3
if fst == 254:
lsize = 8
length = intread(buf[1:lsize + 1])
return (buf[lsize + length + 1:], buf[lsize + 1:length + lsize + 1])
def read_lc_string_list(buf):
"""Reads all length encoded strings from the given buffer
Returns a list of bytes
"""
byteslst = []
sizes = {252: 2, 253: 3, 254: 8}
buf_len = len(buf)
pos = 0
while pos < buf_len:
first = buf[pos]
if first == 255:
# Special case when MySQL error 1317 is returned by MySQL.
# We simply return None.
return None
if first == 251:
# NULL value
byteslst.append(None)
pos += 1
else:
if first <= 250:
length = first
byteslst.append(buf[(pos + 1):length + (pos + 1)])
pos += 1 + length
else:
lsize = 0
try:
lsize = sizes[first]
except KeyError:
return None
length = intread(buf[(pos + 1):lsize + (pos + 1)])
byteslst.append(
buf[pos + 1 + lsize:length + lsize + (pos + 1)])
pos += 1 + lsize + length
return tuple(byteslst)
def read_string(buf, end=None, size=None):
"""
Reads a string up until a character or for a given size.
Returns a tuple (trucated buffer, string).
"""
if end is None and size is None:
raise ValueError('read_string() needs either end or size')
if end is not None:
try:
idx = buf.index(end)
except ValueError:
raise ValueError("end byte not present in buffer")
return (buf[idx + 1:], buf[0:idx])
elif size is not None:
return read_bytes(buf, size)
raise ValueError('read_string() needs either end or size (weird)')
def read_int(buf, size):
"""Read an integer from buffer
Returns a tuple (truncated buffer, int)
"""
try:
res = intread(buf[0:size])
except:
raise
return (buf[size:], res)
def read_lc_int(buf):
"""
Takes a buffer and reads an length code string from the start.
Returns a tuple with buffer less the integer and the integer read.
"""
if not buf:
raise ValueError("Empty buffer.")
lcbyte = buf[0]
if lcbyte == 251:
return (buf[1:], None)
elif lcbyte < 251:
return (buf[1:], int(lcbyte))
elif lcbyte == 252:
return (buf[3:], struct_unpack('<xH', buf[0:3])[0])
elif lcbyte == 253:
return (buf[4:], struct_unpack('<I', buf[1:4] + b'\x00')[0])
elif lcbyte == 254:
return (buf[9:], struct_unpack('<xQ', buf[0:9])[0])
else:
raise ValueError("Failed reading length encoded integer")
#
# For debugging
#
def _digest_buffer(buf):
"""Debug function for showing buffers"""
if not isinstance(buf, str):
return ''.join(["\\x%02x" % c for c in buf])
return ''.join(["\\x%02x" % ord(c) for c in buf])
def print_buffer(abuffer, prefix=None, limit=30):
"""Debug function printing output of _digest_buffer()"""
if prefix:
if limit and limit > 0:
digest = _digest_buffer(abuffer[0:limit])
else:
digest = _digest_buffer(abuffer)
print(prefix + ': ' + digest)
else:
print(_digest_buffer(abuffer))

View file

@ -0,0 +1,37 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
# MySQL Connector/Python is licensed under the terms of the GPLv2
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
# MySQL Connectors. There are special exceptions to the terms and
# conditions of the GPLv2 as it is applied to this software, see the
# FOSS License Exception
# <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""MySQL Connector/Python version information
The file version.py gets installed and is available after installation
as mysql.connector.version.
"""
VERSION = (2, 2, 9, '', 0)
if VERSION[3] and VERSION[4]:
VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION)
else:
VERSION_TEXT = '{0}.{1}.{2}'.format(*VERSION[0:3])
LICENSE = 'GPLv2 with FOSS License Exception'
EDITION = '' # Added in package names, after the version