Skip to content

Commit 6f78fcb

Browse files
committed
Merge pull request #300 from mathieulongtin/issue_288_executemany
fix issue 288: executemany now works with "insert ... on duplicate update"
2 parents 618f844 + e4632e1 commit 6f78fcb

2 files changed

Lines changed: 39 additions & 9 deletions

File tree

pymysql/cursors.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#: Regular expression for :meth:`Cursor.executemany`.
1212
#: executemany only suports simple bulk insert.
1313
#: You can use it to load large dataset.
14-
RE_INSERT_VALUES = re.compile(r"""INSERT\s.+\sVALUES\s+(\(\s*%s\s*(,\s*%s\s*)*\))\s*\Z""",
14+
RE_INSERT_VALUES = re.compile(r"""(INSERT\s.+\sVALUES\s+)(\(\s*%s\s*(?:,\s*%s\s*)*\))(\s*(?:ON DUPLICATE.*)?)\Z""",
1515
re.IGNORECASE | re.DOTALL)
1616

1717

@@ -145,21 +145,24 @@ def executemany(self, query, args):
145145

146146
m = RE_INSERT_VALUES.match(query)
147147
if m:
148-
q_values = m.group(1).rstrip()
148+
q_prefix = m.group(1)
149+
q_values = m.group(2).rstrip()
150+
q_postfix = m.group(3) or ''
149151
assert q_values[0] == '(' and q_values[-1] == ')'
150-
q_prefix = query[:m.start(1)]
151-
return self._do_execute_many(q_prefix, q_values, args,
152+
return self._do_execute_many(q_prefix, q_values, q_postfix, args,
152153
self.max_stmt_length,
153154
self._get_db().encoding)
154155

155156
self.rowcount = sum(self.execute(query, arg) for arg in args)
156157
return self.rowcount
157158

158-
def _do_execute_many(self, prefix, values, args, max_stmt_length, encoding):
159+
def _do_execute_many(self, prefix, values, postfix, args, max_stmt_length, encoding):
159160
conn = self._get_db()
160161
escape = self._escape_args
161162
if isinstance(prefix, text_type):
162163
prefix = prefix.encode(encoding)
164+
if isinstance(postfix, text_type):
165+
postfix = postfix.encode(encoding)
163166
sql = bytearray(prefix)
164167
args = iter(args)
165168
v = values % escape(next(args), conn)
@@ -171,13 +174,13 @@ def _do_execute_many(self, prefix, values, args, max_stmt_length, encoding):
171174
v = values % escape(arg, conn)
172175
if isinstance(v, text_type):
173176
v = v.encode(encoding)
174-
if len(sql) + len(v) + 1 > max_stmt_length:
175-
rows += self.execute(sql)
177+
if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length:
178+
rows += self.execute(sql + postfix)
176179
sql = bytearray(prefix)
177180
else:
178181
sql += b','
179182
sql += v
180-
rows += self.execute(sql)
183+
rows += self.execute(sql + postfix)
181184
self.rowcount = rows
182185
return rows
183186

pymysql/tests/test_basic.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def test_bulk_insert_multiline_statement(self):
296296
%s , %s,
297297
%s )
298298
""", data)
299-
self.assertEqual(cursor._last_executed, bytearray(b"""insert
299+
self.assertEqual(cursor._last_executed.strip(), bytearray(b"""insert
300300
into bulkinsert (id, name,
301301
age, height)
302302
values (0,
@@ -318,6 +318,33 @@ def test_bulk_insert_single_record(self):
318318
cursor.execute('commit')
319319
self._verify_records(data)
320320

321+
def test_issue_288(self):
322+
"""executemany should work with "insert ... on update" """
323+
conn = self.connections[0]
324+
cursor = conn.cursor()
325+
data = [(0, "bob", 21, 123), (1, "jim", 56, 45), (2, "fred", 100, 180)]
326+
cursor.executemany("""insert
327+
into bulkinsert (id, name,
328+
age, height)
329+
values (%s,
330+
%s , %s,
331+
%s ) on duplicate key update
332+
age = values(age)
333+
""", data)
334+
self.assertEqual(cursor._last_executed.strip(), bytearray(b"""insert
335+
into bulkinsert (id, name,
336+
age, height)
337+
values (0,
338+
'bob' , 21,
339+
123 ),(1,
340+
'jim' , 56,
341+
45 ),(2,
342+
'fred' , 100,
343+
180 ) on duplicate key update
344+
age = values(age)"""))
345+
cursor.execute('commit')
346+
self._verify_records(data)
347+
321348
def test_warnings(self):
322349
con = self.connections[0]
323350
cur = con.cursor()

0 commit comments

Comments
 (0)