Cleaned up the directories
This commit is contained in:
parent
f708506d68
commit
a683fcffea
1340 changed files with 554582 additions and 6840 deletions
|
@ -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',
|
||||
]
|
1158
elitebot/lib/python3.11/site-packages/mysql/connector/abstracts.py
Normal file
1158
elitebot/lib/python3.11/site-packages/mysql/connector/abstracts.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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))
|
114
elitebot/lib/python3.11/site-packages/mysql/connector/catch23.py
Normal file
114
elitebot/lib/python3.11/site-packages/mysql/connector/catch23.py
Normal 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
|
|
@ -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
|
||||
]
|
||||
|
1059
elitebot/lib/python3.11/site-packages/mysql/connector/connection.py
Normal file
1059
elitebot/lib/python3.11/site-packages/mysql/connector/connection.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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")
|
|
@ -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))
|
|
@ -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
|
1368
elitebot/lib/python3.11/site-packages/mysql/connector/cursor.py
Normal file
1368
elitebot/lib/python3.11/site-packages/mysql/connector/cursor.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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")
|
||||
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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)) + ";",
|
||||
]
|
|
@ -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'
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
1159
elitebot/lib/python3.11/site-packages/mysql/connector/errorcode.py
Normal file
1159
elitebot/lib/python3.11/site-packages/mysql/connector/errorcode.py
Normal file
File diff suppressed because it is too large
Load diff
304
elitebot/lib/python3.11/site-packages/mysql/connector/errors.py
Normal file
304
elitebot/lib/python3.11/site-packages/mysql/connector/errors.py
Normal 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,
|
||||
}
|
|
@ -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',
|
||||
]
|
|
@ -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
|
|
@ -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
|
@ -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")
|
||||
|
|
@ -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
|
||||
"""
|
|
@ -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
|
||||
|
514
elitebot/lib/python3.11/site-packages/mysql/connector/network.py
Normal file
514
elitebot/lib/python3.11/site-packages/mysql/connector/network.py
Normal 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))
|
|
@ -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
|
353
elitebot/lib/python3.11/site-packages/mysql/connector/pooling.py
Normal file
353
elitebot/lib/python3.11/site-packages/mysql/connector/pooling.py
Normal 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
|
|
@ -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:]
|
338
elitebot/lib/python3.11/site-packages/mysql/connector/utils.py
Normal file
338
elitebot/lib/python3.11/site-packages/mysql/connector/utils.py
Normal 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))
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue