Skip to content

Commit d6d5386

Browse files
committed
fix optimization for bulk updates
- don't optimize if parameters are not supplied yet - check the key type before binding parameters - add test cases for bulk updates This closes #224
1 parent d07ea57 commit d6d5386

File tree

5 files changed

+57
-5
lines changed

5 files changed

+57
-5
lines changed

CHANGES.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ Changes for crate
55
Unreleased
66
==========
77

8+
2017/05/17 0.19.3
9+
=================
10+
11+
- Fix bulk updates which were broken due to query rewrites.
12+
13+
814
2017/04/28 0.19.2
915
=================
1016

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Looking for more help?
7676
.. _contribution docs: CONTRIBUTING.rst
7777
.. _Crate.io: http://crate.io/
7878
.. _CrateDB: https://github.com/crate/crate
79-
.. _DB API 2.0: http://www.python.org/dev/peps/pep-0249/>
79+
.. _DB API 2.0: http://www.python.org/dev/peps/pep-0249/
8080
.. _developer docs: DEVELOP.rst
8181
.. _paid support: https://crate.io/pricing/
8282
.. _pip: https://pypi.python.org/pypi/pip

src/crate/client/sqlalchemy/compiler.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ def rewrite_update(clauseelement, multiparams, params):
4444
The update statement is only rewritten if an item of the MutableDict was
4545
changed.
4646
"""
47-
4847
newmultiparams = []
4948
_multiparams = multiparams[0]
49+
if len(_multiparams) == 0:
50+
return clauseelement, multiparams, params
5051
for _params in _multiparams:
5152
newparams = {}
5253
for key, val in _params.items():
@@ -316,7 +317,7 @@ def visit_update(self, update_stmt, **kw):
316317
set_clauses.append(clause)
317318

318319
for k, v in update_stmt.parameters.items():
319-
if '[' in k:
320+
if isinstance(k, str) and '[' in k:
320321
bindparam = sa.sql.bindparam(k, v)
321322
set_clauses.append(k + ' = ' + self.process(bindparam))
322323

src/crate/client/sqlalchemy/tests/compiler_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from __future__ import absolute_import
2323
from unittest import TestCase
24+
2425
from crate.client.sqlalchemy.compiler import crate_before_execute
2526

2627
import sqlalchemy as sa
@@ -55,3 +56,16 @@ def test_crate_update_rewritten(self):
5556
)
5657

5758
assert hasattr(clauseelement, '_crate_specific') is True
59+
60+
def test_bulk_update_on_builtin_type(self):
61+
"""
62+
The "before_execute" hook in the compiler doesn't get
63+
access to the parameters in case of a bulk update. It
64+
should not try to optimize any parameters.
65+
"""
66+
data = ({},)
67+
clauseelement, multiparams, params = crate_before_execute(
68+
self.crate_engine, self.update, data, None
69+
)
70+
71+
assert hasattr(clauseelement, '_crate_specific') is False

src/crate/client/sqlalchemy/tests/update_test.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
from unittest import TestCase
2323
from datetime import datetime
24+
25+
from crate.client.sqlalchemy.types import Object
2426
from mock import patch, MagicMock
2527

2628
import sqlalchemy as sa
@@ -40,13 +42,14 @@ class SqlAlchemyUpdateTest(TestCase):
4042

4143
def setUp(self):
4244
self.engine = sa.create_engine('crate://')
43-
Base = declarative_base(bind=self.engine)
45+
self.base = declarative_base(bind=self.engine)
4446

45-
class Character(Base):
47+
class Character(self.base):
4648
__tablename__ = 'characters'
4749

4850
name = sa.Column(sa.String, primary_key=True)
4951
age = sa.Column(sa.Integer)
52+
obj = sa.Column(Object)
5053
ts = sa.Column(sa.DateTime, onupdate=datetime.utcnow)
5154

5255
self.character = Character
@@ -79,3 +82,31 @@ def test_onupdate_is_triggered(self):
7982
self.assertTrue(isinstance(dt, datetime))
8083
self.assertTrue(dt > now)
8184
self.assertEqual('Arthur', args[2])
85+
86+
@patch('crate.client.connection.Cursor', FakeCursor)
87+
def test_bulk_update(self):
88+
"""
89+
Checks whether bulk updates work correctly
90+
on native types and Crate types.
91+
"""
92+
before_update_time = datetime.utcnow()
93+
94+
self.session.query(self.character).update({
95+
# change everyone's name to Julia
96+
self.character.name: 'Julia',
97+
self.character.obj: {'favorite_book' : 'Romeo & Juliet'}
98+
})
99+
100+
self.session.commit()
101+
102+
expected_stmt = ("UPDATE characters SET "
103+
"name = ?, obj = ?, ts = ?")
104+
args, kwargs = fake_cursor.execute.call_args
105+
stmt = args[0]
106+
args = args[1]
107+
self.assertEqual(expected_stmt, stmt)
108+
self.assertEqual('Julia', args[0])
109+
self.assertEqual({'favorite_book': 'Romeo & Juliet'}, args[1])
110+
dt = datetime.strptime(args[2], '%Y-%m-%dT%H:%M:%S.%fZ')
111+
self.assertTrue(isinstance(dt, datetime))
112+
self.assertTrue(dt > before_update_time)

0 commit comments

Comments
 (0)