Merge lp:~jamesh/storm/result-randomisation into lp:storm

Proposed by James Henstridge
Status: Work in progress
Proposed branch: lp:~jamesh/storm/result-randomisation
Merge into: lp:storm
Diff against target: 219 lines (has conflicts)
Text conflict in tests/store/base.py
To merge this branch: bzr merge lp:~jamesh/storm/result-randomisation
To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

There are some comments on the bug about this one.

217. By James Henstridge

merge from trunk

Unmerged revisions

217. By James Henstridge

merge from trunk

216. By James Henstridge

Add a few tests for various operations when run with result
randomisation turned on. Currently test_randomise_order_distinct()
fails for Postgres and test_randomise_order_union() fails for SQLite.

215. By James Henstridge

* Add Store.set_randomise_order() method to turn on select results
  randomisation.
* Add a test to verify that Random() is being added to the ORDER BY
  clause of select results when the above is turned on.

214. By James Henstridge

* Add Random() expression to storm.expr, compiling to "RANDOM()".
* Compile Random() to "RAND()" on MySQL.
* Add tests for same.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'storm/databases/mysql.py'
2--- storm/databases/mysql.py 2008-08-16 07:47:33 +0000
3+++ storm/databases/mysql.py 2009-08-13 20:21:08 +0000
4@@ -32,7 +32,7 @@
5
6 from storm.expr import (
7 compile, Insert, Select, compile_select, Undef, And, Eq,
8- SQLRaw, SQLToken, is_safe_token)
9+ Random, SQLRaw, SQLToken, is_safe_token)
10 from storm.variables import Variable
11 from storm.database import Database, Connection, Result
12 from storm.exceptions import (
13@@ -45,6 +45,11 @@
14
15 compile = compile.create_child()
16
17+@compile.when(Random)
18+def compile_random_mysql(compile, func, state):
19+ """MySQL uses RAND() to produce random values."""
20+ return 'RAND(%s)' % compile(func.args, state)
21+
22 @compile.when(Select)
23 def compile_select_mysql(compile, select, state):
24 if select.offset is not Undef and select.limit is Undef:
25
26=== modified file 'storm/expr.py'
27--- storm/expr.py 2009-07-31 01:53:08 +0000
28+++ storm/expr.py 2009-08-13 20:21:08 +0000
29@@ -1243,6 +1243,13 @@
30 __slots__ = ()
31 name = "UPPER"
32
33+class Random(NamedFunc):
34+ """Expression that returns a random value.
35+
36+ Can be used to randomise the order of a result set.
37+ """
38+ name = "RANDOM"
39+
40
41 class Coalesce(NamedFunc):
42 __slots__ = ()
43
44=== modified file 'storm/store.py'
45--- storm/store.py 2009-07-31 01:53:08 +0000
46+++ storm/store.py 2009-08-13 20:21:08 +0000
47@@ -33,7 +33,7 @@
48 from storm.expr import (
49 Expr, Select, Insert, Update, Delete, Column, Count, Max, Min,
50 Avg, Sum, Eq, And, Asc, Desc, compile_python, compare_columns, SQLRaw,
51- Union, Except, Intersect, Alias, SetExpr)
52+ Union, Except, Intersect, Alias, Random, SetExpr)
53 from storm.exceptions import (
54 WrongStoreError, NotFlushedError, OrderLoopError, UnorderedError,
55 NotOneError, FeatureError, CompileError, LostObjectError, ClassInfoError)
56@@ -80,6 +80,17 @@
57 self._cache = cache
58 self._implicit_flush_block_count = 0
59 self._sequence = 0 # Advisory ordering.
60+ self._randomise_results = False
61+
62+ def set_randomise_order(self, value):
63+ """Set whether the order of result sets should be randomised.
64+
65+ If enabled, RANDOM() will be appended to the ORDER BY clause
66+ of SELECT statements. This is intended to aid testing by
67+ preventing tests from relying on the 'natural ordering' of
68+ results.
69+ """
70+ self._randomise_results = bool(value)
71
72 @staticmethod
73 def of(obj):
74@@ -946,9 +957,17 @@
75 return self
76
77 def _get_select(self):
78+ # If results should be randomised, do so here:
79+ if self._store._randomise_results:
80+ if self._order_by is Undef:
81+ order_by = [Random()]
82+ else:
83+ order_by = list(self._order_by) + [Random()]
84+ else:
85+ order_by = self._order_by
86 if self._select is not Undef:
87- if self._order_by is not Undef:
88- self._select.order_by = self._order_by
89+ if order_by is not Undef:
90+ self._select.order_by = order_by
91 if self._limit is not Undef: # XXX UNTESTED!
92 self._select.limit = self._limit
93 if self._offset is not Undef: # XXX UNTESTED!
94@@ -956,7 +975,7 @@
95 return self._select
96 columns, default_tables = self._find_spec.get_columns_and_tables()
97 return Select(columns, self._where, self._tables, default_tables,
98- self._order_by, offset=self._offset, limit=self._limit,
99+ order_by, offset=self._offset, limit=self._limit,
100 distinct=self._distinct, group_by=self._group_by,
101 having=self._having)
102
103
104=== modified file 'tests/databases/mysql.py'
105--- tests/databases/mysql.py 2008-08-16 07:47:33 +0000
106+++ tests/databases/mysql.py 2009-08-13 20:21:08 +0000
107@@ -20,9 +20,9 @@
108 #
109 import os
110
111-from storm.databases.mysql import MySQL
112+from storm.databases.mysql import MySQL, compile
113 from storm.database import create_database
114-from storm.expr import Column, Insert
115+from storm.expr import Column, Insert, Random, State
116 from storm.uri import URI
117 from storm.variables import IntVariable, UnicodeVariable
118
119@@ -75,6 +75,13 @@
120 result = connection.execute("SELECT @@character_set_client")
121 self.assertEquals(result.get_one(), ("ascii",))
122
123+ def test_compile_random(self):
124+ """Test that Random() compiles to RAND() with MySQL"""
125+ expr = Random()
126+ state = State()
127+ statement = compile(expr, state)
128+ self.assertEquals(statement, "RAND()")
129+
130 def test_get_insert_identity(self):
131 # Primary keys are filled in during execute() for MySQL
132 pass
133
134=== modified file 'tests/expr.py'
135--- tests/expr.py 2009-07-23 22:47:10 +0000
136+++ tests/expr.py 2009-08-13 20:21:08 +0000
137@@ -165,6 +165,11 @@
138 self.assertEquals(expr.name, "myfunc")
139 self.assertEquals(expr.args, (elem1, elem2))
140
141+ def test_random(self):
142+ expr = Random()
143+ self.assertEquals(expr.name, "RANDOM")
144+ self.assertEquals(expr.args, ())
145+
146 def test_like(self):
147 expr = Like(elem1, elem2)
148 self.assertEquals(expr.expr1, elem1)
149
150=== modified file 'tests/store/base.py'
151--- tests/store/base.py 2009-07-31 01:53:08 +0000
152+++ tests/store/base.py 2009-08-13 20:21:08 +0000
153@@ -24,14 +24,20 @@
154 import operator
155 import weakref
156
157+from storm import Undef
158 from storm.references import Reference, ReferenceSet, Proxy
159 from storm.database import Result
160 from storm.properties import Int, Float, RawStr, Unicode, Property, Pickle
161 from storm.properties import PropertyPublisherMeta, Decimal
162 from storm.variables import PickleVariable
163 from storm.expr import (
164+<<<<<<< TREE
165 Asc, Desc, Select, Func, LeftJoin, SQL, Count, Sum, Avg, And, Or, Eq,
166 Lower)
167+=======
168+ Asc, Desc, Select, Func, LeftJoin, SQL, Count, Sum, Avg, And, Or, Eq,
169+ Random)
170+>>>>>>> MERGE-SOURCE
171 from storm.variables import Variable, UnicodeVariable, IntVariable
172 from storm.info import get_obj_info, ClassAlias
173 from storm.exceptions import *
174@@ -5484,6 +5490,45 @@
175 " (ensure your database was created with CREATE DATABASE"
176 " ... CHARACTER SET utf8)")
177
178+ def test_randomise_order(self):
179+ self.store.set_randomise_order(True)
180+ # With no order specified, Random() is used as the ordering.
181+ result = self.store.find(Foo)
182+ select = result._get_select()
183+ self.assertNotEquals(select.order_by, Undef)
184+ self.assertEquals(len(select.order_by), 1)
185+ self.assertTrue(isinstance(select.order_by[0], Random))
186+ # If the result already has an ordering, Random() is appended
187+ result.order_by(Foo.title, Foo.id)
188+ select = result._get_select()
189+ self.assertNotEquals(select.order_by, Undef)
190+ self.assertEquals(len(select.order_by), 3)
191+ self.assertEquals(select.order_by[0].table, Foo)
192+ self.assertEquals(select.order_by[0].name, 'title')
193+ self.assertEquals(select.order_by[1].table, Foo)
194+ self.assertEquals(select.order_by[1].name, 'id')
195+ self.assertTrue(isinstance(select.order_by[2], Random))
196+
197+ def test_randomise_order_distinct(self):
198+ self.store.set_randomise_order(True)
199+ result = self.store.find(Foo)
200+ result.config(distinct=True)
201+ names = sorted(foo.title for foo in result)
202+ self.assertEquals(names, ['Title 10', 'Title 20', 'Title 30'])
203+
204+ def test_randomise_order_count(self):
205+ self.store.set_randomise_order(True)
206+ result = self.store.find(Foo)
207+ self.assertEquals(result.count(), 3)
208+
209+ def test_randomise_order_union(self):
210+ self.store.set_randomise_order(True)
211+ result1 = self.store.find(Foo, Foo.id < 20)
212+ result2 = self.store.find(Foo, Foo.id >= 20)
213+ result = result1.union(result2)
214+ names = sorted(foo.title for foo in result)
215+ self.assertEquals(names, ['Title 10', 'Title 20', 'Title 30'])
216+
217 def test_creation_order_is_preserved_when_possible(self):
218 foos = [self.store.add(Foo()) for i in range(10)]
219 self.store.flush()

Subscribers

People subscribed via source and target branches

to status/vote changes: