Merge lp:~therve/storm/update-multi-tables into lp:storm

Proposed by Thomas Herve
Status: Work in progress
Proposed branch: lp:~therve/storm/update-multi-tables
Merge into: lp:storm
Diff against target: 187 lines (+92/-15)
5 files modified
storm/databases/mysql.py (+9/-3)
storm/databases/postgres.py (+11/-4)
storm/expr.py (+43/-7)
storm/store.py (+1/-1)
tests/store/base.py (+28/-0)
To merge this branch: bzr merge lp:~therve/storm/update-multi-tables
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Needs Fixing
Storm Developers Pending
Review via email: mp+40790@code.launchpad.net

Description of the change

I'm a bit fuzzy about the state.context values. The work done on the alive cache is not awesome as well.

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Interesting changes, thanks Thomas. Here we go with some comments:

[1]

36 + if update.where is not Undef:
37 + state.context = EXPR
38 + where = compile(update.where, state, raw=True)
39 + if state.auto_tables:
40 + tables = set(

You were right to wonder about the context. Note that above the table set will potentially be compiled as EXPR expression, rather than TABLE. Same thing in the other version of compile.

[2]

43 + # Override the table token
44 + tokens[1] = ", ".join(tables)

This feels slightly hackish. We just went through the trouble of compiling the
tokens[1] value above. If we don't need it, the code should be organized in
such a way that tables are compiled just once, and in the same place where
the decision to compile it one way or another is taken.

Also, note that build_tables() internally is able to verify that the auto_tables
is available or not, and build it rather than a default set of tables conditionally,
so it feels like this work doesn't even have to be performed at this level, and may
instead be fully handed into build_tables().

[3]

96 + tables.remove(tokens[1])
97 + if tables:
98 + tokens.append(" FROM ")
99 + tokens.append(", ".join(tables))

Same case as above, but this time build_tables() won't be able to work alone.

[4]

It feels that the distinction between the two compilations of Update is quite small
considering the overall size. Can't we have the default (in expr.py) version which
compiles Update expressions accepting a boolean parameter to act one way or the
other, so that we can keep a single implementation and just select a flag in the
subcompiler?

[5]

118 - except CompileError:
119 + except (CompileError, KeyError):

Your gut feeling was also right on this one. This new KeyError here is quite
mysterious. I don't even understand myself where it's coming from, to be honest,
so we should find a better way to avoid this from popping up.

review: Needs Fixing
lp:~therve/storm/update-multi-tables updated
385. By Thomas Herve

Reduce duplication

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

I made a similar comment on the delete-with-using branch, but I think it'd be good to build the "FROM" clause using build_tables rather than accessing auto_tables manually. This would probably involve adding a way to tell it to exclude the primary table for the update from its output.

Unmerged revisions

385. By Thomas Herve

Reduce duplication

384. By Thomas Herve

Attemp implementing multi table update for postgres and mysql

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-12-11 13:15:10 +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, Update, compile_select, Undef, SQLToken,
35 SQLRaw, SQLToken, is_safe_token)35 is_safe_token, compile_update, _MULTIPLE_UPDATE_INLINE)
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,13 @@
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(Update)
55def compile_mysql_update(compile, update, state):
56 return compile_update(compile, update, state,
57 multi_tables=_MULTIPLE_UPDATE_INLINE)
58
59
54@compile.when(SQLToken)60@compile.when(SQLToken)
55def compile_sql_token_mysql(compile, expr, state):61def compile_sql_token_mysql(compile, expr, state):
56 """MySQL uses ` as the escape character by default."""62 """MySQL uses ` as the escape character by default."""
5763
=== modified file 'storm/databases/postgres.py'
--- storm/databases/postgres.py 2010-10-23 21:02:22 +0000
+++ storm/databases/postgres.py 2010-12-11 13:15:10 +0000
@@ -39,10 +39,11 @@
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, Update, 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, compile_update,
46 _MULTIPLE_UPDATE_WITH_FROM)
46from storm.variables import Variable, ListVariable47from storm.variables import Variable, ListVariable
47from storm.database import Database, Connection, Result48from storm.database import Database, Connection, Result
48from storm.exceptions import (49from storm.exceptions import (
@@ -183,6 +184,12 @@
183 return compile_insert(compile, insert, state)184 return compile_insert(compile, insert, state)
184185
185186
187@compile.when(Update)
188def compile_postgres_update(compile, update, state):
189 return compile_update(compile, update, state,
190 multi_tables=_MULTIPLE_UPDATE_WITH_FROM)
191
192
186@compile.when(Sequence)193@compile.when(Sequence)
187def compile_sequence_postgres(compile, sequence, state):194def compile_sequence_postgres(compile, sequence, state):
188 return "nextval('%s')" % sequence.name195 return "nextval('%s')" % sequence.name
189196
=== modified file 'storm/expr.py'
--- storm/expr.py 2010-12-07 15:00:30 +0000
+++ storm/expr.py 2010-12-11 13:15:10 +0000
@@ -741,21 +741,57 @@
741 self.table = table741 self.table = table
742 self.default_table = default_table742 self.default_table = default_table
743743
744
745_MULTIPLE_UPDATE_UNSUPPORTED = 0
746_MULTIPLE_UPDATE_WITH_FROM = 1
747_MULTIPLE_UPDATE_INLINE = 2
748
749
744@compile.when(Update)750@compile.when(Update)
745def compile_update(compile, update, state):751def compile_update(compile, update, state,
752 multi_tables=_MULTIPLE_UPDATE_UNSUPPORTED):
753 """
754 @param multi_tables: if multi-tables update is supported by the DB engine
755 as a SQL extension, pass _MULTIPLE_UPDATE_WITH_FROM to use the syntax
756 with FROM, or _MULTIPLE_UPDATE_INLINE to use the inline syntax.
757 """
746 map = update.map758 map = update.map
747 state.push("context", COLUMN_NAME)759 original_context = COLUMN_NAME
760 if multi_tables == _MULTIPLE_UPDATE_INLINE:
761 original_context = TABLE
762 state.push("context", original_context)
748 sets = ["%s=%s" % (compile(col, state, token=True),763 sets = ["%s=%s" % (compile(col, state, token=True),
749 compile(map[col], state))764 compile(map[col], state))
750 for col in map]765 for col in map]
751 state.context = TABLE766 where = None
752 tokens = ["UPDATE ", build_tables(compile, update.table,
753 update.default_table, state),
754 " SET ", ", ".join(sets)]
755 if update.where is not Undef:767 if update.where is not Undef:
756 state.context = EXPR768 state.context = EXPR
769 where = compile(update.where, state, raw=True)
770 state.context = TABLE
771 if multi_tables and state.auto_tables:
772 if multi_tables == _MULTIPLE_UPDATE_WITH_FROM:
773 first_table = build_tables(
774 compile, update.table, update.default_table, state)
775 other_tables = set(
776 compile(table, state, token=True)
777 for table in state.auto_tables)
778 tokens = ["UPDATE ", first_table, " SET ", ", ".join(sets)]
779 # Omit the original target
780 other_tables.remove(first_table)
781 if other_tables:
782 tokens.append(" FROM ")
783 tokens.append(", ".join(other_tables))
784 elif multi_tables == _MULTIPLE_UPDATE_INLINE:
785 tables = build_tables(
786 compile, Undef, update.default_table, state)
787 tokens = ["UPDATE ", tables, " SET ", ", ".join(sets)]
788 else:
789 tables = build_tables(
790 compile, update.table, update.default_table, state)
791 tokens = ["UPDATE ", tables, " SET ", ", ".join(sets)]
792 if where is not None:
757 tokens.append(" WHERE ")793 tokens.append(" WHERE ")
758 tokens.append(compile(update.where, state, raw=True))794 tokens.append(where)
759 state.pop()795 state.pop()
760 return "".join(tokens)796 return "".join(tokens)
761797
762798
=== modified file 'storm/store.py'
--- storm/store.py 2010-10-14 20:54:44 +0000
+++ storm/store.py 2010-12-11 13:15:10 +0000
@@ -1375,7 +1375,7 @@
13751375
1376 try:1376 try:
1377 cached = self.cached()1377 cached = self.cached()
1378 except CompileError:1378 except (CompileError, KeyError):
1379 # We are iterating through all objects in memory here, so1379 # We are iterating through all objects in memory here, so
1380 # check if the object type matches to avoid trying to1380 # check if the object type matches to avoid trying to
1381 # invalidate a column that does not exist, on an unrelated1381 # invalidate a column that does not exist, on an unrelated
13821382
=== modified file 'tests/store/base.py'
--- tests/store/base.py 2010-10-19 13:36:26 +0000
+++ tests/store/base.py 2010-12-11 13:15:10 +0000
@@ -2724,6 +2724,34 @@
2724 self.assertFalse(hasattr(foo, "tainted"))2724 self.assertFalse(hasattr(foo, "tainted"))
2725 self.assertEquals(foo.title, "Title 40")2725 self.assertEquals(foo.title, "Title 40")
27262726
2727 def test_find_set_with_two_tables(self):
2728 """
2729 Postgres and MySQL supports updating a table using other tables as
2730 filter.
2731 """
2732 cls_name = self.__class__.__name__
2733 if cls_name.startswith("SQLite"):
2734 return
2735 result = self.store.find(
2736 Foo, Foo.id == Bar.foo_id, Bar.title == u"Title 200")
2737 result.set(title=u"Title 2a")
2738 self.assertEqual(u"Title 2a", self.store.get(Foo, 20).title)
2739
2740 def test_find_set_with_multiple_tables(self):
2741 """
2742 Update works with multiple tables in Postgres and MySQL.
2743 """
2744 cls_name = self.__class__.__name__
2745 if cls_name.startswith("SQLite"):
2746 return
2747 result = self.store.find(
2748 Foo, Foo.id == Link.foo_id, Link.bar_id == Bar.id,
2749 Bar.title == u"Title 200")
2750 # Access the result set here, to cache the objects
2751 data = list(result)
2752 result.set(title=u"Title 2a")
2753 self.assertEqual(u"Title 2a", self.store.get(Foo, 20).title)
2754
2727 def test_reference(self):2755 def test_reference(self):
2728 bar = self.store.get(Bar, 100)2756 bar = self.store.get(Bar, 100)
2729 self.assertTrue(bar.foo)2757 self.assertTrue(bar.foo)

Subscribers

People subscribed via source and target branches

to status/vote changes: