Merge lp:~therve/storm/delete-with-using into lp:storm

Proposed by Thomas Herve
Status: Work in progress
Proposed branch: lp:~therve/storm/delete-with-using
Merge into: lp:storm
Diff against target: 153 lines (+85/-7)
4 files modified
storm/databases/mysql.py (+27/-3)
storm/databases/postgres.py (+30/-4)
storm/expr.py (+1/-0)
tests/store/base.py (+27/-0)
To merge this branch: bzr merge lp:~therve/storm/delete-with-using
Reviewer Review Type Date Requested Status
James Henstridge Needs Fixing
Storm Developers Pending
Review via email: mp+40785@code.launchpad.net

Description of the change

I think I'm happy enough with the branch. I added support to MySQL and Postgres, and it looks simple enough.

To post a comment you must log in.
lp:~therve/storm/delete-with-using updated
359. By Thomas Herve

Fix tests naming

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

I notice that you don't do a state.push/pop on auto_tables like compile_select() does. I guess it isn't as important since DELETE statements can't be nested (can they?), but it wouldn't hurt.

Also, the use of build_tables() and manually adding tables from state.auto_tables looks fishy. For compile_select() the automatically generated table list comes from build_tables, so I think there is a problem with how it is being used here.

I think the way it should work is:

 1. just run compile() on Delete.table to generate the "FROM xxx" clause.
 2. use build_tables(compile, Undef, Undef, state) to generate the "USING xxx" clause,
    plus some way to ensure Delete.table does not appear in this list.

Now on to the test changes: While we've used the "if cls_name.startswith(...):" pattern to disable tests for some backends, perhaps it would be cleaner to add a "supports_delete_using()" method that the backend test classes can override to say whether the test should run.

Lastly, it would be good to include a test for the case where the backend does not support "DELETE ... USING" to ensure we give a sensible error rather than generating nonsense SQL like we currently do.

review: Needs Fixing

Unmerged revisions

359. By Thomas Herve

Fix tests naming

358. By Thomas Herve

Cleanup

357. By Thomas Herve

Add one test with 3 tables

356. By Thomas Herve

Use localized compile, add support for mysql

355. By Thomas Herve

Merge forward

354. By Thomas Herve

