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
1=== modified file 'storm/databases/mysql.py'
2--- storm/databases/mysql.py 2009-09-21 17:03:08 +0000
3+++ storm/databases/mysql.py 2010-11-13 12:51:17 +0000
4@@ -31,13 +31,12 @@
5 MySQLdb = dummy
6
7 from storm.expr import (
8- compile, Insert, Select, compile_select, Undef, And, Eq,
9- SQLRaw, SQLToken, is_safe_token)
10+ compile, Insert, Select, Delete, compile_select, Undef, SQLToken,
11+ is_safe_token, EXPR, build_tables, TABLE)
12 from storm.variables import Variable
13 from storm.database import Database, Connection, Result
14 from storm.exceptions import (
15 install_exceptions, DatabaseModuleError, OperationalError)
16-from storm.variables import IntVariable
17
18
19 install_exceptions(MySQLdb)
20@@ -51,6 +50,31 @@
21 select.limit = sys.maxint
22 return compile_select(compile, select, state)
23
24+
25+@compile.when(Delete)
26+def compile_delete(compile, delete, state):
27+ tokens = ["DELETE FROM "]
28+ state.push("context", EXPR)
29+ where = None
30+ if delete.where is not Undef:
31+ where = compile(delete.where, state, raw=True)
32+ # Compile later for auto_tables support.
33+ state.context = TABLE
34+ tokens.append(build_tables(compile, delete.table,
35+ delete.default_table, state))
36+ if state.auto_tables:
37+ tables = set(
38+ compile(table, state, token=True) for table in state.auto_tables)
39+ if tables:
40+ tokens.append(" USING ")
41+ tokens.append(", ".join(tables))
42+ if where is not None:
43+ tokens.append(" WHERE ")
44+ tokens.append(where)
45+ state.pop()
46+ return "".join(tokens)
47+
48+
49 @compile.when(SQLToken)
50 def compile_sql_token_mysql(compile, expr, state):
51 """MySQL uses ` as the escape character by default."""
52
53=== modified file 'storm/databases/postgres.py'
54--- storm/databases/postgres.py 2010-10-23 21:02:22 +0000
55+++ storm/databases/postgres.py 2010-11-13 12:51:17 +0000
56@@ -39,10 +39,10 @@
57 psycopg2 = dummy
58
59 from storm.expr import (
60- Undef, Expr, SetExpr, Select, Insert, Alias, And, Eq, FuncExpr, SQLRaw,
61- Sequence, Like, SQLToken, COLUMN, COLUMN_NAME, COLUMN_PREFIX, TABLE,
62- compile, compile_select, compile_insert, compile_set_expr, compile_like,
63- compile_sql_token)
64+ Undef, Expr, SetExpr, Select, Insert, Delete, Alias, And, Eq, FuncExpr,
65+ SQLRaw, Sequence, Like, SQLToken, COLUMN, COLUMN_NAME, COLUMN_PREFIX,
66+ TABLE, compile, compile_select, compile_insert, compile_set_expr,
67+ compile_like, compile_sql_token, EXPR, build_tables)
68 from storm.variables import Variable, ListVariable
69 from storm.database import Database, Connection, Result
70 from storm.exceptions import (
71@@ -183,6 +183,32 @@
72 return compile_insert(compile, insert, state)
73
74
75+@compile.when(Delete)
76+def compile_delete(compile, delete, state):
77+ tokens = ["DELETE FROM "]
78+ state.push("context", EXPR)
79+ where = None
80+ if delete.where is not Undef:
81+ where = compile(delete.where, state, raw=True)
82+ # Compile later for auto_tables support.
83+ state.context = TABLE
84+ tokens.append(build_tables(compile, delete.table,
85+ delete.default_table, state))
86+ if state.auto_tables:
87+ tables = set(
88+ compile(table, state, token=True) for table in state.auto_tables)
89+ # Remove the target table from the USING clause
90+ tables.discard(tokens[1])
91+ if tables:
92+ tokens.append(" USING ")
93+ tokens.append(", ".join(tables))
94+ if where is not None:
95+ tokens.append(" WHERE ")
96+ tokens.append(where)
97+ state.pop()
98+ return "".join(tokens)
99+
100+
101 @compile.when(Sequence)
102 def compile_sequence_postgres(compile, sequence, state):
103 return "nextval('%s')" % sequence.name
104
105=== modified file 'storm/expr.py'
106--- storm/expr.py 2009-11-02 11:11:20 +0000
107+++ storm/expr.py 2010-11-13 12:51:17 +0000
108@@ -768,6 +768,7 @@
109 self.table = table
110 self.default_table = default_table
111
112+
113 @compile.when(Delete)
114 def compile_delete(compile, delete, state):
115 tokens = ["DELETE FROM ", None]
116
117=== modified file 'tests/store/base.py'
118--- tests/store/base.py 2010-10-19 13:36:26 +0000
119+++ tests/store/base.py 2010-11-13 12:51:17 +0000
120@@ -1153,6 +1153,33 @@
121 (30, "Title 10"),
122 ])
123
124+ def test_find_remove_with_using(self):
125+ """
126+ Postgres and MySQL support C{USING} tables in C{DELETE}, allowing to
127+ select more tables when removing rows.
128+ """
129+ cls_name = self.__class__.__name__
130+ if cls_name.startswith("SQLite"):
131+ return
132+ result = self.store.find(
133+ Bar, Bar.foo_id == Foo.id, Foo.title == u"Title 20")
134+ result.remove()
135+ self.assertEquals(sorted(self.store.find(Bar.id)), [100, 300])
136+
137+ def test_find_remove_with_using_multi_tables(self):
138+ """
139+ The C{USING} syntax supports several tables which can be used to build
140+ check criteria.
141+ """
142+ cls_name = self.__class__.__name__
143+ if cls_name.startswith("SQLite"):
144+ return
145+ result = self.store.find(
146+ Bar, Bar.id == Link.bar_id, Link.foo_id == Foo.id, Foo.title ==
147+ u"Title 10")
148+ result.remove()
149+ self.assertEquals(sorted(self.store.find(Bar.id)), [100, 200])
150+
151 def test_find_cached(self):
152 foo = self.store.get(Foo, 20)
153 bar = self.store.get(Bar, 200)

Subscribers

People subscribed via source and target branches

to status/vote changes: