Skip to content

Commit c04b5c9

Browse files
authored
_binary prefix is now optional (PyMySQL#628)
1 parent 0408bf4 commit c04b5c9

File tree

5 files changed

+46
-36
lines changed

5 files changed

+46
-36
lines changed

pymysql/connections.py

+32-29
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from .charset import MBLENGTH, charset_by_name, charset_by_id
2020
from .constants import CLIENT, COMMAND, CR, FIELD_TYPE, SERVER_STATUS
21-
from .converters import escape_item, escape_string, through, conversions as _conv
21+
from . import converters
2222
from .cursors import Cursor
2323
from .optionfile import Parser
2424
from .util import byte2int, int2byte
@@ -44,39 +44,28 @@
4444

4545
_py_version = sys.version_info[:2]
4646

47+
if PY2:
48+
pass
49+
elif _py_version < (3, 6):
50+
# See http://bugs.python.org/issue24870
51+
_surrogateescape_table = [chr(i) if i < 0x80 else chr(i + 0xdc00) for i in range(256)]
52+
53+
def _fast_surrogateescape(s):
54+
return s.decode('latin1').translate(_surrogateescape_table)
55+
else:
56+
def _fast_surrogateescape(s):
57+
return s.decode('ascii', 'surrogateescape')
4758

4859
# socket.makefile() in Python 2 is not usable because very inefficient and
4960
# bad behavior about timeout.
5061
# XXX: ._socketio doesn't work under IronPython.
51-
if _py_version == (2, 7) and not IRONPYTHON:
62+
if PY2 and not IRONPYTHON:
5263
# read method of file-like returned by sock.makefile() is very slow.
5364
# So we copy io-based one from Python 3.
5465
from ._socketio import SocketIO
5566

5667
def _makefile(sock, mode):
5768
return io.BufferedReader(SocketIO(sock, mode))
58-
elif _py_version == (2, 6):
59-
# Python 2.6 doesn't have fast io module.
60-
# So we make original one.
61-
class SockFile(object):
62-
def __init__(self, sock):
63-
self._sock = sock
64-
65-
def read(self, n):
66-
read = self._sock.recv(n)
67-
if len(read) == n:
68-
return read
69-
while True:
70-
data = self._sock.recv(n-len(read))
71-
if not data:
72-
return read
73-
read += data
74-
if len(read) == n:
75-
return read
76-
77-
def _makefile(sock, mode):
78-
assert mode == 'rb'
79-
return SockFile(sock)
8069
else:
8170
# socket.makefile in Python 3 is nice.
8271
def _makefile(sock, mode):
@@ -570,6 +559,7 @@ class Connection(object):
570559
(if no authenticate method) for returning a string from the user. (experimental)
571560
:param db: Alias for database. (for compatibility to MySQLdb)
572561
:param passwd: Alias for password. (for compatibility to MySQLdb)
562+
:param binary_prefix: Add _binary prefix on bytes and bytearray. (default: False)
573563
"""
574564

575565
_sock = None
@@ -586,7 +576,7 @@ def __init__(self, host=None, user=None, password="",
586576
autocommit=False, db=None, passwd=None, local_infile=False,
587577
max_allowed_packet=16*1024*1024, defer_connect=False,
588578
auth_plugin_map={}, read_timeout=None, write_timeout=None,
589-
bind_address=None):
579+
bind_address=None, binary_prefix=False):
590580
if no_delay is not None:
591581
warnings.warn("no_delay option is deprecated", DeprecationWarning)
592582

@@ -693,14 +683,16 @@ def _config(key, arg):
693683
self.autocommit_mode = autocommit
694684

695685
if conv is None:
696-
conv = _conv
686+
conv = converters.conversions
687+
697688
# Need for MySQLdb compatibility.
698689
self.encoders = dict([(k, v) for (k, v) in conv.items() if type(k) is not int])
699690
self.decoders = dict([(k, v) for (k, v) in conv.items() if type(k) is int])
700691
self.sql_mode = sql_mode
701692
self.init_command = init_command
702693
self.max_allowed_packet = max_allowed_packet
703694
self._auth_plugin_map = auth_plugin_map
695+
self._binary_prefix = binary_prefix
704696
if defer_connect:
705697
self._sock = None
706698
else:
@@ -812,7 +804,12 @@ def escape(self, obj, mapping=None):
812804
"""
813805
if isinstance(obj, str_type):
814806
return "'" + self.escape_string(obj) + "'"
815-
return escape_item(obj, self.charset, mapping=mapping)
807+
if isinstance(obj, (bytes, bytearray)):
808+
ret = self._quote_bytes(obj)
809+
if self._binary_prefix:
810+
ret = "_binary" + ret
811+
return ret
812+
return converters.escape_item(obj, self.charset, mapping=mapping)
816813

817814
def literal(self, obj):
818815
"""Alias for escape()
@@ -825,7 +822,13 @@ def escape_string(self, s):
825822
if (self.server_status &
826823
SERVER_STATUS.SERVER_STATUS_NO_BACKSLASH_ESCAPES):
827824
return s.replace("'", "''")
828-
return escape_string(s)
825+
return converters.escape_string(s)
826+
827+
def _quote_bytes(self, s):
828+
if (self.server_status &
829+
SERVER_STATUS.SERVER_STATUS_NO_BACKSLASH_ESCAPES):
830+
return "'%s'" % (_fast_surrogateescape(s.replace(b"'", b"''")),)
831+
return converters.escape_bytes(s)
829832

830833
def cursor(self, cursor=None):
831834
"""Create a new cursor to execute queries with"""
@@ -1510,7 +1513,7 @@ def _get_descriptions(self):
15101513
else:
15111514
encoding = None
15121515
converter = self.connection.decoders.get(field_type)
1513-
if converter is through:
1516+
if converter is converters.through:
15141517
converter = None
15151518
if DEBUG: print("DEBUG: field={}, converter={}".format(field, converter))
15161519
self.converters.append((encoding, converter))

pymysql/converters.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,14 @@ def escape_string(value, mapping=None):
9090
value = value.replace('"', '\\"')
9191
return value
9292

93-
def escape_bytes(value, mapping=None):
93+
def escape_bytes_prefixed(value, mapping=None):
9494
assert isinstance(value, (bytes, bytearray))
9595
return b"_binary'%s'" % escape_string(value)
96+
97+
def escape_bytes(value, mapping=None):
98+
assert isinstance(value, (bytes, bytearray))
99+
return b"'%s'" % escape_string(value)
100+
96101
else:
97102
escape_string = _escape_unicode
98103

@@ -102,9 +107,12 @@ def escape_bytes(value, mapping=None):
102107
# We can escape special chars and surrogateescape at once.
103108
_escape_bytes_table = _escape_table + [chr(i) for i in range(0xdc80, 0xdd00)]
104109

105-
def escape_bytes(value, mapping=None):
110+
def escape_bytes_prefixed(value, mapping=None):
106111
return "_binary'%s'" % value.decode('latin1').translate(_escape_bytes_table)
107112

113+
def escape_bytes(value, mapping=None):
114+
return "'%s'" % value.decode('latin1').translate(_escape_bytes_table)
115+
108116

109117
def escape_unicode(value, mapping=None):
110118
return u"'%s'" % _escape_unicode(value)
@@ -373,7 +381,6 @@ def convert_characters(connection, field, data):
373381
set: escape_sequence,
374382
frozenset: escape_sequence,
375383
dict: escape_dict,
376-
bytearray: escape_bytes,
377384
type(None): escape_None,
378385
datetime.date: escape_date,
379386
datetime.datetime: escape_datetime,

pymysql/tests/test_issues.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,8 @@ def test_issue_364(self):
415415
"create table issue364 (value_1 binary(3), value_2 varchar(3)) "
416416
"engine=InnoDB default charset=utf8")
417417

418-
sql = "insert into issue364 (value_1, value_2) values (%s, %s)"
419-
usql = u"insert into issue364 (value_1, value_2) values (%s, %s)"
418+
sql = "insert into issue364 (value_1, value_2) values (_binary %s, %s)"
419+
usql = u"insert into issue364 (value_1, value_2) values (_binary %s, %s)"
420420
values = [pymysql.Binary(b"\x00\xff\x00"), u"\xe4\xf6\xfc"]
421421

422422
# test single insert and select

pymysql/tests/thirdparty/test_MySQLdb/capabilities.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class DatabaseTest(unittest.TestCase):
1717

1818
db_module = None
1919
connect_args = ()
20-
connect_kwargs = dict(use_unicode=True, charset="utf8")
20+
connect_kwargs = dict(use_unicode=True, charset="utf8", binary_prefix=True)
2121
create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8"
2222
rows = 10
2323
debug = False

pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class test_MySQLdb(capabilities.DatabaseTest):
1515
connect_args = ()
1616
connect_kwargs = base.PyMySQLTestCase.databases[0].copy()
1717
connect_kwargs.update(dict(read_default_file='~/.my.cnf',
18-
use_unicode=True,
18+
use_unicode=True, binary_prefix=True,
1919
charset='utf8', sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL"))
2020

2121
create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8"

0 commit comments

Comments
 (0)