Merge lp:~edoardo-serra/storm/select-for-update-support into lp:storm

Proposed by Edoardo Serra
Status: Needs review
Proposed branch: lp:~edoardo-serra/storm/select-for-update-support
Merge into: lp:storm
Diff against target: 510 lines (+188/-18)
6 files modified
storm/databases/sqlite.py (+4/-0)
storm/expr.py (+7/-3)
storm/store.py (+51/-14)
storm/zope/interfaces.py (+3/-0)
tests/expr.py (+13/-1)
tests/store/base.py (+110/-0)
To merge this branch: bzr merge lp:~edoardo-serra/storm/select-for-update-support
Reviewer Review Type Date Requested Status
Storm Developers Pending
Storm Developers Pending
Review via email: mp+35112@code.launchpad.net

Description of the change

This branches implements the SELECT ... FOR UPDATE

To post a comment you must log in.
Revision history for this message
Jamu Kakar (jkakar) wrote :

Edoardo, thanks for this branch! Before we spend time reviewing the
changes, have you signed the Canonical copyright assignment forms?
This process will need to be completed before we can accept changes.

Revision history for this message
Edoardo Serra (edoardo-serra) wrote :

Hi Jamu, sorry I didn't find any mention of this on the Wiki.

I just accepdet the form via email.

Regards

On Sep 10, 2010, at 4:52 PM, Jamu Kakar wrote:

> Edoardo, thanks for this branch! Before we spend time reviewing the
> changes, have you signed the Canonical copyright assignment forms?
> This process will need to be completed before we can accept changes.
>
> --
> https://code.launchpad.net/~edoardo-serra/storm/select-for-update-support/+merge/35112
> You are the owner of lp:~edoardo-serra/storm/select-for-update-support.

Revision history for this message
James Henstridge (jamesh) wrote :

This isn't a full review, but here are a few initial comments:

1. If "SELECT FOR UPDATE" is incompatible with "GROUP BY", perhaps it would make sense to check for that in Select's compile function and raise ExprError there rather than trying to protect all the ResultSet entry points that could cause this problem.

2. You've added a comment asking whether it makes sense to remove the "FOR UPDATE" clause for ResultSet.is_empty(). If is_empty() returns True, then no rows would be locked if "FOR UPDATE" was left in. If it returns False, the program is likely to request those rows if it actually wants them locked. So I don't think the comment is necessary.

3. This doesn't need to go in your branch, but I wonder if it would be worth storing the face that a row has been locked in its obj_info, so that Store.get(..., for_update=True) can avoid a query if the row has already been locked? This could be set in Store._load_object() and unset by Store.invalidate().

Revision history for this message
James Henstridge (jamesh) wrote :

One other point: it is probably worth using an API that can support "FOR SHARE" read locks in addition to "FOR UPDATE" write locks.

Unmerged revisions

380. By Edoardo Serra <eserra@barbera>

SELECT ... FOR UPDATE implemented in Store.get

379. By Edoardo Serra <eserra@barbera>

Unit tests

378. By Edoardo Serra <eserra@barbera>

SELECT ... FOR UPDATE is not compatible with GROUP BY

377. By Edoardo Serra <eserra@barbera>

SQLite does not support SELECT ... FOR UPDATE

376. By Edoardo Serra <eserra@barbera>

