Merge lp:~jamesh/storm/bug-242813 into lp:storm
- bug-242813
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~jamesh/storm/bug-242813 |
Merge into: | lp:storm |
Diff against target: |
132 lines 2 files modified
storm/expr.py (+10/-0) tests/expr.py (+87/-0) |
To merge this branch: | bzr merge lp:~jamesh/storm/bug-242813 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jamu Kakar (community) | Approve | ||
Review via email: mp+10508@code.launchpad.net |
Commit message
Description of the change
James Henstridge (jamesh) wrote : | # |
Jamu Kakar (jkakar) wrote : | # |
Nice and simple, +1.
GabrielGrant (gabrielgrant) wrote : | # |
The tests pass, and this fixes a problem I've run into, so I'm eager to see this pushed into mainline.
A few questions:
Should there be a test to explicitly check that heterogeneous set expressions are not flattened? (ie exercising the negative branch of the second if statement)
Is it necessary to skip expressions containing an order_by? If the order_by is the same on each expr, shouldn't the result of the nested and un-nested expressions be equivalent? (I may very well be totally wrong about this, but I can't seem to cook up a counter-example at the moment)
It appears that in addition to addressing https:/
Could/should there be a more graceful failure for these cases than a MySQL parse error? (Sorry if this is too far off track. Perhaps this discussion would be better continued on the appropriate bug? I've subscribed myself)
Cheers!
James Henstridge (jamesh) wrote : | # |
> The tests pass, and this fixes a problem I've run into, so I'm eager to see
> this pushed into mainline.
>
> A few questions:
>
> Should there be a test to explicitly check that heterogeneous set expressions
> are not flattened? (ie exercising the negative branch of the second if
> statement)
Yep. There should be a test for that.
> Is it necessary to skip expressions containing an order_by? If the order_by is
> the same on each expr, shouldn't the result of the nested and un-nested
> expressions be equivalent? (I may very well be totally wrong about this, but I
> can't seem to cook up a counter-example at the moment)
I guess I wrote the code that way to be on the safe side: except in the case of MySQL's broken query parser, this branch is mainly designed to improve performance.
That said, the test can probably go. If the outer set expression has an order, then the inner set expression's ordering doesn't matter. If the outer expression has no order, then there is no guarantee on the ordering so we should be able to ignore it in that case too.
I'll remove the check.
> It appears that in addition to addressing
> https:/
> some of https:/
> I'm having), however I don't think it addresses it fully. The proposed
> solution doesn't tackle heterogeneous nested set queries, which will still
> fail on MySQL. http://
> http://
> http://
> with MySQL that basically makes some such nested queries impossible to
> express, however simpler ones like (SELECT ...) UNION (SELECT ...) INTERSECT
> (SELECT ...) are supported in MySQL, but (I believe) can't be achieved through
> Storm itself.
It might be possible to fix up that sort of chaining by playing with the operator precedences in the expression compiler.
> Could/should there be a more graceful failure for these cases than a MySQL
> parse error? (Sorry if this is too far off track. Perhaps this discussion
> would be better continued on the appropriate bug? I've subscribed myself)
I don't think it is worth the trouble. Writing code to detect expressions that will cause the MySQL query parser trouble is probably as much work as adjusting our expression generators to make expressions that don't trigger bugs in the MySQL query parser.
- 331. By James Henstridge
-
Collapse set expressions with order_by set, and add some extra tests.
Preview Diff
1 | === modified file 'storm/expr.py' | |||
2 | --- storm/expr.py 2009-07-31 01:53:08 +0000 | |||
3 | +++ storm/expr.py 2009-10-21 09:48:13 +0000 | |||
4 | @@ -1099,6 +1099,16 @@ | |||
5 | 1099 | self.order_by = kwargs.get("order_by", Undef) | 1099 | self.order_by = kwargs.get("order_by", Undef) |
6 | 1100 | self.limit = kwargs.get("limit", Undef) | 1100 | self.limit = kwargs.get("limit", Undef) |
7 | 1101 | self.offset = kwargs.get("offset", Undef) | 1101 | self.offset = kwargs.get("offset", Undef) |
8 | 1102 | # If the first expression is of a compatible type, directly | ||
9 | 1103 | # include its sub expressions. | ||
10 | 1104 | if len(self.exprs) > 0: | ||
11 | 1105 | first = self.exprs[0] | ||
12 | 1106 | if (isinstance(first, self.__class__) and | ||
13 | 1107 | first.all == self.all and | ||
14 | 1108 | first.limit is Undef and | ||
15 | 1109 | first.offset is Undef): | ||
16 | 1110 | self.exprs = first.exprs + self.exprs[1:] | ||
17 | 1111 | |||
18 | 1102 | 1112 | ||
19 | 1103 | @compile.when(SetExpr) | 1113 | @compile.when(SetExpr) |
20 | 1104 | def compile_set_expr(compile, expr, state): | 1114 | def compile_set_expr(compile, expr, state): |
21 | 1105 | 1115 | ||
22 | === modified file 'tests/expr.py' | |||
23 | --- tests/expr.py 2009-07-23 22:47:10 +0000 | |||
24 | +++ tests/expr.py 2009-10-21 09:48:13 +0000 | |||
25 | @@ -275,6 +275,35 @@ | |||
26 | 275 | self.assertEquals(expr.limit, 1) | 275 | self.assertEquals(expr.limit, 1) |
27 | 276 | self.assertEquals(expr.offset, 2) | 276 | self.assertEquals(expr.offset, 2) |
28 | 277 | 277 | ||
29 | 278 | def test_union_collapse(self): | ||
30 | 279 | expr = Union(Union(elem1, elem2), elem3) | ||
31 | 280 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) | ||
32 | 281 | |||
33 | 282 | # Only first expression is collapsed. | ||
34 | 283 | expr = Union(elem1, Union(elem2, elem3)) | ||
35 | 284 | self.assertEquals(expr.exprs[0], elem1) | ||
36 | 285 | self.assertTrue(isinstance(expr.exprs[1], Union)) | ||
37 | 286 | |||
38 | 287 | # Don't collapse if all is different. | ||
39 | 288 | expr = Union(Union(elem1, elem2, all=True), elem3) | ||
40 | 289 | self.assertTrue(isinstance(expr.exprs[0], Union)) | ||
41 | 290 | expr = Union(Union(elem1, elem2), elem3, all=True) | ||
42 | 291 | self.assertTrue(isinstance(expr.exprs[0], Union)) | ||
43 | 292 | expr = Union(Union(elem1, elem2, all=True), elem3, all=True) | ||
44 | 293 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) | ||
45 | 294 | |||
46 | 295 | # Don't collapse if limit or offset are set. | ||
47 | 296 | expr = Union(Union(elem1, elem2, limit=1), elem3) | ||
48 | 297 | self.assertTrue(isinstance(expr.exprs[0], Union)) | ||
49 | 298 | expr = Union(Union(elem1, elem2, offset=3), elem3) | ||
50 | 299 | self.assertTrue(isinstance(expr.exprs[0], Union)) | ||
51 | 300 | |||
52 | 301 | # Don't collapse other set expressions. | ||
53 | 302 | expr = Union(Except(elem1, elem2), elem3) | ||
54 | 303 | self.assertTrue(isinstance(expr.exprs[0], Except)) | ||
55 | 304 | expr = Union(Intersect(elem1, elem2), elem3) | ||
56 | 305 | self.assertTrue(isinstance(expr.exprs[0], Intersect)) | ||
57 | 306 | |||
58 | 278 | def test_except(self): | 307 | def test_except(self): |
59 | 279 | expr = Except(elem1, elem2, elem3) | 308 | expr = Except(elem1, elem2, elem3) |
60 | 280 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) | 309 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) |
61 | @@ -287,6 +316,35 @@ | |||
62 | 287 | self.assertEquals(expr.limit, 1) | 316 | self.assertEquals(expr.limit, 1) |
63 | 288 | self.assertEquals(expr.offset, 2) | 317 | self.assertEquals(expr.offset, 2) |
64 | 289 | 318 | ||
65 | 319 | def test_except_collapse(self): | ||
66 | 320 | expr = Except(Except(elem1, elem2), elem3) | ||
67 | 321 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) | ||
68 | 322 | |||
69 | 323 | # Only first expression is collapsed. | ||
70 | 324 | expr = Except(elem1, Except(elem2, elem3)) | ||
71 | 325 | self.assertEquals(expr.exprs[0], elem1) | ||
72 | 326 | self.assertTrue(isinstance(expr.exprs[1], Except)) | ||
73 | 327 | |||
74 | 328 | # Don't collapse if all is different. | ||
75 | 329 | expr = Except(Except(elem1, elem2, all=True), elem3) | ||
76 | 330 | self.assertTrue(isinstance(expr.exprs[0], Except)) | ||
77 | 331 | expr = Except(Except(elem1, elem2), elem3, all=True) | ||
78 | 332 | self.assertTrue(isinstance(expr.exprs[0], Except)) | ||
79 | 333 | expr = Except(Except(elem1, elem2, all=True), elem3, all=True) | ||
80 | 334 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) | ||
81 | 335 | |||
82 | 336 | # Don't collapse if limit or offset are set. | ||
83 | 337 | expr = Except(Except(elem1, elem2, limit=1), elem3) | ||
84 | 338 | self.assertTrue(isinstance(expr.exprs[0], Except)) | ||
85 | 339 | expr = Except(Except(elem1, elem2, offset=3), elem3) | ||
86 | 340 | self.assertTrue(isinstance(expr.exprs[0], Except)) | ||
87 | 341 | |||
88 | 342 | # Don't collapse other set expressions. | ||
89 | 343 | expr = Except(Union(elem1, elem2), elem3) | ||
90 | 344 | self.assertTrue(isinstance(expr.exprs[0], Union)) | ||
91 | 345 | expr = Except(Intersect(elem1, elem2), elem3) | ||
92 | 346 | self.assertTrue(isinstance(expr.exprs[0], Intersect)) | ||
93 | 347 | |||
94 | 290 | def test_intersect(self): | 348 | def test_intersect(self): |
95 | 291 | expr = Intersect(elem1, elem2, elem3) | 349 | expr = Intersect(elem1, elem2, elem3) |
96 | 292 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) | 350 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) |
97 | @@ -300,6 +358,35 @@ | |||
98 | 300 | self.assertEquals(expr.limit, 1) | 358 | self.assertEquals(expr.limit, 1) |
99 | 301 | self.assertEquals(expr.offset, 2) | 359 | self.assertEquals(expr.offset, 2) |
100 | 302 | 360 | ||
101 | 361 | def test_intersect_collapse(self): | ||
102 | 362 | expr = Intersect(Intersect(elem1, elem2), elem3) | ||
103 | 363 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) | ||
104 | 364 | |||
105 | 365 | # Only first expression is collapsed. | ||
106 | 366 | expr = Intersect(elem1, Intersect(elem2, elem3)) | ||
107 | 367 | self.assertEquals(expr.exprs[0], elem1) | ||
108 | 368 | self.assertTrue(isinstance(expr.exprs[1], Intersect)) | ||
109 | 369 | |||
110 | 370 | # Don't collapse if all is different. | ||
111 | 371 | expr = Intersect(Intersect(elem1, elem2, all=True), elem3) | ||
112 | 372 | self.assertTrue(isinstance(expr.exprs[0], Intersect)) | ||
113 | 373 | expr = Intersect(Intersect(elem1, elem2), elem3, all=True) | ||
114 | 374 | self.assertTrue(isinstance(expr.exprs[0], Intersect)) | ||
115 | 375 | expr = Intersect(Intersect(elem1, elem2, all=True), elem3, all=True) | ||
116 | 376 | self.assertEquals(expr.exprs, (elem1, elem2, elem3)) | ||
117 | 377 | |||
118 | 378 | # Don't collapse if limit or offset are set. | ||
119 | 379 | expr = Intersect(Intersect(elem1, elem2, limit=1), elem3) | ||
120 | 380 | self.assertTrue(isinstance(expr.exprs[0], Intersect)) | ||
121 | 381 | expr = Intersect(Intersect(elem1, elem2, offset=3), elem3) | ||
122 | 382 | self.assertTrue(isinstance(expr.exprs[0], Intersect)) | ||
123 | 383 | |||
124 | 384 | # Don't collapse other set expressions. | ||
125 | 385 | expr = Intersect(Union(elem1, elem2), elem3) | ||
126 | 386 | self.assertTrue(isinstance(expr.exprs[0], Union)) | ||
127 | 387 | expr = Intersect(Except(elem1, elem2), elem3) | ||
128 | 388 | self.assertTrue(isinstance(expr.exprs[0], Except)) | ||
129 | 389 | |||
130 | 303 | def test_auto_tables(self): | 390 | def test_auto_tables(self): |
131 | 304 | expr = AutoTables(elem1, [elem2]) | 391 | expr = AutoTables(elem1, [elem2]) |
132 | 305 | self.assertEquals(expr.expr, elem1) | 392 | self.assertEquals(expr.expr, elem1) |
Flatten nested set expressions as they are built so that we are less likely to hit Python's recursion limit when compiling the expression to SQL.
This relies on the left associativity of all the set expressions. (i.e. that Union(Union(a, b), c) is equivalent to Union(a, b, c)).