Merge lp:~jbaker/storm/oracle_support into lp:storm
- oracle_support
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~jbaker/storm/oracle_support |
Merge into: | lp:storm |
Diff against target: |
1573 lines (+1287/-39) 9 files modified
README (+38/-2) oracle_test_setup.sql (+13/-0) storm/databases/oracle.py (+590/-0) storm/expr.py (+1/-1) storm/variables.py (+2/-0) tests/databases/base.py (+32/-31) tests/databases/oracle.py (+372/-0) tests/store/base.py (+5/-5) tests/store/oracle.py (+234/-0) |
To merge this branch: | bzr merge lp:~jbaker/storm/oracle_support |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jamu Kakar (community) | Abstain | ||
Storm Developers | Pending | ||
Review via email: mp+15442@code.launchpad.net |
Commit message
Description of the change
Jamu Kakar (jkakar) wrote : | # |
[1]
I've managed to get Oracle XE installed, but I'm having trouble
getting cx_Oracle in place, so I can't run the test suite yet. I'm
going to keep at it, but if you have any hints on how to get
cx_Oracle in place (on a 64-bit machine) I'd appreciate some help.
Ideally, packages for Ubuntu would be nice.
[2]
I've pushed a branch that adds some details about getting Oracle XE
installed on a 64-bit machine to the README file and makes some
cosmetic changes to make the code match the Storm coding style more
closely. Please merge:
lp:~jkakar/storm/oracle-support-tweaks
[3]
Why isn't the builtin reserved words functionality being used?
I've made a broad pass over the changeset and it looks really good.
I'd like to get the test suite running before I do a more detailed
pass.
Jason Baker (jbaker) wrote : | # |
> [1]
>
> I've managed to get Oracle XE installed, but I'm having trouble
> getting cx_Oracle in place, so I can't run the test suite yet. I'm
> going to keep at it, but if you have any hints on how to get
> cx_Oracle in place (on a 64-bit machine) I'd appreciate some help.
> Ideally, packages for Ubuntu would be nice.
I found this blog post to be a good help: http://
If you can give me a specific error message, I might be able to help out.
> [2]
>
> I've pushed a branch that adds some details about getting Oracle XE
> installed on a 64-bit machine to the README file and makes some
> cosmetic changes to make the code match the Storm coding style more
> closely. Please merge:
>
> lp:~jkakar/storm/oracle-support-tweaks
I'll look at them. Thanks!
> [3]
>
> Why isn't the builtin reserved words functionality being used?
I don't recall the *exact* reason why I did that, but there was some issue with the reserved word keylist considering words to be reserved that weren't. Perhaps a better solution would be to update the reserved word list. It's a bit dated (SQL 1992).
Jason Baker (jbaker) wrote : | # |
> lp:~jkakar/storm/oracle-support-tweaks
Erm.... This branch doesn't seem to exist. Unless of course I'm doing something wrong (which is possible).
Jamu Kakar (jkakar) wrote : | # |
Oops, the branch was private by mistake. It should be visible now.
- 357. By Jason Baker
-
Merging in changes from trunk.
- 358. By Jason Baker
-
Merging some changes in from lp:~jkakar/storm/oracle-support-tweaks
- 359. By Jason Baker
-
Updating from trunk.
- 360. By Jason Baker
-
Making the oracle backend properly handle disconnection errors
Jason Baker (jbaker) wrote : | # |
Where do we stand on this? I've just merged in the latest revisions from trunk and also added a bugfix that makes the disconnection tests now pass.
Jamu Kakar (jkakar) wrote : | # |
I had spent some time looking at this a while ago. I got as far as
getting Oracle installed (which was hard on 64-bit) and then stopped
when I had to figure out how to compile cxOracle DB-API drivers. I
looked over the code, and it looks good, but want to actually be
able to run the test suite.
I've since installed a new disk in my machine, so I'll need to setup
Oracle et al. again to continue. I've been thinking that we really
need a buildbot, since getting Oracle in place is rather awkward and
most Storm developers probably won't want to do it.
Jamu Kakar (jkakar) wrote : | # |
I haven't managed to get Oracle installed. I'm going to abstain
from reviewing this for now.
Unmerged revisions
- 360. By Jason Baker
-
Making the oracle backend properly handle disconnection errors
- 359. By Jason Baker
-
Updating from trunk.
- 358. By Jason Baker
-
Merging some changes in from lp:~jkakar/storm/oracle-support-tweaks
- 357. By Jason Baker
-
Merging in changes from trunk.
- 356. By Jason Baker <jason@ubuntu>
-
Added instructions for getting started with Oracle.
- 355. By Jason Baker <jason@ubuntu>
-
Adding in a script to set up a test user in an oracle database.
- 354. By Jason Baker <jason@ubuntu>
-
Reverting the error handling back to the way it was for now. For whatever reason, this led to spurious error reports when running tests.
- 353. By Jason Baker <jason@ubuntu>
-
Temporarily commenting out the Disconnection tests until they can be fixed.
- 352. By Jason Baker <jason@ubuntu>
-
Adding an error description for cx_Oracle not being found
- 351. By Jason Baker <jason@ubuntu>
-
Merge from trunk.
Preview Diff
1 | === modified file 'README' | |||
2 | --- README 2009-10-08 07:23:43 +0000 | |||
3 | +++ README 2010-03-15 15:48:27 +0000 | |||
4 | @@ -95,8 +95,8 @@ | |||
5 | 95 | same procedure will probably work without changes on a Debian system | 95 | same procedure will probably work without changes on a Debian system |
6 | 96 | and with minimal changes on a non-Debian-based linux distribution. | 96 | and with minimal changes on a non-Debian-based linux distribution. |
7 | 97 | In order to run the test suite, and exercise all supported backends, | 97 | In order to run the test suite, and exercise all supported backends, |
10 | 98 | you will need to install MySQL and PostgreSQL, along with the | 98 | you will need to install MySQL, PostgreSQL and Oracle XE, along with |
11 | 99 | related Python database drivers: | 99 | the related Python database drivers: |
12 | 100 | 100 | ||
13 | 101 | $ sudo apt-get install python-mysqldb python-psycopg2 mysql-server \ | 101 | $ sudo apt-get install python-mysqldb python-psycopg2 mysql-server \ |
14 | 102 | postgresql build-essential | 102 | postgresql build-essential |
15 | @@ -110,6 +110,34 @@ | |||
16 | 110 | asked to enter a password multiple times. Leave it blank in each | 110 | asked to enter a password multiple times. Leave it blank in each |
17 | 111 | case. | 111 | case. |
18 | 112 | 112 | ||
19 | 113 | To install Oracle on a 32-bit system you must first add the | ||
20 | 114 | following line to your /etc/apt/sources.list: | ||
21 | 115 | |||
22 | 116 | deb http://oss.oracle.com/debian unstable main non-free | ||
23 | 117 | |||
24 | 118 | You can then install Oracle XE by running the following commands: | ||
25 | 119 | |||
26 | 120 | $ sudo wget http://oss.oracle.com/el4/RPM-GPG-KEY-oracle -O- | sudo apt-key add - | ||
27 | 121 | $ sudo apt-get update | ||
28 | 122 | $ sudo apt-get install oracle-xe | ||
29 | 123 | |||
30 | 124 | Binaries for Oracle XE on 64-bit systems are not available, so | ||
31 | 125 | you'll need to install the 32-bit versions. Start by downloading | ||
32 | 126 | the packages: | ||
33 | 127 | |||
34 | 128 | $ wget -c http://oss.oracle.com/debian/dists/unstable/main/binary-i386/libaio_0.3.104-1_i386.deb http://oss.oracle.com/debian/dists/unstable/non-free/binary-i386/oracle-xe-universal_10.2.0.1-1.1_i386.deb | ||
35 | 129 | |||
36 | 130 | Once you have the packages you need to install them manually: | ||
37 | 131 | |||
38 | 132 | $ sudo apt-get install libc6-i386 bc | ||
39 | 133 | $ sudo dpkg -i --force-architecture libaio_0.3.104-1_i386.deb | ||
40 | 134 | $ sudo dpkg -i --force-architecture oracle-xe-universal_10.2.0.1-1.1_i386.deb | ||
41 | 135 | |||
42 | 136 | Finally, when the packages are installed, you can then run the | ||
43 | 137 | following line to configure the database: | ||
44 | 138 | |||
45 | 139 | $ sudo /etc/init.d/oracle-xe configure | ||
46 | 140 | |||
47 | 113 | Setting up database users and access security | 141 | Setting up database users and access security |
48 | 114 | --------------------------------------------- | 142 | --------------------------------------------- |
49 | 115 | 143 | ||
50 | @@ -145,6 +173,14 @@ | |||
51 | 145 | mysql> GRANT ALL PRIVILEGES ON *.* TO 'YOUR_USERNAME'@'localhost' | 173 | mysql> GRANT ALL PRIVILEGES ON *.* TO 'YOUR_USERNAME'@'localhost' |
52 | 146 | IDENTIFIED BY '' WITH GRANT OPTION; | 174 | IDENTIFIED BY '' WITH GRANT OPTION; |
53 | 147 | 175 | ||
54 | 176 | For Oracle, we've included a script to set up a test user. To run it, | ||
55 | 177 | do the following: | ||
56 | 178 | |||
57 | 179 | $ sqlplus sys/<sys_password>@localhost:1521 | ||
58 | 180 | SQL> @oracle_test_setup | ||
59 | 181 | |||
60 | 182 | And follow the prompts to create a user. | ||
61 | 183 | |||
62 | 148 | Creating test databases | 184 | Creating test databases |
63 | 149 | ----------------------- | 185 | ----------------------- |
64 | 150 | 186 | ||
65 | 151 | 187 | ||
66 | === added file 'oracle_test_setup.sql' | |||
67 | --- oracle_test_setup.sql 1970-01-01 00:00:00 +0000 | |||
68 | +++ oracle_test_setup.sql 2010-03-15 15:48:27 +0000 | |||
69 | @@ -0,0 +1,13 @@ | |||
70 | 1 | -- Note that this is a script that is intended to be run to set up a | ||
71 | 2 | -- test database. You probably don't want to run this on a production | ||
72 | 3 | -- database! | ||
73 | 4 | |||
74 | 5 | CREATE USER &&user IDENTIFIED BY &&password; | ||
75 | 6 | GRANT CONNECT TO &&user; | ||
76 | 7 | GRANT CREATE ANY TABLE TO &&user; | ||
77 | 8 | GRANT CREATE ANY TRIGGER TO &&user; | ||
78 | 9 | GRANT CREATE ANY SEQUENCE TO &&user; | ||
79 | 10 | GRANT DROP ANY SEQUENCE TO &&user; | ||
80 | 11 | GRANT DROP ANY TABLE TO &&user; | ||
81 | 12 | GRANT DROP ANY TRIGGER TO &&user; | ||
82 | 13 | GRANT UNLIMITED TABLESPACE TO &&user; | ||
83 | 0 | \ No newline at end of file | 14 | \ No newline at end of file |
84 | 1 | 15 | ||
85 | === added file 'storm/databases/oracle.py' | |||
86 | --- storm/databases/oracle.py 1970-01-01 00:00:00 +0000 | |||
87 | +++ storm/databases/oracle.py 2010-03-15 15:48:27 +0000 | |||
88 | @@ -0,0 +1,590 @@ | |||
89 | 1 | # Copyright (c) 2008 Alfaiati | ||
90 | 2 | # | ||
91 | 3 | # Written by Gustavo Noronha <kov@alfaiati.net> | ||
92 | 4 | # Willi Langenberger <wlang@wu-wien.ac.at> | ||
93 | 5 | # | ||
94 | 6 | # This file is part of Storm Object Relational Mapper. | ||
95 | 7 | # | ||
96 | 8 | # Storm is free software; you can redistribute it and/or modify | ||
97 | 9 | # it under the terms of the GNU Lesser General Public License as | ||
98 | 10 | # published by the Free Software Foundation; either version 2.1 of | ||
99 | 11 | # the License, or (at your option) any later version. | ||
100 | 12 | # | ||
101 | 13 | # Storm is distributed in the hope that it will be useful, | ||
102 | 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
103 | 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
104 | 16 | # GNU Lesser General Public License for more details. | ||
105 | 17 | # | ||
106 | 18 | # You should have received a copy of the GNU Lesser General Public License | ||
107 | 19 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
108 | 20 | |||
109 | 21 | import os | ||
110 | 22 | import sys | ||
111 | 23 | from decimal import Decimal | ||
112 | 24 | |||
113 | 25 | from storm.databases import dummy | ||
114 | 26 | |||
115 | 27 | # If no NLS_LANG is set, we'll use english with UTF-8. Note that this | ||
116 | 28 | # variable *must* be set before importing cx_Oracle. | ||
117 | 29 | if not os.environ.get("NLS_LANG", None): | ||
118 | 30 | os.environ["NLS_LANG"] = 'american_america.utf8' | ||
119 | 31 | try: | ||
120 | 32 | import cx_Oracle as oracle | ||
121 | 33 | except Exception, e: | ||
122 | 34 | oracle = dummy | ||
123 | 35 | |||
124 | 36 | from storm.tracer import trace | ||
125 | 37 | from storm.variables import Variable, RawStrVariable, PickleVariable | ||
126 | 38 | from storm.database import Database, Connection, Result | ||
127 | 39 | from storm.exceptions import ( | ||
128 | 40 | install_exceptions, ClosedError, DatabaseModuleError, DatabaseError, | ||
129 | 41 | OperationalError) | ||
130 | 42 | from storm.info import get_cls_info, ClassAlias | ||
131 | 43 | from storm.expr import ( | ||
132 | 44 | Undef, SetExpr, Select, Insert, Alias, And, Eq, FuncExpr, SQLRaw, | ||
133 | 45 | Le, Gt, Column, is_safe_token, Except, Expr, Sequence, SQLToken, | ||
134 | 46 | COLUMN, COLUMN_NAME, COLUMN_PREFIX, TABLE, compile, compile_eq, | ||
135 | 47 | compile_select, compile_insert, compile_set_expr, compile_sql_token, | ||
136 | 48 | State) | ||
137 | 49 | |||
138 | 50 | |||
139 | 51 | install_exceptions(oracle) | ||
140 | 52 | compile = compile.create_child() | ||
141 | 53 | |||
142 | 54 | |||
143 | 55 | RESERVED = set( | ||
144 | 56 | """ACCESS ADD ALL ALTER AND ANY AS ASC AUDIT BETWEEN BY CHAR | ||
145 | 57 | CHECK CLUSTER COLUMN COMMENT COMPRESS CONNECT CREATE CURRENT | ||
146 | 58 | DATE DECIMAL DEFAULT DELETE DESC DISTINCT DROP ELSE EXCLUSIVE | ||
147 | 59 | EXISTS FILE FLOAT FOR FROM GRANT GROUP HAVING IDENTIFIED | ||
148 | 60 | IMMEDIATE IN INCREMENT INDEX INITIAL INSERT INTEGER INTERSECT | ||
149 | 61 | INTO IS LEVEL LIKE LOCK LONG MAXEXTENTS MINUS MLSLABEL MODE | ||
150 | 62 | MODIFY NOAUDIT NOCOMPRESS NOT NOWAIT NULL NUMBER OF OFFLINE | ||
151 | 63 | ON ONLINE OPTION OR ORDER PCTFREE PRIOR PRIVILEGES PUBLIC | ||
152 | 64 | RAW RENAME RESOURCE REVOKE ROW ROWS SELECT SESSION | ||
153 | 65 | SET SHARE SIZE SMALLINT START SUCCESSFUL SYNONYM SYSDATE | ||
154 | 66 | TABLE THEN TO TRIGGER UID UNION UNIQUE UPDATE USER VALIDATE | ||
155 | 67 | VALUES VARCHAR VARCHAR2 VIEW WHENEVER WHERE WITH""".split()) | ||
156 | 68 | |||
157 | 69 | |||
158 | 70 | def alias_names(): | ||
159 | 71 | ct = 0 | ||
160 | 72 | while 1: | ||
161 | 73 | yield "_%x" % ct | ||
162 | 74 | ct += 1 | ||
163 | 75 | |||
164 | 76 | |||
165 | 77 | @compile.when(type) | ||
166 | 78 | def compile_type(compile, expr, state): | ||
167 | 79 | cls_info = get_cls_info(expr) | ||
168 | 80 | table = compile(cls_info.table, state) | ||
169 | 81 | if state.context is TABLE and issubclass(expr, ClassAlias): | ||
170 | 82 | return "%s %s" % (compile(cls_info.cls, state), table) | ||
171 | 83 | return table | ||
172 | 84 | |||
173 | 85 | |||
174 | 86 | @compile.when(Alias) | ||
175 | 87 | def compile_alias(compile, alias, state): | ||
176 | 88 | name = compile(alias.name, state, token=True) | ||
177 | 89 | if state.context is COLUMN or state.context is TABLE: | ||
178 | 90 | return "%s %s" % (compile(alias.expr, state), name) | ||
179 | 91 | return name | ||
180 | 92 | |||
181 | 93 | |||
182 | 94 | @compile.when(Sequence) | ||
183 | 95 | def compile_sequence_oracle(compile, sequence, state): | ||
184 | 96 | return "%s.NEXTVAL" % sequence.name | ||
185 | 97 | |||
186 | 98 | |||
187 | 99 | class Minus(SetExpr): | ||
188 | 100 | oper = " MINUS " | ||
189 | 101 | |||
190 | 102 | |||
191 | 103 | @compile.when(Except) | ||
192 | 104 | def compile_except_oracle(compile, expr, state): | ||
193 | 105 | new_expr = Minus() | ||
194 | 106 | new_expr.exprs = expr.exprs | ||
195 | 107 | new_expr.all = expr.all | ||
196 | 108 | new_expr.order_by = expr.order_by | ||
197 | 109 | new_expr.limit = expr.limit | ||
198 | 110 | new_expr.offset = expr.offset | ||
199 | 111 | return compile_set_expr_oracle(compile, new_expr, state) | ||
200 | 112 | |||
201 | 113 | |||
202 | 114 | @compile.when(SetExpr) | ||
203 | 115 | def compile_set_expr_oracle(compile, expr, state): | ||
204 | 116 | names = alias_names() | ||
205 | 117 | if isinstance(expr, Minus): | ||
206 | 118 | # Build new set expression without arguments (order_by, etc). | ||
207 | 119 | new_expr = expr.__class__() | ||
208 | 120 | new_expr.exprs = expr.exprs | ||
209 | 121 | new_expr.all = expr.all | ||
210 | 122 | |||
211 | 123 | if expr.order_by is not Undef: | ||
212 | 124 | # Make sure that state.aliases isn't None, since we want them to | ||
213 | 125 | # compile our order_by statement below. | ||
214 | 126 | no_aliases = state.aliases is None | ||
215 | 127 | if no_aliases: | ||
216 | 128 | state.push("aliases", {}) | ||
217 | 129 | |||
218 | 130 | aliases = {} | ||
219 | 131 | for subexpr in expr.exprs: | ||
220 | 132 | if isinstance(subexpr, Select): | ||
221 | 133 | columns = subexpr.columns | ||
222 | 134 | if not isinstance(columns, (tuple, list)): | ||
223 | 135 | columns = [columns] | ||
224 | 136 | else: | ||
225 | 137 | columns = list(columns) | ||
226 | 138 | for i, column in enumerate(columns): | ||
227 | 139 | if column not in aliases: | ||
228 | 140 | if isinstance(column, Column): | ||
229 | 141 | aliases[column] = columns[i] = Alias( | ||
230 | 142 | column, name=names.next()) | ||
231 | 143 | elif isinstance(column, Alias): | ||
232 | 144 | aliases[column.expr] = column | ||
233 | 145 | subexpr.columns = columns | ||
234 | 146 | aliases.update(state.aliases) | ||
235 | 147 | state.aliases = aliases | ||
236 | 148 | aliases = None | ||
237 | 149 | |||
238 | 150 | set_statement = SQLRaw("(%s)" % compile(expr.exprs, state, | ||
239 | 151 | join=expr.oper)) | ||
240 | 152 | |||
241 | 153 | if expr.order_by is not Undef: | ||
242 | 154 | # Build order_by statement, using aliases. | ||
243 | 155 | state.push("context", COLUMN_NAME) | ||
244 | 156 | order_by_statement = SQLRaw(compile(expr.order_by, state)) | ||
245 | 157 | state.pop() | ||
246 | 158 | else: | ||
247 | 159 | order_by_statement = Undef | ||
248 | 160 | |||
249 | 161 | # Build wrapping select statement. | ||
250 | 162 | select = Select( | ||
251 | 163 | SQLRaw("*"), tables=Alias(set_statement, name=names.next()), | ||
252 | 164 | limit=expr.limit, offset=expr.offset, order_by=order_by_statement) | ||
253 | 165 | return compile_select(compile, select, state) | ||
254 | 166 | return compile_set_expr(compile, expr, state) | ||
255 | 167 | |||
256 | 168 | |||
257 | 169 | @compile.when(Select) | ||
258 | 170 | def compile_select_oracle(compile, select, state): | ||
259 | 171 | limit = select.limit | ||
260 | 172 | offset = select.offset | ||
261 | 173 | # Make sure limit is Undef'ed. | ||
262 | 174 | select.offset = select.limit = Undef | ||
263 | 175 | |||
264 | 176 | names = alias_names() | ||
265 | 177 | |||
266 | 178 | if select.default_tables is Undef: | ||
267 | 179 | select.default_tables = ["DUAL"] | ||
268 | 180 | |||
269 | 181 | if select.order_by is not Undef: | ||
270 | 182 | # Copied from expr.py's compile_set_expr. | ||
271 | 183 | aliases = {} | ||
272 | 184 | columns = select.columns | ||
273 | 185 | if not isinstance(columns, (tuple, list)): | ||
274 | 186 | columns = [columns] | ||
275 | 187 | else: | ||
276 | 188 | columns = list(columns) | ||
277 | 189 | for i, column in enumerate(columns): | ||
278 | 190 | if column not in aliases: | ||
279 | 191 | if isinstance(column, Column): | ||
280 | 192 | aliases[column] = columns[i] = Alias( | ||
281 | 193 | column, name=names.next()) | ||
282 | 194 | elif isinstance(column, Alias): | ||
283 | 195 | aliases[column.expr] = column | ||
284 | 196 | select.columns = columns | ||
285 | 197 | # Copied from expr.py's compile_set_expr. | ||
286 | 198 | statement = SQLRaw("(%s)" % compile_select(compile, select, state)) | ||
287 | 199 | select = Select(SQLRaw("*"), tables=Alias(statement, | ||
288 | 200 | name=names.next())) | ||
289 | 201 | |||
290 | 202 | if (limit is not Undef) and (offset is not Undef): | ||
291 | 203 | rownum_alias = Alias(SQLRaw("ROWNUM"), name=names.next()) | ||
292 | 204 | |||
293 | 205 | # If we have an SQLRaw here that is because we are dealing with a | ||
294 | 206 | # subquery. | ||
295 | 207 | if isinstance(select.columns, SQLRaw): | ||
296 | 208 | select.columns = [SQLRaw('"' + select.tables.name + '".*'), | ||
297 | 209 | rownum_alias] | ||
298 | 210 | else: | ||
299 | 211 | select.columns.append(rownum_alias) | ||
300 | 212 | |||
301 | 213 | where_expr = Le(SQLRaw("ROWNUM"), limit + offset) | ||
302 | 214 | if select.where is Undef: | ||
303 | 215 | select.where = where_expr | ||
304 | 216 | else: | ||
305 | 217 | select.where = And(select.where, where_expr) | ||
306 | 218 | |||
307 | 219 | statement = SQLRaw("(%s)" % compile_select(compile, select, state)) | ||
308 | 220 | select = Select(SQLRaw("*"), tables=Alias(statement, names.next()), | ||
309 | 221 | where=Gt(rownum_alias, offset)) | ||
310 | 222 | elif limit is not Undef: | ||
311 | 223 | expr = Le(SQLRaw("ROWNUM"), limit) | ||
312 | 224 | if select.where is Undef: | ||
313 | 225 | select.where = expr | ||
314 | 226 | else: | ||
315 | 227 | select.where = And(select.where, expr) | ||
316 | 228 | elif offset is not Undef: | ||
317 | 229 | rownum_alias = Alias(SQLRaw("ROWNUM"), name=names.next()) | ||
318 | 230 | |||
319 | 231 | # If we have an SQLRaw here that is because we are dealing with a | ||
320 | 232 | # subquery. | ||
321 | 233 | if isinstance(select.columns, SQLRaw): | ||
322 | 234 | select.columns = [SQLRaw('"' + select.tables.name + '".*'), | ||
323 | 235 | rownum_alias] | ||
324 | 236 | else: | ||
325 | 237 | select.columns.append(rownum_alias) | ||
326 | 238 | |||
327 | 239 | statement = SQLRaw("(%s)" % compile_select(compile, select, state)) | ||
328 | 240 | select = Select(SQLRaw("*"), tables=Alias(statement, | ||
329 | 241 | name=names.next()), | ||
330 | 242 | where=Gt(rownum_alias, offset)) | ||
331 | 243 | |||
332 | 244 | return compile_select(compile, select, state) | ||
333 | 245 | |||
334 | 246 | |||
335 | 247 | @compile.when(Insert) | ||
336 | 248 | def compile_insert_oracle(compile, insert, state): | ||
337 | 249 | # Shamelessly copied from PostgreSQL. | ||
338 | 250 | if not insert.map and insert.primary_columns is not Undef: | ||
339 | 251 | insert.map.update(dict.fromkeys(insert.primary_columns, | ||
340 | 252 | SQLRaw("DEFAULT"))) | ||
341 | 253 | return compile_insert(compile, insert, state) | ||
342 | 254 | |||
343 | 255 | |||
344 | 256 | @compile.when(Sequence) | ||
345 | 257 | def compile_sequence_oracle(compile, sequence, state): | ||
346 | 258 | return "%s.nextval" % sequence.name | ||
347 | 259 | |||
348 | 260 | |||
349 | 261 | @compile.when(bool) | ||
350 | 262 | def compile_bool(compile, expr, state): | ||
351 | 263 | return compile_eq(compile, Eq(1, int(expr)), state) | ||
352 | 264 | |||
353 | 265 | |||
354 | 266 | class currval(FuncExpr): | ||
355 | 267 | |||
356 | 268 | name = "currval" | ||
357 | 269 | |||
358 | 270 | def __init__(self, column): | ||
359 | 271 | self.column = column | ||
360 | 272 | |||
361 | 273 | @compile.when(currval) | ||
362 | 274 | def compile_currval(compile, expr, state): | ||
363 | 275 | """Compile a L{currval}.""" | ||
364 | 276 | state.push("context", COLUMN_PREFIX) | ||
365 | 277 | table = compile(expr.column.table, state, token=True) | ||
366 | 278 | state.pop() | ||
367 | 279 | return "%s_seq.currval" % (table) | ||
368 | 280 | |||
369 | 281 | |||
370 | 282 | class Rowid(Expr): | ||
371 | 283 | |||
372 | 284 | def __init__(self, rowid): | ||
373 | 285 | self.rowid = rowid | ||
374 | 286 | |||
375 | 287 | @compile.when(Rowid) | ||
376 | 288 | def compile_rowid(compile, expr, state): | ||
377 | 289 | state.parameters.append(expr.rowid) | ||
378 | 290 | return "?" | ||
379 | 291 | |||
380 | 292 | |||
381 | 293 | @compile.when(SQLToken) | ||
382 | 294 | def compile_oracle_sql_token(compile, expr, state): | ||
383 | 295 | if "." in expr and state.context in (TABLE, COLUMN_PREFIX): | ||
384 | 296 | return ".".join(compile_sql_token(compile, subexpr, state) | ||
385 | 297 | for subexpr in expr.split(".")) | ||
386 | 298 | # A bit of a hack: we're skipping the built-in reserved word list and | ||
387 | 299 | # using our own set. | ||
388 | 300 | if is_safe_token(expr) and not str(expr) in RESERVED: | ||
389 | 301 | return expr | ||
390 | 302 | elif (state.context in (COLUMN, COLUMN_NAME) | ||
391 | 303 | and str(expr).lower() in ("rowid", "rownum")): | ||
392 | 304 | return expr | ||
393 | 305 | return '"%s"' % expr.replace('"', "|") | ||
394 | 306 | |||
395 | 307 | |||
396 | 308 | class OracleResult(Result): | ||
397 | 309 | |||
398 | 310 | def __init__(self, connection, raw_cursor, rowid = None): | ||
399 | 311 | super(OracleResult, self).__init__(connection, raw_cursor) | ||
400 | 312 | self.lastrowid = rowid | ||
401 | 313 | |||
402 | 314 | def get_insert_identity(self, primary_key, primary_variables): | ||
403 | 315 | return Eq(Column("rowid"), Rowid(self.lastrowid)) | ||
404 | 316 | |||
405 | 317 | @staticmethod | ||
406 | 318 | def set_variable(variable, value): | ||
407 | 319 | if isinstance(value, float): | ||
408 | 320 | value = Decimal(str(value)) | ||
409 | 321 | |||
410 | 322 | variable.set(value, from_db=True) | ||
411 | 323 | |||
412 | 324 | @staticmethod | ||
413 | 325 | def from_database(row): | ||
414 | 326 | """Convert Oracle-specific datatypes to 'normal' Python types. | ||
415 | 327 | |||
416 | 328 | If there are anny C{buffer} instances in the row, convert them | ||
417 | 329 | to strings. | ||
418 | 330 | """ | ||
419 | 331 | for value in row: | ||
420 | 332 | if isinstance(value, oracle.LOB): | ||
421 | 333 | yield value.read() | ||
422 | 334 | else: | ||
423 | 335 | yield value | ||
424 | 336 | |||
425 | 337 | |||
426 | 338 | class OracleConnection(Connection): | ||
427 | 339 | |||
428 | 340 | result_factory = OracleResult | ||
429 | 341 | compile = compile | ||
430 | 342 | param_mark = "?" | ||
431 | 343 | |||
432 | 344 | def as_read_committed(self): | ||
433 | 345 | return _isolation_context(self) | ||
434 | 346 | |||
435 | 347 | def is_disconnection_error(self, exc): | ||
436 | 348 | if isinstance(exc, (oracle.OperationalError, oracle.DatabaseError)): | ||
437 | 349 | error, = exc.args | ||
438 | 350 | # Sometimes exceptions get caught here without a code attribute. | ||
439 | 351 | if hasattr(error, "code") and error.code in (3135, 3113): | ||
440 | 352 | return True | ||
441 | 353 | return False | ||
442 | 354 | |||
443 | 355 | def execute(self, statement, params=None, noresult=False): | ||
444 | 356 | """Execute a statement with the given parameters. | ||
445 | 357 | |||
446 | 358 | This method is completely overidden because the original from the base | ||
447 | 359 | class expects to receive only a C{raw_cursor} from C{raw_execute}, and | ||
448 | 360 | we need to receive also the C{rowid}, as we cannot set it in the | ||
449 | 361 | cursor object. | ||
450 | 362 | |||
451 | 363 | @type statement: L{Expr} or C{str}. | ||
452 | 364 | @param statement: The statement to execute. It will be compiled if | ||
453 | 365 | necessary. | ||
454 | 366 | @param noresult: If True, no result will be returned. | ||
455 | 367 | @raise DisconnectionError: Raised when the connection is lost. | ||
456 | 368 | Reconnection happens automatically on rollback. | ||
457 | 369 | @return: The result of C{self.result_factory}, or None if C{noresult} | ||
458 | 370 | is True. | ||
459 | 371 | """ | ||
460 | 372 | if self._closed: | ||
461 | 373 | raise ClosedError("Connection is closed") | ||
462 | 374 | self._ensure_connected() | ||
463 | 375 | if self._event: | ||
464 | 376 | self._event.emit("register-transaction") | ||
465 | 377 | if isinstance(statement, Expr): | ||
466 | 378 | if params is not None: | ||
467 | 379 | raise ValueError("Can't pass parameters with expressions") | ||
468 | 380 | state = State() | ||
469 | 381 | statement = self.compile(statement, state) | ||
470 | 382 | params = state.parameters | ||
471 | 383 | statement = convert_to_sequential(statement) | ||
472 | 384 | raw_cursor, rowid = self.raw_execute(statement, params) | ||
473 | 385 | if noresult: | ||
474 | 386 | self._check_disconnect(raw_cursor.close) | ||
475 | 387 | return None | ||
476 | 388 | return self.result_factory(self, raw_cursor, rowid) | ||
477 | 389 | |||
478 | 390 | def raw_execute(self, statement, params): | ||
479 | 391 | """Execute a raw statement with the given parameters. | ||
480 | 392 | |||
481 | 393 | This method is completely overidden because the original from the base | ||
482 | 394 | class converts params to a tuple, and we need a dictionary! It's | ||
483 | 395 | acceptable to override this method in subclasses, but it is not | ||
484 | 396 | intended to be called externally. If the global C{DEBUG} flag is | ||
485 | 397 | True, the statement will be printed to standard out. | ||
486 | 398 | |||
487 | 399 | @return: The DBAPI cursor object, as fetched from L{build_raw_cursor}. | ||
488 | 400 | """ | ||
489 | 401 | rowid = None | ||
490 | 402 | raw_cursor = self.build_raw_cursor() | ||
491 | 403 | |||
492 | 404 | statement = str(statement) | ||
493 | 405 | |||
494 | 406 | if statement.startswith("INSERT INTO"): | ||
495 | 407 | statement = statement + " RETURNING ROWID INTO :out_rowid" | ||
496 | 408 | |||
497 | 409 | # Make sure params is a list as we need to add to it. | ||
498 | 410 | if params is None: | ||
499 | 411 | params = [] | ||
500 | 412 | elif not isinstance(params, list): | ||
501 | 413 | params = list(params) | ||
502 | 414 | |||
503 | 415 | rowid = raw_cursor.var(oracle.ROWID) | ||
504 | 416 | params.append(rowid) | ||
505 | 417 | |||
506 | 418 | if not params: | ||
507 | 419 | params = () | ||
508 | 420 | else: | ||
509 | 421 | params = tuple(self.to_database(params)) | ||
510 | 422 | |||
511 | 423 | trace("connection_raw_execute", self, raw_cursor, statement, | ||
512 | 424 | params or ()) | ||
513 | 425 | |||
514 | 426 | try: | ||
515 | 427 | self._check_disconnect(raw_cursor.execute, statement, params) | ||
516 | 428 | if rowid: | ||
517 | 429 | rowid = rowid.getvalue() | ||
518 | 430 | except DatabaseError, de: | ||
519 | 431 | error, = de.args | ||
520 | 432 | if error == 8177: | ||
521 | 433 | raise OperationalError("database is locked") | ||
522 | 434 | else: | ||
523 | 435 | raise | ||
524 | 436 | except Exception, error: | ||
525 | 437 | trace("connection_raw_execute_error", self, raw_cursor, | ||
526 | 438 | statement, params or (), error) | ||
527 | 439 | raise | ||
528 | 440 | else: | ||
529 | 441 | trace("connection_raw_execute_success", self, raw_cursor, | ||
530 | 442 | statement, params or ()) | ||
531 | 443 | return raw_cursor, rowid | ||
532 | 444 | |||
533 | 445 | @staticmethod | ||
534 | 446 | def to_database(params): | ||
535 | 447 | for bind_var in params: | ||
536 | 448 | if isinstance(bind_var, (RawStrVariable, PickleVariable)): | ||
537 | 449 | yield oracle.Binary(bind_var.get(to_db=True)) | ||
538 | 450 | elif isinstance(bind_var, Variable): | ||
539 | 451 | yield bind_var.get(to_db=True) | ||
540 | 452 | else: | ||
541 | 453 | yield bind_var | ||
542 | 454 | |||
543 | 455 | |||
544 | 456 | def convert_to_sequential(statement): | ||
545 | 457 | """Convert a query using ? bind variables to a query using | ||
546 | 458 | sequential bind variables. For example, SELECT ? FROM DUAL | ||
547 | 459 | will be converted to SELECT :1 FROM DUAL""" | ||
548 | 460 | param_no = 1 | ||
549 | 461 | tokens = statement.split("'") | ||
550 | 462 | for i in range(0, len(tokens), 2): | ||
551 | 463 | while True: | ||
552 | 464 | old_tokens = tokens[i] | ||
553 | 465 | new_tokens = old_tokens.replace('?', ':%s' % param_no, 1) | ||
554 | 466 | if old_tokens == new_tokens: | ||
555 | 467 | break | ||
556 | 468 | else: | ||
557 | 469 | tokens[i] = new_tokens | ||
558 | 470 | param_no += 1 | ||
559 | 471 | return "'".join(tokens) | ||
560 | 472 | |||
561 | 473 | |||
562 | 474 | class _isolation_context(object): | ||
563 | 475 | |||
564 | 476 | def __init__ (self, connection): | ||
565 | 477 | self.connection = connection | ||
566 | 478 | |||
567 | 479 | def __enter__(self): | ||
568 | 480 | self.connection.commit() | ||
569 | 481 | self.connection.execute( | ||
570 | 482 | "ALTER SESSION SET isolation_level = read committed") | ||
571 | 483 | |||
572 | 484 | def __exit__(self, exc_type, exc_value, traceback): | ||
573 | 485 | if exc_value: | ||
574 | 486 | self.connection.rollback() | ||
575 | 487 | else: | ||
576 | 488 | self.connection.commit() | ||
577 | 489 | self.connection.execute( | ||
578 | 490 | "ALTER SESSION SET isolation_level = serializable") | ||
579 | 491 | self.connection.commit() | ||
580 | 492 | |||
581 | 493 | |||
582 | 494 | class _type_converter(object): | ||
583 | 495 | """This class wraps an input type handler and an output type handler. | ||
584 | 496 | The reason being to allow for saving the encoding for the session.""" | ||
585 | 497 | def __init__(self, encoding): | ||
586 | 498 | self.encoding = encoding | ||
587 | 499 | |||
588 | 500 | def convert_unicode(self, value): | ||
589 | 501 | if isinstance(value, Variable): | ||
590 | 502 | value = value.get(to_db=True) | ||
591 | 503 | if not isinstance(value, unicode): | ||
592 | 504 | return unicode(value, "utf8") | ||
593 | 505 | else: | ||
594 | 506 | return value | ||
595 | 507 | |||
596 | 508 | def OutputTypeHandler(self, cursor, name, defaultType, size, precision, | ||
597 | 509 | scale): | ||
598 | 510 | """Prepare cx_Oracle to turn an Oracle datatype into a Python datatype. | ||
599 | 511 | |||
600 | 512 | This function's purpose is to tell cx_Oracle how to convert an Oracle | ||
601 | 513 | datatype into a Python datatype. | ||
602 | 514 | """ | ||
603 | 515 | if defaultType in (oracle.UNICODE, oracle.FIXED_UNICODE): | ||
604 | 516 | return cursor.var(unicode, size, cursor.arraysize, | ||
605 | 517 | outconverter=self.convert_unicode) | ||
606 | 518 | elif defaultType in (oracle.STRING, oracle.FIXED_CHAR): | ||
607 | 519 | return cursor.var(str, size, cursor.arraysize) | ||
608 | 520 | |||
609 | 521 | def InputTypeHandler(self, cursor, value, numElements): | ||
610 | 522 | """Prepare cx_Oracle to turn a Python datatype into an Oracle datatype. | ||
611 | 523 | |||
612 | 524 | This function does the opposite of OutputTypeHandler: it tells | ||
613 | 525 | cx_Oracle how to convert a Python datatype into an Oracle datatype. | ||
614 | 526 | """ | ||
615 | 527 | if isinstance(value, unicode): | ||
616 | 528 | return cursor.var(unicode, arraysize=numElements, | ||
617 | 529 | inconverter=self.convert_unicode) | ||
618 | 530 | |||
619 | 531 | class Oracle(Database): | ||
620 | 532 | |||
621 | 533 | connection_factory = OracleConnection | ||
622 | 534 | converter_factory = _type_converter | ||
623 | 535 | raw_connection_factory = oracle.connect | ||
624 | 536 | |||
625 | 537 | def __init__(self, uri): | ||
626 | 538 | if oracle is dummy: | ||
627 | 539 | raise DatabaseModuleError("'cx_Oracle' failed to load") | ||
628 | 540 | |||
629 | 541 | if not uri.port: | ||
630 | 542 | uri.port = 1521 | ||
631 | 543 | |||
632 | 544 | isolation = uri.options.get("isolation", "serializable") | ||
633 | 545 | isolation_mapping = { | ||
634 | 546 | "serializable": "SERIALIZABLE", | ||
635 | 547 | "read-committed": "READ COMMITTED", | ||
636 | 548 | } | ||
637 | 549 | try: | ||
638 | 550 | self._isolation = isolation_mapping[isolation] | ||
639 | 551 | except KeyError: | ||
640 | 552 | raise ValueError( | ||
641 | 553 | "Unknown serialization level %r: expected one of " | ||
642 | 554 | "'serializable' or 'read-committed'" % | ||
643 | 555 | (isolation,)) | ||
644 | 556 | |||
645 | 557 | # Optionally set ORACLE_HOME and TNS_ADMIN environment | ||
646 | 558 | # variables for controlling tnsnames.ora lookup | ||
647 | 559 | oracle_home = uri.options.get('oracle_home') | ||
648 | 560 | if oracle_home: | ||
649 | 561 | os.environ["ORACLE_HOME"] = oracle_home | ||
650 | 562 | |||
651 | 563 | # If tns is specified in the options, treat the host part of the URI as | ||
652 | 564 | # the TNS name, and therefore the DSN. | ||
653 | 565 | if uri.options.get("tns", False): | ||
654 | 566 | self._dsn = uri.host | ||
655 | 567 | else: | ||
656 | 568 | self._dsn = oracle.makedsn(uri.host, uri.port, uri.database) | ||
657 | 569 | |||
658 | 570 | self._username = uri.username | ||
659 | 571 | self._password = uri.password | ||
660 | 572 | |||
661 | 573 | def raw_connect(self): | ||
662 | 574 | if oracle is dummy: | ||
663 | 575 | raise ImportError, "Could not import cx_Oracle" | ||
664 | 576 | |||
665 | 577 | raw_connection = self.raw_connection_factory(self._username, | ||
666 | 578 | self._password, self._dsn) | ||
667 | 579 | |||
668 | 580 | c = raw_connection.cursor() | ||
669 | 581 | c.execute("alter session set isolation_level=%s" % self._isolation) | ||
670 | 582 | c.close() | ||
671 | 583 | |||
672 | 584 | type_handler = self.converter_factory(raw_connection.nencoding) | ||
673 | 585 | raw_connection.inputtypehandler = type_handler.InputTypeHandler | ||
674 | 586 | raw_connection.outputtypehandler = type_handler.OutputTypeHandler | ||
675 | 587 | |||
676 | 588 | return raw_connection | ||
677 | 589 | |||
678 | 590 | create_from_uri = Oracle | ||
679 | 0 | 591 | ||
680 | === modified file 'storm/expr.py' | |||
681 | --- storm/expr.py 2009-11-02 11:11:20 +0000 | |||
682 | +++ storm/expr.py 2010-03-15 15:48:27 +0000 | |||
683 | @@ -1000,7 +1000,7 @@ | |||
684 | 1000 | return "%s = %s" % (compile(eq.expr1, state), compile(eq.expr2, state)) | 1000 | return "%s = %s" % (compile(eq.expr1, state), compile(eq.expr2, state)) |
685 | 1001 | 1001 | ||
686 | 1002 | @compile_python.when(Eq) | 1002 | @compile_python.when(Eq) |
688 | 1003 | def compile_eq(compile, eq, state): | 1003 | def compile_python_eq(compile, eq, state): |
689 | 1004 | return "%s == %s" % (compile(eq.expr1, state), compile(eq.expr2, state)) | 1004 | return "%s == %s" % (compile(eq.expr1, state), compile(eq.expr2, state)) |
690 | 1005 | 1005 | ||
691 | 1006 | 1006 | ||
692 | 1007 | 1007 | ||
693 | === modified file 'storm/variables.py' | |||
694 | --- storm/variables.py 2009-10-03 16:08:04 +0000 | |||
695 | +++ storm/variables.py 2010-03-15 15:48:27 +0000 | |||
696 | @@ -436,6 +436,8 @@ | |||
697 | 436 | if from_db: | 436 | if from_db: |
698 | 437 | if value is None: | 437 | if value is None: |
699 | 438 | return None | 438 | return None |
700 | 439 | if isinstance(value, datetime): | ||
701 | 440 | return value.date() | ||
702 | 439 | if isinstance(value, date): | 441 | if isinstance(value, date): |
703 | 440 | return value | 442 | return value |
704 | 441 | if not isinstance(value, (str, unicode)): | 443 | if not isinstance(value, (str, unicode)): |
705 | 442 | 444 | ||
706 | === modified file 'tests/databases/base.py' | |||
707 | --- tests/databases/base.py 2009-09-19 15:56:13 +0000 | |||
708 | +++ tests/databases/base.py 2010-03-15 15:48:27 +0000 | |||
709 | @@ -75,7 +75,7 @@ | |||
710 | 75 | raise NotImplementedError | 75 | raise NotImplementedError |
711 | 76 | 76 | ||
712 | 77 | def create_sample_data(self): | 77 | def create_sample_data(self): |
714 | 78 | self.connection.execute("INSERT INTO number VALUES (1, 2, 3)") | 78 | self.connection.execute("INSERT INTO number_table VALUES (1, 2, 3)") |
715 | 79 | self.connection.execute("INSERT INTO test VALUES (10, 'Title 10')") | 79 | self.connection.execute("INSERT INTO test VALUES (10, 'Title 10')") |
716 | 80 | self.connection.execute("INSERT INTO test VALUES (20, 'Title 20')") | 80 | self.connection.execute("INSERT INTO test VALUES (20, 'Title 20')") |
717 | 81 | self.connection.commit() | 81 | self.connection.commit() |
718 | @@ -84,7 +84,7 @@ | |||
719 | 84 | pass | 84 | pass |
720 | 85 | 85 | ||
721 | 86 | def drop_tables(self): | 86 | def drop_tables(self): |
723 | 87 | for table in ["number", "test", "datetime_test", "bin_test"]: | 87 | for table in ["number_table", "test", "datetime_test", "bin_test"]: |
724 | 88 | try: | 88 | try: |
725 | 89 | self.connection.execute("DROP TABLE " + table) | 89 | self.connection.execute("DROP TABLE " + table) |
726 | 90 | self.connection.commit() | 90 | self.connection.commit() |
727 | @@ -131,7 +131,7 @@ | |||
728 | 131 | self.assertTrue(result.get_one()) | 131 | self.assertTrue(result.get_one()) |
729 | 132 | 132 | ||
730 | 133 | def test_execute_result(self): | 133 | def test_execute_result(self): |
732 | 134 | result = self.connection.execute("SELECT 1") | 134 | result = self.connection.execute(Select(1)) |
733 | 135 | self.assertTrue(isinstance(result, Result)) | 135 | self.assertTrue(isinstance(result, Result)) |
734 | 136 | self.assertTrue(result.get_one()) | 136 | self.assertTrue(result.get_one()) |
735 | 137 | 137 | ||
736 | @@ -143,15 +143,15 @@ | |||
737 | 143 | self.assertTrue(isinstance(row[0], unicode)) | 143 | self.assertTrue(isinstance(row[0], unicode)) |
738 | 144 | 144 | ||
739 | 145 | def test_execute_params(self): | 145 | def test_execute_params(self): |
741 | 146 | result = self.connection.execute("SELECT one FROM number " | 146 | result = self.connection.execute("SELECT one FROM number_table " |
742 | 147 | "WHERE 1=?", (1,)) | 147 | "WHERE 1=?", (1,)) |
743 | 148 | self.assertTrue(result.get_one()) | 148 | self.assertTrue(result.get_one()) |
745 | 149 | result = self.connection.execute("SELECT one FROM number " | 149 | result = self.connection.execute("SELECT one FROM number_table " |
746 | 150 | "WHERE 1=?", (2,)) | 150 | "WHERE 1=?", (2,)) |
747 | 151 | self.assertFalse(result.get_one()) | 151 | self.assertFalse(result.get_one()) |
748 | 152 | 152 | ||
749 | 153 | def test_execute_empty_params(self): | 153 | def test_execute_empty_params(self): |
751 | 154 | result = self.connection.execute("SELECT one FROM number", ()) | 154 | result = self.connection.execute("SELECT one FROM number_table", ()) |
752 | 155 | self.assertTrue(result.get_one()) | 155 | self.assertTrue(result.get_one()) |
753 | 156 | 156 | ||
754 | 157 | def test_execute_expression(self): | 157 | def test_execute_expression(self): |
755 | @@ -356,7 +356,7 @@ | |||
756 | 356 | event.hook("register-transaction", register_transaction) | 356 | event.hook("register-transaction", register_transaction) |
757 | 357 | 357 | ||
758 | 358 | connection = self.database.connect(event) | 358 | connection = self.database.connect(event) |
760 | 359 | connection.execute("SELECT 1") | 359 | connection.execute(Select(1)) |
761 | 360 | self.assertEqual(len(calls), 1) | 360 | self.assertEqual(len(calls), 1) |
762 | 361 | self.assertEqual(calls[0], marker) | 361 | self.assertEqual(calls[0], marker) |
763 | 362 | 362 | ||
764 | @@ -364,17 +364,17 @@ | |||
765 | 364 | return [int(item)+1 for item in row] | 364 | return [int(item)+1 for item in row] |
766 | 365 | 365 | ||
767 | 366 | def test_wb_result_get_one_goes_through_from_database(self): | 366 | def test_wb_result_get_one_goes_through_from_database(self): |
769 | 367 | result = self.connection.execute("SELECT one, two FROM number") | 367 | result = self.connection.execute("SELECT one, two FROM number_table") |
770 | 368 | result.from_database = self.from_database | 368 | result.from_database = self.from_database |
771 | 369 | self.assertEquals(result.get_one(), (2, 3)) | 369 | self.assertEquals(result.get_one(), (2, 3)) |
772 | 370 | 370 | ||
773 | 371 | def test_wb_result_get_all_goes_through_from_database(self): | 371 | def test_wb_result_get_all_goes_through_from_database(self): |
775 | 372 | result = self.connection.execute("SELECT one, two FROM number") | 372 | result = self.connection.execute("SELECT one, two FROM number_table") |
776 | 373 | result.from_database = self.from_database | 373 | result.from_database = self.from_database |
777 | 374 | self.assertEquals(result.get_all(), [(2, 3)]) | 374 | self.assertEquals(result.get_all(), [(2, 3)]) |
778 | 375 | 375 | ||
779 | 376 | def test_wb_result_iter_goes_through_from_database(self): | 376 | def test_wb_result_iter_goes_through_from_database(self): |
781 | 377 | result = self.connection.execute("SELECT one, two FROM number") | 377 | result = self.connection.execute("SELECT one, two FROM number_table") |
782 | 378 | result.from_database = self.from_database | 378 | result.from_database = self.from_database |
783 | 379 | self.assertEquals(iter(result).next(), (2, 3)) | 379 | self.assertEquals(iter(result).next(), (2, 3)) |
784 | 380 | 380 | ||
785 | @@ -505,6 +505,7 @@ | |||
786 | 505 | "%s.default_port" % (type(self).__name__, | 505 | "%s.default_port" % (type(self).__name__, |
787 | 506 | type(self).__name__)) | 506 | type(self).__name__)) |
788 | 507 | uri_str = os.environ.get(self.host_environment_variable) | 507 | uri_str = os.environ.get(self.host_environment_variable) |
789 | 508 | |||
790 | 508 | if uri_str: | 509 | if uri_str: |
791 | 509 | uri = URI(uri_str) | 510 | uri = URI(uri_str) |
792 | 510 | if not uri.host: | 511 | if not uri.host: |
793 | @@ -542,24 +543,24 @@ | |||
794 | 542 | 543 | ||
795 | 543 | def test_proxy_works(self): | 544 | def test_proxy_works(self): |
796 | 544 | """Ensure that we can talk to the database through the proxy.""" | 545 | """Ensure that we can talk to the database through the proxy.""" |
798 | 545 | result = self.connection.execute("SELECT 1") | 546 | result = self.connection.execute(Select(1)) |
799 | 546 | self.assertEqual(result.get_one(), (1,)) | 547 | self.assertEqual(result.get_one(), (1,)) |
800 | 547 | 548 | ||
801 | 548 | def test_catch_disconnect_on_execute(self): | 549 | def test_catch_disconnect_on_execute(self): |
802 | 549 | """Test that database disconnections get caught on execute().""" | 550 | """Test that database disconnections get caught on execute().""" |
804 | 550 | result = self.connection.execute("SELECT 1") | 551 | result = self.connection.execute(Select(1)) |
805 | 551 | self.assertTrue(result.get_one()) | 552 | self.assertTrue(result.get_one()) |
806 | 552 | self.proxy.restart() | 553 | self.proxy.restart() |
807 | 553 | self.assertRaises(DisconnectionError, | 554 | self.assertRaises(DisconnectionError, |
809 | 554 | self.connection.execute, "SELECT 1") | 555 | self.connection.execute, Select(1)) |
810 | 555 | 556 | ||
811 | 556 | def test_catch_disconnect_on_commit(self): | 557 | def test_catch_disconnect_on_commit(self): |
812 | 557 | """Test that database disconnections get caught on commit().""" | 558 | """Test that database disconnections get caught on commit().""" |
814 | 558 | result = self.connection.execute("SELECT 1") | 559 | result = self.connection.execute(Select(1)) |
815 | 559 | self.assertTrue(result.get_one()) | 560 | self.assertTrue(result.get_one()) |
816 | 560 | self.proxy.restart() | 561 | self.proxy.restart() |
817 | 561 | self.assertRaises(DisconnectionError, self.connection.commit) | 562 | self.assertRaises(DisconnectionError, self.connection.commit) |
819 | 562 | 563 | ||
820 | 563 | def test_wb_catch_already_disconnected_on_rollback(self): | 564 | def test_wb_catch_already_disconnected_on_rollback(self): |
821 | 564 | """Connection.rollback() swallows disconnection errors. | 565 | """Connection.rollback() swallows disconnection errors. |
822 | 565 | 566 | ||
823 | @@ -567,13 +568,13 @@ | |||
824 | 567 | then it is possible that Storm won't see the disconnection. | 568 | then it is possible that Storm won't see the disconnection. |
825 | 568 | It should be able to recover from this situation though. | 569 | It should be able to recover from this situation though. |
826 | 569 | """ | 570 | """ |
828 | 570 | result = self.connection.execute("SELECT 1") | 571 | result = self.connection.execute(Select(1)) |
829 | 571 | self.assertTrue(result.get_one()) | 572 | self.assertTrue(result.get_one()) |
830 | 572 | self.proxy.restart() | 573 | self.proxy.restart() |
831 | 573 | # Perform an action that should result in a disconnection. | 574 | # Perform an action that should result in a disconnection. |
832 | 574 | try: | 575 | try: |
833 | 575 | cursor = self.connection._raw_connection.cursor() | 576 | cursor = self.connection._raw_connection.cursor() |
835 | 576 | cursor.execute("SELECT 1") | 577 | cursor.execute(Select(1)) |
836 | 577 | cursor.fetchone() | 578 | cursor.fetchone() |
837 | 578 | except Error, exc: | 579 | except Error, exc: |
838 | 579 | self.assertTrue(self.connection.is_disconnection_error(exc)) | 580 | self.assertTrue(self.connection.is_disconnection_error(exc)) |
839 | @@ -600,59 +601,59 @@ | |||
840 | 600 | then it is possible that Storm won't see the disconnection. | 601 | then it is possible that Storm won't see the disconnection. |
841 | 601 | It should be able to recover from this situation though. | 602 | It should be able to recover from this situation though. |
842 | 602 | """ | 603 | """ |
844 | 603 | result = self.connection.execute("SELECT 1") | 604 | result = self.connection.execute(Select(1)) |
845 | 604 | self.assertTrue(result.get_one()) | 605 | self.assertTrue(result.get_one()) |
846 | 605 | self.proxy.restart() | 606 | self.proxy.restart() |
847 | 606 | # Perform an action that should result in a disconnection. | 607 | # Perform an action that should result in a disconnection. |
848 | 607 | try: | 608 | try: |
849 | 608 | cursor = self.connection._raw_connection.cursor() | 609 | cursor = self.connection._raw_connection.cursor() |
851 | 609 | cursor.execute("SELECT 1") | 610 | cursor.execute(Select(1)) |
852 | 610 | cursor.fetchone() | 611 | cursor.fetchone() |
853 | 611 | except DatabaseError, exc: | 612 | except DatabaseError, exc: |
854 | 612 | self.assertTrue(self.connection.is_disconnection_error(exc)) | 613 | self.assertTrue(self.connection.is_disconnection_error(exc)) |
855 | 613 | else: | 614 | else: |
856 | 614 | self.fail("Disconnection was not caught.") | 615 | self.fail("Disconnection was not caught.") |
857 | 615 | self.assertRaises(DisconnectionError, | 616 | self.assertRaises(DisconnectionError, |
859 | 616 | self.connection.execute, "SELECT 1") | 617 | self.connection.execute, Select(1)) |
860 | 617 | 618 | ||
861 | 618 | def test_connection_stays_disconnected_in_transaction(self): | 619 | def test_connection_stays_disconnected_in_transaction(self): |
862 | 619 | """Test that connection does not immediately reconnect.""" | 620 | """Test that connection does not immediately reconnect.""" |
864 | 620 | result = self.connection.execute("SELECT 1") | 621 | result = self.connection.execute(Select(1)) |
865 | 621 | self.assertTrue(result.get_one()) | 622 | self.assertTrue(result.get_one()) |
866 | 622 | self.proxy.restart() | 623 | self.proxy.restart() |
867 | 623 | self.assertRaises(DisconnectionError, | 624 | self.assertRaises(DisconnectionError, |
869 | 624 | self.connection.execute, "SELECT 1") | 625 | self.connection.execute, Select(1)) |
870 | 625 | self.assertRaises(DisconnectionError, | 626 | self.assertRaises(DisconnectionError, |
872 | 626 | self.connection.execute, "SELECT 1") | 627 | self.connection.execute, Select(1)) |
873 | 627 | 628 | ||
874 | 628 | def test_reconnect_after_rollback(self): | 629 | def test_reconnect_after_rollback(self): |
875 | 629 | """Test that we reconnect after rolling back the connection.""" | 630 | """Test that we reconnect after rolling back the connection.""" |
877 | 630 | result = self.connection.execute("SELECT 1") | 631 | result = self.connection.execute(Select(1)) |
878 | 631 | self.assertTrue(result.get_one()) | 632 | self.assertTrue(result.get_one()) |
879 | 632 | self.proxy.restart() | 633 | self.proxy.restart() |
880 | 633 | self.assertRaises(DisconnectionError, | 634 | self.assertRaises(DisconnectionError, |
882 | 634 | self.connection.execute, "SELECT 1") | 635 | self.connection.execute, Select(1)) |
883 | 635 | self.connection.rollback() | 636 | self.connection.rollback() |
885 | 636 | result = self.connection.execute("SELECT 1") | 637 | result = self.connection.execute(Select(1)) |
886 | 637 | self.assertTrue(result.get_one()) | 638 | self.assertTrue(result.get_one()) |
887 | 638 | 639 | ||
888 | 639 | def test_catch_disconnect_on_reconnect(self): | 640 | def test_catch_disconnect_on_reconnect(self): |
889 | 640 | """Test that reconnection failures result in DisconnectionError.""" | 641 | """Test that reconnection failures result in DisconnectionError.""" |
891 | 641 | result = self.connection.execute("SELECT 1") | 642 | result = self.connection.execute(Select(1)) |
892 | 642 | self.assertTrue(result.get_one()) | 643 | self.assertTrue(result.get_one()) |
893 | 643 | self.proxy.stop() | 644 | self.proxy.stop() |
894 | 644 | self.assertRaises(DisconnectionError, | 645 | self.assertRaises(DisconnectionError, |
896 | 645 | self.connection.execute, "SELECT 1") | 646 | self.connection.execute, Select(1)) |
897 | 646 | # Rollback the connection, but because the proxy is still | 647 | # Rollback the connection, but because the proxy is still |
898 | 647 | # down, we get a DisconnectionError again. | 648 | # down, we get a DisconnectionError again. |
899 | 648 | self.connection.rollback() | 649 | self.connection.rollback() |
900 | 649 | self.assertRaises(DisconnectionError, | 650 | self.assertRaises(DisconnectionError, |
902 | 650 | self.connection.execute, "SELECT 1") | 651 | self.connection.execute, Select(1)) |
903 | 651 | 652 | ||
904 | 652 | def test_close_connection_after_disconnect(self): | 653 | def test_close_connection_after_disconnect(self): |
906 | 653 | result = self.connection.execute("SELECT 1") | 654 | result = self.connection.execute(Select(1)) |
907 | 654 | self.assertTrue(result.get_one()) | 655 | self.assertTrue(result.get_one()) |
908 | 655 | self.proxy.stop() | 656 | self.proxy.stop() |
909 | 656 | self.assertRaises(DisconnectionError, | 657 | self.assertRaises(DisconnectionError, |
911 | 657 | self.connection.execute, "SELECT 1") | 658 | self.connection.execute, Select(1)) |
912 | 658 | self.connection.close() | 659 | self.connection.close() |
913 | 659 | 660 | ||
914 | === added file 'tests/databases/oracle.py' | |||
915 | --- tests/databases/oracle.py 1970-01-01 00:00:00 +0000 | |||
916 | +++ tests/databases/oracle.py 2010-03-15 15:48:27 +0000 | |||
917 | @@ -0,0 +1,372 @@ | |||
918 | 1 | from __future__ import with_statement | ||
919 | 2 | |||
920 | 3 | import os | ||
921 | 4 | import pickle | ||
922 | 5 | from binascii import hexlify | ||
923 | 6 | from datetime import time, timedelta | ||
924 | 7 | from unittest import TestSuite, defaultTestLoader as loader, TestCase | ||
925 | 8 | |||
926 | 9 | from cx_Oracle import makedsn, DatabaseError, LOB | ||
927 | 10 | |||
928 | 11 | from storm.database import * | ||
929 | 12 | from storm.databases.oracle import ( | ||
930 | 13 | _type_converter, convert_to_sequential, Oracle, _isolation_context, | ||
931 | 14 | OracleConnection, OracleResult, compile) | ||
932 | 15 | from storm.event import EventSystem | ||
933 | 16 | from storm.exceptions import NotSupportedError | ||
934 | 17 | from storm.expr import Select, SQLToken, Alias | ||
935 | 18 | from storm.tracer import debug | ||
936 | 19 | from storm.uri import URI | ||
937 | 20 | from storm.variables import PickleVariable, RawStrVariable, TimeDeltaVariable | ||
938 | 21 | |||
939 | 22 | from tests.databases.base import ( | ||
940 | 23 | DatabaseTest, DatabaseDisconnectionTest, UnsupportedDatabaseTest) | ||
941 | 24 | from tests.helper import TestHelper | ||
942 | 25 | from tests.mocker import ANY, ARGS | ||
943 | 26 | from tests.store.oracle import create_table_helpers, drop_tables | ||
944 | 27 | |||
945 | 28 | |||
946 | 29 | DEBUG = True | ||
947 | 30 | debug(DEBUG) | ||
948 | 31 | |||
949 | 32 | class Marker(object): | ||
950 | 33 | pass | ||
951 | 34 | |||
952 | 35 | marker = Marker() | ||
953 | 36 | |||
954 | 37 | class OracleDatabaseTest(DatabaseTest, TestHelper): | ||
955 | 38 | _table_names = ['number_table', 'test', 'datetime_test', 'bin_test', | ||
956 | 39 | 'like_case_insensitive_test', 'insert_returning_test'] | ||
957 | 40 | supports_microseconds = False | ||
958 | 41 | def is_supported(self): | ||
959 | 42 | return bool(os.environ.get('STORM_ORACLE_URI')) | ||
960 | 43 | |||
961 | 44 | def create_database(self): | ||
962 | 45 | self.database = create_database(os.environ['STORM_ORACLE_URI']) | ||
963 | 46 | |||
964 | 47 | def create_tables(self): | ||
965 | 48 | try: | ||
966 | 49 | with self.connection.as_read_committed(): | ||
967 | 50 | self.connection.execute("CREATE TABLE number_table " | ||
968 | 51 | "(id INTEGER, one INTEGER, two INTEGER," | ||
969 | 52 | " three INTEGER)") | ||
970 | 53 | self.connection.execute("CREATE TABLE test " | ||
971 | 54 | "(id INTEGER PRIMARY KEY, title NVARCHAR2(2000))") | ||
972 | 55 | self.connection.execute("CREATE TABLE datetime_test " | ||
973 | 56 | "(id INTEGER PRIMARY KEY," | ||
974 | 57 | " dt TIMESTAMP(9), d DATE, t DATE, " | ||
975 | 58 | " td INTERVAL DAY TO SECOND)") | ||
976 | 59 | self.connection.execute("CREATE TABLE bin_test " | ||
977 | 60 | "(id INTEGER PRIMARY KEY, b BLOB)") | ||
978 | 61 | self.connection.execute("CREATE TABLE like_case_insensitive_test " | ||
979 | 62 | "(id INTEGER PRIMARY KEY, description CLOB)") | ||
980 | 63 | self.connection.execute("CREATE TABLE insert_returning_test " | ||
981 | 64 | "(id INTEGER PRIMARY KEY, " | ||
982 | 65 | "id1 INTEGER DEFAULT 123, " | ||
983 | 66 | " id2 INTEGER DEFAULT 456)") | ||
984 | 67 | create_table_helpers(self.connection, self._table_names) | ||
985 | 68 | |||
986 | 69 | except Exception, e: | ||
987 | 70 | self.connection.rollback() | ||
988 | 71 | raise | ||
989 | 72 | finally: | ||
990 | 73 | debug(DEBUG) | ||
991 | 74 | |||
992 | 75 | def create_sample_data(self): | ||
993 | 76 | self.connection.execute("INSERT INTO number_table (one, two, three) VALUES (1, 2, 3)") | ||
994 | 77 | self.connection.execute("INSERT INTO test VALUES (10, 'Title 10')") | ||
995 | 78 | self.connection.execute("INSERT INTO test VALUES (20, 'Title 20')") | ||
996 | 79 | self.connection.commit() | ||
997 | 80 | |||
998 | 81 | def drop_tables(self): | ||
999 | 82 | try: | ||
1000 | 83 | drop_tables(self.connection, self._table_names) | ||
1001 | 84 | finally: | ||
1002 | 85 | debug(DEBUG) | ||
1003 | 86 | |||
1004 | 87 | def test_binary(self): | ||
1005 | 88 | """Ensure database works with high bits and embedded zeros. | ||
1006 | 89 | Overriding this because we need to send the data as a hexlified | ||
1007 | 90 | string.""" | ||
1008 | 91 | |||
1009 | 92 | value = "\xff\x00\xff\x00" | ||
1010 | 93 | self.connection.execute("INSERT INTO bin_test (b) VALUES (?)", | ||
1011 | 94 | (hexlify(value),)) | ||
1012 | 95 | self.connection.commit() | ||
1013 | 96 | result = self.connection.execute("SELECT b FROM bin_test") | ||
1014 | 97 | variable = RawStrVariable() | ||
1015 | 98 | result.set_variable(variable, result.get_one()[0]) | ||
1016 | 99 | self.assertEquals(variable.get(), value) | ||
1017 | 100 | |||
1018 | 101 | def test_binary_ascii(self): | ||
1019 | 102 | """Some databases like pysqlite2 may return unicode for strings. | ||
1020 | 103 | Overriding this because we need to send the data as a hexlified | ||
1021 | 104 | string.""" | ||
1022 | 105 | bin_text = 'Value' | ||
1023 | 106 | self.connection.execute("INSERT INTO bin_test VALUES (10, ?)", | ||
1024 | 107 | (hexlify(bin_text),)) | ||
1025 | 108 | result = self.connection.execute("SELECT b FROM bin_test") | ||
1026 | 109 | variable = RawStrVariable() | ||
1027 | 110 | # If the following doesn't raise a TypeError we're good. | ||
1028 | 111 | result.set_variable(variable, result.get_one()[0]) | ||
1029 | 112 | self.assertEquals(variable.get(), "Value") | ||
1030 | 113 | |||
1031 | 114 | def test_execute_result(self): | ||
1032 | 115 | result = self.connection.execute("SELECT 1 FROM DUAL") | ||
1033 | 116 | self.assertTrue(isinstance(result, Result)) | ||
1034 | 117 | self.assertTrue(result.get_one()) | ||
1035 | 118 | |||
1036 | 119 | def test_execute_sends_event(self): | ||
1037 | 120 | event = EventSystem(marker) | ||
1038 | 121 | calls = [] | ||
1039 | 122 | def register_transaction(owner): | ||
1040 | 123 | calls.append(owner) | ||
1041 | 124 | event.hook("register-transaction", register_transaction) | ||
1042 | 125 | |||
1043 | 126 | connection = self.database.connect(event) | ||
1044 | 127 | connection.execute("SELECT 1 FROM DUAL") | ||
1045 | 128 | self.assertEqual(len(calls), 1) | ||
1046 | 129 | self.assertEqual(calls[0], marker) | ||
1047 | 130 | |||
1048 | 131 | def test_timedelta(self): | ||
1049 | 132 | value = timedelta(12, 34) | ||
1050 | 133 | self.connection.execute("INSERT INTO datetime_test (td) VALUES (?)", | ||
1051 | 134 | (value,)) | ||
1052 | 135 | result = self.connection.execute("SELECT td FROM datetime_test") | ||
1053 | 136 | variable = TimeDeltaVariable() | ||
1054 | 137 | result.set_variable(variable, result.get_one()[0]) | ||
1055 | 138 | self.assertEquals(variable.get(), value) | ||
1056 | 139 | |||
1057 | 140 | def test_pickle(self): | ||
1058 | 141 | value = {"a": 1, "b": 2} | ||
1059 | 142 | value_dump = pickle.dumps(value, -1) | ||
1060 | 143 | self.connection.execute("INSERT INTO bin_test (b) VALUES (?)", | ||
1061 | 144 | (hexlify(value_dump),)) | ||
1062 | 145 | result = self.connection.execute("SELECT b FROM bin_test") | ||
1063 | 146 | variable = PickleVariable() | ||
1064 | 147 | result.set_variable(variable, result.get_one()[0]) | ||
1065 | 148 | self.assertEquals(variable.get(), value) | ||
1066 | 149 | |||
1067 | 150 | def test_time(self): | ||
1068 | 151 | """Oracle doesn't have a time-only datatype, so we need to test | ||
1069 | 152 | that using one fails gracefully.""" | ||
1070 | 153 | self.assertRaises(NotSupportedError, self.connection.execute, | ||
1071 | 154 | 'SELECT * FROM ', (time(1, 2, 3),)) | ||
1072 | 155 | |||
1073 | 156 | |||
1074 | 157 | class OracleUnsupportedDatabaseTest(UnsupportedDatabaseTest, TestHelper): | ||
1075 | 158 | dbapi_module_names = ['cx_Oracle'] | ||
1076 | 159 | db_module_name = 'oracle' | ||
1077 | 160 | |||
1078 | 161 | # FIXME - Oracle doesn't seem to work with these disconnection tests | ||
1079 | 162 | # class OracleDatabaseDisconnectionTest(DatabaseDisconnectionTest, TestHelper): | ||
1080 | 163 | # environment_variable = "STORM_ORACLE_URI" | ||
1081 | 164 | # host_environment_variable = "STORM_ORACLE_HOST_URI" | ||
1082 | 165 | # default_port=1521 | ||
1083 | 166 | |||
1084 | 167 | class MockObject(object): | ||
1085 | 168 | def __init__(self, allowed_sets, allowed_gets, *args, **argd): | ||
1086 | 169 | self.allowed_sets = allowed_sets | ||
1087 | 170 | self.allowed_gets = allowed_gets | ||
1088 | 171 | self.args = args | ||
1089 | 172 | self.argd = argd | ||
1090 | 173 | self.was_set = set() | ||
1091 | 174 | self.was_gotten = set() | ||
1092 | 175 | |||
1093 | 176 | def __getattr__(self, name): | ||
1094 | 177 | if self.allowed_gets.get(name, False): | ||
1095 | 178 | self.was_gotten.add(name) | ||
1096 | 179 | return self.allowed_gets[name] | ||
1097 | 180 | |||
1098 | 181 | else: | ||
1099 | 182 | raise NotImplementedError() | ||
1100 | 183 | |||
1101 | 184 | def __setattr__(self, name, val): | ||
1102 | 185 | |||
1103 | 186 | if self.allowed_sets.get(name, False): | ||
1104 | 187 | self.was_set.add(name) | ||
1105 | 188 | self.__dict__[name] = val | ||
1106 | 189 | else: | ||
1107 | 190 | raise NotImplementedError() | ||
1108 | 191 | |||
1109 | 192 | def __delattr__(self, name): | ||
1110 | 193 | raise NotImplementedError() | ||
1111 | 194 | |||
1112 | 195 | def __call__(self, *args, **argd): | ||
1113 | 196 | return self | ||
1114 | 197 | |||
1115 | 198 | def dummy_func(*args, **argd): | ||
1116 | 199 | pass | ||
1117 | 200 | |||
1118 | 201 | |||
1119 | 202 | class UnicodeUnitTests(TestCase): | ||
1120 | 203 | unit = True | ||
1121 | 204 | def setUp(self): | ||
1122 | 205 | self.converter = _type_converter('utf8') | ||
1123 | 206 | |||
1124 | 207 | def test_identity(self): | ||
1125 | 208 | expected = u"Some string" | ||
1126 | 209 | actual = self.converter.convert_unicode(expected) | ||
1127 | 210 | self.assertEqual(actual, expected) | ||
1128 | 211 | |||
1129 | 212 | def test_cross_conversion(self): | ||
1130 | 213 | initial = unicode('Some string', 'latin-1') | ||
1131 | 214 | expected = unicode('Some string', 'utf8') | ||
1132 | 215 | actual = self.converter.convert_unicode(initial) | ||
1133 | 216 | self.assertEqual(actual, expected) | ||
1134 | 217 | |||
1135 | 218 | class OracleUnitTests(TestHelper): | ||
1136 | 219 | unit = True | ||
1137 | 220 | def test_tns_uri(self): | ||
1138 | 221 | uri = URI('oracle://foo:bar@test?tns=true') | ||
1139 | 222 | ora = Oracle(uri) | ||
1140 | 223 | self.assertEqual(ora._dsn, 'test') | ||
1141 | 224 | self.assertEqual(ora._username, 'foo') | ||
1142 | 225 | self.assertEqual(ora._password, 'bar') | ||
1143 | 226 | |||
1144 | 227 | def test_host_str(self): | ||
1145 | 228 | uri = URI('oracle://foo:bar@localhost:1521/test') | ||
1146 | 229 | ora = Oracle(uri) | ||
1147 | 230 | self.assertEqual(ora._username, 'foo') | ||
1148 | 231 | self.assertEqual(ora._password, 'bar') | ||
1149 | 232 | expected_dsn = makedsn('localhost', 1521, 'test') | ||
1150 | 233 | self.assertEqual(ora._dsn, expected_dsn) | ||
1151 | 234 | |||
1152 | 235 | def test_type_handlers_set(self): | ||
1153 | 236 | mocker = self.mocker | ||
1154 | 237 | mock = mocker.mock() | ||
1155 | 238 | converter = mocker.mock() | ||
1156 | 239 | mock(ARGS) | ||
1157 | 240 | mocker.result(mock) | ||
1158 | 241 | mock.cursor() | ||
1159 | 242 | mocker.result(mock) | ||
1160 | 243 | mock.execute(ARGS) | ||
1161 | 244 | mocker.result(None) | ||
1162 | 245 | mock.nencoding | ||
1163 | 246 | mocker.result('utf8') | ||
1164 | 247 | converter('utf8') | ||
1165 | 248 | mocker.result(converter) | ||
1166 | 249 | |||
1167 | 250 | converter.InputTypeHandler | ||
1168 | 251 | mocker.result(converter) | ||
1169 | 252 | |||
1170 | 253 | mock.inputtypehandler = converter | ||
1171 | 254 | mocker.result(None) | ||
1172 | 255 | |||
1173 | 256 | converter.OutputTypeHandler | ||
1174 | 257 | mocker.result(converter) | ||
1175 | 258 | mock.outputtypehandler = converter | ||
1176 | 259 | mock.close() | ||
1177 | 260 | mocker.result(None) | ||
1178 | 261 | mocker.replay() | ||
1179 | 262 | |||
1180 | 263 | class DummyOracle(Oracle): | ||
1181 | 264 | raw_connection_factory = mock | ||
1182 | 265 | converter_factory = converter | ||
1183 | 266 | def __init__(self): | ||
1184 | 267 | self._username = '' | ||
1185 | 268 | self._password = '' | ||
1186 | 269 | self._dsn = '' | ||
1187 | 270 | self._isolation = '' | ||
1188 | 271 | |||
1189 | 272 | DummyOracle().raw_connect() | ||
1190 | 273 | |||
1191 | 274 | |||
1192 | 275 | class OracleConnectionTests(TestHelper): | ||
1193 | 276 | unit = True | ||
1194 | 277 | def test_isolation_context_pass(self): | ||
1195 | 278 | mocker = self.mocker | ||
1196 | 279 | mock = mocker.mock() | ||
1197 | 280 | mock.commit() | ||
1198 | 281 | mocker.count(0, None) | ||
1199 | 282 | mock.execute('ALTER SESSION SET isolation_level = read committed') | ||
1200 | 283 | mock.execute('ALTER SESSION SET isolation_level = serializable') | ||
1201 | 284 | mocker.replay() | ||
1202 | 285 | |||
1203 | 286 | with _isolation_context(mock): | ||
1204 | 287 | pass | ||
1205 | 288 | |||
1206 | 289 | def test_isolation_context_fail(self): | ||
1207 | 290 | mocker = self.mocker | ||
1208 | 291 | mock = mocker.mock() | ||
1209 | 292 | mock.commit() | ||
1210 | 293 | mocker.result(None) | ||
1211 | 294 | mock.execute('ALTER SESSION SET isolation_level = read committed') | ||
1212 | 295 | mock.rollback() | ||
1213 | 296 | mocker.result(None) | ||
1214 | 297 | mock.execute('ALTER SESSION SET isolation_level = serializable') | ||
1215 | 298 | mock.commit() | ||
1216 | 299 | mocker.result(None) | ||
1217 | 300 | mocker.replay() | ||
1218 | 301 | |||
1219 | 302 | try: | ||
1220 | 303 | with _isolation_context(mock): | ||
1221 | 304 | raise TypeError() | ||
1222 | 305 | except TypeError: | ||
1223 | 306 | pass | ||
1224 | 307 | |||
1225 | 308 | def test_is_disconnection_error(self): | ||
1226 | 309 | codes = [3135, 3113] | ||
1227 | 310 | non_codes = [0, 1000] | ||
1228 | 311 | |||
1229 | 312 | database = self.mocker.mock() | ||
1230 | 313 | database.raw_connect() | ||
1231 | 314 | self.mocker.result(database) | ||
1232 | 315 | self.mocker.replay() | ||
1233 | 316 | |||
1234 | 317 | conn = OracleConnection(database) | ||
1235 | 318 | |||
1236 | 319 | class DummyError(object): | ||
1237 | 320 | def __init__(self, code): | ||
1238 | 321 | self.code = code | ||
1239 | 322 | |||
1240 | 323 | for code in codes: | ||
1241 | 324 | exc = DatabaseError(DummyError(code)) | ||
1242 | 325 | assert conn.is_disconnection_error(exc) | ||
1243 | 326 | |||
1244 | 327 | for code in non_codes: | ||
1245 | 328 | exc = DatabaseError(DummyError(code)) | ||
1246 | 329 | assert not conn.is_disconnection_error(exc) | ||
1247 | 330 | |||
1248 | 331 | def test_from_database_LOB(self): | ||
1249 | 332 | mocker = self.mocker | ||
1250 | 333 | mock = mocker.mock(LOB) | ||
1251 | 334 | |||
1252 | 335 | iter(mock) | ||
1253 | 336 | mocker.generate([mock]) | ||
1254 | 337 | expected = 'result string' | ||
1255 | 338 | mock.read() | ||
1256 | 339 | mocker.result(expected) | ||
1257 | 340 | mocker.replay() | ||
1258 | 341 | |||
1259 | 342 | row_iter = OracleResult.from_database(mock) | ||
1260 | 343 | self.assertEqual(row_iter.next(), expected) | ||
1261 | 344 | self.assertRaises(StopIteration, row_iter.next) | ||
1262 | 345 | |||
1263 | 346 | |||
1264 | 347 | class QueryUnitTests(TestHelper): | ||
1265 | 348 | unit = True | ||
1266 | 349 | def test_convert_to_sequential(self): | ||
1267 | 350 | initial = 'SELECT ? FROM DUAL' | ||
1268 | 351 | expected = 'SELECT :1 FROM DUAL' | ||
1269 | 352 | actual = convert_to_sequential(initial) | ||
1270 | 353 | self.assertEqual(expected, actual) | ||
1271 | 354 | |||
1272 | 355 | def test_convert_to_sequential_in_str(self): | ||
1273 | 356 | expected = "SELECT '?' FROM DUAL" | ||
1274 | 357 | actual = convert_to_sequential(expected) | ||
1275 | 358 | self.assertEqual(expected, actual) | ||
1276 | 359 | |||
1277 | 360 | def test_conver_to_sequential_not_in_str(self): | ||
1278 | 361 | initial = "SELECT 'Foo', ? FROM DUAL" | ||
1279 | 362 | expected = "SELECT 'Foo', :1 FROM DUAL" | ||
1280 | 363 | actual = convert_to_sequential(initial) | ||
1281 | 364 | self.assertEqual(expected, actual) | ||
1282 | 365 | |||
1283 | 366 | def test_double_quotes(self): | ||
1284 | 367 | initial = 'word"word' | ||
1285 | 368 | expected = '"word|word"' | ||
1286 | 369 | actual = compile(SQLToken(initial)) | ||
1287 | 370 | self.assertEqual(expected, actual) | ||
1288 | 371 | |||
1289 | 372 | |||
1290 | 0 | 373 | ||
1291 | === modified file 'tests/store/base.py' | |||
1292 | --- tests/store/base.py 2010-02-08 11:30:44 +0000 | |||
1293 | +++ tests/store/base.py 2010-03-15 15:48:27 +0000 | |||
1294 | @@ -280,11 +280,11 @@ | |||
1295 | 280 | return store._cache | 280 | return store._cache |
1296 | 281 | 281 | ||
1297 | 282 | def test_execute(self): | 282 | def test_execute(self): |
1299 | 283 | result = self.store.execute("SELECT 1") | 283 | result = self.store.execute(Select(1)) |
1300 | 284 | self.assertTrue(isinstance(result, Result)) | 284 | self.assertTrue(isinstance(result, Result)) |
1301 | 285 | self.assertEquals(result.get_one(), (1,)) | 285 | self.assertEquals(result.get_one(), (1,)) |
1302 | 286 | 286 | ||
1304 | 287 | result = self.store.execute("SELECT 1", noresult=True) | 287 | result = self.store.execute(Select(1), noresult=True) |
1305 | 288 | self.assertEquals(result, None) | 288 | self.assertEquals(result, None) |
1306 | 289 | 289 | ||
1307 | 290 | def test_execute_params(self): | 290 | def test_execute_params(self): |
1308 | @@ -302,7 +302,7 @@ | |||
1309 | 302 | def test_close(self): | 302 | def test_close(self): |
1310 | 303 | store = Store(self.database) | 303 | store = Store(self.database) |
1311 | 304 | store.close() | 304 | store.close() |
1313 | 305 | self.assertRaises(ClosedError, store.execute, "SELECT 1") | 305 | self.assertRaises(ClosedError, store.execute, Select(1)) |
1314 | 306 | 306 | ||
1315 | 307 | def test_get(self): | 307 | def test_get(self): |
1316 | 308 | foo = self.store.get(Foo, 10) | 308 | foo = self.store.get(Foo, 10) |
1317 | @@ -1423,7 +1423,7 @@ | |||
1318 | 1423 | result.group_by(Foo) | 1423 | result.group_by(Foo) |
1319 | 1424 | foo1 = self.store.get(Foo, 10) | 1424 | foo1 = self.store.get(Foo, 10) |
1320 | 1425 | foo2 = self.store.get(Foo, 20) | 1425 | foo2 = self.store.get(Foo, 20) |
1322 | 1426 | self.assertEquals(list(result), [(5, foo1), (16, foo2)]) | 1426 | self.assertEquals(sorted(list(result)), [(5, foo1), (16, foo2)]) |
1323 | 1427 | 1427 | ||
1324 | 1428 | def test_find_group_by_table_contains(self): | 1428 | def test_find_group_by_table_contains(self): |
1325 | 1429 | result = self.store.find( | 1429 | result = self.store.find( |
1326 | @@ -5675,7 +5675,7 @@ | |||
1327 | 5675 | def register_transaction(owner): | 5675 | def register_transaction(owner): |
1328 | 5676 | calls.append(owner) | 5676 | calls.append(owner) |
1329 | 5677 | self.store._event.hook("register-transaction", register_transaction) | 5677 | self.store._event.hook("register-transaction", register_transaction) |
1331 | 5678 | self.store.execute("SELECT 1") | 5678 | self.store.execute(Select(1)) |
1332 | 5679 | self.assertEqual(len(calls), 1) | 5679 | self.assertEqual(len(calls), 1) |
1333 | 5680 | self.assertEqual(calls[0], self.store) | 5680 | self.assertEqual(calls[0], self.store) |
1334 | 5681 | 5681 | ||
1335 | 5682 | 5682 | ||
1336 | === added file 'tests/store/oracle.py' | |||
1337 | --- tests/store/oracle.py 1970-01-01 00:00:00 +0000 | |||
1338 | +++ tests/store/oracle.py 2010-03-15 15:48:27 +0000 | |||
1339 | @@ -0,0 +1,234 @@ | |||
1340 | 1 | # | ||
1341 | 2 | # Copyright (c) 2006, 2007 Canonical | ||
1342 | 3 | # | ||
1343 | 4 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
1344 | 5 | # | ||
1345 | 6 | # This file is part of Storm Object Relational Mapper. | ||
1346 | 7 | # | ||
1347 | 8 | # Storm is free software; you can redistribute it and/or modify | ||
1348 | 9 | # it under the terms of the GNU Lesser General Public License as | ||
1349 | 10 | # published by the Free Software Foundation; either version 2.1 of | ||
1350 | 11 | # the License, or (at your option) any later version. | ||
1351 | 12 | # | ||
1352 | 13 | # Storm is distributed in the hope that it will be useful, | ||
1353 | 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1354 | 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1355 | 16 | # GNU Lesser General Public License for more details. | ||
1356 | 17 | # | ||
1357 | 18 | # You should have received a copy of the GNU Lesser General Public License | ||
1358 | 19 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1359 | 20 | # | ||
1360 | 21 | from __future__ import with_statement | ||
1361 | 22 | import os, sys | ||
1362 | 23 | |||
1363 | 24 | from storm.database import create_database | ||
1364 | 25 | from storm.databases.oracle import Oracle | ||
1365 | 26 | from storm.uri import URI | ||
1366 | 27 | from cx_Oracle import DatabaseError | ||
1367 | 28 | |||
1368 | 29 | from storm.database import Result | ||
1369 | 30 | from tests.store.base import StoreTest, EmptyResultSetTest | ||
1370 | 31 | from tests.store.base import Foo | ||
1371 | 32 | from storm.properties import Int, Float, RawStr, Unicode, Property, Pickle | ||
1372 | 33 | from tests.helper import TestHelper, MakePath | ||
1373 | 34 | |||
1374 | 35 | from storm.info import ClassAlias | ||
1375 | 36 | |||
1376 | 37 | from storm.tracer import debug | ||
1377 | 38 | |||
1378 | 39 | DEBUG = False | ||
1379 | 40 | debug(DEBUG) | ||
1380 | 41 | |||
1381 | 42 | def _safe_drop(connection, query): | ||
1382 | 43 | """ | ||
1383 | 44 | This function will execute a given DROP query | ||
1384 | 45 | and ignore any error codes that indicate that | ||
1385 | 46 | the object doesn't exist. | ||
1386 | 47 | """ | ||
1387 | 48 | with connection.as_read_committed(): | ||
1388 | 49 | try: | ||
1389 | 50 | connection.execute(query) | ||
1390 | 51 | except DatabaseError, exc: | ||
1391 | 52 | error, = exc.args | ||
1392 | 53 | if error.code not in [942, 4080, 2289, 903]: | ||
1393 | 54 | raise | ||
1394 | 55 | |||
1395 | 56 | def create_table_helpers(connection, table_names): | ||
1396 | 57 | with connection.as_read_committed(): | ||
1397 | 58 | for table_name in table_names: | ||
1398 | 59 | connection.execute("CREATE SEQUENCE %s_s " | ||
1399 | 60 | "START WITH 1 " | ||
1400 | 61 | "INCREMENT BY 1 " | ||
1401 | 62 | "NOMAXVALUE " | ||
1402 | 63 | "ORDER NOCYCLE" % (table_name), | ||
1403 | 64 | ) | ||
1404 | 65 | connection.execute("CREATE OR REPLACE TRIGGER %s_t " | ||
1405 | 66 | "BEFORE INSERT ON %s " | ||
1406 | 67 | "FOR EACH ROW " | ||
1407 | 68 | "WHEN (new.id is null) " | ||
1408 | 69 | "BEGIN " | ||
1409 | 70 | "SELECT %s_s.nextval INTO :new.id FROM DUAL; " | ||
1410 | 71 | "END;" % (table_name, table_name, table_name), | ||
1411 | 72 | ) | ||
1412 | 73 | |||
1413 | 74 | def drop_tables(connection, tables): | ||
1414 | 75 | for table in tables: | ||
1415 | 76 | _safe_drop(connection, "DROP TRIGGER %s_t" % table) | ||
1416 | 77 | _safe_drop(connection, "DROP SEQUENCE %s_s" % table) | ||
1417 | 78 | |||
1418 | 79 | _safe_drop(connection, "DROP TABLE %s" % table) | ||
1419 | 80 | |||
1420 | 81 | class OracleStoreTest(TestHelper, StoreTest): | ||
1421 | 82 | DUMMY_SELECT = 'SELECT 1 FROM DUAL' | ||
1422 | 83 | helpers = [MakePath] | ||
1423 | 84 | _tables = ['foo', 'bar', 'bin', 'link', 'money', 'selfref', 'foovalue'] | ||
1424 | 85 | |||
1425 | 86 | def create_tables(self): | ||
1426 | 87 | connection = self.connection | ||
1427 | 88 | with connection.as_read_committed(): | ||
1428 | 89 | connection.execute("CREATE TABLE foo (id NUMBER, " | ||
1429 | 90 | "title NVARCHAR2(2000) DEFAULT 'Default Title')") | ||
1430 | 91 | connection.execute("CREATE TABLE bar " | ||
1431 | 92 | "(id NUMBER, foo_id NUMBER, " | ||
1432 | 93 | "title NVARCHAR2(2000))") | ||
1433 | 94 | connection.execute("CREATE TABLE bin " | ||
1434 | 95 | "(id NUMBER, bin BLOB, foo_id INTEGER)") | ||
1435 | 96 | connection.execute("CREATE TABLE link " | ||
1436 | 97 | "(id NUMBER, foo_id NUMBER, bar_id NUMBER)") | ||
1437 | 98 | connection.execute("CREATE TABLE money " | ||
1438 | 99 | "(id NUMBER, value NUMBER(6,4))") | ||
1439 | 100 | connection.execute("CREATE TABLE selfref " | ||
1440 | 101 | "(id NUMBER, title NVARCHAR2(2000)," | ||
1441 | 102 | " selfref_id NUMBER)") | ||
1442 | 103 | connection.execute("CREATE TABLE foovalue " | ||
1443 | 104 | "(id NUMBER, foo_id INTEGER," | ||
1444 | 105 | " value1 INTEGER, value2 INTEGER)") | ||
1445 | 106 | create_table_helpers(connection, self._tables) | ||
1446 | 107 | |||
1447 | 108 | def drop_tables(self): | ||
1448 | 109 | drop_tables(self.connection, self._tables) | ||
1449 | 110 | |||
1450 | 111 | def create_sample_data(self): | ||
1451 | 112 | connection = self.connection | ||
1452 | 113 | connection.execute("INSERT INTO foo (id, title)" | ||
1453 | 114 | " VALUES (10, 'Title 30')") | ||
1454 | 115 | connection.execute("INSERT INTO foo (id, title)" | ||
1455 | 116 | " VALUES (20, 'Title 20')") | ||
1456 | 117 | connection.execute("INSERT INTO foo (id, title)" | ||
1457 | 118 | " VALUES (30, 'Title 10')") | ||
1458 | 119 | connection.execute("INSERT INTO bar (id, foo_id, title)" | ||
1459 | 120 | " VALUES (100, 10, 'Title 300')") | ||
1460 | 121 | connection.execute("INSERT INTO bar (id, foo_id, title)" | ||
1461 | 122 | " VALUES (200, 20, 'Title 200')") | ||
1462 | 123 | connection.execute("INSERT INTO bar (id, foo_id, title)" | ||
1463 | 124 | " VALUES (300, 30, 'Title 100')") | ||
1464 | 125 | connection.execute("INSERT INTO bin (id, bin) VALUES (10, rawtohex('Blob 30'))") | ||
1465 | 126 | connection.execute("INSERT INTO bin (id, bin) VALUES (20, rawtohex('Blob 20'))") | ||
1466 | 127 | connection.execute("INSERT INTO bin (id, bin) VALUES (30, rawtohex('Blob 10'))") | ||
1467 | 128 | connection.execute("INSERT INTO link (foo_id, bar_id) VALUES (10, 100)") | ||
1468 | 129 | connection.execute("INSERT INTO link (foo_id, bar_id) VALUES (10, 200)") | ||
1469 | 130 | connection.execute("INSERT INTO link (foo_id, bar_id) VALUES (10, 300)") | ||
1470 | 131 | connection.execute("INSERT INTO link (foo_id, bar_id) VALUES (20, 100)") | ||
1471 | 132 | connection.execute("INSERT INTO link (foo_id, bar_id) VALUES (20, 200)") | ||
1472 | 133 | connection.execute("INSERT INTO link (foo_id, bar_id) VALUES (30, 300)") | ||
1473 | 134 | connection.execute("INSERT INTO money (id, value)" | ||
1474 | 135 | " VALUES (10, '12.3455')") | ||
1475 | 136 | connection.execute("INSERT INTO selfref (id, title, selfref_id)" | ||
1476 | 137 | " VALUES (15, 'SelfRef 15', NULL)") | ||
1477 | 138 | connection.execute("INSERT INTO selfref (id, title, selfref_id)" | ||
1478 | 139 | " VALUES (25, 'SelfRef 25', NULL)") | ||
1479 | 140 | connection.execute("INSERT INTO selfref (id, title, selfref_id)" | ||
1480 | 141 | " VALUES (35, 'SelfRef 35', 15)") | ||
1481 | 142 | connection.execute("INSERT INTO foovalue (id, foo_id, value1, value2)" | ||
1482 | 143 | " VALUES (1, 10, 2, 1)") | ||
1483 | 144 | connection.execute("INSERT INTO foovalue (id, foo_id, value1, value2)" | ||
1484 | 145 | " VALUES (2, 10, 2, 1)") | ||
1485 | 146 | connection.execute("INSERT INTO foovalue (id, foo_id, value1, value2)" | ||
1486 | 147 | " VALUES (3, 10, 2, 1)") | ||
1487 | 148 | connection.execute("INSERT INTO foovalue (id, foo_id, value1, value2)" | ||
1488 | 149 | " VALUES (4, 10, 2, 2)") | ||
1489 | 150 | connection.execute("INSERT INTO foovalue (id, foo_id, value1, value2)" | ||
1490 | 151 | " VALUES (5, 20, 1, 3)") | ||
1491 | 152 | connection.execute("INSERT INTO foovalue (id, foo_id, value1, value2)" | ||
1492 | 153 | " VALUES (6, 20, 1, 3)") | ||
1493 | 154 | connection.execute("INSERT INTO foovalue (id, foo_id, value1, value2)" | ||
1494 | 155 | " VALUES (7, 20, 1, 4)") | ||
1495 | 156 | connection.execute("INSERT INTO foovalue (id, foo_id, value1, value2)" | ||
1496 | 157 | " VALUES (8, 20, 1, 4)") | ||
1497 | 158 | connection.execute("INSERT INTO foovalue (id, foo_id, value1, value2)" | ||
1498 | 159 | " VALUES (9, 20, 1, 2)") | ||
1499 | 160 | connection.commit() | ||
1500 | 161 | |||
1501 | 162 | #Overriding this because SELECT ? should be SELECT ? FROM DUAL | ||
1502 | 163 | def test_execute_params(self): | ||
1503 | 164 | result = self.store.execute("SELECT ? FROM DUAL", (1,)) | ||
1504 | 165 | self.assertTrue(isinstance(result, Result)) | ||
1505 | 166 | self.assertEquals(result.get_one(), (1,)) | ||
1506 | 167 | |||
1507 | 168 | def test_is_in_empty_list(self): | ||
1508 | 169 | result2 = self.store.find(Foo, Foo.id.is_in([])) | ||
1509 | 170 | self.assertEquals(result2.count(), 0) | ||
1510 | 171 | |||
1511 | 172 | def setUp(self): | ||
1512 | 173 | TestHelper.setUp(self) | ||
1513 | 174 | StoreTest.setUp(self) | ||
1514 | 175 | self.connection = self.database.connect() | ||
1515 | 176 | |||
1516 | 177 | def tearDown(self): | ||
1517 | 178 | TestHelper.tearDown(self) | ||
1518 | 179 | StoreTest.tearDown(self) | ||
1519 | 180 | self.connection.close() | ||
1520 | 181 | |||
1521 | 182 | def is_supported(self): | ||
1522 | 183 | return bool(os.environ.get("STORM_ORACLE_URI")) | ||
1523 | 184 | |||
1524 | 185 | def create_database(self): | ||
1525 | 186 | self.database = create_database(os.environ["STORM_ORACLE_URI"]) | ||
1526 | 187 | |||
1527 | 188 | |||
1528 | 189 | class testOracleEmptyResultSet(TestHelper, EmptyResultSetTest): | ||
1529 | 190 | |||
1530 | 191 | helpers = [MakePath] | ||
1531 | 192 | |||
1532 | 193 | def setUp(self): | ||
1533 | 194 | TestHelper.setUp(self) | ||
1534 | 195 | EmptyResultSetTest.setUp(self) | ||
1535 | 196 | self.connection = self.database.connect() | ||
1536 | 197 | |||
1537 | 198 | def tearDown(self): | ||
1538 | 199 | TestHelper.tearDown(self) | ||
1539 | 200 | EmptyResultSetTest.tearDown(self) | ||
1540 | 201 | self.connection.close() | ||
1541 | 202 | |||
1542 | 203 | def is_supported(self): | ||
1543 | 204 | return bool(os.environ.get("STORM_ORACLE_URI")) | ||
1544 | 205 | |||
1545 | 206 | def create_database(self): | ||
1546 | 207 | self.database = create_database(os.environ["STORM_ORACLE_URI"]) | ||
1547 | 208 | |||
1548 | 209 | def create_tables(self): | ||
1549 | 210 | connection = self.connection | ||
1550 | 211 | with connection.as_read_committed(): | ||
1551 | 212 | connection.execute("CREATE TABLE foo (id NUMBER, " | ||
1552 | 213 | "title VARCHAR2(4000) DEFAULT 'Default Title')") | ||
1553 | 214 | #connection.execute("CREATE OR REPLACE SEQUENCE foo_seq " | ||
1554 | 215 | #"START WITH 1 " | ||
1555 | 216 | #"INCREMENT BY 1 " | ||
1556 | 217 | #"NOMAXVALUE") | ||
1557 | 218 | #connection.execute("CREATE OR REPLACE TRIGGER foo_trigger " | ||
1558 | 219 | #"BEFORE INSERT ON foo " | ||
1559 | 220 | #"FOR EACH ROW " | ||
1560 | 221 | #"BEGIN " | ||
1561 | 222 | #"SELECT foo_seq.nextval INTO :new.id FROM DUAL; " | ||
1562 | 223 | #"END") | ||
1563 | 224 | |||
1564 | 225 | def drop_tables(self): | ||
1565 | 226 | for table in ["foo"]: | ||
1566 | 227 | connection = self.connection | ||
1567 | 228 | try: | ||
1568 | 229 | #connection.execute("DROP TRIGGER %s_trigger" % table) | ||
1569 | 230 | #connection.execute("DROP SEQUENCE %s_seq" % table) | ||
1570 | 231 | connection.execute("DROP TABLE %s" % table) | ||
1571 | 232 | connection.commit() | ||
1572 | 233 | except: | ||
1573 | 234 | connection.rollback() |
This should be ready for merge. I've commented out the disconnection tests for now. Some parts of this are still somewhat ugly, but it's a good starting point.