Starting implementation of SELECT ... FOR UPATE

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'storm/databases/sqlite.py'
--- storm/databases/sqlite.py 2010-06-14 11:52:25 +0000
+++ storm/databases/sqlite.py 2010-09-10 14:44:22 +0000
@@ -49,6 +49,10 @@
49def compile_select_sqlite(compile, select, state):49def compile_select_sqlite(compile, select, state):
50 if select.offset is not Undef and select.limit is Undef:50 if select.offset is not Undef and select.limit is Undef:
51 select.limit = sys.maxint51 select.limit = sys.maxint
52 # SQLite does not support SELECT ... FOR UPDATE
53 # Can we just ignore it?
54 if select.for_update:
55 select.for_update = False
52 statement = compile_select(compile, select, state)56 statement = compile_select(compile, select, state)
53 if state.context is SELECT:57 if state.context is SELECT:
54 # SQLite breaks with (SELECT ...) UNION (SELECT ...), so we58 # SQLite breaks with (SELECT ...) UNION (SELECT ...), so we
5559
=== modified file 'storm/expr.py'
--- storm/expr.py 2009-11-02 11:11:20 +0000
+++ storm/expr.py 2010-09-10 14:44:22 +0000
@@ -636,12 +636,13 @@
636636
637class Select(Expr):637class Select(Expr):
638 __slots__ = ("columns", "where", "tables", "default_tables", "order_by",638 __slots__ = ("columns", "where", "tables", "default_tables", "order_by",
639 "group_by", "limit", "offset", "distinct", "having")639 "group_by", "limit", "offset", "distinct", "having",
640 'for_update')
640641
641 def __init__(self, columns, where=Undef,642 def __init__(self, columns, where=Undef,
642 tables=Undef, default_tables=Undef,643 tables=Undef, default_tables=Undef,
643 order_by=Undef, group_by=Undef,644 order_by=Undef, group_by=Undef, limit=Undef,
644 limit=Undef, offset=Undef, distinct=False, having=Undef):645 offset=Undef, distinct=False, having=Undef, for_update=False):
645 self.columns = columns646 self.columns = columns
646 self.where = where647 self.where = where
647 self.tables = tables648 self.tables = tables
@@ -652,6 +653,7 @@
652 self.offset = offset653 self.offset = offset
653 self.distinct = distinct654 self.distinct = distinct
654 self.having = having655 self.having = having
656 self.for_update = for_update
655657
656@compile.when(Select)658@compile.when(Select)
657def compile_select(compile, select, state):659def compile_select(compile, select, state):
@@ -680,6 +682,8 @@
680 tokens.append(" LIMIT %d" % select.limit)682 tokens.append(" LIMIT %d" % select.limit)
681 if select.offset is not Undef:683 if select.offset is not Undef:
682 tokens.append(" OFFSET %d" % select.offset)684 tokens.append(" OFFSET %d" % select.offset)
685 if select.for_update:
686 tokens.append(" FOR UPDATE")
683 if has_tables(state, select):687 if has_tables(state, select):
684 state.context = TABLE688 state.context = TABLE
685 state.push("parameters", [])689 state.push("parameters", [])
686690
=== modified file 'storm/store.py'
--- storm/store.py 2010-09-01 15:00:42 +0000
+++ storm/store.py 2010-09-10 14:44:22 +0000
@@ -137,13 +137,16 @@
137 self.invalidate()137 self.invalidate()
138 self._connection.rollback()138 self._connection.rollback()
139139
140 def get(self, cls, key):140 def get(self, cls, key, for_update=False):
141 """Get object of type cls with the given primary key from the database.141 """Get object of type cls with the given primary key from the database.
142142
143 If the object is alive the database won't be touched.143 If the object is alive the database won't be touched.
144144
145 If the FOR UPDATE clause is used, the query is always executed
146
145 @param cls: Class of the object to be retrieved.147 @param cls: Class of the object to be retrieved.
146 @param key: Primary key of object. May be a tuple for composed keys.148 @param key: Primary key of object. May be a tuple for composed keys.
149 @param for_update: Indicates wether using the clause FOR UPDATE
147150
148 @return: The object found with the given primary key, or None151 @return: The object found with the given primary key, or None
149 if no object is found.152 if no object is found.
@@ -165,20 +168,22 @@
165 variable = column.variable_factory(value=variable)168 variable = column.variable_factory(value=variable)
166 primary_vars.append(variable)169 primary_vars.append(variable)
167170
168 primary_values = tuple(var.get(to_db=True) for var in primary_vars)171 if not for_update:
169 obj_info = self._alive.get((cls_info.cls, primary_values))172 primary_values = tuple(var.get(to_db=True) for var in primary_vars)
170 if obj_info is not None:173 obj_info = self._alive.get((cls_info.cls, primary_values))
171 if obj_info.get("invalidated"):174 if obj_info is not None:
172 try:175 if obj_info.get("invalidated"):
173 self._validate_alive(obj_info)176 try:
174 except LostObjectError:177 self._validate_alive(obj_info)
175 return None178 except LostObjectError:
176 return self._get_object(obj_info)179 return None
180 return self._get_object(obj_info)
177181
178 where = compare_columns(cls_info.primary_key, primary_vars)182 where = compare_columns(cls_info.primary_key, primary_vars)
179183
180 select = Select(cls_info.columns, where,184 select = Select(cls_info.columns, where,
181 default_tables=cls_info.table, limit=1)185 default_tables=cls_info.table, limit=1,
186 for_update=for_update)
182187
183 result = self._connection.execute(select)188 result = self._connection.execute(select)
184 values = result.get_one()189 values = result.get_one()
@@ -921,6 +926,7 @@
921 self._distinct = False926 self._distinct = False
922 self._group_by = Undef927 self._group_by = Undef
923 self._having = Undef928 self._having = Undef
929 self._for_update = False
924930
925 def copy(self):931 def copy(self):
926 """Return a copy of this ResultSet object, with the same configuration.932 """Return a copy of this ResultSet object, with the same configuration.
@@ -933,7 +939,7 @@
933 result_set._select = copy(self._select)939 result_set._select = copy(self._select)
934 return result_set940 return result_set
935941
936 def config(self, distinct=None, offset=None, limit=None):942 def config(self, distinct=None, offset=None, limit=None, for_update=None):
937 """Configure this result object in-place. All parameters are optional.943 """Configure this result object in-place. All parameters are optional.
938944
939 @param distinct: Boolean enabling/disabling usage of the DISTINCT945 @param distinct: Boolean enabling/disabling usage of the DISTINCT
@@ -952,6 +958,8 @@
952 self._offset = offset958 self._offset = offset
953 if limit is not None:959 if limit is not None:
954 self._limit = limit960 self._limit = limit
961 if for_update is not None:
962 self._for_update = for_update
955 return self963 return self
956964
957 def _get_select(self):965 def _get_select(self):
@@ -967,7 +975,7 @@
967 return Select(columns, self._where, self._tables, default_tables,975 return Select(columns, self._where, self._tables, default_tables,
968 self._order_by, offset=self._offset, limit=self._limit,976 self._order_by, offset=self._offset, limit=self._limit,
969 distinct=self._distinct, group_by=self._group_by,977 distinct=self._distinct, group_by=self._group_by,
970 having=self._having)978 having=self._having, for_update=self._for_update)
971979
972 def _load_objects(self, result, values):980 def _load_objects(self, result, values):
973 return self._find_spec.load_objects(self._store, result, values)981 return self._find_spec.load_objects(self._store, result, values)
@@ -1054,6 +1062,9 @@
1054 subselect = self._get_select()1062 subselect = self._get_select()
1055 subselect.limit = 11063 subselect.limit = 1
1056 subselect.order_by = Undef1064 subselect.order_by = Undef
1065 # Should we really strip FOR UPDATE clause or should we raise a
1066 # FeatureError?
1067 subselect.for_update = False
1057 select = Select(1, tables=Alias(subselect, "_tmp"), limit=1)1068 select = Select(1, tables=Alias(subselect, "_tmp"), limit=1)
1058 result = self._store._connection.execute(select)1069 result = self._store._connection.execute(select)
1059 return (not result.get_one())1070 return (not result.get_one())
@@ -1162,6 +1173,15 @@
1162 self._order_by = args or Undef1173 self._order_by = args or Undef
1163 return self1174 return self
11641175
1176 def for_update(self):
1177 """Acquire an exclusive row-level lock on the rows in the ResultSet
1178 """
1179 if self._group_by is not Undef:
1180 raise FeatureError("SELECT ... FOR UPDATE isn't supported with a "
1181 " GROUP BY clause ")
1182 self._for_update = True
1183 return self
1184
1165 def remove(self):1185 def remove(self):
1166 """Remove all rows represented by this ResultSet from the database.1186 """Remove all rows represented by this ResultSet from the database.
11671187
@@ -1193,6 +1213,9 @@
1193 if self._select is not Undef:1213 if self._select is not Undef:
1194 raise FeatureError("Grouping isn't supported with "1214 raise FeatureError("Grouping isn't supported with "
1195 "set expressions (unions, etc)")1215 "set expressions (unions, etc)")
1216 if self._for_update:
1217 raise FeatureError("Grouping isn't supported with "
1218 "FOR UPDATE clause")
11961219
1197 find_spec = FindSpec(expr)1220 find_spec = FindSpec(expr)
1198 columns, dummy = find_spec.get_columns_and_tables()1221 columns, dummy = find_spec.get_columns_and_tables()
@@ -1216,6 +1239,9 @@
1216 if self._group_by is not Undef:1239 if self._group_by is not Undef:
1217 raise FeatureError("Single aggregates aren't supported after a "1240 raise FeatureError("Single aggregates aren't supported after a "
1218 " GROUP BY clause ")1241 " GROUP BY clause ")
1242 if self._for_update:
1243 raise FeatureError("Single aggregates aren't supported with"
1244 " FOR UPDATE clause ")
1219 columns, default_tables = self._find_spec.get_columns_and_tables()1245 columns, default_tables = self._find_spec.get_columns_and_tables()
1220 if (self._select is Undef and not self._distinct and1246 if (self._select is Undef and not self._distinct and
1221 self._offset is Undef and self._limit is Undef):1247 self._offset is Undef and self._limit is Undef):
@@ -1471,6 +1497,8 @@
1471 """1497 """
1472 if isinstance(other, EmptyResultSet):1498 if isinstance(other, EmptyResultSet):
1473 return self1499 return self
1500 if self._for_update or other._for_update:
1501 raise FeatureError("SELECT FOR UPDATE is not allowed with UNION")
1474 return self._set_expr(Union, other, all)1502 return self._set_expr(Union, other, all)
14751503
1476 def difference(self, other, all=False):1504 def difference(self, other, all=False):
@@ -1480,6 +1508,8 @@
1480 """1508 """
1481 if isinstance(other, EmptyResultSet):1509 if isinstance(other, EmptyResultSet):
1482 return self1510 return self
1511 if self._for_update or other._for_update:
1512 raise FeatureError("SELECT FOR UPDATE is not allowed with EXCEPT")
1483 return self._set_expr(Except, other, all)1513 return self._set_expr(Except, other, all)
14841514
1485 def intersection(self, other, all=False):1515 def intersection(self, other, all=False):
@@ -1489,6 +1519,9 @@
1489 """1519 """
1490 if isinstance(other, EmptyResultSet):1520 if isinstance(other, EmptyResultSet):
1491 return other1521 return other
1522 if self._for_update or other._for_update:
1523 raise FeatureError("SELECT FOR UPDATE is not allowed with "
1524 "INTERSECT")
1492 return self._set_expr(Intersect, other, all)1525 return self._set_expr(Intersect, other, all)
14931526
14941527
@@ -1516,7 +1549,7 @@
1516 result = EmptyResultSet(self._order_by)1549 result = EmptyResultSet(self._order_by)
1517 return result1550 return result
15181551
1519 def config(self, distinct=None, offset=None, limit=None):1552 def config(self, distinct=None, offset=None, limit=None, for_update=None):
1520 pass1553 pass
15211554
1522 def __iter__(self):1555 def __iter__(self):
@@ -1552,6 +1585,10 @@
1552 self._order_by = True1585 self._order_by = True
1553 return self1586 return self
15541587
1588 def for_update(self):
1589 self._for_update = True
1590 return self
1591
1555 def remove(self):1592 def remove(self):
1556 return 01593 return 0
15571594
15581595
=== modified file 'storm/zope/interfaces.py'
--- storm/zope/interfaces.py 2008-11-27 09:37:56 +0000
+++ storm/zope/interfaces.py 2010-09-10 14:44:22 +0000
@@ -93,6 +93,9 @@
93 def order_by(*args):93 def order_by(*args):
94 """Order the result set based on expressions in C{args}."""94 """Order the result set based on expressions in C{args}."""
9595
96 def for_update():
97 """Acquire an exclusive row-level lock on the rows in the ResultSet"""
98
96 def count(column=Undef, distinct=False):99 def count(column=Undef, distinct=False):
97 """Returns the number of rows in the result set.100 """Returns the number of rows in the result set.
98101
99102
=== modified file 'tests/expr.py'
--- tests/expr.py 2009-11-02 11:11:20 +0000
+++ tests/expr.py 2010-09-10 14:44:22 +0000
@@ -65,9 +65,11 @@
65 self.assertEquals(expr.limit, Undef)65 self.assertEquals(expr.limit, Undef)
66 self.assertEquals(expr.offset, Undef)66 self.assertEquals(expr.offset, Undef)
67 self.assertEquals(expr.distinct, False)67 self.assertEquals(expr.distinct, False)
68 self.assertEquals(expr.having, Undef)
69 self.assertEquals(expr.for_update, False)
6870
69 def test_select_constructor(self):71 def test_select_constructor(self):
70 objects = [object() for i in range(9)]72 objects = [object() for i in range(11)]
71 expr = Select(*objects)73 expr = Select(*objects)
72 self.assertEquals(expr.columns, objects[0])74 self.assertEquals(expr.columns, objects[0])
73 self.assertEquals(expr.where, objects[1])75 self.assertEquals(expr.where, objects[1])
@@ -78,6 +80,8 @@
78 self.assertEquals(expr.limit, objects[6])80 self.assertEquals(expr.limit, objects[6])
79 self.assertEquals(expr.offset, objects[7])81 self.assertEquals(expr.offset, objects[7])
80 self.assertEquals(expr.distinct, objects[8])82 self.assertEquals(expr.distinct, objects[8])
83 self.assertEquals(expr.having, objects[9])
84 self.assertEquals(expr.for_update, objects[10])
8185
82 def test_insert_default(self):86 def test_insert_default(self):
83 expr = Insert(None)87 expr = Insert(None)
@@ -657,6 +661,14 @@
657 'SELECT DISTINCT column1, column2 FROM "table 1"')661 'SELECT DISTINCT column1, column2 FROM "table 1"')
658 self.assertEquals(state.parameters, [])662 self.assertEquals(state.parameters, [])
659663
664 def test_select_for_update(self):
665 expr = Select([column1, column2], Undef, [table1], for_update=True)
666 state = State()
667 statement = compile(expr, state)
668 self.assertEquals(statement,
669 'SELECT column1, column2 FROM "table 1" FOR UPDATE')
670 self.assertEquals(state.parameters, [])
671
660 def test_select_where(self):672 def test_select_where(self):
661 expr = Select([column1, Func1()],673 expr = Select([column1, Func1()],
662 Func1(),674 Func1(),
663675
=== modified file 'tests/store/base.py'
--- tests/store/base.py 2010-09-01 09:02:41 +0000
+++ tests/store/base.py 2010-09-10 14:44:22 +0000
@@ -321,6 +321,18 @@
321 foo = self.store.get(Foo, 40)321 foo = self.store.get(Foo, 40)
322 self.assertEquals(foo, None)322 self.assertEquals(foo, None)
323323
324 def test_get_for_update(self):
325 stream = StringIO()
326 self.addCleanup(debug, False)
327 debug(True, stream)
328
329 foo = self.store.get(Foo, 10, for_update=True)
330 self.assertEquals(foo.id, 10)
331 self.assertEquals(foo.title, "Title 30")
332 if self.__class__.__name__.startswith("SQLite"):
333 return
334 self.assertIn("FOR UPDATE", stream.getvalue())
335
324 def test_get_cached(self):336 def test_get_cached(self):
325 foo = self.store.get(Foo, 10)337 foo = self.store.get(Foo, 10)
326 self.assertTrue(self.store.get(Foo, 10) is foo)338 self.assertTrue(self.store.get(Foo, 10) is foo)
@@ -332,6 +344,13 @@
332 self.store.get(Foo, 10)344 self.store.get(Foo, 10)
333 self.store._connection = connection345 self.store._connection = connection
334346
347 def test_wb_get_cached_for_update_needa_connection(self):
348 foo = self.store.get(Foo, 10)
349 connection = self.store._connection
350 self.store._connection = None
351 self.assertRaises(AttributeError, self.store.get, Foo, 10, for_update=True)
352 self.store._connection = connection
353
335 def test_cache_cleanup(self):354 def test_cache_cleanup(self):
336 # Disable the cache, which holds strong references.355 # Disable the cache, which holds strong references.
337 self.get_cache(self.store).set_size(0)356 self.get_cache(self.store).set_size(0)
@@ -569,6 +588,21 @@
569 self.assertEqual(True, result.is_empty())588 self.assertEqual(True, result.is_empty())
570 self.assertNotIn("ORDER BY", stream.getvalue())589 self.assertNotIn("ORDER BY", stream.getvalue())
571590
591 def test_is_empty_strips_for_update(self):
592 """
593 L{ResultSet.is_empty} strips the C{FOR UPDATE} clause, if one is
594 present, since it's not supported in subqueries and thre is no reason
595 to acquire a row-level lock while testing if a ResultSet is empty
596 """
597 stream = StringIO()
598 self.addCleanup(debug, False)
599 debug(True, stream)
600
601 result = self.store.find(Foo, Foo.id == 300).for_update()
602 result.order_by(Foo.id)
603 self.assertEqual(True, result.is_empty())
604 self.assertNotIn("FOR UPDATE", stream.getvalue())
605
572 def test_is_empty_with_composed_key(self):606 def test_is_empty_with_composed_key(self):
573 result = self.store.find(Link, foo_id=300, bar_id=3000)607 result = self.store.find(Link, foo_id=300, bar_id=3000)
574 self.assertEquals(result.is_empty(), True)608 self.assertEquals(result.is_empty(), True)
@@ -592,6 +626,23 @@
592 (30, "Title 10"),626 (30, "Title 10"),
593 ])627 ])
594628
629 def test_find_for_update(self):
630 stream = StringIO()
631 self.addCleanup(debug, False)
632 debug(True, stream)
633
634 result = self.store.find(Foo).for_update()
635 lst = [(foo.id, foo.title) for foo in result]
636 lst.sort()
637 self.assertEquals(lst, [
638 (10, "Title 30"),
639 (20, "Title 20"),
640 (30, "Title 10"),
641 ])
642 if self.__class__.__name__.startswith("SQLite"):
643 return
644 self.assertIn("FOR UPDATE", stream.getvalue())
645
595 def test_find_from_cache(self):646 def test_find_from_cache(self):
596 foo = self.store.get(Foo, 10)647 foo = self.store.get(Foo, 10)
597 self.assertTrue(self.store.find(Foo, id=10).one() is foo)648 self.assertTrue(self.store.find(Foo, id=10).one() is foo)
@@ -940,6 +991,11 @@
940 def test_find_count(self):991 def test_find_count(self):
941 self.assertEquals(self.store.find(Foo).count(), 3)992 self.assertEquals(self.store.find(Foo).count(), 3)
942993
994 def test_find_count_for_update(self):
995 result = self.store.find(Foo)
996 result.for_update()
997 self.assertRaises(FeatureError, result.count, 3)
998
943 def test_find_count_after_slice(self):999 def test_find_count_after_slice(self):
944 """1000 """
945 When we slice a ResultSet obtained after a set operation (like union),1001 When we slice a ResultSet obtained after a set operation (like union),
@@ -1007,6 +1063,11 @@
1007 def test_find_max(self):1063 def test_find_max(self):
1008 self.assertEquals(self.store.find(Foo).max(Foo.id), 30)1064 self.assertEquals(self.store.find(Foo).max(Foo.id), 30)
10091065
1066 def test_find_max_for_update(self):
1067 result = self.store.find(Foo)
1068 result.for_update()
1069 self.assertRaises(FeatureError, result.max, Foo.id)
1070
1010 def test_find_max_expr(self):1071 def test_find_max_expr(self):
1011 self.assertEquals(self.store.find(Foo).max(Foo.id + 1), 31)1072 self.assertEquals(self.store.find(Foo).max(Foo.id + 1), 31)
10121073
@@ -1028,6 +1089,11 @@
1028 def test_find_min(self):1089 def test_find_min(self):
1029 self.assertEquals(self.store.find(Foo).min(Foo.id), 10)1090 self.assertEquals(self.store.find(Foo).min(Foo.id), 10)
10301091
1092 def test_find_min_for_update(self):
1093 result = self.store.find(Foo)
1094 result.for_update()
1095 self.assertRaises(FeatureError, result.min, Foo.id)
1096
1031 def test_find_min_expr(self):1097 def test_find_min_expr(self):
1032 self.assertEquals(self.store.find(Foo).min(Foo.id - 1), 9)1098 self.assertEquals(self.store.find(Foo).min(Foo.id - 1), 9)
10331099
@@ -1049,6 +1115,11 @@
1049 def test_find_avg(self):1115 def test_find_avg(self):
1050 self.assertEquals(self.store.find(Foo).avg(Foo.id), 20)1116 self.assertEquals(self.store.find(Foo).avg(Foo.id), 20)
10511117
1118 def test_find_avg_for_update(self):
1119 result = self.store.find(Foo)
1120 result.for_update()
1121 self.assertRaises(FeatureError, result.avg, Foo.id)
1122
1052 def test_find_avg_expr(self):1123 def test_find_avg_expr(self):
1053 self.assertEquals(self.store.find(Foo).avg(Foo.id + 10), 30)1124 self.assertEquals(self.store.find(Foo).avg(Foo.id + 10), 30)
10541125
@@ -1062,6 +1133,11 @@
1062 def test_find_sum(self):1133 def test_find_sum(self):
1063 self.assertEquals(self.store.find(Foo).sum(Foo.id), 60)1134 self.assertEquals(self.store.find(Foo).sum(Foo.id), 60)
10641135
1136 def test_find_sum_for_update(self):
1137 result = self.store.find(Foo)
1138 result.for_update()
1139 self.assertRaises(FeatureError, result.sum, Foo.id)
1140
1065 def test_find_sum_expr(self):1141 def test_find_sum_expr(self):
1066 self.assertEquals(self.store.find(Foo).sum(Foo.id * 2), 120)1142 self.assertEquals(self.store.find(Foo).sum(Foo.id * 2), 120)
10671143
@@ -1546,6 +1622,11 @@
1546 result = list(result)1622 result = list(result)
1547 self.assertEquals(result, [(2L, 2L), (2L, 2L), (2L, 3L), (3L, 6L)])1623 self.assertEquals(result, [(2L, 2L), (2L, 2L), (2L, 3L), (3L, 6L)])
15481624
1625 def test_find_group_by_for_update(self):
1626 result = self.store.find((Count(FooValue.id), Sum(FooValue.value1)))
1627 result.group_by(FooValue.value2)
1628 self.assertRaises(FeatureError, result.for_update)
1629
1549 def test_find_group_by_table(self):1630 def test_find_group_by_table(self):
1550 result = self.store.find(1631 result = self.store.find(
1551 (Sum(FooValue.value2), Foo), Foo.id == FooValue.foo_id)1632 (Sum(FooValue.value2), Foo), Foo.id == FooValue.foo_id)
@@ -2271,6 +2352,13 @@
2271 self.assertEquals(foo.id, 20)2352 self.assertEquals(foo.id, 20)
2272 self.assertEquals(foo.title, "Title 20")2353 self.assertEquals(foo.title, "Title 20")
22732354
2355 def test_sub_select_for_update(self):
2356 foo = self.store.find(Foo, Foo.id == Select(SQL("20"))
2357 ).for_update().one()
2358 self.assertTrue(foo)
2359 self.assertEquals(foo.id, 20)
2360 self.assertEquals(foo.title, "Title 20")
2361
2274 def test_cache_has_improper_object(self):2362 def test_cache_has_improper_object(self):
2275 foo = self.store.get(Foo, 20)2363 foo = self.store.get(Foo, 20)
2276 self.store.remove(foo)2364 self.store.remove(foo)
@@ -5472,6 +5560,12 @@
5472 (30, "Title 10"),5560 (30, "Title 10"),
5473 ])5561 ])
54745562
5563 def test_result_union_for_update(self):
5564 result1 = self.store.find(Foo, id=30).for_update()
5565 result2 = self.store.find(Foo, id=10)
5566 self.assertRaises(FeatureError, result1.union, result2)
5567 self.assertRaises(FeatureError, result2.union, result1)
5568
5475 def test_result_union_duplicated(self):5569 def test_result_union_duplicated(self):
5476 result1 = self.store.find(Foo, id=30)5570 result1 = self.store.find(Foo, id=30)
5477 result2 = self.store.find(Foo, id=30)5571 result2 = self.store.find(Foo, id=30)
@@ -5544,6 +5638,12 @@
5544 (30, "Title 10"),5638 (30, "Title 10"),
5545 ])5639 ])
55465640
5641 def test_result_difference_for_update(self):
5642 result1 = self.store.find(Foo).for_update()
5643 result2 = self.store.find(Foo, id=10)
5644 self.assertRaises(FeatureError, result1.difference, result2)
5645 self.assertRaises(FeatureError, result2.difference, result1)
5646
5547 def test_result_difference_with_empty(self):5647 def test_result_difference_with_empty(self):
5548 if self.__class__.__name__.startswith("MySQL"):5648 if self.__class__.__name__.startswith("MySQL"):
5549 return5649 return
@@ -5605,6 +5705,12 @@
5605 (30, "Title 10"),5705 (30, "Title 10"),
5606 ])5706 ])
56075707
5708 def test_result_intersection_for_update(self):
5709 result1 = self.store.find(Foo).for_update()
5710 result2 = self.store.find(Foo, Foo.id.is_in((10, 30)))
5711 self.assertRaises(FeatureError, result1.intersection, result2)
5712 self.assertRaises(FeatureError, result2.intersection, result1)
5713
5608 def test_result_intersection_with_empty(self):5714 def test_result_intersection_with_empty(self):
5609 if self.__class__.__name__.startswith("MySQL"):5715 if self.__class__.__name__.startswith("MySQL"):
5610 return5716 return
@@ -6048,6 +6154,10 @@
6048 self.assertEquals(self.result.order_by(Foo.title), self.result)6154 self.assertEquals(self.result.order_by(Foo.title), self.result)
6049 self.assertEquals(self.empty.order_by(Foo.title), self.empty)6155 self.assertEquals(self.empty.order_by(Foo.title), self.empty)
60506156
6157 def test_for_update(self):
6158 self.assertEquals(self.result.for_update(), self.result)
6159 self.assertEquals(self.empty.for_update(), self.empty)
6160
6051 def test_remove(self):6161 def test_remove(self):
6052 self.assertEquals(self.result.remove(), 0)6162 self.assertEquals(self.result.remove(), 0)
6053 self.assertEquals(self.empty.remove(), 0)6163 self.assertEquals(self.empty.remove(), 0)

Subscribers

People subscribed via source and target branches

to status/vote changes: