Merge lp:~gerdusvanzyl/storm/firebird into lp:storm
- firebird
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~gerdusvanzyl/storm/firebird |
Merge into: | lp:storm |
Diff against target: |
587 lines (+427/-27) 4 files modified
storm/databases/firebird.py (+229/-0) tests/databases/base.py (+25/-25) tests/databases/firebird.py (+171/-0) tests/databases/proxy.py (+2/-2) |
To merge this branch: | bzr merge lp:~gerdusvanzyl/storm/firebird |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Storm Developers | Pending | ||
Storm Developers | Pending | ||
Review via email: mp+28559@code.launchpad.net |
Commit message
Description of the change
Basic Firebird database support.
Passes all tests except "Connection.
example connection string env variable for tests: STORM_FIREBIRD_
Gerdus van Zyl (gerdusvanzyl) wrote : | # |
No problem, I have just sent the email as per the instructions.
On Tue, Aug 10, 2010 at 7:23 PM, Gustavo Niemeyer <email address hidden> wrote:
> Hi Gerdus,
>
> Thanks for pushing this forward into a request!
>
> Sorry if this has been asked already, but if not, can you please sign the contributor agreement at:
>
> http://
> --
> https:/
> You are the owner of lp:~gerdusvanzyl/storm/firebird.
>
--
Gerdus van Zyl
Unmerged revisions
- 368. By Gerdus van Zyl
-
merge from trunk
- 367. By Gerdus van Zyl
-
cleanup unused comments
- 366. By Gerdus van Zyl
-
fixed proxy to work on windows
replaced os.read(self.request. fileno( ) .. with self.request.recv - 365. By Gerdus van Zyl
-
-
- 364. By Gerdus van Zyl
-
disconnect cleanup, push to vpc
- 363. By Gerdus van Zyl
-
is_disconnectio
n_error - 362. By Gerdus van Zyl
-
test cases for returning and select limit
- 361. By Gerdus van Zyl
-
start of disconnect test, fix base database test to not use "select 1"
- 360. By Gerdus van Zyl
-
cleanup and override non implementable tests
- 359. By Gerdus van Zyl
-
code to pass test test_execute_
expression and test_execute_ expression_ empty_params
Preview Diff
1 | === added file 'storm/databases/firebird.py' | |||
2 | --- storm/databases/firebird.py 1970-01-01 00:00:00 +0000 | |||
3 | +++ storm/databases/firebird.py 2010-06-26 13:26:23 +0000 | |||
4 | @@ -0,0 +1,229 @@ | |||
5 | 1 | # | ||
6 | 2 | # Copyright (c) 2010 Canonical | ||
7 | 3 | # | ||
8 | 4 | # Initial Code by Gerdus van Zyl | ||
9 | 5 | # | ||
10 | 6 | # This file is part of Storm Object Relational Mapper. | ||
11 | 7 | # | ||
12 | 8 | # | ||
13 | 9 | # Storm is free software; you can redistribute it and/or modify | ||
14 | 10 | # it under the terms of the GNU Lesser General Public License as | ||
15 | 11 | # published by the Free Software Foundation; either version 2.1 of | ||
16 | 12 | # the License, or (at your option) any later version. | ||
17 | 13 | # | ||
18 | 14 | # Storm is distributed in the hope that it will be useful, | ||
19 | 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
20 | 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
21 | 17 | # GNU Lesser General Public License for more details. | ||
22 | 18 | # | ||
23 | 19 | # You should have received a copy of the GNU Lesser General Public License | ||
24 | 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
25 | 21 | # | ||
26 | 22 | # | ||
27 | 23 | """ | ||
28 | 24 | Tested only with: Kinterbase 3.3 and Firebird 2.1 | ||
29 | 25 | """ | ||
30 | 26 | |||
31 | 27 | import os.path | ||
32 | 28 | from datetime import timedelta | ||
33 | 29 | from storm.databases import dummy | ||
34 | 30 | |||
35 | 31 | from storm.expr import (compile, Select, compile_select, Undef, | ||
36 | 32 | Expr, Insert,COLUMN,Sequence) | ||
37 | 33 | from storm.variables import (Variable,IntVariable) | ||
38 | 34 | from storm.database import Database, Connection, Result | ||
39 | 35 | |||
40 | 36 | from storm.exceptions import ( | ||
41 | 37 | install_exceptions, DatabaseError, DatabaseModuleError, InterfaceError, | ||
42 | 38 | OperationalError, ProgrammingError, TimeoutError) | ||
43 | 39 | |||
44 | 40 | try: | ||
45 | 41 | import kinterbasdb | ||
46 | 42 | install_exceptions(kinterbasdb) | ||
47 | 43 | except ImportError: | ||
48 | 44 | kinterbasdb = dummy | ||
49 | 45 | |||
50 | 46 | try: | ||
51 | 47 | kinterbasdb.init(type_conv=200) | ||
52 | 48 | except kinterbasdb.ProgrammingError: | ||
53 | 49 | pass #Cannot initialize module more than once. | ||
54 | 50 | |||
55 | 51 | compile = compile.create_child() | ||
56 | 52 | |||
57 | 53 | @compile.when(int, long) | ||
58 | 54 | def compile_int(compile, expr, state): | ||
59 | 55 | state.parameters.append(IntVariable(expr)) | ||
60 | 56 | return "cast(? as integer)" | ||
61 | 57 | |||
62 | 58 | @compile.when(Select) | ||
63 | 59 | def compile_select_firebird(compile, select, state): | ||
64 | 60 | limit = select.limit | ||
65 | 61 | offset = select.offset | ||
66 | 62 | # Make sure limit is Undef'ed. | ||
67 | 63 | select.offset = select.limit = Undef | ||
68 | 64 | |||
69 | 65 | if select.default_tables is Undef: | ||
70 | 66 | select.default_tables = ["RDB$DATABASE"] | ||
71 | 67 | |||
72 | 68 | sql = compile_select(compile, select, state) | ||
73 | 69 | |||
74 | 70 | if limit is not Undef or offset is not Undef: | ||
75 | 71 | rowstart = 1 | ||
76 | 72 | rowstop = None | ||
77 | 73 | if offset is not Undef: | ||
78 | 74 | rowstart = offset + 1 | ||
79 | 75 | if limit is not Undef: | ||
80 | 76 | rowstop = rowstart + limit - 1 | ||
81 | 77 | if rowstop < rowstart: | ||
82 | 78 | rowstop = rowstart | ||
83 | 79 | |||
84 | 80 | sql += " ROWS %i" % rowstart | ||
85 | 81 | if rowstop: | ||
86 | 82 | sql += " TO %i " % rowstop | ||
87 | 83 | #print sql | ||
88 | 84 | |||
89 | 85 | return sql | ||
90 | 86 | |||
91 | 87 | @compile.when(Sequence) | ||
92 | 88 | def compile_sequence_firebird(compile, sequence, state): | ||
93 | 89 | return "gen_id(%s, 1)" % sequence.name | ||
94 | 90 | |||
95 | 91 | class Returning(Expr): | ||
96 | 92 | """Appends the "RETURNING <primary_columns>" suffix to an INSERT. | ||
97 | 93 | |||
98 | 94 | This is only supported in Firebird 2.0 | ||
99 | 95 | """ | ||
100 | 96 | def __init__(self, insert): | ||
101 | 97 | self.insert = insert | ||
102 | 98 | |||
103 | 99 | @compile.when(Returning) | ||
104 | 100 | def compile_returning(compile, expr, state): | ||
105 | 101 | state.push("context", COLUMN) | ||
106 | 102 | columns = compile(expr.insert.primary_columns, state) | ||
107 | 103 | state.pop() | ||
108 | 104 | state.push("precedence", 0) | ||
109 | 105 | insert = compile(expr.insert, state) | ||
110 | 106 | state.pop() | ||
111 | 107 | return "%s RETURNING %s" % (insert, columns) | ||
112 | 108 | |||
113 | 109 | class FirebirdResult(Result): | ||
114 | 110 | @staticmethod | ||
115 | 111 | def from_database(row): | ||
116 | 112 | """Convert Firebird-specific datatypes to "normal" Python types. | ||
117 | 113 | |||
118 | 114 | If there are any C{array} instances in the row, convert them | ||
119 | 115 | to strings. | ||
120 | 116 | """ | ||
121 | 117 | for value in row: | ||
122 | 118 | yield value | ||
123 | 119 | |||
124 | 120 | def get_insert_identity(self, primary_key, primary_variables): | ||
125 | 121 | """ | ||
126 | 122 | Firebird does not support insert identity | ||
127 | 123 | - autoinc is implemented using custom triggers | ||
128 | 124 | so no clear way to support it in a generic way | ||
129 | 125 | """ | ||
130 | 126 | raise NotImplementedError | ||
131 | 127 | |||
132 | 128 | class FirebirdConnection(Connection): | ||
133 | 129 | |||
134 | 130 | result_factory = FirebirdResult | ||
135 | 131 | param_mark = "?" | ||
136 | 132 | compile = compile | ||
137 | 133 | server_version = None | ||
138 | 134 | |||
139 | 135 | def execute(self, statement, params=None, noresult=False): | ||
140 | 136 | """Execute a statement with the given parameters. | ||
141 | 137 | |||
142 | 138 | This extends the L{Connection.execute} method to add support | ||
143 | 139 | for automatic retrieval of inserted primary keys to link | ||
144 | 140 | in-memory objects with their specific rows. | ||
145 | 141 | """ | ||
146 | 142 | if not self.server_version: | ||
147 | 143 | version = 0 | ||
148 | 144 | version = self._raw_connection.db_info(kinterbasdb.isc_info_version) | ||
149 | 145 | version = str(version).split("Firebird")[1].strip() | ||
150 | 146 | version = float(version) | ||
151 | 147 | self.server_version = version | ||
152 | 148 | |||
153 | 149 | if (isinstance(statement, Insert) and | ||
154 | 150 | self.server_version >= 2 and | ||
155 | 151 | statement.primary_variables is not Undef and | ||
156 | 152 | statement.primary_columns is not Undef): | ||
157 | 153 | |||
158 | 154 | # Here we decorate the Insert statement with a Returning | ||
159 | 155 | # expression, so that we get back in the result the values | ||
160 | 156 | # for the primary key just inserted. This prevents a round | ||
161 | 157 | # trip to the database for obtaining these values. | ||
162 | 158 | result = Connection.execute(self, Returning(statement), params) | ||
163 | 159 | for variable, value in zip(statement.primary_variables, | ||
164 | 160 | result.get_one()): | ||
165 | 161 | result.set_variable(variable, value) | ||
166 | 162 | return result | ||
167 | 163 | |||
168 | 164 | return Connection.execute(self, statement, params, noresult) | ||
169 | 165 | |||
170 | 166 | def is_disconnection_error(self, exc): | ||
171 | 167 | """Check whether an exception represents a database disconnection. | ||
172 | 168 | |||
173 | 169 | """ | ||
174 | 170 | if isinstance(exc, ProgrammingError) or isinstance(exc,OperationalError): | ||
175 | 171 | code,description = exc.args | ||
176 | 172 | if code == -902: | ||
177 | 173 | return True | ||
178 | 174 | |||
179 | 175 | |||
180 | 176 | return False | ||
181 | 177 | |||
182 | 178 | def to_database(self, params): | ||
183 | 179 | for param in params: | ||
184 | 180 | if isinstance(param, Variable): | ||
185 | 181 | param = param.get(to_db=True) | ||
186 | 182 | if isinstance(param, timedelta): | ||
187 | 183 | yield str(param) | ||
188 | 184 | else: | ||
189 | 185 | yield param | ||
190 | 186 | |||
191 | 187 | class Firebird(Database): | ||
192 | 188 | |||
193 | 189 | connection_factory = FirebirdConnection | ||
194 | 190 | _converters = None | ||
195 | 191 | |||
196 | 192 | def __init__(self, uri): | ||
197 | 193 | if kinterbasdb is dummy: | ||
198 | 194 | raise DatabaseModuleError("'kinterbasdb' module not found") | ||
199 | 195 | self._connect_kwargs = {} | ||
200 | 196 | if uri.database is not None: | ||
201 | 197 | if os.path.isfile(uri.database): | ||
202 | 198 | uri.database = os.path.abspath(uri.database) | ||
203 | 199 | self._connect_kwargs["database"] = uri.database | ||
204 | 200 | if uri.host is not None: | ||
205 | 201 | self._connect_kwargs["host"] = uri.host | ||
206 | 202 | if uri.port is not None: | ||
207 | 203 | #firebird expects nonstandard port spec: http://www.firebirdfaq.org/faq259/ | ||
208 | 204 | self._connect_kwargs["host"] = "%s/%s" % (uri.host,uri.port) | ||
209 | 205 | if uri.port is not None: | ||
210 | 206 | self._connect_kwargs["port"] = uri.port | ||
211 | 207 | if uri.username is not None: | ||
212 | 208 | self._connect_kwargs["user"] = uri.username | ||
213 | 209 | if uri.password is not None: | ||
214 | 210 | self._connect_kwargs["password"] = uri.password | ||
215 | 211 | for option in ["unix_socket"]: | ||
216 | 212 | if option in uri.options: | ||
217 | 213 | self._connect_kwargs[option] = uri.options.get(option) | ||
218 | 214 | |||
219 | 215 | |||
220 | 216 | |||
221 | 217 | def raw_connect(self): | ||
222 | 218 | customTPB = ( | ||
223 | 219 | kinterbasdb.isc_tpb_write | ||
224 | 220 | + kinterbasdb.isc_tpb_concurrency | ||
225 | 221 | ) | ||
226 | 222 | |||
227 | 223 | raw_connection = kinterbasdb.connect(**self._connect_kwargs) | ||
228 | 224 | raw_connection.default_tpb = customTPB | ||
229 | 225 | |||
230 | 226 | return raw_connection | ||
231 | 227 | |||
232 | 228 | |||
233 | 229 | create_from_uri = Firebird | ||
234 | 0 | 230 | ||
235 | === modified file 'tests/databases/base.py' | |||
236 | --- tests/databases/base.py 2010-04-16 07:12:13 +0000 | |||
237 | +++ tests/databases/base.py 2010-06-26 13:26:23 +0000 | |||
238 | @@ -132,7 +132,7 @@ | |||
239 | 132 | self.assertTrue(result.get_one()) | 132 | self.assertTrue(result.get_one()) |
240 | 133 | 133 | ||
241 | 134 | def test_execute_result(self): | 134 | def test_execute_result(self): |
243 | 135 | result = self.connection.execute("SELECT 1") | 135 | result = self.connection.execute("SELECT 1 FROM TEST") |
244 | 136 | self.assertTrue(isinstance(result, Result)) | 136 | self.assertTrue(isinstance(result, Result)) |
245 | 137 | self.assertTrue(result.get_one()) | 137 | self.assertTrue(result.get_one()) |
246 | 138 | 138 | ||
247 | @@ -357,7 +357,7 @@ | |||
248 | 357 | event.hook("register-transaction", register_transaction) | 357 | event.hook("register-transaction", register_transaction) |
249 | 358 | 358 | ||
250 | 359 | connection = self.database.connect(event) | 359 | connection = self.database.connect(event) |
252 | 360 | connection.execute("SELECT 1") | 360 | connection.execute("SELECT 1 FROM TEST") |
253 | 361 | self.assertEqual(len(calls), 1) | 361 | self.assertEqual(len(calls), 1) |
254 | 362 | self.assertEqual(calls[0], marker) | 362 | self.assertEqual(calls[0], marker) |
255 | 363 | 363 | ||
256 | @@ -425,16 +425,16 @@ | |||
257 | 425 | 425 | ||
258 | 426 | def test_block_access(self): | 426 | def test_block_access(self): |
259 | 427 | """Access to the connection is blocked by block_access().""" | 427 | """Access to the connection is blocked by block_access().""" |
261 | 428 | self.connection.execute("SELECT 1") | 428 | self.connection.execute("SELECT 1 FROM TEST") |
262 | 429 | self.connection.block_access() | 429 | self.connection.block_access() |
263 | 430 | self.assertRaises(ConnectionBlockedError, | 430 | self.assertRaises(ConnectionBlockedError, |
265 | 431 | self.connection.execute, "SELECT 1") | 431 | self.connection.execute, "SELECT 1 FROM TEST") |
266 | 432 | self.assertRaises(ConnectionBlockedError, self.connection.commit) | 432 | self.assertRaises(ConnectionBlockedError, self.connection.commit) |
267 | 433 | # Allow rolling back a blocked connection. | 433 | # Allow rolling back a blocked connection. |
268 | 434 | self.connection.rollback() | 434 | self.connection.rollback() |
269 | 435 | # Unblock the connection, allowing access again. | 435 | # Unblock the connection, allowing access again. |
270 | 436 | self.connection.unblock_access() | 436 | self.connection.unblock_access() |
272 | 437 | self.connection.execute("SELECT 1") | 437 | self.connection.execute("SELECT 1 FROM TEST") |
273 | 438 | 438 | ||
274 | 439 | 439 | ||
275 | 440 | class UnsupportedDatabaseTest(object): | 440 | class UnsupportedDatabaseTest(object): |
276 | @@ -556,20 +556,20 @@ | |||
277 | 556 | 556 | ||
278 | 557 | def test_proxy_works(self): | 557 | def test_proxy_works(self): |
279 | 558 | """Ensure that we can talk to the database through the proxy.""" | 558 | """Ensure that we can talk to the database through the proxy.""" |
281 | 559 | result = self.connection.execute("SELECT 1") | 559 | result = self.connection.execute(Select(1)) |
282 | 560 | self.assertEqual(result.get_one(), (1,)) | 560 | self.assertEqual(result.get_one(), (1,)) |
283 | 561 | 561 | ||
284 | 562 | def test_catch_disconnect_on_execute(self): | 562 | def test_catch_disconnect_on_execute(self): |
285 | 563 | """Test that database disconnections get caught on execute().""" | 563 | """Test that database disconnections get caught on execute().""" |
287 | 564 | result = self.connection.execute("SELECT 1") | 564 | result = self.connection.execute(Select(1)) |
288 | 565 | self.assertTrue(result.get_one()) | 565 | self.assertTrue(result.get_one()) |
289 | 566 | self.proxy.restart() | 566 | self.proxy.restart() |
290 | 567 | self.assertRaises(DisconnectionError, | 567 | self.assertRaises(DisconnectionError, |
292 | 568 | self.connection.execute, "SELECT 1") | 568 | self.connection.execute, Select(1)) |
293 | 569 | 569 | ||
294 | 570 | def test_catch_disconnect_on_commit(self): | 570 | def test_catch_disconnect_on_commit(self): |
295 | 571 | """Test that database disconnections get caught on commit().""" | 571 | """Test that database disconnections get caught on commit().""" |
297 | 572 | result = self.connection.execute("SELECT 1") | 572 | result = self.connection.execute(Select(1)) |
298 | 573 | self.assertTrue(result.get_one()) | 573 | self.assertTrue(result.get_one()) |
299 | 574 | self.proxy.restart() | 574 | self.proxy.restart() |
300 | 575 | self.assertRaises(DisconnectionError, self.connection.commit) | 575 | self.assertRaises(DisconnectionError, self.connection.commit) |
301 | @@ -581,13 +581,13 @@ | |||
302 | 581 | then it is possible that Storm won't see the disconnection. | 581 | then it is possible that Storm won't see the disconnection. |
303 | 582 | It should be able to recover from this situation though. | 582 | It should be able to recover from this situation though. |
304 | 583 | """ | 583 | """ |
306 | 584 | result = self.connection.execute("SELECT 1") | 584 | result = self.connection.execute(Select(1)) |
307 | 585 | self.assertTrue(result.get_one()) | 585 | self.assertTrue(result.get_one()) |
308 | 586 | self.proxy.restart() | 586 | self.proxy.restart() |
309 | 587 | # Perform an action that should result in a disconnection. | 587 | # Perform an action that should result in a disconnection. |
310 | 588 | try: | 588 | try: |
311 | 589 | cursor = self.connection._raw_connection.cursor() | 589 | cursor = self.connection._raw_connection.cursor() |
313 | 590 | cursor.execute("SELECT 1") | 590 | cursor.execute("select 1 from test") |
314 | 591 | cursor.fetchone() | 591 | cursor.fetchone() |
315 | 592 | except Error, exc: | 592 | except Error, exc: |
316 | 593 | self.assertTrue(self.connection.is_disconnection_error(exc)) | 593 | self.assertTrue(self.connection.is_disconnection_error(exc)) |
317 | @@ -614,59 +614,59 @@ | |||
318 | 614 | then it is possible that Storm won't see the disconnection. | 614 | then it is possible that Storm won't see the disconnection. |
319 | 615 | It should be able to recover from this situation though. | 615 | It should be able to recover from this situation though. |
320 | 616 | """ | 616 | """ |
322 | 617 | result = self.connection.execute("SELECT 1") | 617 | result = self.connection.execute(Select(1)) |
323 | 618 | self.assertTrue(result.get_one()) | 618 | self.assertTrue(result.get_one()) |
324 | 619 | self.proxy.restart() | 619 | self.proxy.restart() |
325 | 620 | # Perform an action that should result in a disconnection. | 620 | # Perform an action that should result in a disconnection. |
326 | 621 | try: | 621 | try: |
327 | 622 | cursor = self.connection._raw_connection.cursor() | 622 | cursor = self.connection._raw_connection.cursor() |
329 | 623 | cursor.execute("SELECT 1") | 623 | cursor.execute("SELECT 1 FROM TEST") |
330 | 624 | cursor.fetchone() | 624 | cursor.fetchone() |
331 | 625 | except DatabaseError, exc: | 625 | except DatabaseError, exc: |
332 | 626 | self.assertTrue(self.connection.is_disconnection_error(exc)) | 626 | self.assertTrue(self.connection.is_disconnection_error(exc)) |
333 | 627 | else: | 627 | else: |
334 | 628 | self.fail("Disconnection was not caught.") | 628 | self.fail("Disconnection was not caught.") |
335 | 629 | self.assertRaises(DisconnectionError, | 629 | self.assertRaises(DisconnectionError, |
337 | 630 | self.connection.execute, "SELECT 1") | 630 | self.connection.execute, Select(1)) |
338 | 631 | 631 | ||
339 | 632 | def test_connection_stays_disconnected_in_transaction(self): | 632 | def test_connection_stays_disconnected_in_transaction(self): |
340 | 633 | """Test that connection does not immediately reconnect.""" | 633 | """Test that connection does not immediately reconnect.""" |
342 | 634 | result = self.connection.execute("SELECT 1") | 634 | result = self.connection.execute(Select(1)) |
343 | 635 | self.assertTrue(result.get_one()) | 635 | self.assertTrue(result.get_one()) |
344 | 636 | self.proxy.restart() | 636 | self.proxy.restart() |
345 | 637 | self.assertRaises(DisconnectionError, | 637 | self.assertRaises(DisconnectionError, |
347 | 638 | self.connection.execute, "SELECT 1") | 638 | self.connection.execute, Select(1)) |
348 | 639 | self.assertRaises(DisconnectionError, | 639 | self.assertRaises(DisconnectionError, |
350 | 640 | self.connection.execute, "SELECT 1") | 640 | self.connection.execute, Select(1)) |
351 | 641 | 641 | ||
352 | 642 | def test_reconnect_after_rollback(self): | 642 | def test_reconnect_after_rollback(self): |
353 | 643 | """Test that we reconnect after rolling back the connection.""" | 643 | """Test that we reconnect after rolling back the connection.""" |
355 | 644 | result = self.connection.execute("SELECT 1") | 644 | result = self.connection.execute(Select(1)) |
356 | 645 | self.assertTrue(result.get_one()) | 645 | self.assertTrue(result.get_one()) |
357 | 646 | self.proxy.restart() | 646 | self.proxy.restart() |
358 | 647 | self.assertRaises(DisconnectionError, | 647 | self.assertRaises(DisconnectionError, |
360 | 648 | self.connection.execute, "SELECT 1") | 648 | self.connection.execute, Select(1)) |
361 | 649 | self.connection.rollback() | 649 | self.connection.rollback() |
363 | 650 | result = self.connection.execute("SELECT 1") | 650 | result = self.connection.execute(Select(1)) |
364 | 651 | self.assertTrue(result.get_one()) | 651 | self.assertTrue(result.get_one()) |
365 | 652 | 652 | ||
366 | 653 | def test_catch_disconnect_on_reconnect(self): | 653 | def test_catch_disconnect_on_reconnect(self): |
367 | 654 | """Test that reconnection failures result in DisconnectionError.""" | 654 | """Test that reconnection failures result in DisconnectionError.""" |
369 | 655 | result = self.connection.execute("SELECT 1") | 655 | result = self.connection.execute(Select(1)) |
370 | 656 | self.assertTrue(result.get_one()) | 656 | self.assertTrue(result.get_one()) |
371 | 657 | self.proxy.stop() | 657 | self.proxy.stop() |
372 | 658 | self.assertRaises(DisconnectionError, | 658 | self.assertRaises(DisconnectionError, |
374 | 659 | self.connection.execute, "SELECT 1") | 659 | self.connection.execute, Select(1)) |
375 | 660 | # Rollback the connection, but because the proxy is still | 660 | # Rollback the connection, but because the proxy is still |
376 | 661 | # down, we get a DisconnectionError again. | 661 | # down, we get a DisconnectionError again. |
377 | 662 | self.connection.rollback() | 662 | self.connection.rollback() |
378 | 663 | self.assertRaises(DisconnectionError, | 663 | self.assertRaises(DisconnectionError, |
380 | 664 | self.connection.execute, "SELECT 1") | 664 | self.connection.execute, Select(1)) |
381 | 665 | 665 | ||
382 | 666 | def test_close_connection_after_disconnect(self): | 666 | def test_close_connection_after_disconnect(self): |
384 | 667 | result = self.connection.execute("SELECT 1") | 667 | result = self.connection.execute(Select(1)) |
385 | 668 | self.assertTrue(result.get_one()) | 668 | self.assertTrue(result.get_one()) |
386 | 669 | self.proxy.stop() | 669 | self.proxy.stop() |
387 | 670 | self.assertRaises(DisconnectionError, | 670 | self.assertRaises(DisconnectionError, |
389 | 671 | self.connection.execute, "SELECT 1") | 671 | self.connection.execute, Select(1)) |
390 | 672 | self.connection.close() | 672 | self.connection.close() |
391 | 673 | 673 | ||
392 | === added file 'tests/databases/firebird.py' | |||
393 | --- tests/databases/firebird.py 1970-01-01 00:00:00 +0000 | |||
394 | +++ tests/databases/firebird.py 2010-06-26 13:26:23 +0000 | |||
395 | @@ -0,0 +1,171 @@ | |||
396 | 1 | # | ||
397 | 2 | # Copyright (c) 2010 Canonical | ||
398 | 3 | # | ||
399 | 4 | # Initial Code by Gerdus van Zyl | ||
400 | 5 | # | ||
401 | 6 | # This file is part of Storm Object Relational Mapper. | ||
402 | 7 | # | ||
403 | 8 | # Storm is free software; you can redistribute it and/or modify | ||
404 | 9 | # it under the terms of the GNU Lesser General Public License as | ||
405 | 10 | # published by the Free Software Foundation; either version 2.1 of | ||
406 | 11 | # the License, or (at your option) any later version. | ||
407 | 12 | # | ||
408 | 13 | # Storm is distributed in the hope that it will be useful, | ||
409 | 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
410 | 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
411 | 16 | # GNU Lesser General Public License for more details. | ||
412 | 17 | # | ||
413 | 18 | # You should have received a copy of the GNU Lesser General Public License | ||
414 | 19 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
415 | 20 | # | ||
416 | 21 | import os | ||
417 | 22 | |||
418 | 23 | from storm.database import create_database | ||
419 | 24 | from storm.database import * | ||
420 | 25 | |||
421 | 26 | from tests.helper import TestHelper | ||
422 | 27 | |||
423 | 28 | from tests.databases.base import ( | ||
424 | 29 | DatabaseTest, UnsupportedDatabaseTest, DatabaseDisconnectionTest) | ||
425 | 30 | |||
426 | 31 | from storm.variables import ( | ||
427 | 32 | IntVariable, Variable ) | ||
428 | 33 | |||
429 | 34 | from storm.exceptions import ( | ||
430 | 35 | DatabaseError, DatabaseModuleError, | ||
431 | 36 | DisconnectionError, Error, OperationalError, ConnectionBlockedError) | ||
432 | 37 | |||
433 | 38 | from tests.expr import ( | ||
434 | 39 | Select,Column,Insert, column1, column2, column3, elem1, | ||
435 | 40 | table1, TrackContext,Sequence) | ||
436 | 41 | |||
437 | 42 | #import storm.tracer | ||
438 | 43 | #import sys | ||
439 | 44 | #storm.tracer.debug(True, stream=sys.stdout) | ||
440 | 45 | |||
441 | 46 | class FirebirdTest(DatabaseTest, TestHelper): | ||
442 | 47 | supports_microseconds = False | ||
443 | 48 | |||
444 | 49 | def is_supported(self): | ||
445 | 50 | return bool(os.environ.get("STORM_FIREBIRD_URI")) | ||
446 | 51 | |||
447 | 52 | def create_database(self): | ||
448 | 53 | self.database = create_database(os.environ["STORM_FIREBIRD_URI"]) | ||
449 | 54 | |||
450 | 55 | def create_tables(self): | ||
451 | 56 | self.connection.execute('CREATE TABLE NUMBER (one INTEGER, two INTEGER, three INTEGER)') | ||
452 | 57 | self.connection.execute('CREATE TABLE TEST (id INTEGER PRIMARY KEY, title VARCHAR(50) CHARACTER SET UTF8)') | ||
453 | 58 | self.connection.execute('CREATE TABLE DATETIME_TEST (id INT UNIQUE,dt TIMESTAMP, d DATE, t TIME, td BLOB SUB_TYPE TEXT)') | ||
454 | 59 | |||
455 | 60 | self.connection.execute('CREATE TABLE BIN_TEST (id INT UNIQUE,b BLOB)') | ||
456 | 61 | |||
457 | 62 | self.connection.execute('CREATE GENERATOR GEN_TEST_AUTOID') | ||
458 | 63 | self.connection.execute('SET GENERATOR GEN_TEST_AUTOID TO 0') | ||
459 | 64 | |||
460 | 65 | self.connection.execute("""CREATE TRIGGER TEST_PK_AUTO FOR TEST ACTIVE BEFORE INSERT POSITION 0 AS | ||
461 | 66 | begin | ||
462 | 67 | if ( (new.ID is null) or (new.ID = 0) ) | ||
463 | 68 | then new.ID = gen_id(GEN_TEST_AUTOID, 1); | ||
464 | 69 | end""") | ||
465 | 70 | |||
466 | 71 | self.connection.execute("""CREATE TABLE insert_returning_test | ||
467 | 72 | (id0 INTEGER, | ||
468 | 73 | id1 INTEGER DEFAULT 123, | ||
469 | 74 | id2 INTEGER DEFAULT 456)""") | ||
470 | 75 | |||
471 | 76 | self.connection.commit() | ||
472 | 77 | |||
473 | 78 | def drop_tables(self): | ||
474 | 79 | try: | ||
475 | 80 | self.connection.execute("DROP TRIGGER TEST_PK_AUTO") | ||
476 | 81 | self.connection.execute("DROP GENERATOR GEN_TEST_AUTOID") | ||
477 | 82 | self.connection.commit() | ||
478 | 83 | except: | ||
479 | 84 | self.connection.rollback() | ||
480 | 85 | |||
481 | 86 | for table in ["number", "test", "datetime_test", "bin_test", "insert_returning_test"]: | ||
482 | 87 | try: | ||
483 | 88 | self.connection.execute("DROP TABLE " + table) | ||
484 | 89 | self.connection.commit() | ||
485 | 90 | except: | ||
486 | 91 | self.connection.rollback() | ||
487 | 92 | |||
488 | 93 | def test_get_insert_identity(self): | ||
489 | 94 | """test_get_insert_identity -Does not support insert identity""" | ||
490 | 95 | #http://www.firebirdfaq.org/faq243/ | ||
491 | 96 | pass | ||
492 | 97 | |||
493 | 98 | def test_get_insert_identity_composed(self): | ||
494 | 99 | """test_get_insert_identity_composed - Does not support insert identity""" | ||
495 | 100 | #http://www.firebirdfaq.org/faq243/ | ||
496 | 101 | pass | ||
497 | 102 | |||
498 | 103 | def test_execute_insert_returning(self): | ||
499 | 104 | if self.connection.server_version < 2: | ||
500 | 105 | return # Can't run this test with old PostgreSQL versions. | ||
501 | 106 | |||
502 | 107 | column0 = Column("id0", "insert_returning_test") | ||
503 | 108 | column1 = Column("id1", "insert_returning_test") | ||
504 | 109 | column2 = Column("id2", "insert_returning_test") | ||
505 | 110 | variable1 = IntVariable() | ||
506 | 111 | variable2 = IntVariable() | ||
507 | 112 | insert = Insert({column0: 999}, primary_columns=(column1, column2), | ||
508 | 113 | primary_variables=(variable1, variable2)) | ||
509 | 114 | self.connection.execute(insert) | ||
510 | 115 | |||
511 | 116 | self.assertTrue(variable1.is_defined()) | ||
512 | 117 | self.assertTrue(variable2.is_defined()) | ||
513 | 118 | |||
514 | 119 | self.assertEquals(variable1.get(), 123) | ||
515 | 120 | self.assertEquals(variable2.get(), 456) | ||
516 | 121 | |||
517 | 122 | result = self.connection.execute("SELECT * FROM insert_returning_test") | ||
518 | 123 | self.assertEquals(result.get_one(), (999,123, 456)) | ||
519 | 124 | |||
520 | 125 | def test_sequence(self): | ||
521 | 126 | expr1 = Select(Sequence("GEN_TEST_AUTOID")) | ||
522 | 127 | expr2 = "SELECT gen_id(GEN_TEST_AUTOID,0) FROM RDB$DATABASE" | ||
523 | 128 | value1 = self.connection.execute(expr1).get_one()[0] | ||
524 | 129 | value2 = self.connection.execute(expr2).get_one()[0] | ||
525 | 130 | value3 = self.connection.execute(expr1).get_one()[0] | ||
526 | 131 | self.assertEquals(value1, value2) | ||
527 | 132 | self.assertEquals(value3-value1, 1) | ||
528 | 133 | |||
529 | 134 | def test_limit_offset(self): | ||
530 | 135 | self.connection.execute("delete from test") | ||
531 | 136 | self.connection.commit() | ||
532 | 137 | |||
533 | 138 | for z in range(100,200+1): | ||
534 | 139 | sql = "INSERT INTO test (id,title) VALUES (%i,'%i')" % (z,z) | ||
535 | 140 | self.connection.execute(sql) | ||
536 | 141 | self.connection.commit() | ||
537 | 142 | |||
538 | 143 | select = Select(Column("id", "test")) | ||
539 | 144 | select.limit = 1 | ||
540 | 145 | select.offset = 0 | ||
541 | 146 | select.order_by = Column("id", "test") | ||
542 | 147 | |||
543 | 148 | result = self.connection.execute(select) | ||
544 | 149 | self.assertEquals(result.get_all(), [(100,),] ) | ||
545 | 150 | |||
546 | 151 | |||
547 | 152 | select = Select(Column("id", "test")) | ||
548 | 153 | select.limit = 2 | ||
549 | 154 | select.offset = 50 | ||
550 | 155 | select.order_by = Column("id", "test") | ||
551 | 156 | |||
552 | 157 | result = self.connection.execute(select) | ||
553 | 158 | self.assertEquals(result.get_all(), [(150,),(151,)] ) | ||
554 | 159 | |||
555 | 160 | |||
556 | 161 | class FirebirdUnsupportedTest(UnsupportedDatabaseTest, TestHelper): | ||
557 | 162 | |||
558 | 163 | dbapi_module_names = ["kinterbasdb"] | ||
559 | 164 | db_module_name = "firebird" | ||
560 | 165 | |||
561 | 166 | |||
562 | 167 | class FirebirdDisconnectionTest(DatabaseDisconnectionTest, TestHelper): | ||
563 | 168 | |||
564 | 169 | environment_variable = "STORM_FIREBIRD_URI" | ||
565 | 170 | host_environment_variable = "STORM_FIREBIRD_HOST_URI" | ||
566 | 171 | default_port = 3050 | ||
567 | 0 | 172 | ||
568 | === modified file 'tests/databases/proxy.py' | |||
569 | --- tests/databases/proxy.py 2007-10-24 06:27:06 +0000 | |||
570 | +++ tests/databases/proxy.py 2010-06-26 13:26:23 +0000 | |||
571 | @@ -52,14 +52,14 @@ | |||
572 | 52 | return | 52 | return |
573 | 53 | 53 | ||
574 | 54 | if self.request in rlist: | 54 | if self.request in rlist: |
576 | 55 | chunk = os.read(self.request.fileno(), 1024) | 55 | chunk = self.request.recv(1024) |
577 | 56 | dst.send(chunk) | 56 | dst.send(chunk) |
578 | 57 | if chunk == "": | 57 | if chunk == "": |
579 | 58 | readers.remove(self.request) | 58 | readers.remove(self.request) |
580 | 59 | dst.shutdown(socket.SHUT_WR) | 59 | dst.shutdown(socket.SHUT_WR) |
581 | 60 | 60 | ||
582 | 61 | if dst in rlist: | 61 | if dst in rlist: |
584 | 62 | chunk = os.read(dst.fileno(), 1024) | 62 | chunk = dst.recv(1024) |
585 | 63 | self.request.send(chunk) | 63 | self.request.send(chunk) |
586 | 64 | if chunk == "": | 64 | if chunk == "": |
587 | 65 | readers.remove(dst) | 65 | readers.remove(dst) |
Hi Gerdus,
Thanks for pushing this forward into a request!
Sorry if this has been asked already, but if not, can you please sign the contributor agreement at:
http:// www.canonical. com/contributor s