├── README.md ├── asynctorndb ├── __init__.py ├── _compat.py ├── _socketio.py ├── charset.py ├── connection.py ├── constants │ ├── CLIENT.py │ ├── COMMAND.py │ ├── ER.py │ ├── FIELD_TYPE.py │ ├── FLAG.py │ ├── SERVER_STATUS.py │ └── __init__.py ├── converters.py ├── cursors.py ├── err.py ├── tcpclient.py ├── times.py └── util.py └── setup.py /README.md: -------------------------------------------------------------------------------- 1 | AsyncTorndb(inactive) 2 | ===================== 3 | 4 | Async mysql client for tornado base on [PyMySQL](https://github.com/PyMySQL/PyMySQL). 5 | 6 | Documentation 7 | =========== 8 | 9 | AsyncTorndb behavior is almost like torndb behavior, but async. Refer to [torndb](http://torndb.readthedocs.org) 10 | and have a try. 11 | 12 | Requirements 13 | =========== 14 | 15 | * [tornado](https://github.com/tornadoweb/tornado) with latest 16 | 17 | Demo 18 | =========== 19 | 20 | Here is a simple "Hello, world" example web app for AsyncTorndb:: 21 | 22 | import tornado.ioloop 23 | import tornado.web 24 | import tornado.gen 25 | import asynctorndb 26 | 27 | class MainHandler(tornado.web.RequestHandler): 28 | @tornado.gen.coroutine 29 | def get(self): 30 | conn = asynctorndb.Connect(user='demo', passwd='demo', database='demo') 31 | yield conn.connect() 32 | result = yield conn.query('select * from user') 33 | # do something with result 34 | self.write("Hello, world") 35 | 36 | application = tornado.web.Application([ 37 | (r"/", MainHandler), 38 | ]) 39 | 40 | if __name__ == "__main__": 41 | application.listen(8888) 42 | tornado.ioloop.IOLoop.instance().start() 43 | 44 | License 45 | =========== 46 | 47 | AsyncTorndb is released under the MIT License. See LICENSE for more information. 48 | -------------------------------------------------------------------------------- /asynctorndb/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | PyMySQL: A pure-Python MySQL client library. 3 | 4 | Copyright (c) 2010, 2013 PyMySQL contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | ''' 25 | 26 | VERSION = (0, 6, 2, None) 27 | 28 | from ._compat import text_type, JYTHON, IRONPYTHON 29 | from .constants import FIELD_TYPE 30 | from .converters import escape_dict, escape_sequence, escape_string 31 | from .err import Warning, Error, InterfaceError, DataError, \ 32 | DatabaseError, OperationalError, IntegrityError, InternalError, \ 33 | NotSupportedError, ProgrammingError, MySQLError 34 | from .times import Date, Time, Timestamp, \ 35 | DateFromTicks, TimeFromTicks, TimestampFromTicks 36 | 37 | import sys 38 | 39 | 40 | threadsafety = 1 41 | apilevel = "2.0" 42 | paramstyle = "format" 43 | 44 | class DBAPISet(frozenset): 45 | 46 | 47 | def __ne__(self, other): 48 | if isinstance(other, set): 49 | return super(DBAPISet, self).__ne__(self, other) 50 | else: 51 | return other not in self 52 | 53 | def __eq__(self, other): 54 | if isinstance(other, frozenset): 55 | return frozenset.__eq__(self, other) 56 | else: 57 | return other in self 58 | 59 | def __hash__(self): 60 | return frozenset.__hash__(self) 61 | 62 | 63 | STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING, 64 | FIELD_TYPE.VAR_STRING]) 65 | BINARY = DBAPISet([FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB, 66 | FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.TINY_BLOB]) 67 | NUMBER = DBAPISet([FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT, 68 | FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG, 69 | FIELD_TYPE.TINY, FIELD_TYPE.YEAR]) 70 | DATE = DBAPISet([FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE]) 71 | TIME = DBAPISet([FIELD_TYPE.TIME]) 72 | TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME]) 73 | DATETIME = TIMESTAMP 74 | ROWID = DBAPISet() 75 | 76 | def Binary(x): 77 | """Return x as a binary type.""" 78 | if isinstance(x, text_type) and not (JYTHON or IRONPYTHON): 79 | return x.encode() 80 | return bytes(x) 81 | 82 | def Connect(*args, **kwargs): 83 | """ 84 | Connect to the database; see connection.Connection.__init__() for 85 | more information. 86 | """ 87 | from .connection import Connection 88 | return Connection(*args, **kwargs) 89 | 90 | from asynctorndb import connection as _orig_conn 91 | if _orig_conn.Connection.__init__.__doc__ is not None: 92 | Connect.__doc__ = _orig_conn.Connection.__init__.__doc__ + (""" 93 | See connection.Connection.__init__() for information about defaults. 94 | """) 95 | del _orig_conn 96 | 97 | def get_client_info(): # for MySQLdb compatibility 98 | return '.'.join(map(str, VERSION)) 99 | 100 | connect = Connection = Connect 101 | 102 | # we include a doctored version_info here for MySQLdb compatibility 103 | version_info = (1,2,2,"final",0) 104 | 105 | NULL = "NULL" 106 | 107 | __version__ = get_client_info() 108 | 109 | def thread_safe(): 110 | return True # match MySQLdb.thread_safe() 111 | 112 | def install_as_MySQLdb(): 113 | """ 114 | After this function is called, any application that imports MySQLdb or 115 | _mysql will unwittingly actually use 116 | """ 117 | sys.modules["MySQLdb"] = sys.modules["_mysql"] = sys.modules["pymysql"] 118 | 119 | __all__ = [ 120 | 'BINARY', 'Binary', 'Connect', 'Connection', 'DATE', 'Date', 121 | 'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks', 122 | 'DataError', 'DatabaseError', 'Error', 'FIELD_TYPE', 'IntegrityError', 123 | 'InterfaceError', 'InternalError', 'MySQLError', 'NULL', 'NUMBER', 124 | 'NotSupportedError', 'DBAPISet', 'OperationalError', 'ProgrammingError', 125 | 'ROWID', 'STRING', 'TIME', 'TIMESTAMP', 'Warning', 'apilevel', 'connect', 126 | 'connection', 'constants', 'converters', 'cursors', 127 | 'escape_dict', 'escape_sequence', 'escape_string', 'get_client_info', 128 | 'paramstyle', 'threadsafety', 'version_info', 129 | 130 | "install_as_MySQLdb", 131 | 132 | "NULL","__version__", 133 | ] 134 | -------------------------------------------------------------------------------- /asynctorndb/_compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | PY2 = sys.version_info[0] == 2 4 | PYPY = hasattr(sys, 'pypy_translation_info') 5 | JYTHON = sys.platform.startswith('java') 6 | IRONPYTHON = sys.platform == 'cli' 7 | 8 | if PY2: 9 | range_type = xrange 10 | text_type = unicode 11 | long_type = long 12 | str_type = basestring 13 | else: 14 | range_type = range 15 | text_type = str 16 | long_type = int 17 | str_type = str 18 | -------------------------------------------------------------------------------- /asynctorndb/_socketio.py: -------------------------------------------------------------------------------- 1 | """ 2 | SocketIO imported from socket module in Python 3. 3 | 4 | Copyright (c) 2001-2013 Python Software Foundation; All Rights Reserved. 5 | """ 6 | 7 | from socket import * 8 | import io 9 | import errno 10 | 11 | __all__ = ['SocketIO'] 12 | 13 | EINTR = errno.EINTR 14 | _blocking_errnos = { errno.EAGAIN, errno.EWOULDBLOCK } 15 | 16 | class SocketIO(io.RawIOBase): 17 | 18 | """Raw I/O implementation for stream sockets. 19 | 20 | This class supports the makefile() method on sockets. It provides 21 | the raw I/O interface on top of a socket object. 22 | """ 23 | 24 | # One might wonder why not let FileIO do the job instead. There are two 25 | # main reasons why FileIO is not adapted: 26 | # - it wouldn't work under Windows (where you can't used read() and 27 | # write() on a socket handle) 28 | # - it wouldn't work with socket timeouts (FileIO would ignore the 29 | # timeout and consider the socket non-blocking) 30 | 31 | # XXX More docs 32 | 33 | def __init__(self, sock, mode): 34 | if mode not in ("r", "w", "rw", "rb", "wb", "rwb"): 35 | raise ValueError("invalid mode: %r" % mode) 36 | io.RawIOBase.__init__(self) 37 | self._sock = sock 38 | if "b" not in mode: 39 | mode += "b" 40 | self._mode = mode 41 | self._reading = "r" in mode 42 | self._writing = "w" in mode 43 | self._timeout_occurred = False 44 | 45 | def readinto(self, b): 46 | """Read up to len(b) bytes into the writable buffer *b* and return 47 | the number of bytes read. If the socket is non-blocking and no bytes 48 | are available, None is returned. 49 | 50 | If *b* is non-empty, a 0 return value indicates that the connection 51 | was shutdown at the other end. 52 | """ 53 | self._checkClosed() 54 | self._checkReadable() 55 | if self._timeout_occurred: 56 | raise IOError("cannot read from timed out object") 57 | while True: 58 | try: 59 | return self._sock.recv_into(b) 60 | except timeout: 61 | self._timeout_occurred = True 62 | raise 63 | except error as e: 64 | n = e.args[0] 65 | if n == EINTR: 66 | continue 67 | if n in _blocking_errnos: 68 | return None 69 | raise 70 | 71 | def write(self, b): 72 | """Write the given bytes or bytearray object *b* to the socket 73 | and return the number of bytes written. This can be less than 74 | len(b) if not all data could be written. If the socket is 75 | non-blocking and no bytes could be written None is returned. 76 | """ 77 | self._checkClosed() 78 | self._checkWritable() 79 | try: 80 | return self._sock.send(b) 81 | except error as e: 82 | # XXX what about EINTR? 83 | if e.args[0] in _blocking_errnos: 84 | return None 85 | raise 86 | 87 | def readable(self): 88 | """True if the SocketIO is open for reading. 89 | """ 90 | if self.closed: 91 | raise ValueError("I/O operation on closed socket.") 92 | return self._reading 93 | 94 | def writable(self): 95 | """True if the SocketIO is open for writing. 96 | """ 97 | if self.closed: 98 | raise ValueError("I/O operation on closed socket.") 99 | return self._writing 100 | 101 | def seekable(self): 102 | """True if the SocketIO is open for seeking. 103 | """ 104 | if self.closed: 105 | raise ValueError("I/O operation on closed socket.") 106 | return super().seekable() 107 | 108 | def fileno(self): 109 | """Return the file descriptor of the underlying socket. 110 | """ 111 | self._checkClosed() 112 | return self._sock.fileno() 113 | 114 | @property 115 | def name(self): 116 | if not self.closed: 117 | return self.fileno() 118 | else: 119 | return -1 120 | 121 | @property 122 | def mode(self): 123 | return self._mode 124 | 125 | def close(self): 126 | """Close the SocketIO object. This doesn't close the underlying 127 | socket, except if all references to it have disappeared. 128 | """ 129 | if self.closed: 130 | return 131 | io.RawIOBase.close(self) 132 | self._sock._decref_socketios() 133 | self._sock = None 134 | 135 | -------------------------------------------------------------------------------- /asynctorndb/charset.py: -------------------------------------------------------------------------------- 1 | MBLENGTH = { 2 | 8:1, 3 | 33:3, 4 | 88:2, 5 | 91:2 6 | } 7 | 8 | 9 | class Charset(object): 10 | def __init__(self, id, name, collation, is_default): 11 | self.id, self.name, self.collation = id, name, collation 12 | self.is_default = is_default == 'Yes' 13 | 14 | @property 15 | def encoding(self): 16 | name = self.name 17 | if name == 'utf8mb4': 18 | return 'utf8' 19 | return name 20 | 21 | @property 22 | def is_binary(self): 23 | return self.id == 63 24 | 25 | 26 | class Charsets: 27 | def __init__(self): 28 | self._by_id = {} 29 | 30 | def add(self, c): 31 | self._by_id[c.id] = c 32 | 33 | def by_id(self, id): 34 | return self._by_id[id] 35 | 36 | def by_name(self, name): 37 | name = name.lower() 38 | for c in list(self._by_id.values()): 39 | if c.name == name and c.is_default: 40 | return c 41 | 42 | _charsets = Charsets() 43 | """ 44 | Generated with: 45 | 46 | mysql -N -s -e "select id, character_set_name, collation_name, is_default 47 | from information_schema.collations order by id;" | python -c "import sys 48 | for l in sys.stdin.readlines(): 49 | id, name, collation, is_default = l.split(chr(9)) 50 | print '_charsets.add(Charset(%s, \'%s\', \'%s\', \'%s\'))' \ 51 | % (id, name, collation, is_default.strip()) 52 | " 53 | 54 | """ 55 | _charsets.add(Charset(1, 'big5', 'big5_chinese_ci', 'Yes')) 56 | _charsets.add(Charset(2, 'latin2', 'latin2_czech_cs', '')) 57 | _charsets.add(Charset(3, 'dec8', 'dec8_swedish_ci', 'Yes')) 58 | _charsets.add(Charset(4, 'cp850', 'cp850_general_ci', 'Yes')) 59 | _charsets.add(Charset(5, 'latin1', 'latin1_german1_ci', '')) 60 | _charsets.add(Charset(6, 'hp8', 'hp8_english_ci', 'Yes')) 61 | _charsets.add(Charset(7, 'koi8r', 'koi8r_general_ci', 'Yes')) 62 | _charsets.add(Charset(8, 'latin1', 'latin1_swedish_ci', 'Yes')) 63 | _charsets.add(Charset(9, 'latin2', 'latin2_general_ci', 'Yes')) 64 | _charsets.add(Charset(10, 'swe7', 'swe7_swedish_ci', 'Yes')) 65 | _charsets.add(Charset(11, 'ascii', 'ascii_general_ci', 'Yes')) 66 | _charsets.add(Charset(12, 'ujis', 'ujis_japanese_ci', 'Yes')) 67 | _charsets.add(Charset(13, 'sjis', 'sjis_japanese_ci', 'Yes')) 68 | _charsets.add(Charset(14, 'cp1251', 'cp1251_bulgarian_ci', '')) 69 | _charsets.add(Charset(15, 'latin1', 'latin1_danish_ci', '')) 70 | _charsets.add(Charset(16, 'hebrew', 'hebrew_general_ci', 'Yes')) 71 | _charsets.add(Charset(18, 'tis620', 'tis620_thai_ci', 'Yes')) 72 | _charsets.add(Charset(19, 'euckr', 'euckr_korean_ci', 'Yes')) 73 | _charsets.add(Charset(20, 'latin7', 'latin7_estonian_cs', '')) 74 | _charsets.add(Charset(21, 'latin2', 'latin2_hungarian_ci', '')) 75 | _charsets.add(Charset(22, 'koi8u', 'koi8u_general_ci', 'Yes')) 76 | _charsets.add(Charset(23, 'cp1251', 'cp1251_ukrainian_ci', '')) 77 | _charsets.add(Charset(24, 'gb2312', 'gb2312_chinese_ci', 'Yes')) 78 | _charsets.add(Charset(25, 'greek', 'greek_general_ci', 'Yes')) 79 | _charsets.add(Charset(26, 'cp1250', 'cp1250_general_ci', 'Yes')) 80 | _charsets.add(Charset(27, 'latin2', 'latin2_croatian_ci', '')) 81 | _charsets.add(Charset(28, 'gbk', 'gbk_chinese_ci', 'Yes')) 82 | _charsets.add(Charset(29, 'cp1257', 'cp1257_lithuanian_ci', '')) 83 | _charsets.add(Charset(30, 'latin5', 'latin5_turkish_ci', 'Yes')) 84 | _charsets.add(Charset(31, 'latin1', 'latin1_german2_ci', '')) 85 | _charsets.add(Charset(32, 'armscii8', 'armscii8_general_ci', 'Yes')) 86 | _charsets.add(Charset(33, 'utf8', 'utf8_general_ci', 'Yes')) 87 | _charsets.add(Charset(34, 'cp1250', 'cp1250_czech_cs', '')) 88 | _charsets.add(Charset(35, 'ucs2', 'ucs2_general_ci', 'Yes')) 89 | _charsets.add(Charset(36, 'cp866', 'cp866_general_ci', 'Yes')) 90 | _charsets.add(Charset(37, 'keybcs2', 'keybcs2_general_ci', 'Yes')) 91 | _charsets.add(Charset(38, 'macce', 'macce_general_ci', 'Yes')) 92 | _charsets.add(Charset(39, 'macroman', 'macroman_general_ci', 'Yes')) 93 | _charsets.add(Charset(40, 'cp852', 'cp852_general_ci', 'Yes')) 94 | _charsets.add(Charset(41, 'latin7', 'latin7_general_ci', 'Yes')) 95 | _charsets.add(Charset(42, 'latin7', 'latin7_general_cs', '')) 96 | _charsets.add(Charset(43, 'macce', 'macce_bin', '')) 97 | _charsets.add(Charset(44, 'cp1250', 'cp1250_croatian_ci', '')) 98 | _charsets.add(Charset(45, 'utf8mb4', 'utf8mb4_general_ci', 'Yes')) 99 | _charsets.add(Charset(46, 'utf8mb4', 'utf8mb4_bin', '')) 100 | _charsets.add(Charset(47, 'latin1', 'latin1_bin', '')) 101 | _charsets.add(Charset(48, 'latin1', 'latin1_general_ci', '')) 102 | _charsets.add(Charset(49, 'latin1', 'latin1_general_cs', '')) 103 | _charsets.add(Charset(50, 'cp1251', 'cp1251_bin', '')) 104 | _charsets.add(Charset(51, 'cp1251', 'cp1251_general_ci', 'Yes')) 105 | _charsets.add(Charset(52, 'cp1251', 'cp1251_general_cs', '')) 106 | _charsets.add(Charset(53, 'macroman', 'macroman_bin', '')) 107 | _charsets.add(Charset(54, 'utf16', 'utf16_general_ci', 'Yes')) 108 | _charsets.add(Charset(55, 'utf16', 'utf16_bin', '')) 109 | _charsets.add(Charset(57, 'cp1256', 'cp1256_general_ci', 'Yes')) 110 | _charsets.add(Charset(58, 'cp1257', 'cp1257_bin', '')) 111 | _charsets.add(Charset(59, 'cp1257', 'cp1257_general_ci', 'Yes')) 112 | _charsets.add(Charset(60, 'utf32', 'utf32_general_ci', 'Yes')) 113 | _charsets.add(Charset(61, 'utf32', 'utf32_bin', '')) 114 | _charsets.add(Charset(63, 'binary', 'binary', 'Yes')) 115 | _charsets.add(Charset(64, 'armscii8', 'armscii8_bin', '')) 116 | _charsets.add(Charset(65, 'ascii', 'ascii_bin', '')) 117 | _charsets.add(Charset(66, 'cp1250', 'cp1250_bin', '')) 118 | _charsets.add(Charset(67, 'cp1256', 'cp1256_bin', '')) 119 | _charsets.add(Charset(68, 'cp866', 'cp866_bin', '')) 120 | _charsets.add(Charset(69, 'dec8', 'dec8_bin', '')) 121 | _charsets.add(Charset(70, 'greek', 'greek_bin', '')) 122 | _charsets.add(Charset(71, 'hebrew', 'hebrew_bin', '')) 123 | _charsets.add(Charset(72, 'hp8', 'hp8_bin', '')) 124 | _charsets.add(Charset(73, 'keybcs2', 'keybcs2_bin', '')) 125 | _charsets.add(Charset(74, 'koi8r', 'koi8r_bin', '')) 126 | _charsets.add(Charset(75, 'koi8u', 'koi8u_bin', '')) 127 | _charsets.add(Charset(77, 'latin2', 'latin2_bin', '')) 128 | _charsets.add(Charset(78, 'latin5', 'latin5_bin', '')) 129 | _charsets.add(Charset(79, 'latin7', 'latin7_bin', '')) 130 | _charsets.add(Charset(80, 'cp850', 'cp850_bin', '')) 131 | _charsets.add(Charset(81, 'cp852', 'cp852_bin', '')) 132 | _charsets.add(Charset(82, 'swe7', 'swe7_bin', '')) 133 | _charsets.add(Charset(83, 'utf8', 'utf8_bin', '')) 134 | _charsets.add(Charset(84, 'big5', 'big5_bin', '')) 135 | _charsets.add(Charset(85, 'euckr', 'euckr_bin', '')) 136 | _charsets.add(Charset(86, 'gb2312', 'gb2312_bin', '')) 137 | _charsets.add(Charset(87, 'gbk', 'gbk_bin', '')) 138 | _charsets.add(Charset(88, 'sjis', 'sjis_bin', '')) 139 | _charsets.add(Charset(89, 'tis620', 'tis620_bin', '')) 140 | _charsets.add(Charset(90, 'ucs2', 'ucs2_bin', '')) 141 | _charsets.add(Charset(91, 'ujis', 'ujis_bin', '')) 142 | _charsets.add(Charset(92, 'geostd8', 'geostd8_general_ci', 'Yes')) 143 | _charsets.add(Charset(93, 'geostd8', 'geostd8_bin', '')) 144 | _charsets.add(Charset(94, 'latin1', 'latin1_spanish_ci', '')) 145 | _charsets.add(Charset(95, 'cp932', 'cp932_japanese_ci', 'Yes')) 146 | _charsets.add(Charset(96, 'cp932', 'cp932_bin', '')) 147 | _charsets.add(Charset(97, 'eucjpms', 'eucjpms_japanese_ci', 'Yes')) 148 | _charsets.add(Charset(98, 'eucjpms', 'eucjpms_bin', '')) 149 | _charsets.add(Charset(99, 'cp1250', 'cp1250_polish_ci', '')) 150 | _charsets.add(Charset(101, 'utf16', 'utf16_unicode_ci', '')) 151 | _charsets.add(Charset(102, 'utf16', 'utf16_icelandic_ci', '')) 152 | _charsets.add(Charset(103, 'utf16', 'utf16_latvian_ci', '')) 153 | _charsets.add(Charset(104, 'utf16', 'utf16_romanian_ci', '')) 154 | _charsets.add(Charset(105, 'utf16', 'utf16_slovenian_ci', '')) 155 | _charsets.add(Charset(106, 'utf16', 'utf16_polish_ci', '')) 156 | _charsets.add(Charset(107, 'utf16', 'utf16_estonian_ci', '')) 157 | _charsets.add(Charset(108, 'utf16', 'utf16_spanish_ci', '')) 158 | _charsets.add(Charset(109, 'utf16', 'utf16_swedish_ci', '')) 159 | _charsets.add(Charset(110, 'utf16', 'utf16_turkish_ci', '')) 160 | _charsets.add(Charset(111, 'utf16', 'utf16_czech_ci', '')) 161 | _charsets.add(Charset(112, 'utf16', 'utf16_danish_ci', '')) 162 | _charsets.add(Charset(113, 'utf16', 'utf16_lithuanian_ci', '')) 163 | _charsets.add(Charset(114, 'utf16', 'utf16_slovak_ci', '')) 164 | _charsets.add(Charset(115, 'utf16', 'utf16_spanish2_ci', '')) 165 | _charsets.add(Charset(116, 'utf16', 'utf16_roman_ci', '')) 166 | _charsets.add(Charset(117, 'utf16', 'utf16_persian_ci', '')) 167 | _charsets.add(Charset(118, 'utf16', 'utf16_esperanto_ci', '')) 168 | _charsets.add(Charset(119, 'utf16', 'utf16_hungarian_ci', '')) 169 | _charsets.add(Charset(120, 'utf16', 'utf16_sinhala_ci', '')) 170 | _charsets.add(Charset(128, 'ucs2', 'ucs2_unicode_ci', '')) 171 | _charsets.add(Charset(129, 'ucs2', 'ucs2_icelandic_ci', '')) 172 | _charsets.add(Charset(130, 'ucs2', 'ucs2_latvian_ci', '')) 173 | _charsets.add(Charset(131, 'ucs2', 'ucs2_romanian_ci', '')) 174 | _charsets.add(Charset(132, 'ucs2', 'ucs2_slovenian_ci', '')) 175 | _charsets.add(Charset(133, 'ucs2', 'ucs2_polish_ci', '')) 176 | _charsets.add(Charset(134, 'ucs2', 'ucs2_estonian_ci', '')) 177 | _charsets.add(Charset(135, 'ucs2', 'ucs2_spanish_ci', '')) 178 | _charsets.add(Charset(136, 'ucs2', 'ucs2_swedish_ci', '')) 179 | _charsets.add(Charset(137, 'ucs2', 'ucs2_turkish_ci', '')) 180 | _charsets.add(Charset(138, 'ucs2', 'ucs2_czech_ci', '')) 181 | _charsets.add(Charset(139, 'ucs2', 'ucs2_danish_ci', '')) 182 | _charsets.add(Charset(140, 'ucs2', 'ucs2_lithuanian_ci', '')) 183 | _charsets.add(Charset(141, 'ucs2', 'ucs2_slovak_ci', '')) 184 | _charsets.add(Charset(142, 'ucs2', 'ucs2_spanish2_ci', '')) 185 | _charsets.add(Charset(143, 'ucs2', 'ucs2_roman_ci', '')) 186 | _charsets.add(Charset(144, 'ucs2', 'ucs2_persian_ci', '')) 187 | _charsets.add(Charset(145, 'ucs2', 'ucs2_esperanto_ci', '')) 188 | _charsets.add(Charset(146, 'ucs2', 'ucs2_hungarian_ci', '')) 189 | _charsets.add(Charset(147, 'ucs2', 'ucs2_sinhala_ci', '')) 190 | _charsets.add(Charset(159, 'ucs2', 'ucs2_general_mysql500_ci', '')) 191 | _charsets.add(Charset(160, 'utf32', 'utf32_unicode_ci', '')) 192 | _charsets.add(Charset(161, 'utf32', 'utf32_icelandic_ci', '')) 193 | _charsets.add(Charset(162, 'utf32', 'utf32_latvian_ci', '')) 194 | _charsets.add(Charset(163, 'utf32', 'utf32_romanian_ci', '')) 195 | _charsets.add(Charset(164, 'utf32', 'utf32_slovenian_ci', '')) 196 | _charsets.add(Charset(165, 'utf32', 'utf32_polish_ci', '')) 197 | _charsets.add(Charset(166, 'utf32', 'utf32_estonian_ci', '')) 198 | _charsets.add(Charset(167, 'utf32', 'utf32_spanish_ci', '')) 199 | _charsets.add(Charset(168, 'utf32', 'utf32_swedish_ci', '')) 200 | _charsets.add(Charset(169, 'utf32', 'utf32_turkish_ci', '')) 201 | _charsets.add(Charset(170, 'utf32', 'utf32_czech_ci', '')) 202 | _charsets.add(Charset(171, 'utf32', 'utf32_danish_ci', '')) 203 | _charsets.add(Charset(172, 'utf32', 'utf32_lithuanian_ci', '')) 204 | _charsets.add(Charset(173, 'utf32', 'utf32_slovak_ci', '')) 205 | _charsets.add(Charset(174, 'utf32', 'utf32_spanish2_ci', '')) 206 | _charsets.add(Charset(175, 'utf32', 'utf32_roman_ci', '')) 207 | _charsets.add(Charset(176, 'utf32', 'utf32_persian_ci', '')) 208 | _charsets.add(Charset(177, 'utf32', 'utf32_esperanto_ci', '')) 209 | _charsets.add(Charset(178, 'utf32', 'utf32_hungarian_ci', '')) 210 | _charsets.add(Charset(179, 'utf32', 'utf32_sinhala_ci', '')) 211 | _charsets.add(Charset(192, 'utf8', 'utf8_unicode_ci', '')) 212 | _charsets.add(Charset(193, 'utf8', 'utf8_icelandic_ci', '')) 213 | _charsets.add(Charset(194, 'utf8', 'utf8_latvian_ci', '')) 214 | _charsets.add(Charset(195, 'utf8', 'utf8_romanian_ci', '')) 215 | _charsets.add(Charset(196, 'utf8', 'utf8_slovenian_ci', '')) 216 | _charsets.add(Charset(197, 'utf8', 'utf8_polish_ci', '')) 217 | _charsets.add(Charset(198, 'utf8', 'utf8_estonian_ci', '')) 218 | _charsets.add(Charset(199, 'utf8', 'utf8_spanish_ci', '')) 219 | _charsets.add(Charset(200, 'utf8', 'utf8_swedish_ci', '')) 220 | _charsets.add(Charset(201, 'utf8', 'utf8_turkish_ci', '')) 221 | _charsets.add(Charset(202, 'utf8', 'utf8_czech_ci', '')) 222 | _charsets.add(Charset(203, 'utf8', 'utf8_danish_ci', '')) 223 | _charsets.add(Charset(204, 'utf8', 'utf8_lithuanian_ci', '')) 224 | _charsets.add(Charset(205, 'utf8', 'utf8_slovak_ci', '')) 225 | _charsets.add(Charset(206, 'utf8', 'utf8_spanish2_ci', '')) 226 | _charsets.add(Charset(207, 'utf8', 'utf8_roman_ci', '')) 227 | _charsets.add(Charset(208, 'utf8', 'utf8_persian_ci', '')) 228 | _charsets.add(Charset(209, 'utf8', 'utf8_esperanto_ci', '')) 229 | _charsets.add(Charset(210, 'utf8', 'utf8_hungarian_ci', '')) 230 | _charsets.add(Charset(211, 'utf8', 'utf8_sinhala_ci', '')) 231 | _charsets.add(Charset(223, 'utf8', 'utf8_general_mysql500_ci', '')) 232 | _charsets.add(Charset(224, 'utf8mb4', 'utf8mb4_unicode_ci', '')) 233 | _charsets.add(Charset(225, 'utf8mb4', 'utf8mb4_icelandic_ci', '')) 234 | _charsets.add(Charset(226, 'utf8mb4', 'utf8mb4_latvian_ci', '')) 235 | _charsets.add(Charset(227, 'utf8mb4', 'utf8mb4_romanian_ci', '')) 236 | _charsets.add(Charset(228, 'utf8mb4', 'utf8mb4_slovenian_ci', '')) 237 | _charsets.add(Charset(229, 'utf8mb4', 'utf8mb4_polish_ci', '')) 238 | _charsets.add(Charset(230, 'utf8mb4', 'utf8mb4_estonian_ci', '')) 239 | _charsets.add(Charset(231, 'utf8mb4', 'utf8mb4_spanish_ci', '')) 240 | _charsets.add(Charset(232, 'utf8mb4', 'utf8mb4_swedish_ci', '')) 241 | _charsets.add(Charset(233, 'utf8mb4', 'utf8mb4_turkish_ci', '')) 242 | _charsets.add(Charset(234, 'utf8mb4', 'utf8mb4_czech_ci', '')) 243 | _charsets.add(Charset(235, 'utf8mb4', 'utf8mb4_danish_ci', '')) 244 | _charsets.add(Charset(236, 'utf8mb4', 'utf8mb4_lithuanian_ci', '')) 245 | _charsets.add(Charset(237, 'utf8mb4', 'utf8mb4_slovak_ci', '')) 246 | _charsets.add(Charset(238, 'utf8mb4', 'utf8mb4_spanish2_ci', '')) 247 | _charsets.add(Charset(239, 'utf8mb4', 'utf8mb4_roman_ci', '')) 248 | _charsets.add(Charset(240, 'utf8mb4', 'utf8mb4_persian_ci', '')) 249 | _charsets.add(Charset(241, 'utf8mb4', 'utf8mb4_esperanto_ci', '')) 250 | _charsets.add(Charset(242, 'utf8mb4', 'utf8mb4_hungarian_ci', '')) 251 | _charsets.add(Charset(243, 'utf8mb4', 'utf8mb4_sinhala_ci', '')) 252 | 253 | 254 | charset_by_name = _charsets.by_name 255 | charset_by_id = _charsets.by_id 256 | 257 | 258 | def charset_to_encoding(name): 259 | """Convert MySQL's charset name to Python's codec name""" 260 | if name == 'utf8mb4': 261 | return 'utf8' 262 | return name 263 | -------------------------------------------------------------------------------- /asynctorndb/connection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2014 Mayflower 4 | # 5 | 6 | """ async mysql client for tornado 7 | 8 | Base no PyMySQL (https://github.com/PyMySQL/PyMySQL) that's a pure 9 | python mysql client. 10 | """ 11 | 12 | from __future__ import print_function 13 | from ._compat import PY2, range_type, text_type, str_type, JYTHON, IRONPYTHON 14 | 15 | from tornado.gen import coroutine, Return 16 | from tornado.tcpclient import TCPClient 17 | import tornado.ioloop 18 | import errno 19 | from functools import partial 20 | import os 21 | import hashlib 22 | import socket 23 | import itertools 24 | import time 25 | 26 | try: 27 | import ssl 28 | SSL_ENABLED = True 29 | except ImportError: 30 | SSL_ENABLED = False 31 | 32 | import struct 33 | import sys 34 | if PY2: 35 | import ConfigParser as configparser 36 | else: 37 | import configparser 38 | 39 | import io 40 | 41 | try: 42 | import getpass 43 | DEFAULT_USER = getpass.getuser() 44 | except ImportError: 45 | DEFAULT_USER = None 46 | 47 | 48 | from .charset import MBLENGTH, charset_by_name, charset_by_id 49 | from .cursors import Cursor 50 | from .constants import FIELD_TYPE 51 | from .constants import SERVER_STATUS 52 | from .constants.CLIENT import * 53 | from .constants.COMMAND import * 54 | from .util import byte2int, int2byte 55 | from .converters import escape_item, encoders, decoders, escape_string 56 | from .err import ( 57 | raise_mysql_exception, Warning, Error, 58 | InterfaceError, DataError, DatabaseError, OperationalError, 59 | IntegrityError, InternalError, NotSupportedError, ProgrammingError) 60 | 61 | _py_version = sys.version_info[:2] 62 | 63 | 64 | # socket.makefile() in Python 2 is not usable because very inefficient and 65 | # bad behavior about timeout. 66 | # XXX: ._socketio doesn't work under IronPython. 67 | if _py_version == (2, 7) and not IRONPYTHON: 68 | # read method of file-like returned by sock.makefile() is very slow. 69 | # So we copy io-based one from Python 3. 70 | from _socketio import SocketIO 71 | def _makefile(sock, mode): 72 | return io.BufferedReader(SocketIO(sock, mode)) 73 | elif _py_version == (2, 6): 74 | # Python 2.6 doesn't have fast io module. 75 | # So we make original one. 76 | class SockFile(object): 77 | def __init__(self, sock): 78 | self._sock = sock 79 | def read(self, n): 80 | read = self._sock.recv(n) 81 | if len(read) == n: 82 | return read 83 | while True: 84 | data = self._sock.recv(n-len(read)) 85 | if not data: 86 | return read 87 | read += data 88 | if len(read) == n: 89 | return read 90 | 91 | def _makefile(sock, mode): 92 | assert mode == 'rb' 93 | return SockFile(sock) 94 | else: 95 | # socket.makefile in Python 3 is nice. 96 | def _makefile(sock, mode): 97 | return sock.makefile(mode) 98 | 99 | 100 | TEXT_TYPES = set([ 101 | FIELD_TYPE.BIT, 102 | FIELD_TYPE.BLOB, 103 | FIELD_TYPE.LONG_BLOB, 104 | FIELD_TYPE.MEDIUM_BLOB, 105 | FIELD_TYPE.STRING, 106 | FIELD_TYPE.TINY_BLOB, 107 | FIELD_TYPE.VAR_STRING, 108 | FIELD_TYPE.VARCHAR]) 109 | 110 | sha_new = partial(hashlib.new, 'sha1') 111 | 112 | DEBUG = False 113 | 114 | NULL_COLUMN = 251 115 | UNSIGNED_CHAR_COLUMN = 251 116 | UNSIGNED_SHORT_COLUMN = 252 117 | UNSIGNED_INT24_COLUMN = 253 118 | UNSIGNED_INT64_COLUMN = 254 119 | UNSIGNED_CHAR_LENGTH = 1 120 | UNSIGNED_SHORT_LENGTH = 2 121 | UNSIGNED_INT24_LENGTH = 3 122 | UNSIGNED_INT64_LENGTH = 8 123 | 124 | DEFAULT_CHARSET = 'latin1' 125 | 126 | MAX_PACKET_LEN = 2**24-1 127 | 128 | 129 | def dump_packet(data): 130 | 131 | def is_ascii(data): 132 | if 65 <= byte2int(data) <= 122: #data.isalnum(): 133 | if isinstance(data, int): 134 | return chr(data) 135 | return data 136 | return '.' 137 | 138 | try: 139 | print("packet length:", len(data)) 140 | print("method call[1]:", sys._getframe(1).f_code.co_name) 141 | print("method call[2]:", sys._getframe(2).f_code.co_name) 142 | print("method call[3]:", sys._getframe(3).f_code.co_name) 143 | print("method call[4]:", sys._getframe(4).f_code.co_name) 144 | print("method call[5]:", sys._getframe(5).f_code.co_name) 145 | print("-" * 88) 146 | except ValueError: 147 | pass 148 | dump_data = [data[i:i+16] for i in range_type(0, min(len(data), 256), 16)] 149 | for d in dump_data: 150 | print(' '.join(["{:02X}".format(byte2int(x)) for x in d]) + 151 | ' ' * (16 - len(d)) + ' ' * 2 + 152 | ' '.join(["{}".format(is_ascii(x)) for x in d])) 153 | print("-" * 88) 154 | print() 155 | 156 | 157 | def _scramble(password, message): 158 | if not password: 159 | return b'\0' 160 | if DEBUG: print('password=' + password) 161 | stage1 = sha_new(password).digest() 162 | stage2 = sha_new(stage1).digest() 163 | s = sha_new() 164 | s.update(message) 165 | s.update(stage2) 166 | result = s.digest() 167 | return _my_crypt(result, stage1) 168 | 169 | 170 | def _my_crypt(message1, message2): 171 | length = len(message1) 172 | result = struct.pack('B', length) 173 | for i in range_type(length): 174 | x = (struct.unpack('B', message1[i:i+1])[0] ^ 175 | struct.unpack('B', message2[i:i+1])[0]) 176 | result += struct.pack('B', x) 177 | return result 178 | 179 | # old_passwords support ported from libmysql/password.c 180 | SCRAMBLE_LENGTH_323 = 8 181 | 182 | 183 | class RandStruct_323(object): 184 | def __init__(self, seed1, seed2): 185 | self.max_value = 0x3FFFFFFF 186 | self.seed1 = seed1 % self.max_value 187 | self.seed2 = seed2 % self.max_value 188 | 189 | def my_rnd(self): 190 | self.seed1 = (self.seed1 * 3 + self.seed2) % self.max_value 191 | self.seed2 = (self.seed1 + self.seed2 + 33) % self.max_value 192 | return float(self.seed1) / float(self.max_value) 193 | 194 | 195 | def _scramble_323(password, message): 196 | hash_pass = _hash_password_323(password) 197 | hash_message = _hash_password_323(message[:SCRAMBLE_LENGTH_323]) 198 | hash_pass_n = struct.unpack(">LL", hash_pass) 199 | hash_message_n = struct.unpack(">LL", hash_message) 200 | 201 | rand_st = RandStruct_323(hash_pass_n[0] ^ hash_message_n[0], 202 | hash_pass_n[1] ^ hash_message_n[1]) 203 | outbuf = io.BytesIO() 204 | for _ in range_type(min(SCRAMBLE_LENGTH_323, len(message))): 205 | outbuf.write(int2byte(int(rand_st.my_rnd() * 31) + 64)) 206 | extra = int2byte(int(rand_st.my_rnd() * 31)) 207 | out = outbuf.getvalue() 208 | outbuf = io.BytesIO() 209 | for c in out: 210 | outbuf.write(int2byte(byte2int(c) ^ byte2int(extra))) 211 | return outbuf.getvalue() 212 | 213 | 214 | def _hash_password_323(password): 215 | nr = 1345345333 216 | add = 7 217 | nr2 = 0x12345671 218 | 219 | for c in [byte2int(x) for x in password if x not in (' ', '\t')]: 220 | nr^= (((nr & 63)+add)*c)+ (nr << 8) & 0xFFFFFFFF 221 | nr2= (nr2 + ((nr2 << 8) ^ nr)) & 0xFFFFFFFF 222 | add= (add + c) & 0xFFFFFFFF 223 | 224 | r1 = nr & ((1 << 31) - 1) # kill sign bits 225 | r2 = nr2 & ((1 << 31) - 1) 226 | 227 | # pack 228 | return struct.pack(">LL", r1, r2) 229 | 230 | 231 | def pack_int24(n): 232 | return struct.pack(' len(self._data): 314 | raise Exception('Invalid advance amount (%s) for cursor. ' 315 | 'Position=%s' % (length, new_position)) 316 | self._position = new_position 317 | 318 | def rewind(self, position=0): 319 | """Set the position of the data buffer cursor to 'position'.""" 320 | if position < 0 or position > len(self._data): 321 | raise Exception("Invalid position to rewind cursor to: %s." % position) 322 | self._position = position 323 | 324 | def get_bytes(self, position, length=1): 325 | """Get 'length' bytes starting at 'position'. 326 | 327 | Position is start of payload (first four packet header bytes are not 328 | included) starting at index '0'. 329 | 330 | No error checking is done. If requesting outside end of buffer 331 | an empty string (or string shorter than 'length') may be returned! 332 | """ 333 | return self._data[position:(position+length)] 334 | 335 | def read_length_encoded_integer(self): 336 | """Read a 'Length Coded Binary' number from the data buffer. 337 | 338 | Length coded numbers can be anywhere from 1 to 9 bytes depending 339 | on the value of the first byte. 340 | """ 341 | c = ord(self.read(1)) 342 | if c == NULL_COLUMN: 343 | return None 344 | if c < UNSIGNED_CHAR_COLUMN: 345 | return c 346 | elif c == UNSIGNED_SHORT_COLUMN: 347 | return unpack_uint16(self.read(UNSIGNED_SHORT_LENGTH)) 348 | elif c == UNSIGNED_INT24_COLUMN: 349 | return unpack_int24(self.read(UNSIGNED_INT24_LENGTH)) 350 | elif c == UNSIGNED_INT64_COLUMN: 351 | return unpack_int64(self.read(UNSIGNED_INT64_LENGTH)) 352 | 353 | def read_length_coded_string(self): 354 | """Read a 'Length Coded String' from the data buffer. 355 | 356 | A 'Length Coded String' consists first of a length coded 357 | (unsigned, positive) integer represented in 1-9 bytes followed by 358 | that many bytes of binary data. (For example "cat" would be "3cat".) 359 | """ 360 | length = self.read_length_encoded_integer() 361 | if length is None: 362 | return None 363 | return self.read(length) 364 | 365 | def is_ok_packet(self): 366 | return self._data[0:1] == b'\0' 367 | 368 | def is_eof_packet(self): 369 | # http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-EOF_Packet 370 | # Caution: \xFE may be LengthEncodedInteger. 371 | # If \xFE is LengthEncodedInteger header, 8bytes followed. 372 | return len(self._data) < 9 and self._data[0:1] == b'\xfe' 373 | 374 | def is_resultset_packet(self): 375 | field_count = ord(self._data[0:1]) 376 | return 1 <= field_count <= 250 377 | 378 | def is_error_packet(self): 379 | return self._data[0:1] == b'\xff' 380 | 381 | def check_error(self): 382 | if self.is_error_packet(): 383 | self.rewind() 384 | self.advance(1) # field_count == error (we already know that) 385 | errno = unpack_uint16(self.read(2)) 386 | if DEBUG: print("errno =", errno) 387 | raise_mysql_exception(self._data) 388 | 389 | def dump(self): 390 | dump_packet(self._data) 391 | 392 | 393 | class FieldDescriptorPacket(MysqlPacket): 394 | """A MysqlPacket that represents a specific column's metadata in the result. 395 | 396 | Parsing is automatically done and the results are exported via public 397 | attributes on the class such as: db, table_name, name, length, type_code. 398 | """ 399 | 400 | def __init__(self, connection): 401 | MysqlPacket.__init__(self, connection) 402 | 403 | @coroutine 404 | def recv_packet(self): 405 | yield super(FieldDescriptorPacket, self).recv_packet() 406 | self.__parse_field_descriptor(self.connection.encoding) 407 | 408 | def __parse_field_descriptor(self, encoding): 409 | """Parse the 'Field Descriptor' (Metadata) packet. 410 | 411 | This is compatible with MySQL 4.1+ (not compatible with MySQL 4.0). 412 | """ 413 | self.catalog = self.read_length_coded_string() 414 | self.db = self.read_length_coded_string() 415 | self.table_name = self.read_length_coded_string().decode(encoding) 416 | self.org_table = self.read_length_coded_string().decode(encoding) 417 | self.name = self.read_length_coded_string().decode(encoding) 418 | self.org_name = self.read_length_coded_string().decode(encoding) 419 | self.advance(1) # non-null filler 420 | self.charsetnr = struct.unpack(' 2: 558 | use_unicode = True 559 | 560 | if db is not None and database is None: 561 | database = db 562 | 563 | if compress or named_pipe: 564 | raise NotImplementedError("compress and named_pipe arguments are not supported") 565 | 566 | if ssl and ('capath' in ssl or 'cipher' in ssl): 567 | raise NotImplementedError('ssl options capath and cipher are not supported') 568 | 569 | self.ssl = False 570 | if ssl: 571 | if not SSL_ENABLED: 572 | raise NotImplementedError("ssl module not found") 573 | self.ssl = True 574 | client_flag |= SSL 575 | for k in ('key', 'cert', 'ca'): 576 | v = None 577 | if k in ssl: 578 | v = ssl[k] 579 | setattr(self, k, v) 580 | 581 | if read_default_group and not read_default_file: 582 | if sys.platform.startswith("win"): 583 | read_default_file = "c:\\my.ini" 584 | else: 585 | read_default_file = "/etc/my.cnf" 586 | 587 | if read_default_file: 588 | if not read_default_group: 589 | read_default_group = "client" 590 | 591 | cfg = configparser.RawConfigParser() 592 | cfg.read(os.path.expanduser(read_default_file)) 593 | 594 | def _config(key, default): 595 | try: 596 | return cfg.get(read_default_group, key) 597 | except Exception: 598 | return default 599 | 600 | user = _config("user", user) 601 | passwd = _config("password", passwd) 602 | host = _config("host", host) 603 | database = _config("database", database) 604 | unix_socket = _config("socket", unix_socket) 605 | port = int(_config("port", port)) 606 | charset = _config("default-character-set", charset) 607 | 608 | self.host = host 609 | self.port = port 610 | self.user = user or DEFAULT_USER 611 | self.password = passwd or "" 612 | self.db = database 613 | self.no_delay = no_delay 614 | self.unix_socket = unix_socket 615 | if charset: 616 | self.charset = charset 617 | self.use_unicode = True 618 | else: 619 | self.charset = DEFAULT_CHARSET 620 | self.use_unicode = False 621 | 622 | if use_unicode is not None: 623 | self.use_unicode = use_unicode 624 | 625 | self.encoding = charset_by_name(self.charset).encoding 626 | 627 | client_flag |= CAPABILITIES 628 | client_flag |= MULTI_STATEMENTS 629 | if self.db: 630 | client_flag |= CONNECT_WITH_DB 631 | self.client_flag = client_flag 632 | 633 | self.cursorclass = cursorclass 634 | self.connect_timeout = connect_timeout 635 | 636 | self._result = None 637 | self._affected_rows = 0 638 | self.host_info = "Not connected" 639 | 640 | #: specified autocommit mode. None means use server default. 641 | self.autocommit_mode = autocommit 642 | 643 | self.encoders = encoders # Need for MySQLdb compatibility. 644 | self.decoders = conv 645 | self.sql_mode = sql_mode 646 | self.init_command = init_command 647 | self.io_loop = io_loop or tornado.ioloop.IOLoop.current() 648 | self.stream = None 649 | 650 | @coroutine 651 | def close(self): 652 | ''' Send the quit message and close the socket ''' 653 | if self.stream.closed(): 654 | raise Error("Already closed") 655 | send_data = struct.pack(' 1: 768 | raise Exception("Multiple rows returned for Database.get() query") 769 | else: 770 | raise Return(rows[0]) 771 | 772 | @coroutine 773 | def execute(self, sql, *args): 774 | """Executes the given query, returning the lastrowid from the query.""" 775 | raise Return((yield self.execute_lastrowid(sql, *args))) 776 | 777 | @coroutine 778 | def execute_lastrowid(self, sql, *args): 779 | """Executes the given query, returning the lastrowid from the query.""" 780 | cur = self.cursor() 781 | try: 782 | yield self._execute(cur, sql, *args) 783 | raise Return(cur.lastrowid) 784 | finally: 785 | cur.close() 786 | 787 | @coroutine 788 | def execute_rowcount(self, sql, *args): 789 | """Executes the given query, returning the rowcount from the query.""" 790 | cur = self.cursor() 791 | try: 792 | yield self._execute(cur, sql, *args) 793 | raise Return(cur.rowcount) 794 | finally: 795 | cur.close() 796 | 797 | @coroutine 798 | def executemany(self, sql, args): 799 | """Executes the given query against all the given param sequences. 800 | 801 | We return the lastrowid from the query. 802 | """ 803 | raise Return((yield self.executemany_lastrowid(sql, args))) 804 | 805 | @coroutine 806 | def executemany_lastrowid(self, sql, args): 807 | """Executes the given query against all the given param sequences. 808 | 809 | We return the lastrowid from the query. 810 | """ 811 | cur = self.cursor() 812 | try: 813 | yield cur.executemany(sql, args) 814 | raise Return(cur.lastrowid) 815 | finally: 816 | cur.close() 817 | 818 | @coroutine 819 | def executemany_rowcount(self, sql, args): 820 | """Executes the given query against all the given param sequences. 821 | 822 | We return the rowcount from the query. 823 | """ 824 | cur = self.cursor() 825 | try: 826 | yield cur.executemany(sql, args) 827 | raise Return(cur.rowcount) 828 | finally: 829 | cur.close() 830 | 831 | update = execute_rowcount 832 | updatemany = executemany_rowcount 833 | 834 | insert = execute_lastrowid 835 | insertmany = executemany_lastrowid 836 | 837 | @coroutine 838 | def _execute(self, cur, sql, *args): 839 | try: 840 | yield cur.execute(sql, *args) 841 | finally: 842 | cur.close() 843 | 844 | # The following methods are INTERNAL USE ONLY (called from Cursor) 845 | @coroutine 846 | def execute_query(self, sql, unbuffered=False): 847 | #if DEBUG: 848 | # print("DEBUG: sending query:", sql) 849 | if isinstance(sql, text_type) and not (JYTHON or IRONPYTHON): 850 | sql = sql.encode(self.encoding) 851 | yield self._execute_command(COM_QUERY, sql) 852 | self._affected_rows = yield self._read_query_result(unbuffered=unbuffered) 853 | # return self._affected_rows 854 | 855 | def next_result(self): 856 | self._affected_rows = self._read_query_result() 857 | return self._affected_rows 858 | 859 | def affected_rows(self): 860 | return self._affected_rows 861 | 862 | def kill(self, thread_id): 863 | arg = struct.pack('= i + 6: 1147 | lang, stat, cap_h, salt_len = struct.unpack('= i + salt_len: 1163 | self.salt += data[i:i+salt_len] # salt_len includes auth_plugin_data_part_1 and filler 1164 | #TODO: AUTH PLUGIN NAME may appeare here. 1165 | 1166 | def get_server_info(self): 1167 | return self.server_version 1168 | 1169 | Warning = Warning 1170 | Error = Error 1171 | InterfaceError = InterfaceError 1172 | DatabaseError = DatabaseError 1173 | DataError = DataError 1174 | OperationalError = OperationalError 1175 | IntegrityError = IntegrityError 1176 | InternalError = InternalError 1177 | ProgrammingError = ProgrammingError 1178 | NotSupportedError = NotSupportedError 1179 | 1180 | 1181 | # TODO: move OK and EOF packet parsing/logic into a proper subclass 1182 | # of MysqlPacket like has been done with FieldDescriptorPacket. 1183 | class MySQLResult(object): 1184 | 1185 | def __init__(self, connection): 1186 | self.connection = connection 1187 | self.affected_rows = None 1188 | self.insert_id = None 1189 | self.server_status = None 1190 | self.warning_count = 0 1191 | self.message = None 1192 | self.field_count = 0 1193 | self.description = None 1194 | self.rows = None 1195 | self.has_next = None 1196 | self.unbuffered_active = False 1197 | 1198 | def __del__(self): 1199 | if self.unbuffered_active: 1200 | self._finish_unbuffered_query() 1201 | 1202 | @coroutine 1203 | def read(self): 1204 | try: 1205 | first_packet = yield self.connection._read_packet() 1206 | 1207 | # TODO: use classes for different packet types? 1208 | if first_packet.is_ok_packet(): 1209 | self._read_ok_packet(first_packet) 1210 | else: 1211 | yield self._read_result_packet(first_packet) 1212 | finally: 1213 | self.connection = False 1214 | 1215 | @coroutine 1216 | def init_unbuffered_query(self): 1217 | self.unbuffered_active = True 1218 | first_packet = yield self.connection._read_packet() 1219 | 1220 | if first_packet.is_ok_packet(): 1221 | self._read_ok_packet(first_packet) 1222 | self.unbuffered_active = False 1223 | self.connection = None 1224 | else: 1225 | self.field_count = first_packet.read_length_encoded_integer() 1226 | yield self._get_descriptions() 1227 | 1228 | # Apparently, MySQLdb picks this number because it's the maximum 1229 | # value of a 64bit unsigned integer. Since we're emulating MySQLdb, 1230 | # we set it to this instead of None, which would be preferred. 1231 | self.affected_rows = 18446744073709551615 1232 | 1233 | def _read_ok_packet(self, first_packet): 1234 | ok_packet = OKPacketWrapper(first_packet) 1235 | self.affected_rows = ok_packet.affected_rows 1236 | self.insert_id = ok_packet.insert_id 1237 | self.server_status = ok_packet.server_status 1238 | self.warning_count = ok_packet.warning_count 1239 | self.message = ok_packet.message 1240 | self.has_next = ok_packet.has_next 1241 | 1242 | def _check_packet_is_eof(self, packet): 1243 | if packet.is_eof_packet(): 1244 | eof_packet = EOFPacketWrapper(packet) 1245 | self.warning_count = eof_packet.warning_count 1246 | self.has_next = eof_packet.has_next 1247 | return True 1248 | return False 1249 | 1250 | @coroutine 1251 | def _read_result_packet(self, first_packet): 1252 | self.field_count = first_packet.read_length_encoded_integer() 1253 | yield self._get_descriptions() 1254 | yield self._read_rowdata_packet() 1255 | 1256 | @coroutine 1257 | def _read_rowdata_packet_unbuffered(self): 1258 | # Check if in an active query 1259 | if not self.unbuffered_active: 1260 | return 1261 | 1262 | # EOF 1263 | packet = yield self.connection._read_packet() 1264 | if self._check_packet_is_eof(packet): 1265 | self.unbuffered_active = False 1266 | self.connection = None 1267 | self.rows = None 1268 | raise Return() 1269 | 1270 | row = yield self._read_row_from_packet(packet) 1271 | self.affected_rows = 1 1272 | self.rows = (row,) # rows should tuple of row for MySQL-python compatibility. 1273 | raise Return(row) 1274 | 1275 | @coroutine 1276 | def _finish_unbuffered_query(self): 1277 | # After much reading on the MySQL protocol, it appears that there is, 1278 | # in fact, no way to stop MySQL from sending all the data after 1279 | # executing a query, so we just spin, and wait for an EOF packet. 1280 | while self.unbuffered_active: 1281 | packet = yield self.connection._read_packet() 1282 | if self._check_packet_is_eof(packet): 1283 | self.unbuffered_active = False 1284 | self.connection = None # release reference to kill cyclic reference. 1285 | 1286 | @coroutine 1287 | def _read_rowdata_packet(self): 1288 | """Read a rowdata packet for each data row in the result set.""" 1289 | rows = [] 1290 | while True: 1291 | packet = MysqlPacket(self.connection) 1292 | buff = b'' 1293 | while True: 1294 | packet_header = yield self.connection.stream.read_bytes(4) 1295 | if DEBUG: dump_packet(packet_header) 1296 | packet_length_bin = packet_header[:3] 1297 | 1298 | #TODO: check sequence id 1299 | self._packet_number = byte2int(packet_header[3]) 1300 | 1301 | bin_length = packet_length_bin + b'\0' # pad little-endian number 1302 | bytes_to_read = struct.unpack('>> datetime_or_None('2007-02-25 23:06:20') 104 | datetime.datetime(2007, 2, 25, 23, 6, 20) 105 | >>> datetime_or_None('2007-02-25T23:06:20') 106 | datetime.datetime(2007, 2, 25, 23, 6, 20) 107 | 108 | Illegal values are returned as None: 109 | 110 | >>> datetime_or_None('2007-02-31T23:06:20') is None 111 | True 112 | >>> datetime_or_None('0000-00-00 00:00:00') is None 113 | True 114 | 115 | """ 116 | if ' ' in obj: 117 | sep = ' ' 118 | elif 'T' in obj: 119 | sep = 'T' 120 | else: 121 | return convert_date(obj) 122 | 123 | try: 124 | ymd, hms = obj.split(sep, 1) 125 | usecs = '0' 126 | if '.' in hms: 127 | hms, usecs = hms.split('.') 128 | usecs = float('0.' + usecs) * 1e6 129 | return datetime.datetime(*[ int(x) for x in ymd.split('-')+hms.split(':')+[usecs] ]) 130 | except ValueError: 131 | return convert_date(obj) 132 | 133 | 134 | def convert_timedelta(obj): 135 | """Returns a TIME column as a timedelta object: 136 | 137 | >>> timedelta_or_None('25:06:17') 138 | datetime.timedelta(1, 3977) 139 | >>> timedelta_or_None('-25:06:17') 140 | datetime.timedelta(-2, 83177) 141 | 142 | Illegal values are returned as None: 143 | 144 | >>> timedelta_or_None('random crap') is None 145 | True 146 | 147 | Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but 148 | can accept values as (+|-)DD HH:MM:SS. The latter format will not 149 | be parsed correctly by this function. 150 | """ 151 | try: 152 | microseconds = 0 153 | if "." in obj: 154 | (obj, tail) = obj.split('.') 155 | microseconds = float('0.' + tail) * 1e6 156 | hours, minutes, seconds = obj.split(':') 157 | tdelta = datetime.timedelta( 158 | hours = int(hours), 159 | minutes = int(minutes), 160 | seconds = int(seconds), 161 | microseconds = int(microseconds) 162 | ) 163 | return tdelta 164 | except ValueError: 165 | return None 166 | 167 | def convert_time(obj): 168 | """Returns a TIME column as a time object: 169 | 170 | >>> time_or_None('15:06:17') 171 | datetime.time(15, 6, 17) 172 | 173 | Illegal values are returned as None: 174 | 175 | >>> time_or_None('-25:06:17') is None 176 | True 177 | >>> time_or_None('random crap') is None 178 | True 179 | 180 | Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but 181 | can accept values as (+|-)DD HH:MM:SS. The latter format will not 182 | be parsed correctly by this function. 183 | 184 | Also note that MySQL's TIME column corresponds more closely to 185 | Python's timedelta and not time. However if you want TIME columns 186 | to be treated as time-of-day and not a time offset, then you can 187 | use set this function as the converter for FIELD_TYPE.TIME. 188 | """ 189 | try: 190 | microseconds = 0 191 | if "." in obj: 192 | (obj, tail) = obj.split('.') 193 | microseconds = float('0.' + tail) * 1e6 194 | hours, minutes, seconds = obj.split(':') 195 | return datetime.time(hour=int(hours), minute=int(minutes), 196 | second=int(seconds), microsecond=int(microseconds)) 197 | except ValueError: 198 | return None 199 | 200 | def convert_date(obj): 201 | """Returns a DATE column as a date object: 202 | 203 | >>> date_or_None('2007-02-26') 204 | datetime.date(2007, 2, 26) 205 | 206 | Illegal values are returned as None: 207 | 208 | >>> date_or_None('2007-02-31') is None 209 | True 210 | >>> date_or_None('0000-00-00') is None 211 | True 212 | 213 | """ 214 | try: 215 | return datetime.date(*[ int(x) for x in obj.split('-', 2) ]) 216 | except ValueError: 217 | return None 218 | 219 | 220 | def convert_mysql_timestamp(timestamp): 221 | """Convert a MySQL TIMESTAMP to a Timestamp object. 222 | 223 | MySQL >= 4.1 returns TIMESTAMP in the same format as DATETIME: 224 | 225 | >>> mysql_timestamp_converter('2007-02-25 22:32:17') 226 | datetime.datetime(2007, 2, 25, 22, 32, 17) 227 | 228 | MySQL < 4.1 uses a big string of numbers: 229 | 230 | >>> mysql_timestamp_converter('20070225223217') 231 | datetime.datetime(2007, 2, 25, 22, 32, 17) 232 | 233 | Illegal values are returned as None: 234 | 235 | >>> mysql_timestamp_converter('2007-02-31 22:32:17') is None 236 | True 237 | >>> mysql_timestamp_converter('00000000000000') is None 238 | True 239 | 240 | """ 241 | if timestamp[4] == '-': 242 | return convert_datetime(timestamp) 243 | timestamp += "0"*(14-len(timestamp)) # padding 244 | year, month, day, hour, minute, second = \ 245 | int(timestamp[:4]), int(timestamp[4:6]), int(timestamp[6:8]), \ 246 | int(timestamp[8:10]), int(timestamp[10:12]), int(timestamp[12:14]) 247 | try: 248 | return datetime.datetime(year, month, day, hour, minute, second) 249 | except ValueError: 250 | return None 251 | 252 | def convert_set(s): 253 | return set(s.split(",")) 254 | 255 | def convert_bit(b): 256 | #b = "\x00" * (8 - len(b)) + b # pad w/ zeroes 257 | #return struct.unpack(">Q", b)[0] 258 | # 259 | # the snippet above is right, but MySQLdb doesn't process bits, 260 | # so we shouldn't either 261 | return b 262 | 263 | def convert_characters(connection, field, data): 264 | field_charset = charset_by_id(field.charsetnr).name 265 | encoding = charset_to_encoding(field_charset) 266 | if field.flags & FLAG.SET: 267 | return convert_set(data.decode(encoding)) 268 | if field.flags & FLAG.BINARY: 269 | return data 270 | 271 | if connection.use_unicode: 272 | data = data.decode(encoding) 273 | elif connection.charset != field_charset: 274 | data = data.decode(encoding) 275 | data = data.encode(connection.encoding) 276 | return data 277 | 278 | encoders = { 279 | bool: escape_bool, 280 | int: escape_int, 281 | long_type: escape_int, 282 | float: escape_float, 283 | str: escape_str, 284 | text_type: escape_unicode, 285 | tuple: escape_sequence, 286 | list: escape_sequence, 287 | set: escape_sequence, 288 | dict: escape_dict, 289 | type(None): escape_None, 290 | datetime.date: escape_date, 291 | datetime.datetime: escape_datetime, 292 | datetime.timedelta: escape_timedelta, 293 | datetime.time: escape_time, 294 | time.struct_time: escape_struct_time, 295 | Decimal: str, 296 | } 297 | 298 | 299 | def through(x): 300 | return x 301 | 302 | if not PY2 or JYTHON or IRONPYTHON: 303 | encoders[bytes] = escape_bytes 304 | 305 | decoders = { 306 | FIELD_TYPE.BIT: convert_bit, 307 | FIELD_TYPE.TINY: int, 308 | FIELD_TYPE.SHORT: int, 309 | FIELD_TYPE.LONG: int, 310 | FIELD_TYPE.FLOAT: float, 311 | FIELD_TYPE.DOUBLE: float, 312 | FIELD_TYPE.DECIMAL: float, 313 | FIELD_TYPE.NEWDECIMAL: float, 314 | FIELD_TYPE.LONGLONG: int, 315 | FIELD_TYPE.INT24: int, 316 | FIELD_TYPE.YEAR: int, 317 | FIELD_TYPE.TIMESTAMP: convert_mysql_timestamp, 318 | FIELD_TYPE.DATETIME: convert_datetime, 319 | FIELD_TYPE.TIME: convert_timedelta, 320 | FIELD_TYPE.DATE: convert_date, 321 | FIELD_TYPE.SET: convert_set, 322 | FIELD_TYPE.BLOB: through, 323 | FIELD_TYPE.TINY_BLOB: through, 324 | FIELD_TYPE.MEDIUM_BLOB: through, 325 | FIELD_TYPE.LONG_BLOB: through, 326 | FIELD_TYPE.STRING: through, 327 | FIELD_TYPE.VAR_STRING: through, 328 | FIELD_TYPE.VARCHAR: through, 329 | FIELD_TYPE.DECIMAL: Decimal, 330 | FIELD_TYPE.NEWDECIMAL: Decimal, 331 | } 332 | 333 | 334 | # for MySQLdb compatibility 335 | conversions = decoders 336 | 337 | def Thing2Literal(obj): 338 | return escape_str(str(obj)) 339 | -------------------------------------------------------------------------------- /asynctorndb/cursors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function, absolute_import 3 | import re 4 | 5 | from ._compat import range_type, text_type, PY2 6 | from tornado.gen import coroutine, Return 7 | 8 | from .err import ( 9 | Warning, Error, InterfaceError, DataError, 10 | DatabaseError, OperationalError, IntegrityError, InternalError, 11 | NotSupportedError, ProgrammingError) 12 | 13 | 14 | #: Regular expression for :meth:`Cursor.executemany`. 15 | #: executemany only suports simple bulk insert. 16 | #: You can use it to load large dataset. 17 | RE_INSERT_VALUES = re.compile(r"""INSERT\s.+\sVALUES\s+(\(\s*%s\s*(,\s*%s\s*)*\))\s*\Z""", 18 | re.IGNORECASE | re.DOTALL) 19 | 20 | 21 | class Cursor(object): 22 | ''' 23 | This is the object you use to interact with the database. 24 | ''' 25 | 26 | #: Max stetement size which :meth:`executemany` generates. 27 | #: 28 | #: Max size of allowed statement is max_allowed_packet - packet_header_size. 29 | #: Default value of max_allowed_packet is 1048576. 30 | max_stmt_length = 1024000 31 | 32 | def __init__(self, connection): 33 | ''' 34 | Do not create an instance of a Cursor yourself. Call 35 | connections.Connection.cursor(). 36 | ''' 37 | self.connection = connection 38 | self.description = None 39 | self.rownumber = 0 40 | self.rowcount = -1 41 | self.arraysize = 1 42 | self._executed = None 43 | self._result = None 44 | self._rows = None 45 | 46 | def __del__(self): 47 | ''' 48 | When this gets GC'd close it. 49 | ''' 50 | self.close() 51 | 52 | def close(self): 53 | ''' 54 | Closing a cursor just exhausts all remaining data. 55 | ''' 56 | conn = self.connection 57 | if conn is None: 58 | return 59 | try: 60 | while self.nextset(): 61 | pass 62 | finally: 63 | self.connection = None 64 | 65 | def _get_db(self): 66 | if not self.connection: 67 | raise ProgrammingError("Cursor closed") 68 | return self.connection 69 | 70 | def _check_executed(self): 71 | if not self._executed: 72 | raise ProgrammingError("execute() first") 73 | 74 | def _conv_row(self, row): 75 | return row 76 | 77 | def setinputsizes(self, *args): 78 | """Does nothing, required by DB API.""" 79 | 80 | def setoutputsizes(self, *args): 81 | """Does nothing, required by DB API.""" 82 | 83 | def nextset(self): 84 | """Get the next query set""" 85 | conn = self._get_db() 86 | current_result = self._result 87 | if current_result is None or current_result is not conn._result: 88 | return None 89 | if not current_result.has_next: 90 | return None 91 | conn.next_result() 92 | self._do_get_result() 93 | return True 94 | 95 | def _escape_args(self, args, conn): 96 | if isinstance(args, (tuple, list)): 97 | return tuple(conn.escape(arg) for arg in args) 98 | elif isinstance(args, dict): 99 | return dict((key, conn.escape(val)) for (key, val) in list(args.items())) 100 | else: 101 | #If it's not a dictionary let's try escaping it anyways. 102 | #Worst case it will throw a Value error 103 | return conn.escape(args) 104 | 105 | @coroutine 106 | def execute(self, query, *args): 107 | '''Execute a query''' 108 | conn = self._get_db() 109 | 110 | while self.nextset(): 111 | pass 112 | 113 | if PY2: # Use bytes on Python 2 always 114 | encoding = conn.encoding 115 | 116 | def ensure_bytes(x): 117 | if isinstance(x, unicode): 118 | x = x.encode(encoding) 119 | return x 120 | 121 | query = ensure_bytes(query) 122 | 123 | if args is not None: 124 | if isinstance(args, (tuple, list)): 125 | args = tuple(map(ensure_bytes, args)) 126 | elif isinstance(args, dict): 127 | args = dict((ensure_bytes(key), ensure_bytes(val)) for (key, val) in args.items()) 128 | else: 129 | args = ensure_bytes(args) 130 | 131 | if args is not None: 132 | query = query % self._escape_args(args, conn) 133 | 134 | result = yield self._query(query) 135 | self._executed = query 136 | raise Return(result) 137 | 138 | @coroutine 139 | def executemany(self, query, args): 140 | """Run several data against one query 141 | 142 | PyMySQL can execute bulkinsert for query like 'INSERT ... VALUES (%s)'. 143 | In other form of queries, just run :meth:`execute` many times. 144 | """ 145 | if not args: 146 | raise Return() 147 | 148 | m = RE_INSERT_VALUES.match(query) 149 | if m: 150 | q_values = m.group(1).rstrip() 151 | assert q_values[0] == '(' and q_values[-1] == ')' 152 | q_prefix = query[:m.start(1)] 153 | raise Return((yield self._do_execute_many(q_prefix, q_values, args, 154 | self.max_stmt_length, 155 | self._get_db().encoding))) 156 | 157 | for arg in args: 158 | row = yield self.execute(query, *arg) 159 | self.rowcount += row 160 | raise Return(self.rowcount) 161 | 162 | @coroutine 163 | def _do_execute_many(self, prefix, values, args, max_stmt_length, encoding): 164 | conn = self._get_db() 165 | escape = self._escape_args 166 | if isinstance(prefix, text_type): 167 | prefix = prefix.encode(encoding) 168 | sql = bytearray(prefix) 169 | args = iter(args) 170 | v = values % escape(next(args), conn) 171 | if isinstance(v, text_type): 172 | v = v.encode(encoding) 173 | sql += v 174 | rows = 0 175 | for arg in args: 176 | v = values % escape(arg, conn) 177 | if isinstance(v, text_type): 178 | v = v.encode(encoding) 179 | if len(sql) + len(v) + 1 > max_stmt_length: 180 | row = yield self.execute(str(sql)) 181 | rows += row 182 | sql = bytearray(prefix) 183 | else: 184 | sql += b',' 185 | sql += v 186 | row = yield self.execute(str(sql)) 187 | rows += row 188 | self.rowcount = rows 189 | raise Return(rows) 190 | 191 | def callproc(self, procname, args=()): 192 | """Execute stored procedure procname with args 193 | 194 | procname -- string, name of procedure to execute on server 195 | 196 | args -- Sequence of parameters to use with procedure 197 | 198 | Returns the original args.x 199 | 200 | Compatibility warning: PEP-249 specifies that any modified 201 | parameters must be returned. This is currently impossible 202 | as they are only available by storing them in a server 203 | variable and then retrieved by a query. Since stored 204 | procedures return zero or more result sets, there is no 205 | reliable way to get at OUT or INOUT parameters via callproc. 206 | The server variables are named @_procname_n, where procname 207 | is the parameter above and n is the position of the parameter 208 | (from zero). Once all result sets generated by the procedure 209 | have been fetched, you can issue a SELECT @_procname_0, ... 210 | query using .execute() to get any OUT or INOUT values. 211 | 212 | Compatibility warning: The act of calling a stored procedure 213 | itself creates an empty result set. This appears after any 214 | result sets generated by the procedure. This is non-standard 215 | behavior with respect to the DB-API. Be sure to use nextset() 216 | to advance through all result sets; otherwise you may get 217 | disconnected. 218 | """ 219 | conn = self._get_db() 220 | for index, arg in enumerate(args): 221 | q = "SET @_%s_%d=%s" % (procname, index, conn.escape(arg)) 222 | self._query(q) 223 | self.nextset() 224 | 225 | q = "CALL %s(%s)" % (procname, 226 | ','.join(['@_%s_%d' % (procname, i) 227 | for i in range_type(len(args))])) 228 | self._query(q) 229 | self._executed = q 230 | return args 231 | 232 | def fetchone(self): 233 | ''' Fetch the next row ''' 234 | self._check_executed() 235 | if self._rows is None or self.rownumber >= len(self._rows): 236 | return None 237 | result = self._rows[self.rownumber] 238 | self.rownumber += 1 239 | return result 240 | 241 | def fetchmany(self, size=None): 242 | ''' Fetch several rows ''' 243 | self._check_executed() 244 | if self._rows is None: 245 | return None 246 | end = self.rownumber + (size or self.arraysize) 247 | result = self._rows[self.rownumber:end] 248 | self.rownumber = min(end, len(self._rows)) 249 | return result 250 | 251 | def fetchall(self): 252 | ''' Fetch all the rows ''' 253 | self._check_executed() 254 | if self._rows is None: 255 | return None 256 | if self.rownumber: 257 | result = self._rows[self.rownumber:] 258 | else: 259 | result = self._rows 260 | self.rownumber = len(self._rows) 261 | return result 262 | 263 | def scroll(self, value, mode='relative'): 264 | self._check_executed() 265 | if mode == 'relative': 266 | r = self.rownumber + value 267 | elif mode == 'absolute': 268 | r = value 269 | else: 270 | raise ProgrammingError("unknown scroll mode %s" % mode) 271 | 272 | if not (0 <= r < len(self._rows)): 273 | raise IndexError("out of range") 274 | self.rownumber = r 275 | 276 | @coroutine 277 | def _query(self, q): 278 | conn = self._get_db() 279 | self._last_executed = q 280 | yield conn.execute_query(q) 281 | self._do_get_result() 282 | raise Return(self.rowcount) 283 | 284 | def _do_get_result(self): 285 | conn = self._get_db() 286 | 287 | self.rownumber = 0 288 | self._result = result = conn._result 289 | 290 | self.rowcount = result.affected_rows 291 | self.description = result.description 292 | self.lastrowid = result.insert_id 293 | self._rows = result.rows 294 | 295 | def __iter__(self): 296 | return iter(self.fetchone, None) 297 | 298 | Warning = Warning 299 | Error = Error 300 | InterfaceError = InterfaceError 301 | DatabaseError = DatabaseError 302 | DataError = DataError 303 | OperationalError = OperationalError 304 | IntegrityError = IntegrityError 305 | InternalError = InternalError 306 | ProgrammingError = ProgrammingError 307 | NotSupportedError = NotSupportedError 308 | 309 | 310 | class DictCursorMixin(object): 311 | # You can override this to use OrderedDict or other dict-like types. 312 | dict_type = dict 313 | 314 | def _do_get_result(self): 315 | super(DictCursorMixin, self)._do_get_result() 316 | fields = [] 317 | if self.description: 318 | for f in self._result.fields: 319 | name = f.name 320 | if name in fields: 321 | name = f.table_name + '.' + name 322 | fields.append(name) 323 | self._fields = fields 324 | 325 | if fields and self._rows: 326 | self._rows = [self._conv_row(r) for r in self._rows] 327 | 328 | def _conv_row(self, row): 329 | if row is None: 330 | return None 331 | return self.dict_type(list(zip(self._fields, row))) 332 | 333 | 334 | class DictCursor(DictCursorMixin, Cursor): 335 | """A cursor which returns results as a dictionary""" 336 | 337 | 338 | class SSCursor(Cursor): 339 | """ 340 | Unbuffered Cursor, mainly useful for queries that return a lot of data, 341 | or for connections to remote servers over a slow network. 342 | 343 | Instead of copying every row of data into a buffer, this will fetch 344 | rows as needed. The upside of this, is the client uses much less memory, 345 | and rows are returned much faster when traveling over a slow network, 346 | or if the result set is very big. 347 | 348 | There are limitations, though. The MySQL protocol doesn't support 349 | returning the total number of rows, so the only way to tell how many rows 350 | there are is to iterate over every row returned. Also, it currently isn't 351 | possible to scroll backwards, as only the current row is held in memory. 352 | """ 353 | 354 | def _conv_row(self, row): 355 | return row 356 | 357 | def close(self): 358 | conn = self.connection 359 | if conn is None: 360 | return 361 | 362 | if self._result is not None and self._result is conn._result: 363 | self._result._finish_unbuffered_query() 364 | 365 | try: 366 | while self.nextset(): 367 | pass 368 | finally: 369 | self.connection = None 370 | 371 | def _query(self, q): 372 | conn = self._get_db() 373 | self._last_executed = q 374 | conn.execute_query(q, unbuffered=True) 375 | self._do_get_result() 376 | return self.rowcount 377 | 378 | def read_next(self): 379 | """ Read next row """ 380 | return self._conv_row(self._result._read_rowdata_packet_unbuffered()) 381 | 382 | def fetchone(self): 383 | """ Fetch next row """ 384 | self._check_executed() 385 | row = self.read_next() 386 | if row is None: 387 | return None 388 | self.rownumber += 1 389 | return row 390 | 391 | def fetchall(self): 392 | """ 393 | Fetch all, as per MySQLdb. Pretty useless for large queries, as 394 | it is buffered. See fetchall_unbuffered(), if you want an unbuffered 395 | generator version of this method. 396 | 397 | """ 398 | return list(self.fetchall_unbuffered()) 399 | 400 | def fetchall_unbuffered(self): 401 | """ 402 | Fetch all, implemented as a generator, which isn't to standard, 403 | however, it doesn't make sense to return everything in a list, as that 404 | would use ridiculous memory for large result sets. 405 | """ 406 | return iter(self.fetchone, None) 407 | 408 | def __iter__(self): 409 | return self.fetchall_unbuffered() 410 | 411 | def fetchmany(self, size=None): 412 | """ Fetch many """ 413 | 414 | self._check_executed() 415 | if size is None: 416 | size = self.arraysize 417 | 418 | rows = [] 419 | for i in range_type(size): 420 | row = self.read_next() 421 | if row is None: 422 | break 423 | rows.append(row) 424 | self.rownumber += 1 425 | return rows 426 | 427 | def scroll(self, value, mode='relative'): 428 | self._check_executed() 429 | 430 | if mode == 'relative': 431 | if value < 0: 432 | raise NotSupportedError( 433 | "Backwards scrolling not supported by this cursor") 434 | 435 | for _ in range_type(value): 436 | self.read_next() 437 | self.rownumber += value 438 | elif mode == 'absolute': 439 | if value < self.rownumber: 440 | raise NotSupportedError( 441 | "Backwards scrolling not supported by this cursor") 442 | 443 | end = value - self.rownumber 444 | for _ in range_type(end): 445 | self.read_next() 446 | self.rownumber = value 447 | else: 448 | raise ProgrammingError("unknown scroll mode %s" % mode) 449 | 450 | 451 | class SSDictCursor(DictCursorMixin, SSCursor): 452 | """ An unbuffered cursor, which returns results as a dictionary """ 453 | -------------------------------------------------------------------------------- /asynctorndb/err.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | from .constants import ER 4 | 5 | class MySQLError(Exception): 6 | """Exception related to operation with MySQL.""" 7 | 8 | 9 | class Warning(Warning, MySQLError): 10 | """Exception raised for important warnings like data truncations 11 | while inserting, etc.""" 12 | 13 | class Error(MySQLError): 14 | """Exception that is the base class of all other error exceptions 15 | (not Warning).""" 16 | 17 | 18 | class InterfaceError(Error): 19 | """Exception raised for errors that are related to the database 20 | interface rather than the database itself.""" 21 | 22 | 23 | class DatabaseError(Error): 24 | """Exception raised for errors that are related to the 25 | database.""" 26 | 27 | 28 | class DataError(DatabaseError): 29 | """Exception raised for errors that are due to problems with the 30 | processed data like division by zero, numeric value out of range, 31 | etc.""" 32 | 33 | 34 | class OperationalError(DatabaseError): 35 | """Exception raised for errors that are related to the database's 36 | operation and not necessarily under the control of the programmer, 37 | e.g. an unexpected disconnect occurs, the data source name is not 38 | found, a transaction could not be processed, a memory allocation 39 | error occurred during processing, etc.""" 40 | 41 | 42 | class IntegrityError(DatabaseError): 43 | """Exception raised when the relational integrity of the database 44 | is affected, e.g. a foreign key check fails, duplicate key, 45 | etc.""" 46 | 47 | 48 | class InternalError(DatabaseError): 49 | """Exception raised when the database encounters an internal 50 | error, e.g. the cursor is not valid anymore, the transaction is 51 | out of sync, etc.""" 52 | 53 | 54 | class ProgrammingError(DatabaseError): 55 | """Exception raised for programming errors, e.g. table not found 56 | or already exists, syntax error in the SQL statement, wrong number 57 | of parameters specified, etc.""" 58 | 59 | 60 | class NotSupportedError(DatabaseError): 61 | """Exception raised in case a method or database API was used 62 | which is not supported by the database, e.g. requesting a 63 | .rollback() on a connection that does not support transaction or 64 | has transactions turned off.""" 65 | 66 | 67 | error_map = {} 68 | 69 | def _map_error(exc, *errors): 70 | for error in errors: 71 | error_map[error] = exc 72 | 73 | _map_error(ProgrammingError, ER.DB_CREATE_EXISTS, ER.SYNTAX_ERROR, 74 | ER.PARSE_ERROR, ER.NO_SUCH_TABLE, ER.WRONG_DB_NAME, 75 | ER.WRONG_TABLE_NAME, ER.FIELD_SPECIFIED_TWICE, 76 | ER.INVALID_GROUP_FUNC_USE, ER.UNSUPPORTED_EXTENSION, 77 | ER.TABLE_MUST_HAVE_COLUMNS, ER.CANT_DO_THIS_DURING_AN_TRANSACTION) 78 | _map_error(DataError, ER.WARN_DATA_TRUNCATED, ER.WARN_NULL_TO_NOTNULL, 79 | ER.WARN_DATA_OUT_OF_RANGE, ER.NO_DEFAULT, ER.PRIMARY_CANT_HAVE_NULL, 80 | ER.DATA_TOO_LONG, ER.DATETIME_FUNCTION_OVERFLOW) 81 | _map_error(IntegrityError, ER.DUP_ENTRY, ER.NO_REFERENCED_ROW, 82 | ER.NO_REFERENCED_ROW_2, ER.ROW_IS_REFERENCED, ER.ROW_IS_REFERENCED_2, 83 | ER.CANNOT_ADD_FOREIGN, ER.BAD_NULL_ERROR) 84 | _map_error(NotSupportedError, ER.WARNING_NOT_COMPLETE_ROLLBACK, 85 | ER.NOT_SUPPORTED_YET, ER.FEATURE_DISABLED, ER.UNKNOWN_STORAGE_ENGINE) 86 | _map_error(OperationalError, ER.DBACCESS_DENIED_ERROR, ER.ACCESS_DENIED_ERROR, 87 | ER.CON_COUNT_ERROR, ER.TABLEACCESS_DENIED_ERROR, 88 | ER.COLUMNACCESS_DENIED_ERROR) 89 | 90 | del _map_error, ER 91 | 92 | 93 | def _get_error_info(data): 94 | errno = struct.unpack('