Merge lp:~ack/storm/update-returning into lp:storm

Proposed by Alberto Donato
Status: Merged
Approved by: Jamu Kakar
Approved revision: 435
Merged at revision: 434
Proposed branch: lp:~ack/storm/update-returning
Merge into: lp:storm
Diff against target: 228 lines (+62/-31)
5 files modified
NEWS (+4/-0)
storm/databases/postgres.py (+12/-7)
storm/expr.py (+4/-2)
tests/databases/postgres.py (+41/-21)
tests/store/postgres.py (+1/-1)
To merge this branch: bzr merge lp:~ack/storm/update-returning
Reviewer Review Type Date Requested Status
Jamu Kakar (community) Approve
Free Ekanayaka (community) Approve
Review via email: mp+96576@code.launchpad.net

Description of the change

This branch adds support for Returning(Update(...)) expressions, with an optional list of columns to return.

To post a comment you must log in.
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Looks good, +1!

[1]

Please update the NEWS file, mentioning this new feature.

[2]

- """Appends the "RETURNING <primary_columns>" suffix to an INSERT.
+ """Appends the "RETURNING <primary_columns>" suffix to an INSERT or UPDATE.

This is slightly inaccurate now, as you can have arbitrary columns. I'd change it to:

    """Appends the "RETURNING <columns>" suffix to an INSERT or UPDATE.

    @param expression: ...
    @param columns: The columns to return, if C{None} then C{expression.primary_columns}
        will be used.

[3]

+ def __init__(self, expression, columns=None):

In this particular case I'd s/expression/expr/, as the class name is Expr, and
the current convention in the code is to use the name "expr" for instances of Expr.

review: Approve
Revision history for this message
Jamu Kakar (jkakar) wrote :

Nice work, +1! I have the same comments as Free, please fix them
before merging.

review: Approve
lp:~ack/storm/update-returning updated
436. By Alberto Donato

Addressed review comments.

Revision history for this message
Alberto Donato (ack) wrote :

> Looks good, +1!
>
> [1]
>
> Please update the NEWS file, mentioning this new feature.
>
> [2]
>
> - """Appends the "RETURNING <primary_columns>" suffix to an INSERT.
> + """Appends the "RETURNING <primary_columns>" suffix to an INSERT or
> UPDATE.
>
> This is slightly inaccurate now, as you can have arbitrary columns. I'd change
> it to:
>
> """Appends the "RETURNING <columns>" suffix to an INSERT or UPDATE.
>
> @param expression: ...
> @param columns: The columns to return, if C{None} then
> C{expression.primary_columns}
> will be used.
>
> [3]
>
> + def __init__(self, expression, columns=None):
>
> In this particular case I'd s/expression/expr/, as the class name is Expr, and
> the current convention in the code is to use the name "expr" for instances of
> Expr.

Addressed all points, thanks!

lp:~ack/storm/update-returning updated
437. By Alberto Donato