Support USING in DELETE

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'storm/databases/mysql.py'
--- storm/databases/mysql.py 2009-09-21 17:03:08 +0000
+++ storm/databases/mysql.py 2010-11-13 12:51:17 +0000
@@ -31,13 +31,12 @@
31 MySQLdb = dummy31 MySQLdb = dummy
3232
33from storm.expr import (33from storm.expr import (
34 compile, Insert, Select, compile_select, Undef, And, Eq,34 compile, Insert, Select, Delete, compile_select, Undef, SQLToken,
35 SQLRaw, SQLToken, is_safe_token)35 is_safe_token, EXPR, build_tables, TABLE)
36from storm.variables import Variable36from storm.variables import Variable
37from storm.database import Database, Connection, Result37from storm.database import Database, Connection, Result
38from storm.exceptions import (38from storm.exceptions import (
39 install_exceptions, DatabaseModuleError, OperationalError)39 install_exceptions, DatabaseModuleError, OperationalError)
40from storm.variables import IntVariable
4140
4241
43install_exceptions(MySQLdb)42install_exceptions(MySQLdb)
@@ -51,6 +50,31 @@
51 select.limit = sys.maxint50 select.limit = sys.maxint
52 return compile_select(compile, select, state)51 return compile_select(compile, select, state)
5352
53
54@compile.when(Delete)
55def compile_delete(compile, delete, state):
56 tokens = ["DELETE FROM "]
57 state.push("context", EXPR)
58 where = None
59 if delete.where is not Undef:
60 where = compile(delete.where, state, raw=True)
61 # Compile later for auto_tables support.
62 state.context = TABLE
63 tokens.append(build_tables(compile, delete.table,
64 delete.default_table, state))
65 if state.auto_tables:
66 tables = set(
67 compile(table, state, token=True) for table in state.auto_tables)
68 if tables:
69 tokens.append(" USING ")
70 tokens.append(", ".join(tables))
71 if where is not None:
72 tokens.append(" WHERE ")
73 tokens.append(where)
74 state.pop()
75 return "".join(tokens)
76
77
54@compile.when(SQLToken)78@compile.when(SQLToken)
55def compile_sql_token_mysql(compile, expr, state):79def compile_sql_token_mysql(compile, expr, state):
56 """MySQL uses ` as the escape character by default."""80 """MySQL uses ` as the escape character by default."""
5781
=== modified file 'storm/databases/postgres.py'
--- storm/databases/postgres.py 2010-10-23 21:02:22 +0000
+++ storm/databases/postgres.py 2010-11-13 12:51:17 +0000
@@ -39,10 +39,10 @@
39 psycopg2 = dummy39 psycopg2 = dummy
4040
41from storm.expr import (41from storm.expr import (
42 Undef, Expr, SetExpr, Select, Insert, Alias, And, Eq, FuncExpr, SQLRaw,42 Undef, Expr, SetExpr, Select, Insert, Delete, Alias, And, Eq, FuncExpr,
43 Sequence, Like, SQLToken, COLUMN, COLUMN_NAME, COLUMN_PREFIX, TABLE,43 SQLRaw, Sequence, Like, SQLToken, COLUMN, COLUMN_NAME, COLUMN_PREFIX,
44 compile, compile_select, compile_insert, compile_set_expr, compile_like,44 TABLE, compile, compile_select, compile_insert, compile_set_expr,
45 compile_sql_token)45 compile_like, compile_sql_token, EXPR, build_tables)
46from storm.variables import Variable, ListVariable46from storm.variables import Variable, ListVariable
47from storm.database import Database, Connection, Result47from storm.database import Database, Connection, Result
48from storm.exceptions import (48from storm.exceptions import (
@@ -183,6 +183,32 @@
183 return compile_insert(compile, insert, state)183 return compile_insert(compile, insert, state)
184184
185185
186@compile.when(Delete)
187def compile_delete(compile, delete, state):
188 tokens = ["DELETE FROM "]
189 state.push("context", EXPR)
190 where = None
191 if delete.where is not Undef:
192 where = compile(delete.where, state, raw=True)
193 # Compile later for auto_tables support.
194 state.context = TABLE
195 tokens.append(build_tables(compile, delete.table,
196 delete.default_table, state))
197 if state.auto_tables:
198 tables = set(
199 compile(table, state, token=True) for table in state.auto_tables)
200 # Remove the target table from the USING clause
201 tables.discard(tokens[1])
202 if tables:
203 tokens.append(" USING ")
204 tokens.append(", ".join(tables))
205 if where is not None:
206 tokens.append(" WHERE ")
207 tokens.append(where)
208 state.pop()
209 return "".join(tokens)
210
211
186@compile.when(Sequence)212@compile.when(Sequence)
187def compile_sequence_postgres(compile, sequence, state):213def compile_sequence_postgres(compile, sequence, state):
188 return "nextval('%s')" % sequence.name214 return "nextval('%s')" % sequence.name
189215
=== modified file 'storm/expr.py'
--- storm/expr.py 2009-11-02 11:11:20 +0000
+++ storm/expr.py 2010-11-13 12:51:17 +0000
@@ -768,6 +768,7 @@
768 self.table = table768 self.table = table
769 self.default_table = default_table769 self.default_table = default_table
770770
771
771@compile.when(Delete)772@compile.when(Delete)
772def compile_delete(compile, delete, state):773def compile_delete(compile, delete, state):
773 tokens = ["DELETE FROM ", None]774 tokens = ["DELETE FROM ", None]
774775
=== modified file 'tests/store/base.py'
--- tests/store/base.py 2010-10-19 13:36:26 +0000
+++ tests/store/base.py 2010-11-13 12:51:17 +0000
@@ -1153,6 +1153,33 @@
1153 (30, "Title 10"),1153 (30, "Title 10"),
1154 ])1154 ])
11551155
1156 def test_find_remove_with_using(self):
1157 """
1158 Postgres and MySQL support C{USING} tables in C{DELETE}, allowing to
1159 select more tables when removing rows.
1160 """
1161 cls_name = self.__class__.__name__
1162 if cls_name.startswith("SQLite"):
1163 return
1164 result = self.store.find(
1165 Bar, Bar.foo_id == Foo.id, Foo.title == u"Title 20")
1166 result.remove()
1167 self.assertEquals(sorted(self.store.find(Bar.id)), [100, 300])
1168
1169 def test_find_remove_with_using_multi_tables(self):
1170 """
1171 The C{USING} syntax supports several tables which can be used to build
1172 check criteria.
1173 """
1174 cls_name = self.__class__.__name__
1175 if cls_name.startswith("SQLite"):
1176 return
1177 result = self.store.find(
1178 Bar, Bar.id == Link.bar_id, Link.foo_id == Foo.id, Foo.title ==
1179 u"Title 10")
1180 result.remove()
1181 self.assertEquals(sorted(self.store.find(Bar.id)), [100, 200])
1182
1156 def test_find_cached(self):1183 def test_find_cached(self):
1157 foo = self.store.get(Foo, 20)1184 foo = self.store.get(Foo, 20)
1158 bar = self.store.get(Bar, 200)1185 bar = self.store.get(Bar, 200)

Subscribers

People subscribed via source and target branches

to status/vote changes: