1 | # oracle.py |
---|
2 | # Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com |
---|
3 | # |
---|
4 | # This module is part of SQLAlchemy and is released under |
---|
5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php |
---|
6 | """Support for the Oracle database. |
---|
7 | |
---|
8 | Oracle version 8 through current (11g at the time of this writing) are supported. |
---|
9 | |
---|
10 | Driver |
---|
11 | ------ |
---|
12 | |
---|
13 | The Oracle dialect uses the cx_oracle driver, available at |
---|
14 | http://cx-oracle.sourceforge.net/ . The dialect has several behaviors |
---|
15 | which are specifically tailored towards compatibility with this module. |
---|
16 | |
---|
17 | Connecting |
---|
18 | ---------- |
---|
19 | |
---|
20 | Connecting with create_engine() uses the standard URL approach of |
---|
21 | ``oracle://user:pass@host:port/dbname[?key=value&key=value...]``. If dbname is present, the |
---|
22 | host, port, and dbname tokens are converted to a TNS name using the cx_oracle |
---|
23 | :func:`makedsn()` function. Otherwise, the host token is taken directly as a TNS name. |
---|
24 | |
---|
25 | Additional arguments which may be specified either as query string arguments on the |
---|
26 | URL, or as keyword arguments to :func:`~sqlalchemy.create_engine()` are: |
---|
27 | |
---|
28 | * *allow_twophase* - enable two-phase transactions. Defaults to ``True``. |
---|
29 | |
---|
30 | * *auto_convert_lobs* - defaults to True, see the section on LOB objects. |
---|
31 | |
---|
32 | * *auto_setinputsizes* - the cx_oracle.setinputsizes() call is issued for all bind parameters. |
---|
33 | This is required for LOB datatypes but can be disabled to reduce overhead. Defaults |
---|
34 | to ``True``. |
---|
35 | |
---|
36 | * *mode* - This is given the string value of SYSDBA or SYSOPER, or alternatively an |
---|
37 | integer value. This value is only available as a URL query string argument. |
---|
38 | |
---|
39 | * *threaded* - enable multithreaded access to cx_oracle connections. Defaults |
---|
40 | to ``True``. Note that this is the opposite default of cx_oracle itself. |
---|
41 | |
---|
42 | * *use_ansi* - Use ANSI JOIN constructs (see the section on Oracle 8). Defaults |
---|
43 | to ``True``. If ``False``, Oracle-8 compatible constructs are used for joins. |
---|
44 | |
---|
45 | * *optimize_limits* - defaults to ``False``. see the section on LIMIT/OFFSET. |
---|
46 | |
---|
47 | Auto Increment Behavior |
---|
48 | ----------------------- |
---|
49 | |
---|
50 | SQLAlchemy Table objects which include integer primary keys are usually assumed to have |
---|
51 | "autoincrementing" behavior, meaning they can generate their own primary key values upon |
---|
52 | INSERT. Since Oracle has no "autoincrement" feature, SQLAlchemy relies upon sequences |
---|
53 | to produce these values. With the Oracle dialect, *a sequence must always be explicitly |
---|
54 | specified to enable autoincrement*. This is divergent with the majority of documentation |
---|
55 | examples which assume the usage of an autoincrement-capable database. To specify sequences, |
---|
56 | use the sqlalchemy.schema.Sequence object which is passed to a Column construct:: |
---|
57 | |
---|
58 | t = Table('mytable', metadata, |
---|
59 | Column('id', Integer, Sequence('id_seq'), primary_key=True), |
---|
60 | Column(...), ... |
---|
61 | ) |
---|
62 | |
---|
63 | This step is also required when using table reflection, i.e. autoload=True:: |
---|
64 | |
---|
65 | t = Table('mytable', metadata, |
---|
66 | Column('id', Integer, Sequence('id_seq'), primary_key=True), |
---|
67 | autoload=True |
---|
68 | ) |
---|
69 | |
---|
70 | LOB Objects |
---|
71 | ----------- |
---|
72 | |
---|
73 | cx_oracle presents some challenges when fetching LOB objects. A LOB object in a result set |
---|
74 | is presented by cx_oracle as a cx_oracle.LOB object which has a read() method. By default, |
---|
75 | SQLAlchemy converts these LOB objects into Python strings. This is for two reasons. First, |
---|
76 | the LOB object requires an active cursor association, meaning if you were to fetch many rows |
---|
77 | at once such that cx_oracle had to go back to the database and fetch a new batch of rows, |
---|
78 | the LOB objects in the already-fetched rows are now unreadable and will raise an error. |
---|
79 | SQLA "pre-reads" all LOBs so that their data is fetched before further rows are read. |
---|
80 | The size of a "batch of rows" is controlled by the cursor.arraysize value, which SQLAlchemy |
---|
81 | defaults to 50 (cx_oracle normally defaults this to one). |
---|
82 | |
---|
83 | Secondly, the LOB object is not a standard DBAPI return value so SQLAlchemy seeks to |
---|
84 | "normalize" the results to look more like other DBAPIs. |
---|
85 | |
---|
86 | The conversion of LOB objects by this dialect is unique in SQLAlchemy in that it takes place |
---|
87 | for all statement executions, even plain string-based statements for which SQLA has no awareness |
---|
88 | of result typing. This is so that calls like fetchmany() and fetchall() can work in all cases |
---|
89 | without raising cursor errors. The conversion of LOB in all cases, as well as the "prefetch" |
---|
90 | of LOB objects, can be disabled using auto_convert_lobs=False. |
---|
91 | |
---|
92 | LIMIT/OFFSET Support |
---|
93 | -------------------- |
---|
94 | |
---|
95 | Oracle has no support for the LIMIT or OFFSET keywords. Whereas previous versions of SQLAlchemy |
---|
96 | used the "ROW NUMBER OVER..." construct to simulate LIMIT/OFFSET, SQLAlchemy 0.5 now uses |
---|
97 | a wrapped subquery approach in conjunction with ROWNUM. The exact methodology is taken from |
---|
98 | http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html . Note that the |
---|
99 | "FIRST ROWS()" optimization keyword mentioned is not used by default, as the user community felt |
---|
100 | this was stepping into the bounds of optimization that is better left on the DBA side, but this |
---|
101 | prefix can be added by enabling the optimize_limits=True flag on create_engine(). |
---|
102 | |
---|
103 | Two Phase Transaction Support |
---|
104 | ----------------------------- |
---|
105 | |
---|
106 | Two Phase transactions are implemented using XA transactions. Success has been reported of them |
---|
107 | working successfully but this should be regarded as an experimental feature. |
---|
108 | |
---|
109 | Oracle 8 Compatibility |
---|
110 | ---------------------- |
---|
111 | |
---|
112 | When using Oracle 8, a "use_ansi=False" flag is available which converts all |
---|
113 | JOIN phrases into the WHERE clause, and in the case of LEFT OUTER JOIN |
---|
114 | makes use of Oracle's (+) operator. |
---|
115 | |
---|
116 | Synonym/DBLINK Reflection |
---|
117 | ------------------------- |
---|
118 | |
---|
119 | When using reflection with Table objects, the dialect can optionally search for tables |
---|
120 | indicated by synonyms that reference DBLINK-ed tables by passing the flag |
---|
121 | oracle_resolve_synonyms=True as a keyword argument to the Table construct. If DBLINK |
---|
122 | is not in use this flag should be left off. |
---|
123 | |
---|
124 | """ |
---|
125 | |
---|
126 | import datetime, random, re |
---|
127 | |
---|
128 | from sqlalchemy import util, sql, schema, log |
---|
129 | from sqlalchemy.engine import default, base |
---|
130 | from sqlalchemy.sql import compiler, visitors, expression |
---|
131 | from sqlalchemy.sql import operators as sql_operators, functions as sql_functions |
---|
132 | from sqlalchemy import types as sqltypes |
---|
133 | |
---|
134 | |
---|
135 | class OracleNumeric(sqltypes.Numeric): |
---|
136 | def get_col_spec(self): |
---|
137 | if self.precision is None: |
---|
138 | return "NUMERIC" |
---|
139 | else: |
---|
140 | return "NUMERIC(%(precision)s, %(scale)s)" % {'precision': self.precision, 'scale' : self.scale} |
---|
141 | |
---|
142 | class OracleInteger(sqltypes.Integer): |
---|
143 | def get_col_spec(self): |
---|
144 | return "INTEGER" |
---|
145 | |
---|
146 | class OracleSmallInteger(sqltypes.Smallinteger): |
---|
147 | def get_col_spec(self): |
---|
148 | return "SMALLINT" |
---|
149 | |
---|
150 | class OracleDate(sqltypes.Date): |
---|
151 | def get_col_spec(self): |
---|
152 | return "DATE" |
---|
153 | def bind_processor(self, dialect): |
---|
154 | return None |
---|
155 | |
---|
156 | def result_processor(self, dialect): |
---|
157 | def process(value): |
---|
158 | if not isinstance(value, datetime.datetime): |
---|
159 | return value |
---|
160 | else: |
---|
161 | return value.date() |
---|
162 | return process |
---|
163 | |
---|
164 | class OracleDateTime(sqltypes.DateTime): |
---|
165 | def get_col_spec(self): |
---|
166 | return "DATE" |
---|
167 | |
---|
168 | def result_processor(self, dialect): |
---|
169 | def process(value): |
---|
170 | if value is None or isinstance(value, datetime.datetime): |
---|
171 | return value |
---|
172 | else: |
---|
173 | # convert cx_oracle datetime object returned pre-python 2.4 |
---|
174 | return datetime.datetime(value.year, value.month, |
---|
175 | value.day,value.hour, value.minute, value.second) |
---|
176 | return process |
---|
177 | |
---|
178 | # Note: |
---|
179 | # Oracle DATE == DATETIME |
---|
180 | # Oracle does not allow milliseconds in DATE |
---|
181 | # Oracle does not support TIME columns |
---|
182 | |
---|
183 | # only if cx_oracle contains TIMESTAMP |
---|
184 | class OracleTimestamp(sqltypes.TIMESTAMP): |
---|
185 | def get_col_spec(self): |
---|
186 | return "TIMESTAMP" |
---|
187 | |
---|
188 | def get_dbapi_type(self, dialect): |
---|
189 | return dialect.TIMESTAMP |
---|
190 | |
---|
191 | def result_processor(self, dialect): |
---|
192 | def process(value): |
---|
193 | if value is None or isinstance(value, datetime.datetime): |
---|
194 | return value |
---|
195 | else: |
---|
196 | # convert cx_oracle datetime object returned pre-python 2.4 |
---|
197 | return datetime.datetime(value.year, value.month, |
---|
198 | value.day,value.hour, value.minute, value.second) |
---|
199 | return process |
---|
200 | |
---|
201 | class OracleString(sqltypes.String): |
---|
202 | def get_col_spec(self): |
---|
203 | return "VARCHAR(%(length)s)" % {'length' : self.length} |
---|
204 | |
---|
205 | class OracleNVarchar(sqltypes.Unicode, OracleString): |
---|
206 | def get_col_spec(self): |
---|
207 | return "NVARCHAR2(%(length)s)" % {'length' : self.length} |
---|
208 | |
---|
209 | class OracleText(sqltypes.Text): |
---|
210 | def get_dbapi_type(self, dbapi): |
---|
211 | return dbapi.CLOB |
---|
212 | |
---|
213 | def get_col_spec(self): |
---|
214 | return "CLOB" |
---|
215 | |
---|
216 | def result_processor(self, dialect): |
---|
217 | super_process = super(OracleText, self).result_processor(dialect) |
---|
218 | if not dialect.auto_convert_lobs: |
---|
219 | return super_process |
---|
220 | lob = dialect.dbapi.LOB |
---|
221 | def process(value): |
---|
222 | if isinstance(value, lob): |
---|
223 | if super_process: |
---|
224 | return super_process(value.read()) |
---|
225 | else: |
---|
226 | return value.read() |
---|
227 | else: |
---|
228 | if super_process: |
---|
229 | return super_process(value) |
---|
230 | else: |
---|
231 | return value |
---|
232 | return process |
---|
233 | |
---|
234 | |
---|
235 | class OracleChar(sqltypes.CHAR): |
---|
236 | def get_col_spec(self): |
---|
237 | return "CHAR(%(length)s)" % {'length' : self.length} |
---|
238 | |
---|
239 | class OracleBinary(sqltypes.Binary): |
---|
240 | def get_dbapi_type(self, dbapi): |
---|
241 | return dbapi.BLOB |
---|
242 | |
---|
243 | def get_col_spec(self): |
---|
244 | return "BLOB" |
---|
245 | |
---|
246 | def bind_processor(self, dialect): |
---|
247 | return None |
---|
248 | |
---|
249 | def result_processor(self, dialect): |
---|
250 | if not dialect.auto_convert_lobs: |
---|
251 | return None |
---|
252 | lob = dialect.dbapi.LOB |
---|
253 | def process(value): |
---|
254 | if isinstance(value, lob): |
---|
255 | return value.read() |
---|
256 | else: |
---|
257 | return value |
---|
258 | return process |
---|
259 | |
---|
260 | class OracleRaw(OracleBinary): |
---|
261 | def get_col_spec(self): |
---|
262 | return "RAW(%(length)s)" % {'length' : self.length} |
---|
263 | |
---|
264 | class OracleBoolean(sqltypes.Boolean): |
---|
265 | def get_col_spec(self): |
---|
266 | return "SMALLINT" |
---|
267 | |
---|
268 | def result_processor(self, dialect): |
---|
269 | def process(value): |
---|
270 | if value is None: |
---|
271 | return None |
---|
272 | return value and True or False |
---|
273 | return process |
---|
274 | |
---|
275 | def bind_processor(self, dialect): |
---|
276 | def process(value): |
---|
277 | if value is True: |
---|
278 | return 1 |
---|
279 | elif value is False: |
---|
280 | return 0 |
---|
281 | elif value is None: |
---|
282 | return None |
---|
283 | else: |
---|
284 | return value and True or False |
---|
285 | return process |
---|
286 | |
---|
287 | colspecs = { |
---|
288 | sqltypes.Integer : OracleInteger, |
---|
289 | sqltypes.Smallinteger : OracleSmallInteger, |
---|
290 | sqltypes.Numeric : OracleNumeric, |
---|
291 | sqltypes.Float : OracleNumeric, |
---|
292 | sqltypes.DateTime : OracleDateTime, |
---|
293 | sqltypes.Date : OracleDate, |
---|
294 | sqltypes.String : OracleString, |
---|
295 | sqltypes.Binary : OracleBinary, |
---|
296 | sqltypes.Boolean : OracleBoolean, |
---|
297 | sqltypes.Text : OracleText, |
---|
298 | sqltypes.TIMESTAMP : OracleTimestamp, |
---|
299 | sqltypes.CHAR: OracleChar, |
---|
300 | } |
---|
301 | |
---|
302 | ischema_names = { |
---|
303 | 'VARCHAR2' : OracleString, |
---|
304 | 'NVARCHAR2' : OracleNVarchar, |
---|
305 | 'CHAR' : OracleString, |
---|
306 | 'DATE' : OracleDateTime, |
---|
307 | 'DATETIME' : OracleDateTime, |
---|
308 | 'NUMBER' : OracleNumeric, |
---|
309 | 'BLOB' : OracleBinary, |
---|
310 | 'BFILE' : OracleBinary, |
---|
311 | 'CLOB' : OracleText, |
---|
312 | 'TIMESTAMP' : OracleTimestamp, |
---|
313 | 'RAW' : OracleRaw, |
---|
314 | 'FLOAT' : OracleNumeric, |
---|
315 | 'DOUBLE PRECISION' : OracleNumeric, |
---|
316 | 'LONG' : OracleText, |
---|
317 | } |
---|
318 | |
---|
319 | class OracleExecutionContext(default.DefaultExecutionContext): |
---|
320 | def pre_exec(self): |
---|
321 | super(OracleExecutionContext, self).pre_exec() |
---|
322 | if self.dialect.auto_setinputsizes: |
---|
323 | self.set_input_sizes() |
---|
324 | if self.compiled_parameters is not None and len(self.compiled_parameters) == 1: |
---|
325 | for key in self.compiled.binds: |
---|
326 | bindparam = self.compiled.binds[key] |
---|
327 | name = self.compiled.bind_names[bindparam] |
---|
328 | value = self.compiled_parameters[0][name] |
---|
329 | if bindparam.isoutparam: |
---|
330 | dbtype = bindparam.type.dialect_impl(self.dialect).get_dbapi_type(self.dialect.dbapi) |
---|
331 | if not hasattr(self, 'out_parameters'): |
---|
332 | self.out_parameters = {} |
---|
333 | self.out_parameters[name] = self.cursor.var(dbtype) |
---|
334 | self.parameters[0][name] = self.out_parameters[name] |
---|
335 | |
---|
336 | def create_cursor(self): |
---|
337 | c = self._connection.connection.cursor() |
---|
338 | if self.dialect.arraysize: |
---|
339 | c.cursor.arraysize = self.dialect.arraysize |
---|
340 | return c |
---|
341 | |
---|
342 | def get_result_proxy(self): |
---|
343 | if hasattr(self, 'out_parameters'): |
---|
344 | if self.compiled_parameters is not None and len(self.compiled_parameters) == 1: |
---|
345 | for bind, name in self.compiled.bind_names.iteritems(): |
---|
346 | if name in self.out_parameters: |
---|
347 | type = bind.type |
---|
348 | result_processor = type.dialect_impl(self.dialect).result_processor(self.dialect) |
---|
349 | if result_processor is not None: |
---|
350 | self.out_parameters[name] = result_processor(self.out_parameters[name].getvalue()) |
---|
351 | else: |
---|
352 | self.out_parameters[name] = self.out_parameters[name].getvalue() |
---|
353 | else: |
---|
354 | for k in self.out_parameters: |
---|
355 | self.out_parameters[k] = self.out_parameters[k].getvalue() |
---|
356 | |
---|
357 | if self.cursor.description is not None: |
---|
358 | for column in self.cursor.description: |
---|
359 | type_code = column[1] |
---|
360 | if type_code in self.dialect.ORACLE_BINARY_TYPES: |
---|
361 | return base.BufferedColumnResultProxy(self) |
---|
362 | |
---|
363 | return base.ResultProxy(self) |
---|
364 | |
---|
365 | class OracleDialect(default.DefaultDialect): |
---|
366 | name = 'oracle' |
---|
367 | supports_alter = True |
---|
368 | supports_unicode_statements = False |
---|
369 | max_identifier_length = 30 |
---|
370 | supports_sane_rowcount = True |
---|
371 | supports_sane_multi_rowcount = False |
---|
372 | preexecute_pk_sequences = True |
---|
373 | supports_pk_autoincrement = False |
---|
374 | default_paramstyle = 'named' |
---|
375 | |
---|
376 | def __init__(self, use_ansi=True, auto_setinputsizes=True, auto_convert_lobs=True, threaded=True, allow_twophase=True, optimize_limits=False, arraysize=50, **kwargs): |
---|
377 | default.DefaultDialect.__init__(self, **kwargs) |
---|
378 | self.use_ansi = use_ansi |
---|
379 | self.threaded = threaded |
---|
380 | self.arraysize = arraysize |
---|
381 | self.allow_twophase = allow_twophase |
---|
382 | self.optimize_limits = optimize_limits |
---|
383 | self.supports_timestamp = self.dbapi is None or hasattr(self.dbapi, 'TIMESTAMP' ) |
---|
384 | self.auto_setinputsizes = auto_setinputsizes |
---|
385 | self.auto_convert_lobs = auto_convert_lobs |
---|
386 | if self.dbapi is None or not self.auto_convert_lobs or not 'CLOB' in self.dbapi.__dict__: |
---|
387 | self.dbapi_type_map = {} |
---|
388 | self.ORACLE_BINARY_TYPES = [] |
---|
389 | else: |
---|
390 | # only use this for LOB objects. using it for strings, dates |
---|
391 | # etc. leads to a little too much magic, reflection doesn't know if it should |
---|
392 | # expect encoded strings or unicodes, etc. |
---|
393 | self.dbapi_type_map = { |
---|
394 | self.dbapi.CLOB: OracleText(), |
---|
395 | self.dbapi.BLOB: OracleBinary(), |
---|
396 | self.dbapi.BINARY: OracleRaw(), |
---|
397 | } |
---|
398 | self.ORACLE_BINARY_TYPES = [getattr(self.dbapi, k) for k in ["BFILE", "CLOB", "NCLOB", "BLOB"] if hasattr(self.dbapi, k)] |
---|
399 | |
---|
400 | def dbapi(cls): |
---|
401 | import cx_Oracle |
---|
402 | return cx_Oracle |
---|
403 | dbapi = classmethod(dbapi) |
---|
404 | |
---|
405 | def create_connect_args(self, url): |
---|
406 | dialect_opts = dict(url.query) |
---|
407 | for opt in ('use_ansi', 'auto_setinputsizes', 'auto_convert_lobs', |
---|
408 | 'threaded', 'allow_twophase'): |
---|
409 | if opt in dialect_opts: |
---|
410 | util.coerce_kw_type(dialect_opts, opt, bool) |
---|
411 | setattr(self, opt, dialect_opts[opt]) |
---|
412 | |
---|
413 | if url.database: |
---|
414 | # if we have a database, then we have a remote host |
---|
415 | port = url.port |
---|
416 | if port: |
---|
417 | port = int(port) |
---|
418 | else: |
---|
419 | port = 1521 |
---|
420 | dsn = self.dbapi.makedsn(url.host, port, url.database) |
---|
421 | else: |
---|
422 | # we have a local tnsname |
---|
423 | dsn = url.host |
---|
424 | |
---|
425 | opts = dict( |
---|
426 | user=url.username, |
---|
427 | password=url.password, |
---|
428 | dsn=dsn, |
---|
429 | threaded=self.threaded, |
---|
430 | twophase=self.allow_twophase, |
---|
431 | ) |
---|
432 | if 'mode' in url.query: |
---|
433 | opts['mode'] = url.query['mode'] |
---|
434 | if isinstance(opts['mode'], basestring): |
---|
435 | mode = opts['mode'].upper() |
---|
436 | if mode == 'SYSDBA': |
---|
437 | opts['mode'] = self.dbapi.SYSDBA |
---|
438 | elif mode == 'SYSOPER': |
---|
439 | opts['mode'] = self.dbapi.SYSOPER |
---|
440 | else: |
---|
441 | util.coerce_kw_type(opts, 'mode', int) |
---|
442 | # Can't set 'handle' or 'pool' via URL query args, use connect_args |
---|
443 | |
---|
444 | return ([], opts) |
---|
445 | |
---|
446 | def is_disconnect(self, e): |
---|
447 | if isinstance(e, self.dbapi.InterfaceError): |
---|
448 | return "not connected" in str(e) |
---|
449 | else: |
---|
450 | return "ORA-03114" in str(e) or "ORA-03113" in str(e) |
---|
451 | |
---|
452 | def type_descriptor(self, typeobj): |
---|
453 | return sqltypes.adapt_type(typeobj, colspecs) |
---|
454 | |
---|
455 | def create_xid(self): |
---|
456 | """create a two-phase transaction ID. |
---|
457 | |
---|
458 | this id will be passed to do_begin_twophase(), do_rollback_twophase(), |
---|
459 | do_commit_twophase(). its format is unspecified.""" |
---|
460 | |
---|
461 | id = random.randint(0, 2 ** 128) |
---|
462 | return (0x1234, "%032x" % id, "%032x" % 9) |
---|
463 | |
---|
464 | def do_release_savepoint(self, connection, name): |
---|
465 | # Oracle does not support RELEASE SAVEPOINT |
---|
466 | pass |
---|
467 | |
---|
468 | def do_begin_twophase(self, connection, xid): |
---|
469 | connection.connection.begin(*xid) |
---|
470 | |
---|
471 | def do_prepare_twophase(self, connection, xid): |
---|
472 | connection.connection.prepare() |
---|
473 | |
---|
474 | def do_rollback_twophase(self, connection, xid, is_prepared=True, recover=False): |
---|
475 | self.do_rollback(connection.connection) |
---|
476 | |
---|
477 | def do_commit_twophase(self, connection, xid, is_prepared=True, recover=False): |
---|
478 | self.do_commit(connection.connection) |
---|
479 | |
---|
480 | def do_recover_twophase(self, connection): |
---|
481 | pass |
---|
482 | |
---|
483 | def has_table(self, connection, table_name, schema=None): |
---|
484 | if not schema: |
---|
485 | schema = self.get_default_schema_name(connection) |
---|
486 | cursor = connection.execute("""select table_name from all_tables where table_name=:name and owner=:schema_name""", {'name':self._denormalize_name(table_name), 'schema_name':self._denormalize_name(schema)}) |
---|
487 | return cursor.fetchone() is not None |
---|
488 | |
---|
489 | def has_sequence(self, connection, sequence_name, schema=None): |
---|
490 | if not schema: |
---|
491 | schema = self.get_default_schema_name(connection) |
---|
492 | cursor = connection.execute("""select sequence_name from all_sequences where sequence_name=:name and sequence_owner=:schema_name""", {'name':self._denormalize_name(sequence_name), 'schema_name':self._denormalize_name(schema)}) |
---|
493 | return cursor.fetchone() is not None |
---|
494 | |
---|
495 | def _normalize_name(self, name): |
---|
496 | if name is None: |
---|
497 | return None |
---|
498 | elif name.upper() == name and not self.identifier_preparer._requires_quotes(name.lower().decode(self.encoding)): |
---|
499 | return name.lower().decode(self.encoding) |
---|
500 | else: |
---|
501 | return name.decode(self.encoding) |
---|
502 | |
---|
503 | def _denormalize_name(self, name): |
---|
504 | if name is None: |
---|
505 | return None |
---|
506 | elif name.lower() == name and not self.identifier_preparer._requires_quotes(name.lower()): |
---|
507 | return name.upper().encode(self.encoding) |
---|
508 | else: |
---|
509 | return name.encode(self.encoding) |
---|
510 | |
---|
511 | def get_default_schema_name(self, connection): |
---|
512 | return self._normalize_name(connection.execute('SELECT USER FROM DUAL').scalar()) |
---|
513 | get_default_schema_name = base.connection_memoize( |
---|
514 | ('dialect', 'default_schema_name'))(get_default_schema_name) |
---|
515 | |
---|
516 | def table_names(self, connection, schema): |
---|
517 | # note that table_names() isnt loading DBLINKed or synonym'ed tables |
---|
518 | if schema is None: |
---|
519 | s = "select table_name from all_tables where nvl(tablespace_name, 'no tablespace') NOT IN ('SYSTEM', 'SYSAUX')" |
---|
520 | cursor = connection.execute(s) |
---|
521 | else: |
---|
522 | s = "select table_name from all_tables where nvl(tablespace_name, 'no tablespace') NOT IN ('SYSTEM','SYSAUX') AND OWNER = :owner" |
---|
523 | cursor = connection.execute(s, {'owner': self._denormalize_name(schema)}) |
---|
524 | return [self._normalize_name(row[0]) for row in cursor] |
---|
525 | |
---|
526 | def _resolve_synonym(self, connection, desired_owner=None, desired_synonym=None, desired_table=None): |
---|
527 | """search for a local synonym matching the given desired owner/name. |
---|
528 | |
---|
529 | if desired_owner is None, attempts to locate a distinct owner. |
---|
530 | |
---|
531 | returns the actual name, owner, dblink name, and synonym name if found. |
---|
532 | """ |
---|
533 | |
---|
534 | sql = """select OWNER, TABLE_OWNER, TABLE_NAME, DB_LINK, SYNONYM_NAME |
---|
535 | from ALL_SYNONYMS WHERE """ |
---|
536 | |
---|
537 | clauses = [] |
---|
538 | params = {} |
---|
539 | if desired_synonym: |
---|
540 | clauses.append("SYNONYM_NAME=:synonym_name") |
---|
541 | params['synonym_name'] = desired_synonym |
---|
542 | if desired_owner: |
---|
543 | clauses.append("TABLE_OWNER=:desired_owner") |
---|
544 | params['desired_owner'] = desired_owner |
---|
545 | if desired_table: |
---|
546 | clauses.append("TABLE_NAME=:tname") |
---|
547 | params['tname'] = desired_table |
---|
548 | |
---|
549 | sql += " AND ".join(clauses) |
---|
550 | |
---|
551 | result = connection.execute(sql, **params) |
---|
552 | if desired_owner: |
---|
553 | row = result.fetchone() |
---|
554 | if row: |
---|
555 | return row['TABLE_NAME'], row['TABLE_OWNER'], row['DB_LINK'], row['SYNONYM_NAME'] |
---|
556 | else: |
---|
557 | return None, None, None, None |
---|
558 | else: |
---|
559 | rows = result.fetchall() |
---|
560 | if len(rows) > 1: |
---|
561 | raise AssertionError("There are multiple tables visible to the schema, you must specify owner") |
---|
562 | elif len(rows) == 1: |
---|
563 | row = rows[0] |
---|
564 | return row['TABLE_NAME'], row['TABLE_OWNER'], row['DB_LINK'], row['SYNONYM_NAME'] |
---|
565 | else: |
---|
566 | return None, None, None, None |
---|
567 | |
---|
568 | def reflecttable(self, connection, table, include_columns): |
---|
569 | preparer = self.identifier_preparer |
---|
570 | |
---|
571 | resolve_synonyms = table.kwargs.get('oracle_resolve_synonyms', False) |
---|
572 | |
---|
573 | if resolve_synonyms: |
---|
574 | actual_name, owner, dblink, synonym = self._resolve_synonym(connection, desired_owner=self._denormalize_name(table.schema), desired_synonym=self._denormalize_name(table.name)) |
---|
575 | else: |
---|
576 | actual_name, owner, dblink, synonym = None, None, None, None |
---|
577 | |
---|
578 | if not actual_name: |
---|
579 | actual_name = self._denormalize_name(table.name) |
---|
580 | if not dblink: |
---|
581 | dblink = '' |
---|
582 | if not owner: |
---|
583 | owner = self._denormalize_name(table.schema or self.get_default_schema_name(connection)) |
---|
584 | |
---|
585 | c = connection.execute ("select COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE, NULLABLE, DATA_DEFAULT from ALL_TAB_COLUMNS%(dblink)s where TABLE_NAME = :table_name and OWNER = :owner" % {'dblink':dblink}, {'table_name':actual_name, 'owner':owner}) |
---|
586 | |
---|
587 | while True: |
---|
588 | row = c.fetchone() |
---|
589 | if row is None: |
---|
590 | break |
---|
591 | |
---|
592 | (colname, coltype, length, precision, scale, nullable, default) = (self._normalize_name(row[0]), row[1], row[2], row[3], row[4], row[5]=='Y', row[6]) |
---|
593 | |
---|
594 | if include_columns and colname not in include_columns: |
---|
595 | continue |
---|
596 | |
---|
597 | # INTEGER if the scale is 0 and precision is null |
---|
598 | # NUMBER if the scale and precision are both null |
---|
599 | # NUMBER(9,2) if the precision is 9 and the scale is 2 |
---|
600 | # NUMBER(3) if the precision is 3 and scale is 0 |
---|
601 | #length is ignored except for CHAR and VARCHAR2 |
---|
602 | if coltype == 'NUMBER' : |
---|
603 | if precision is None and scale is None: |
---|
604 | coltype = OracleNumeric |
---|
605 | elif precision is None and scale == 0 : |
---|
606 | coltype = OracleInteger |
---|
607 | else : |
---|
608 | coltype = OracleNumeric(precision, scale) |
---|
609 | elif coltype=='CHAR' or coltype=='VARCHAR2': |
---|
610 | coltype = ischema_names.get(coltype, OracleString)(length) |
---|
611 | else: |
---|
612 | coltype = re.sub(r'\(\d+\)', '', coltype) |
---|
613 | try: |
---|
614 | coltype = ischema_names[coltype] |
---|
615 | except KeyError: |
---|
616 | util.warn("Did not recognize type '%s' of column '%s'" % |
---|
617 | (coltype, colname)) |
---|
618 | coltype = sqltypes.NULLTYPE |
---|
619 | |
---|
620 | colargs = [] |
---|
621 | if default is not None: |
---|
622 | colargs.append(schema.DefaultClause(sql.text(default))) |
---|
623 | |
---|
624 | table.append_column(schema.Column(colname, coltype, nullable=nullable, *colargs)) |
---|
625 | |
---|
626 | if not table.columns: |
---|
627 | raise AssertionError("Couldn't find any column information for table %s" % actual_name) |
---|
628 | |
---|
629 | c = connection.execute("""SELECT |
---|
630 | ac.constraint_name, |
---|
631 | ac.constraint_type, |
---|
632 | loc.column_name AS local_column, |
---|
633 | rem.table_name AS remote_table, |
---|
634 | rem.column_name AS remote_column, |
---|
635 | rem.owner AS remote_owner |
---|
636 | FROM all_constraints%(dblink)s ac, |
---|
637 | all_cons_columns%(dblink)s loc, |
---|
638 | all_cons_columns%(dblink)s rem |
---|
639 | WHERE ac.table_name = :table_name |
---|
640 | AND ac.constraint_type IN ('R','P') |
---|
641 | AND ac.owner = :owner |
---|
642 | AND ac.owner = loc.owner |
---|
643 | AND ac.constraint_name = loc.constraint_name |
---|
644 | AND ac.r_owner = rem.owner(+) |
---|
645 | AND ac.r_constraint_name = rem.constraint_name(+) |
---|
646 | -- order multiple primary keys correctly |
---|
647 | ORDER BY ac.constraint_name, loc.position, rem.position""" |
---|
648 | % {'dblink':dblink}, {'table_name' : actual_name, 'owner' : owner}) |
---|
649 | |
---|
650 | fks = {} |
---|
651 | while True: |
---|
652 | row = c.fetchone() |
---|
653 | if row is None: |
---|
654 | break |
---|
655 | #print "ROW:" , row |
---|
656 | (cons_name, cons_type, local_column, remote_table, remote_column, remote_owner) = row[0:2] + tuple([self._normalize_name(x) for x in row[2:]]) |
---|
657 | if cons_type == 'P': |
---|
658 | table.primary_key.add(table.c[local_column]) |
---|
659 | elif cons_type == 'R': |
---|
660 | try: |
---|
661 | fk = fks[cons_name] |
---|
662 | except KeyError: |
---|
663 | fk = ([], []) |
---|
664 | fks[cons_name] = fk |
---|
665 | if remote_table is None: |
---|
666 | # ticket 363 |
---|
667 | util.warn( |
---|
668 | ("Got 'None' querying 'table_name' from " |
---|
669 | "all_cons_columns%(dblink)s - does the user have " |
---|
670 | "proper rights to the table?") % {'dblink':dblink}) |
---|
671 | continue |
---|
672 | |
---|
673 | if resolve_synonyms: |
---|
674 | ref_remote_name, ref_remote_owner, ref_dblink, ref_synonym = self._resolve_synonym(connection, desired_owner=self._denormalize_name(remote_owner), desired_table=self._denormalize_name(remote_table)) |
---|
675 | if ref_synonym: |
---|
676 | remote_table = self._normalize_name(ref_synonym) |
---|
677 | remote_owner = self._normalize_name(ref_remote_owner) |
---|
678 | |
---|
679 | if not table.schema and self._denormalize_name(remote_owner) == owner: |
---|
680 | refspec = ".".join([remote_table, remote_column]) |
---|
681 | t = schema.Table(remote_table, table.metadata, autoload=True, autoload_with=connection, oracle_resolve_synonyms=resolve_synonyms, useexisting=True) |
---|
682 | else: |
---|
683 | refspec = ".".join([x for x in [remote_owner, remote_table, remote_column] if x]) |
---|
684 | t = schema.Table(remote_table, table.metadata, autoload=True, autoload_with=connection, schema=remote_owner, oracle_resolve_synonyms=resolve_synonyms, useexisting=True) |
---|
685 | |
---|
686 | if local_column not in fk[0]: |
---|
687 | fk[0].append(local_column) |
---|
688 | if refspec not in fk[1]: |
---|
689 | fk[1].append(refspec) |
---|
690 | |
---|
691 | for name, value in fks.iteritems(): |
---|
692 | table.append_constraint(schema.ForeignKeyConstraint(value[0], value[1], name=name, link_to_name=True)) |
---|
693 | |
---|
694 | |
---|
695 | class _OuterJoinColumn(sql.ClauseElement): |
---|
696 | __visit_name__ = 'outer_join_column' |
---|
697 | |
---|
698 | def __init__(self, column): |
---|
699 | self.column = column |
---|
700 | |
---|
701 | class OracleCompiler(compiler.DefaultCompiler): |
---|
702 | """Oracle compiler modifies the lexical structure of Select |
---|
703 | statements to work under non-ANSI configured Oracle databases, if |
---|
704 | the use_ansi flag is False. |
---|
705 | """ |
---|
706 | |
---|
707 | operators = compiler.DefaultCompiler.operators.copy() |
---|
708 | operators.update( |
---|
709 | { |
---|
710 | sql_operators.mod : lambda x, y:"mod(%s, %s)" % (x, y), |
---|
711 | sql_operators.match_op: lambda x, y: "CONTAINS (%s, %s)" % (x, y) |
---|
712 | } |
---|
713 | ) |
---|
714 | |
---|
715 | functions = compiler.DefaultCompiler.functions.copy() |
---|
716 | functions.update ( |
---|
717 | { |
---|
718 | sql_functions.now : 'CURRENT_TIMESTAMP' |
---|
719 | } |
---|
720 | ) |
---|
721 | |
---|
722 | def __init__(self, *args, **kwargs): |
---|
723 | super(OracleCompiler, self).__init__(*args, **kwargs) |
---|
724 | self.__wheres = {} |
---|
725 | |
---|
726 | def default_from(self): |
---|
727 | """Called when a ``SELECT`` statement has no froms, and no ``FROM`` clause is to be appended. |
---|
728 | |
---|
729 | The Oracle compiler tacks a "FROM DUAL" to the statement. |
---|
730 | """ |
---|
731 | |
---|
732 | return " FROM DUAL" |
---|
733 | |
---|
734 | def apply_function_parens(self, func): |
---|
735 | return len(func.clauses) > 0 |
---|
736 | |
---|
737 | def visit_join(self, join, **kwargs): |
---|
738 | if self.dialect.use_ansi: |
---|
739 | return compiler.DefaultCompiler.visit_join(self, join, **kwargs) |
---|
740 | else: |
---|
741 | return self.process(join.left, asfrom=True) + ", " + self.process(join.right, asfrom=True) |
---|
742 | |
---|
743 | def _get_nonansi_join_whereclause(self, froms): |
---|
744 | clauses = [] |
---|
745 | |
---|
746 | def visit_join(join): |
---|
747 | if join.isouter: |
---|
748 | def visit_binary(binary): |
---|
749 | if binary.operator == sql_operators.eq: |
---|
750 | if binary.left.table is join.right: |
---|
751 | binary.left = _OuterJoinColumn(binary.left) |
---|
752 | elif binary.right.table is join.right: |
---|
753 | binary.right = _OuterJoinColumn(binary.right) |
---|
754 | clauses.append(visitors.cloned_traverse(join.onclause, {}, {'binary':visit_binary})) |
---|
755 | else: |
---|
756 | clauses.append(join.onclause) |
---|
757 | |
---|
758 | for f in froms: |
---|
759 | visitors.traverse(f, {}, {'join':visit_join}) |
---|
760 | return sql.and_(*clauses) |
---|
761 | |
---|
762 | def visit_outer_join_column(self, vc): |
---|
763 | return self.process(vc.column) + "(+)" |
---|
764 | |
---|
765 | def visit_sequence(self, seq): |
---|
766 | return self.dialect.identifier_preparer.format_sequence(seq) + ".nextval" |
---|
767 | |
---|
768 | def visit_alias(self, alias, asfrom=False, **kwargs): |
---|
769 | """Oracle doesn't like ``FROM table AS alias``. Is the AS standard SQL??""" |
---|
770 | |
---|
771 | if asfrom: |
---|
772 | alias_name = isinstance(alias.name, expression._generated_label) and \ |
---|
773 | self._truncated_identifier("alias", alias.name) or alias.name |
---|
774 | |
---|
775 | return self.process(alias.original, asfrom=True, **kwargs) + " " +\ |
---|
776 | self.preparer.format_alias(alias, alias_name) |
---|
777 | else: |
---|
778 | return self.process(alias.original, **kwargs) |
---|
779 | |
---|
780 | def _TODO_visit_compound_select(self, select): |
---|
781 | """Need to determine how to get ``LIMIT``/``OFFSET`` into a ``UNION`` for Oracle.""" |
---|
782 | pass |
---|
783 | |
---|
784 | def visit_select(self, select, **kwargs): |
---|
785 | """Look for ``LIMIT`` and OFFSET in a select statement, and if |
---|
786 | so tries to wrap it in a subquery with ``rownum`` criterion. |
---|
787 | """ |
---|
788 | |
---|
789 | if not getattr(select, '_oracle_visit', None): |
---|
790 | if not self.dialect.use_ansi: |
---|
791 | if self.stack and 'from' in self.stack[-1]: |
---|
792 | existingfroms = self.stack[-1]['from'] |
---|
793 | else: |
---|
794 | existingfroms = None |
---|
795 | |
---|
796 | froms = select._get_display_froms(existingfroms) |
---|
797 | whereclause = self._get_nonansi_join_whereclause(froms) |
---|
798 | if whereclause: |
---|
799 | select = select.where(whereclause) |
---|
800 | select._oracle_visit = True |
---|
801 | |
---|
802 | if select._limit is not None or select._offset is not None: |
---|
803 | # See http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html |
---|
804 | # |
---|
805 | # Generalized form of an Oracle pagination query: |
---|
806 | # select ... from ( |
---|
807 | # select /*+ FIRST_ROWS(N) */ ...., rownum as ora_rn from ( |
---|
808 | # select distinct ... where ... order by ... |
---|
809 | # ) where ROWNUM <= :limit+:offset |
---|
810 | # ) where ora_rn > :offset |
---|
811 | # Outer select and "ROWNUM as ora_rn" can be dropped if limit=0 |
---|
812 | |
---|
813 | # TODO: use annotations instead of clone + attr set ? |
---|
814 | select = select._generate() |
---|
815 | select._oracle_visit = True |
---|
816 | |
---|
817 | # Wrap the middle select and add the hint |
---|
818 | limitselect = sql.select([c for c in select.c]) |
---|
819 | if select._limit and self.dialect.optimize_limits: |
---|
820 | limitselect = limitselect.prefix_with("/*+ FIRST_ROWS(%d) */" % select._limit) |
---|
821 | |
---|
822 | limitselect._oracle_visit = True |
---|
823 | limitselect._is_wrapper = True |
---|
824 | |
---|
825 | # If needed, add the limiting clause |
---|
826 | if select._limit is not None: |
---|
827 | max_row = select._limit |
---|
828 | if select._offset is not None: |
---|
829 | max_row += select._offset |
---|
830 | limitselect.append_whereclause( |
---|
831 | sql.literal_column("ROWNUM")<=max_row) |
---|
832 | |
---|
833 | # If needed, add the ora_rn, and wrap again with offset. |
---|
834 | if select._offset is None: |
---|
835 | select = limitselect |
---|
836 | else: |
---|
837 | limitselect = limitselect.column( |
---|
838 | sql.literal_column("ROWNUM").label("ora_rn")) |
---|
839 | limitselect._oracle_visit = True |
---|
840 | limitselect._is_wrapper = True |
---|
841 | |
---|
842 | offsetselect = sql.select( |
---|
843 | [c for c in limitselect.c if c.key!='ora_rn']) |
---|
844 | offsetselect._oracle_visit = True |
---|
845 | offsetselect._is_wrapper = True |
---|
846 | |
---|
847 | offsetselect.append_whereclause( |
---|
848 | sql.literal_column("ora_rn")>select._offset) |
---|
849 | |
---|
850 | select = offsetselect |
---|
851 | |
---|
852 | kwargs['iswrapper'] = getattr(select, '_is_wrapper', False) |
---|
853 | return compiler.DefaultCompiler.visit_select(self, select, **kwargs) |
---|
854 | |
---|
855 | def limit_clause(self, select): |
---|
856 | return "" |
---|
857 | |
---|
858 | def for_update_clause(self, select): |
---|
859 | if select.for_update == "nowait": |
---|
860 | return " FOR UPDATE NOWAIT" |
---|
861 | else: |
---|
862 | return super(OracleCompiler, self).for_update_clause(select) |
---|
863 | |
---|
864 | |
---|
865 | class OracleSchemaGenerator(compiler.SchemaGenerator): |
---|
866 | def get_column_specification(self, column, **kwargs): |
---|
867 | colspec = self.preparer.format_column(column) |
---|
868 | colspec += " " + column.type.dialect_impl(self.dialect).get_col_spec() |
---|
869 | default = self.get_column_default_string(column) |
---|
870 | if default is not None: |
---|
871 | colspec += " DEFAULT " + default |
---|
872 | |
---|
873 | if not column.nullable: |
---|
874 | colspec += " NOT NULL" |
---|
875 | return colspec |
---|
876 | |
---|
877 | def visit_sequence(self, sequence): |
---|
878 | if not self.checkfirst or not self.dialect.has_sequence(self.connection, sequence.name, sequence.schema): |
---|
879 | self.append("CREATE SEQUENCE %s" % self.preparer.format_sequence(sequence)) |
---|
880 | self.execute() |
---|
881 | |
---|
882 | class OracleSchemaDropper(compiler.SchemaDropper): |
---|
883 | def visit_sequence(self, sequence): |
---|
884 | if not self.checkfirst or self.dialect.has_sequence(self.connection, sequence.name, sequence.schema): |
---|
885 | self.append("DROP SEQUENCE %s" % self.preparer.format_sequence(sequence)) |
---|
886 | self.execute() |
---|
887 | |
---|
888 | class OracleDefaultRunner(base.DefaultRunner): |
---|
889 | def visit_sequence(self, seq): |
---|
890 | return self.execute_string("SELECT " + self.dialect.identifier_preparer.format_sequence(seq) + ".nextval FROM DUAL", {}) |
---|
891 | |
---|
892 | class OracleIdentifierPreparer(compiler.IdentifierPreparer): |
---|
893 | def format_savepoint(self, savepoint): |
---|
894 | name = re.sub(r'^_+', '', savepoint.ident) |
---|
895 | return super(OracleIdentifierPreparer, self).format_savepoint(savepoint, name) |
---|
896 | |
---|
897 | |
---|
898 | dialect = OracleDialect |
---|
899 | dialect.statement_compiler = OracleCompiler |
---|
900 | dialect.schemagenerator = OracleSchemaGenerator |
---|
901 | dialect.schemadropper = OracleSchemaDropper |
---|
902 | dialect.preparer = OracleIdentifierPreparer |
---|
903 | dialect.defaultrunner = OracleDefaultRunner |
---|
904 | dialect.execution_ctx_cls = OracleExecutionContext |
---|