| 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 |
|---|