Update NEWS file.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2012-03-08 10:22:31 +0000
3+++ NEWS 2012-03-10 15:24:18 +0000
4@@ -26,6 +26,10 @@
5 - Insert expressions now support multi-row and subquery INSERT
6 statements.
7
8+- Support in the postgres backend to use the RETURNING extension for UPDATE
9+ statement, optionally specifying the columns to return.
10+ (PostgreSQL >= 8.2 only)
11+
12 Bug fixes
13 ---------
14
15
16=== modified file 'storm/databases/postgres.py'
17--- storm/databases/postgres.py 2012-01-25 19:52:04 +0000
18+++ storm/databases/postgres.py 2012-03-10 15:24:18 +0000
19@@ -56,23 +56,28 @@
20
21
22 class Returning(Expr):
23- """Appends the "RETURNING <primary_columns>" suffix to an INSERT.
24+ """Appends the "RETURNING <columns>" suffix to an INSERT or UPDATE.
25+
26+ @param expr: an L{Insert} or L{Update} expression.
27+ @param columns: The columns to return, if C{None} then
28+ C{expr.primary_columns} will be used.
29
30 This is only supported in PostgreSQL 8.2+.
31 """
32-
33- def __init__(self, insert):
34- self.insert = insert
35+ def __init__(self, expr, columns=None):
36+ self.expr = expr
37+ self.columns = columns
38
39 @compile.when(Returning)
40 def compile_returning(compile, expr, state):
41 state.push("context", COLUMN)
42- columns = compile(expr.insert.primary_columns, state)
43+ columns = expr.columns or expr.expr.primary_columns
44+ columns = compile(columns, state)
45 state.pop()
46 state.push("precedence", 0)
47- insert = compile(expr.insert, state)
48+ expr = compile(expr.expr, state)
49 state.pop()
50- return "%s RETURNING %s" % (insert, columns)
51+ return "%s RETURNING %s" % (expr, columns)
52
53
54 class currval(FuncExpr):
55
56=== modified file 'storm/expr.py'
57--- storm/expr.py 2012-03-08 10:09:38 +0000
58+++ storm/expr.py 2012-03-10 15:24:18 +0000
59@@ -749,13 +749,15 @@
60
61
62 class Update(Expr):
63- __slots__ = ("map", "where", "table", "default_table")
64+ __slots__ = ("map", "where", "table", "default_table", "primary_columns")
65
66- def __init__(self, map, where=Undef, table=Undef, default_table=Undef):
67+ def __init__(self, map, where=Undef, table=Undef, default_table=Undef,
68+ primary_columns=Undef):
69 self.map = map
70 self.where = where
71 self.table = table
72 self.default_table = default_table
73+ self.primary_columns = primary_columns
74
75 @compile.when(Update)
76 def compile_update(compile, update, state):
77
78=== modified file 'tests/databases/postgres.py'
79--- tests/databases/postgres.py 2012-01-25 22:03:17 +0000
80+++ tests/databases/postgres.py 2012-03-10 15:24:18 +0000
81@@ -29,7 +29,7 @@
82 from storm.variables import ListVariable, IntVariable, Variable
83 from storm.properties import Int
84 from storm.exceptions import DisconnectionError
85-from storm.expr import (Union, Select, Insert, Alias, SQLRaw, State,
86+from storm.expr import (Union, Select, Insert, Update, Alias, SQLRaw, State,
87 Sequence, Like, Column, COLUMN)
88 from storm.tracer import install_tracer, TimeoutError
89 from storm.uri import URI
90@@ -90,13 +90,13 @@
91 "(id SERIAL PRIMARY KEY, b BYTEA)")
92 self.connection.execute("CREATE TABLE like_case_insensitive_test "
93 "(id SERIAL PRIMARY KEY, description TEXT)")
94- self.connection.execute("CREATE TABLE insert_returning_test "
95+ self.connection.execute("CREATE TABLE returning_test "
96 "(id1 INTEGER DEFAULT 123, "
97 " id2 INTEGER DEFAULT 456)")
98
99 def drop_tables(self):
100 super(PostgresTest, self).drop_tables()
101- for table in ["like_case_insensitive_test", "insert_returning_test"]:
102+ for table in ["like_case_insensitive_test", "returning_test"]:
103 try:
104 self.connection.execute("DROP TABLE %s" % table)
105 self.connection.commit()
106@@ -389,25 +389,32 @@
107 "thetable.thecolumn = "
108 "(SELECT currval('thetable_thecolumn_seq'))")
109
110- def test_returning(self):
111- insert = Insert({column1: elem1}, table1,
112- primary_columns=(column2, column3))
113- self.assertEquals(compile(Returning(insert)),
114- 'INSERT INTO "table 1" (column1) VALUES (elem1) '
115- 'RETURNING column2, column3')
116-
117 def test_returning_column_context(self):
118 column2 = TrackContext()
119 insert = Insert({column1: elem1}, table1, primary_columns=column2)
120 compile(Returning(insert))
121 self.assertEquals(column2.context, COLUMN)
122
123+ def test_returning_update(self):
124+ update = Update({column1: elem1}, table=table1,
125+ primary_columns=(column2, column3))
126+ self.assertEquals(compile(Returning(update)),
127+ 'UPDATE "table 1" SET column1=elem1 '
128+ 'RETURNING column2, column3')
129+
130+ def test_returning_update_with_columns(self):
131+ update = Update({column1: elem1}, table=table1,
132+ primary_columns=(column2, column3))
133+ self.assertEquals(compile(Returning(update, columns=[column3])),
134+ 'UPDATE "table 1" SET column1=elem1 '
135+ 'RETURNING column3')
136+
137 def test_execute_insert_returning(self):
138- if self.database._version < (8, 2):
139+ if self.database._version < 80200:
140 return # Can't run this test with old PostgreSQL versions.
141
142- column1 = Column("id1", "insert_returning_test")
143- column2 = Column("id2", "insert_returning_test")
144+ column1 = Column("id1", "returning_test")
145+ column2 = Column("id2", "returning_test")
146 variable1 = IntVariable()
147 variable2 = IntVariable()
148 insert = Insert({}, primary_columns=(column1, column2),
149@@ -420,13 +427,13 @@
150 self.assertEquals(variable1.get(), 123)
151 self.assertEquals(variable2.get(), 456)
152
153- result = self.connection.execute("SELECT * FROM insert_returning_test")
154+ result = self.connection.execute("SELECT * FROM returning_test")
155 self.assertEquals(result.get_one(), (123, 456))
156
157 def test_wb_execute_insert_returning_not_used_with_old_postgres(self):
158 """Shouldn't try to use RETURNING with PostgreSQL < 8.2."""
159- column1 = Column("id1", "insert_returning_test")
160- column2 = Column("id2", "insert_returning_test")
161+ column1 = Column("id1", "returning_test")
162+ column2 = Column("id2", "returning_test")
163 variable1 = IntVariable()
164 variable2 = IntVariable()
165 insert = Insert({}, primary_columns=(column1, column2),
166@@ -438,31 +445,44 @@
167 self.assertFalse(variable1.is_defined())
168 self.assertFalse(variable2.is_defined())
169
170- result = self.connection.execute("SELECT * FROM insert_returning_test")
171+ result = self.connection.execute("SELECT * FROM returning_test")
172 self.assertEquals(result.get_one(), (123, 456))
173
174 def test_execute_insert_returning_without_columns(self):
175 """Without primary_columns, the RETURNING system won't be used."""
176- column1 = Column("id1", "insert_returning_test")
177+ column1 = Column("id1", "returning_test")
178 variable1 = IntVariable()
179 insert = Insert({column1: 123}, primary_variables=(variable1,))
180 self.connection.execute(insert)
181
182 self.assertFalse(variable1.is_defined())
183
184- result = self.connection.execute("SELECT * FROM insert_returning_test")
185+ result = self.connection.execute("SELECT * FROM returning_test")
186 self.assertEquals(result.get_one(), (123, 456))
187
188 def test_execute_insert_returning_without_variables(self):
189 """Without primary_variables, the RETURNING system won't be used."""
190- column1 = Column("id1", "insert_returning_test")
191+ column1 = Column("id1", "returning_test")
192 insert = Insert({}, primary_columns=(column1,))
193 self.connection.execute(insert)
194
195- result = self.connection.execute("SELECT * FROM insert_returning_test")
196+ result = self.connection.execute("SELECT * FROM returning_test")
197
198 self.assertEquals(result.get_one(), (123, 456))
199
200+ def test_execute_update_returning(self):
201+ if self.database._version < 80200:
202+ return # Can't run this test with old PostgreSQL versions.
203+
204+ column1 = Column("id1", "returning_test")
205+ column2 = Column("id2", "returning_test")
206+ self.connection.execute(
207+ "INSERT INTO returning_test VALUES (1, 2)")
208+ update = Update({"id2": 3}, column1 == 1,
209+ primary_columns=(column1, column2))
210+ result = self.connection.execute(Returning(update))
211+ self.assertEquals(result.get_one(), (1, 3))
212+
213 def test_isolation_autocommit(self):
214 database = create_database(
215 os.environ["STORM_POSTGRES_URI"] + "?isolation=autocommit")
216
217=== modified file 'tests/store/postgres.py'
218--- tests/store/postgres.py 2011-02-14 12:17:54 +0000
219+++ tests/store/postgres.py 2012-03-10 15:24:18 +0000
220@@ -183,7 +183,7 @@
221 Ensure that the currval()-based identity retrieval continues
222 to work, even if we're currently running on a 8.2+ database.
223 """
224- self.database._version = (8, 1, 9)
225+ self.database._version = 80109
226 foo1 = self.store.add(Foo())
227 self.store.flush()
228 foo2 = self.store.add(Foo())

Subscribers

People subscribed via source and target branches

to status/vote changes: