PageRenderTime 63ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/SQLAlchemy-0.7.8/lib/sqlalchemy/dialects/oracle/cx_oracle.py

#
Python | 757 lines | 663 code | 27 blank | 67 comment | 41 complexity | 9d1fd35ee385210896819d51080e4c38 MD5 | raw file
  1. # oracle/cx_oracle.py
  2. # Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
  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 via the cx_oracle driver.
  7. Driver
  8. ------
  9. The Oracle dialect uses the cx_oracle driver, available at
  10. http://cx-oracle.sourceforge.net/ . The dialect has several behaviors
  11. which are specifically tailored towards compatibility with this module.
  12. Version 5.0 or greater is **strongly** recommended, as SQLAlchemy makes
  13. extensive use of the cx_oracle output converters for numeric and
  14. string conversions.
  15. Connecting
  16. ----------
  17. Connecting with create_engine() uses the standard URL approach of
  18. ``oracle://user:pass@host:port/dbname[?key=value&key=value...]``. If dbname is present, the
  19. host, port, and dbname tokens are converted to a TNS name using the cx_oracle
  20. :func:`makedsn()` function. Otherwise, the host token is taken directly as a TNS name.
  21. Additional arguments which may be specified either as query string arguments on the
  22. URL, or as keyword arguments to :func:`~sqlalchemy.create_engine()` are:
  23. * *allow_twophase* - enable two-phase transactions. Defaults to ``True``.
  24. * *arraysize* - set the cx_oracle.arraysize value on cursors, in SQLAlchemy
  25. it defaults to 50. See the section on "LOB Objects" below.
  26. * *auto_convert_lobs* - defaults to True, see the section on LOB objects.
  27. * *auto_setinputsizes* - the cx_oracle.setinputsizes() call is issued for all bind parameters.
  28. This is required for LOB datatypes but can be disabled to reduce overhead. Defaults
  29. to ``True``.
  30. * *mode* - This is given the string value of SYSDBA or SYSOPER, or alternatively an
  31. integer value. This value is only available as a URL query string argument.
  32. * *threaded* - enable multithreaded access to cx_oracle connections. Defaults
  33. to ``True``. Note that this is the opposite default of cx_oracle itself.
  34. Unicode
  35. -------
  36. cx_oracle 5 fully supports Python unicode objects. SQLAlchemy will pass
  37. all unicode strings directly to cx_oracle, and additionally uses an output
  38. handler so that all string based result values are returned as unicode as well.
  39. Generally, the ``NLS_LANG`` environment variable determines the nature
  40. of the encoding to be used.
  41. Note that this behavior is disabled when Oracle 8 is detected, as it has been
  42. observed that issues remain when passing Python unicodes to cx_oracle with Oracle 8.
  43. LOB Objects
  44. -----------
  45. cx_oracle returns oracle LOBs using the cx_oracle.LOB object. SQLAlchemy converts
  46. these to strings so that the interface of the Binary type is consistent with that of
  47. other backends, and so that the linkage to a live cursor is not needed in scenarios
  48. like result.fetchmany() and result.fetchall(). This means that by default, LOB
  49. objects are fully fetched unconditionally by SQLAlchemy, and the linkage to a live
  50. cursor is broken.
  51. To disable this processing, pass ``auto_convert_lobs=False`` to :func:`create_engine()`.
  52. Two Phase Transaction Support
  53. -----------------------------
  54. Two Phase transactions are implemented using XA transactions. Success has been reported
  55. with this feature but it should be regarded as experimental.
  56. Precision Numerics
  57. ------------------
  58. The SQLAlchemy dialect goes through a lot of steps to ensure
  59. that decimal numbers are sent and received with full accuracy.
  60. An "outputtypehandler" callable is associated with each
  61. cx_oracle connection object which detects numeric types and
  62. receives them as string values, instead of receiving a Python
  63. ``float`` directly, which is then passed to the Python
  64. ``Decimal`` constructor. The :class:`.Numeric` and
  65. :class:`.Float` types under the cx_oracle dialect are aware of
  66. this behavior, and will coerce the ``Decimal`` to ``float`` if
  67. the ``asdecimal`` flag is ``False`` (default on :class:`.Float`,
  68. optional on :class:`.Numeric`).
  69. Because the handler coerces to ``Decimal`` in all cases first,
  70. the feature can detract significantly from performance.
  71. If precision numerics aren't required, the decimal handling
  72. can be disabled by passing the flag ``coerce_to_decimal=False``
  73. to :func:`.create_engine`::
  74. engine = create_engine("oracle+cx_oracle://dsn",
  75. coerce_to_decimal=False)
  76. .. versionadded:: 0.7.6
  77. Add the ``coerce_to_decimal`` flag.
  78. Another alternative to performance is to use the
  79. `cdecimal <http://pypi.python.org/pypi/cdecimal/>`_ library;
  80. see :class:`.Numeric` for additional notes.
  81. The handler attempts to use the "precision" and "scale"
  82. attributes of the result set column to best determine if
  83. subsequent incoming values should be received as ``Decimal`` as
  84. opposed to int (in which case no processing is added). There are
  85. several scenarios where OCI_ does not provide unambiguous data
  86. as to the numeric type, including some situations where
  87. individual rows may return a combination of floating point and
  88. integer values. Certain values for "precision" and "scale" have
  89. been observed to determine this scenario. When it occurs, the
  90. outputtypehandler receives as string and then passes off to a
  91. processing function which detects, for each returned value, if a
  92. decimal point is present, and if so converts to ``Decimal``,
  93. otherwise to int. The intention is that simple int-based
  94. statements like "SELECT my_seq.nextval() FROM DUAL" continue to
  95. return ints and not ``Decimal`` objects, and that any kind of
  96. floating point value is received as a string so that there is no
  97. floating point loss of precision.
  98. The "decimal point is present" logic itself is also sensitive to
  99. locale. Under OCI_, this is controlled by the NLS_LANG
  100. environment variable. Upon first connection, the dialect runs a
  101. test to determine the current "decimal" character, which can be
  102. a comma "," for european locales. From that point forward the
  103. outputtypehandler uses that character to represent a decimal
  104. point. Note that cx_oracle 5.0.3 or greater is required
  105. when dealing with numerics with locale settings that don't use
  106. a period "." as the decimal character.
  107. .. versionchanged:: 0.6.6
  108. The outputtypehandler uses a comma "," character to represent
  109. a decimal point.
  110. .. _OCI: http://www.oracle.com/technetwork/database/features/oci/index.html
  111. """
  112. from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \
  113. RESERVED_WORDS, OracleExecutionContext
  114. from sqlalchemy.dialects.oracle import base as oracle
  115. from sqlalchemy.engine import base
  116. from sqlalchemy import types as sqltypes, util, exc, processors
  117. from datetime import datetime
  118. import random
  119. import collections
  120. from sqlalchemy.util.compat import decimal
  121. import re
  122. class _OracleNumeric(sqltypes.Numeric):
  123. def bind_processor(self, dialect):
  124. # cx_oracle accepts Decimal objects and floats
  125. return None
  126. def result_processor(self, dialect, coltype):
  127. # we apply a cx_oracle type handler to all connections
  128. # that converts floating point strings to Decimal().
  129. # However, in some subquery situations, Oracle doesn't
  130. # give us enough information to determine int or Decimal.
  131. # It could even be int/Decimal differently on each row,
  132. # regardless of the scale given for the originating type.
  133. # So we still need an old school isinstance() handler
  134. # here for decimals.
  135. if dialect.supports_native_decimal:
  136. if self.asdecimal:
  137. if self.scale is None:
  138. fstring = "%.10f"
  139. else:
  140. fstring = "%%.%df" % self.scale
  141. def to_decimal(value):
  142. if value is None:
  143. return None
  144. elif isinstance(value, decimal.Decimal):
  145. return value
  146. else:
  147. return decimal.Decimal(fstring % value)
  148. return to_decimal
  149. else:
  150. if self.precision is None and self.scale is None:
  151. return processors.to_float
  152. elif not getattr(self, '_is_oracle_number', False) \
  153. and self.scale is not None:
  154. return processors.to_float
  155. else:
  156. return None
  157. else:
  158. # cx_oracle 4 behavior, will assume
  159. # floats
  160. return super(_OracleNumeric, self).\
  161. result_processor(dialect, coltype)
  162. class _OracleDate(sqltypes.Date):
  163. def bind_processor(self, dialect):
  164. return None
  165. def result_processor(self, dialect, coltype):
  166. def process(value):
  167. if value is not None:
  168. return value.date()
  169. else:
  170. return value
  171. return process
  172. class _LOBMixin(object):
  173. def result_processor(self, dialect, coltype):
  174. if not dialect.auto_convert_lobs:
  175. # return the cx_oracle.LOB directly.
  176. return None
  177. def process(value):
  178. if value is not None:
  179. return value.read()
  180. else:
  181. return value
  182. return process
  183. class _NativeUnicodeMixin(object):
  184. # Py3K
  185. #pass
  186. # Py2K
  187. def bind_processor(self, dialect):
  188. if dialect._cx_oracle_with_unicode:
  189. def process(value):
  190. if value is None:
  191. return value
  192. else:
  193. return unicode(value)
  194. return process
  195. else:
  196. return super(_NativeUnicodeMixin, self).bind_processor(dialect)
  197. # end Py2K
  198. # we apply a connection output handler that returns
  199. # unicode in all cases, so the "native_unicode" flag
  200. # will be set for the default String.result_processor.
  201. class _OracleChar(_NativeUnicodeMixin, sqltypes.CHAR):
  202. def get_dbapi_type(self, dbapi):
  203. return dbapi.FIXED_CHAR
  204. class _OracleNVarChar(_NativeUnicodeMixin, sqltypes.NVARCHAR):
  205. def get_dbapi_type(self, dbapi):
  206. return getattr(dbapi, 'UNICODE', dbapi.STRING)
  207. class _OracleText(_LOBMixin, sqltypes.Text):
  208. def get_dbapi_type(self, dbapi):
  209. return dbapi.CLOB
  210. class _OracleString(_NativeUnicodeMixin, sqltypes.String):
  211. pass
  212. class _OracleUnicodeText(_LOBMixin, _NativeUnicodeMixin, sqltypes.UnicodeText):
  213. def get_dbapi_type(self, dbapi):
  214. return dbapi.NCLOB
  215. def result_processor(self, dialect, coltype):
  216. lob_processor = _LOBMixin.result_processor(self, dialect, coltype)
  217. if lob_processor is None:
  218. return None
  219. string_processor = sqltypes.UnicodeText.result_processor(self, dialect, coltype)
  220. if string_processor is None:
  221. return lob_processor
  222. else:
  223. def process(value):
  224. return string_processor(lob_processor(value))
  225. return process
  226. class _OracleInteger(sqltypes.Integer):
  227. def result_processor(self, dialect, coltype):
  228. def to_int(val):
  229. if val is not None:
  230. val = int(val)
  231. return val
  232. return to_int
  233. class _OracleBinary(_LOBMixin, sqltypes.LargeBinary):
  234. def get_dbapi_type(self, dbapi):
  235. return dbapi.BLOB
  236. def bind_processor(self, dialect):
  237. return None
  238. class _OracleInterval(oracle.INTERVAL):
  239. def get_dbapi_type(self, dbapi):
  240. return dbapi.INTERVAL
  241. class _OracleRaw(oracle.RAW):
  242. pass
  243. class _OracleRowid(oracle.ROWID):
  244. def get_dbapi_type(self, dbapi):
  245. return dbapi.ROWID
  246. class OracleCompiler_cx_oracle(OracleCompiler):
  247. def bindparam_string(self, name):
  248. if self.preparer._bindparam_requires_quotes(name):
  249. quoted_name = '"%s"' % name
  250. self._quoted_bind_names[name] = quoted_name
  251. return OracleCompiler.bindparam_string(self, quoted_name)
  252. else:
  253. return OracleCompiler.bindparam_string(self, name)
  254. class OracleExecutionContext_cx_oracle(OracleExecutionContext):
  255. def pre_exec(self):
  256. quoted_bind_names = \
  257. getattr(self.compiled, '_quoted_bind_names', None)
  258. if quoted_bind_names:
  259. if not self.dialect.supports_unicode_statements:
  260. # if DBAPI doesn't accept unicode statements,
  261. # keys in self.parameters would have been encoded
  262. # here. so convert names in quoted_bind_names
  263. # to encoded as well.
  264. quoted_bind_names = \
  265. dict(
  266. (fromname.encode(self.dialect.encoding),
  267. toname.encode(self.dialect.encoding))
  268. for fromname, toname in
  269. quoted_bind_names.items()
  270. )
  271. for param in self.parameters:
  272. for fromname, toname in quoted_bind_names.items():
  273. param[toname] = param[fromname]
  274. del param[fromname]
  275. if self.dialect.auto_setinputsizes:
  276. # cx_oracle really has issues when you setinputsizes
  277. # on String, including that outparams/RETURNING
  278. # breaks for varchars
  279. self.set_input_sizes(quoted_bind_names,
  280. exclude_types=self.dialect._cx_oracle_string_types
  281. )
  282. # if a single execute, check for outparams
  283. if len(self.compiled_parameters) == 1:
  284. for bindparam in self.compiled.binds.values():
  285. if bindparam.isoutparam:
  286. dbtype = bindparam.type.dialect_impl(self.dialect).\
  287. get_dbapi_type(self.dialect.dbapi)
  288. if not hasattr(self, 'out_parameters'):
  289. self.out_parameters = {}
  290. if dbtype is None:
  291. raise exc.InvalidRequestError("Cannot create out parameter for parameter "
  292. "%r - it's type %r is not supported by"
  293. " cx_oracle" %
  294. (name, bindparam.type)
  295. )
  296. name = self.compiled.bind_names[bindparam]
  297. self.out_parameters[name] = self.cursor.var(dbtype)
  298. self.parameters[0][quoted_bind_names.get(name, name)] = \
  299. self.out_parameters[name]
  300. def create_cursor(self):
  301. c = self._dbapi_connection.cursor()
  302. if self.dialect.arraysize:
  303. c.arraysize = self.dialect.arraysize
  304. return c
  305. def get_result_proxy(self):
  306. if hasattr(self, 'out_parameters') and self.compiled.returning:
  307. returning_params = dict(
  308. (k, v.getvalue())
  309. for k, v in self.out_parameters.items()
  310. )
  311. return ReturningResultProxy(self, returning_params)
  312. result = None
  313. if self.cursor.description is not None:
  314. for column in self.cursor.description:
  315. type_code = column[1]
  316. if type_code in self.dialect._cx_oracle_binary_types:
  317. result = base.BufferedColumnResultProxy(self)
  318. if result is None:
  319. result = base.ResultProxy(self)
  320. if hasattr(self, 'out_parameters'):
  321. if self.compiled_parameters is not None and \
  322. len(self.compiled_parameters) == 1:
  323. result.out_parameters = out_parameters = {}
  324. for bind, name in self.compiled.bind_names.items():
  325. if name in self.out_parameters:
  326. type = bind.type
  327. impl_type = type.dialect_impl(self.dialect)
  328. dbapi_type = impl_type.get_dbapi_type(self.dialect.dbapi)
  329. result_processor = impl_type.\
  330. result_processor(self.dialect,
  331. dbapi_type)
  332. if result_processor is not None:
  333. out_parameters[name] = \
  334. result_processor(self.out_parameters[name].getvalue())
  335. else:
  336. out_parameters[name] = self.out_parameters[name].getvalue()
  337. else:
  338. result.out_parameters = dict(
  339. (k, v.getvalue())
  340. for k, v in self.out_parameters.items()
  341. )
  342. return result
  343. class OracleExecutionContext_cx_oracle_with_unicode(OracleExecutionContext_cx_oracle):
  344. """Support WITH_UNICODE in Python 2.xx.
  345. WITH_UNICODE allows cx_Oracle's Python 3 unicode handling
  346. behavior under Python 2.x. This mode in some cases disallows
  347. and in other cases silently passes corrupted data when
  348. non-Python-unicode strings (a.k.a. plain old Python strings)
  349. are passed as arguments to connect(), the statement sent to execute(),
  350. or any of the bind parameter keys or values sent to execute().
  351. This optional context therefore ensures that all statements are
  352. passed as Python unicode objects.
  353. """
  354. def __init__(self, *arg, **kw):
  355. OracleExecutionContext_cx_oracle.__init__(self, *arg, **kw)
  356. self.statement = unicode(self.statement)
  357. def _execute_scalar(self, stmt):
  358. return super(OracleExecutionContext_cx_oracle_with_unicode, self).\
  359. _execute_scalar(unicode(stmt))
  360. class ReturningResultProxy(base.FullyBufferedResultProxy):
  361. """Result proxy which stuffs the _returning clause + outparams into the fetch."""
  362. def __init__(self, context, returning_params):
  363. self._returning_params = returning_params
  364. super(ReturningResultProxy, self).__init__(context)
  365. def _cursor_description(self):
  366. returning = self.context.compiled.returning
  367. ret = []
  368. for c in returning:
  369. if hasattr(c, 'name'):
  370. ret.append((c.name, c.type))
  371. else:
  372. ret.append((c.anon_label, c.type))
  373. return ret
  374. def _buffer_rows(self):
  375. return collections.deque([tuple(self._returning_params["ret_%d" % i]
  376. for i, c in enumerate(self._returning_params))])
  377. class OracleDialect_cx_oracle(OracleDialect):
  378. execution_ctx_cls = OracleExecutionContext_cx_oracle
  379. statement_compiler = OracleCompiler_cx_oracle
  380. driver = "cx_oracle"
  381. colspecs = colspecs = {
  382. sqltypes.Numeric: _OracleNumeric,
  383. sqltypes.Date : _OracleDate, # generic type, assume datetime.date is desired
  384. oracle.DATE: oracle.DATE, # non generic type - passthru
  385. sqltypes.LargeBinary : _OracleBinary,
  386. sqltypes.Boolean : oracle._OracleBoolean,
  387. sqltypes.Interval : _OracleInterval,
  388. oracle.INTERVAL : _OracleInterval,
  389. sqltypes.Text : _OracleText,
  390. sqltypes.String : _OracleString,
  391. sqltypes.UnicodeText : _OracleUnicodeText,
  392. sqltypes.CHAR : _OracleChar,
  393. sqltypes.Integer : _OracleInteger, # this is only needed for OUT parameters.
  394. # it would be nice if we could not use it otherwise.
  395. oracle.RAW: _OracleRaw,
  396. sqltypes.Unicode: _OracleNVarChar,
  397. sqltypes.NVARCHAR : _OracleNVarChar,
  398. oracle.ROWID: _OracleRowid,
  399. }
  400. execute_sequence_format = list
  401. def __init__(self,
  402. auto_setinputsizes=True,
  403. auto_convert_lobs=True,
  404. threaded=True,
  405. allow_twophase=True,
  406. coerce_to_decimal=True,
  407. arraysize=50, **kwargs):
  408. OracleDialect.__init__(self, **kwargs)
  409. self.threaded = threaded
  410. self.arraysize = arraysize
  411. self.allow_twophase = allow_twophase
  412. self.supports_timestamp = self.dbapi is None or hasattr(self.dbapi, 'TIMESTAMP' )
  413. self.auto_setinputsizes = auto_setinputsizes
  414. self.auto_convert_lobs = auto_convert_lobs
  415. if hasattr(self.dbapi, 'version'):
  416. self.cx_oracle_ver = tuple([int(x) for x in self.dbapi.version.split('.')])
  417. else:
  418. self.cx_oracle_ver = (0, 0, 0)
  419. def types(*names):
  420. return set([
  421. getattr(self.dbapi, name, None) for name in names
  422. ]).difference([None])
  423. self._cx_oracle_string_types = types("STRING", "UNICODE", "NCLOB", "CLOB")
  424. self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
  425. self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
  426. self.supports_unicode_binds = self.cx_oracle_ver >= (5, 0)
  427. self.supports_native_decimal = (
  428. self.cx_oracle_ver >= (5, 0) and
  429. coerce_to_decimal
  430. )
  431. self._cx_oracle_native_nvarchar = self.cx_oracle_ver >= (5, 0)
  432. if self.cx_oracle_ver is None:
  433. # this occurs in tests with mock DBAPIs
  434. self._cx_oracle_string_types = set()
  435. self._cx_oracle_with_unicode = False
  436. elif self.cx_oracle_ver >= (5,) and not hasattr(self.dbapi, 'UNICODE'):
  437. # cx_Oracle WITH_UNICODE mode. *only* python
  438. # unicode objects accepted for anything
  439. self.supports_unicode_statements = True
  440. self.supports_unicode_binds = True
  441. self._cx_oracle_with_unicode = True
  442. # Py2K
  443. # There's really no reason to run with WITH_UNICODE under Python 2.x.
  444. # Give the user a hint.
  445. util.warn("cx_Oracle is compiled under Python 2.xx using the "
  446. "WITH_UNICODE flag. Consider recompiling cx_Oracle without "
  447. "this flag, which is in no way necessary for full support of Unicode. "
  448. "Otherwise, all string-holding bind parameters must "
  449. "be explicitly typed using SQLAlchemy's String type or one of its subtypes,"
  450. "or otherwise be passed as Python unicode. Plain Python strings "
  451. "passed as bind parameters will be silently corrupted by cx_Oracle."
  452. )
  453. self.execution_ctx_cls = OracleExecutionContext_cx_oracle_with_unicode
  454. # end Py2K
  455. else:
  456. self._cx_oracle_with_unicode = False
  457. if self.cx_oracle_ver is None or \
  458. not self.auto_convert_lobs or \
  459. not hasattr(self.dbapi, 'CLOB'):
  460. self.dbapi_type_map = {}
  461. else:
  462. # only use this for LOB objects. using it for strings, dates
  463. # etc. leads to a little too much magic, reflection doesn't know if it should
  464. # expect encoded strings or unicodes, etc.
  465. self.dbapi_type_map = {
  466. self.dbapi.CLOB: oracle.CLOB(),
  467. self.dbapi.NCLOB:oracle.NCLOB(),
  468. self.dbapi.BLOB: oracle.BLOB(),
  469. self.dbapi.BINARY: oracle.RAW(),
  470. }
  471. @classmethod
  472. def dbapi(cls):
  473. import cx_Oracle
  474. return cx_Oracle
  475. def initialize(self, connection):
  476. super(OracleDialect_cx_oracle, self).initialize(connection)
  477. if self._is_oracle_8:
  478. self.supports_unicode_binds = False
  479. self._detect_decimal_char(connection)
  480. def _detect_decimal_char(self, connection):
  481. """detect if the decimal separator character is not '.', as
  482. is the case with european locale settings for NLS_LANG.
  483. cx_oracle itself uses similar logic when it formats Python
  484. Decimal objects to strings on the bind side (as of 5.0.3),
  485. as Oracle sends/receives string numerics only in the
  486. current locale.
  487. """
  488. if self.cx_oracle_ver < (5,):
  489. # no output type handlers before version 5
  490. return
  491. cx_Oracle = self.dbapi
  492. conn = connection.connection
  493. # override the output_type_handler that's
  494. # on the cx_oracle connection with a plain
  495. # one on the cursor
  496. def output_type_handler(cursor, name, defaultType,
  497. size, precision, scale):
  498. return cursor.var(
  499. cx_Oracle.STRING,
  500. 255, arraysize=cursor.arraysize)
  501. cursor = conn.cursor()
  502. cursor.outputtypehandler = output_type_handler
  503. cursor.execute("SELECT 0.1 FROM DUAL")
  504. val = cursor.fetchone()[0]
  505. cursor.close()
  506. char = re.match(r"([\.,])", val).group(1)
  507. if char != '.':
  508. _detect_decimal = self._detect_decimal
  509. self._detect_decimal = \
  510. lambda value: _detect_decimal(value.replace(char, '.'))
  511. self._to_decimal = \
  512. lambda value: decimal.Decimal(value.replace(char, '.'))
  513. def _detect_decimal(self, value):
  514. if "." in value:
  515. return decimal.Decimal(value)
  516. else:
  517. return int(value)
  518. _to_decimal = decimal.Decimal
  519. def on_connect(self):
  520. if self.cx_oracle_ver < (5,):
  521. # no output type handlers before version 5
  522. return
  523. cx_Oracle = self.dbapi
  524. def output_type_handler(cursor, name, defaultType,
  525. size, precision, scale):
  526. # convert all NUMBER with precision + positive scale to Decimal
  527. # this almost allows "native decimal" mode.
  528. if self.supports_native_decimal and \
  529. defaultType == cx_Oracle.NUMBER and \
  530. precision and scale > 0:
  531. return cursor.var(
  532. cx_Oracle.STRING,
  533. 255,
  534. outconverter=self._to_decimal,
  535. arraysize=cursor.arraysize)
  536. # if NUMBER with zero precision and 0 or neg scale, this appears
  537. # to indicate "ambiguous". Use a slower converter that will
  538. # make a decision based on each value received - the type
  539. # may change from row to row (!). This kills
  540. # off "native decimal" mode, handlers still needed.
  541. elif self.supports_native_decimal and \
  542. defaultType == cx_Oracle.NUMBER \
  543. and not precision and scale <= 0:
  544. return cursor.var(
  545. cx_Oracle.STRING,
  546. 255,
  547. outconverter=self._detect_decimal,
  548. arraysize=cursor.arraysize)
  549. # allow all strings to come back natively as Unicode
  550. elif defaultType in (cx_Oracle.STRING, cx_Oracle.FIXED_CHAR):
  551. return cursor.var(unicode, size, cursor.arraysize)
  552. def on_connect(conn):
  553. conn.outputtypehandler = output_type_handler
  554. return on_connect
  555. def create_connect_args(self, url):
  556. dialect_opts = dict(url.query)
  557. for opt in ('use_ansi', 'auto_setinputsizes', 'auto_convert_lobs',
  558. 'threaded', 'allow_twophase'):
  559. if opt in dialect_opts:
  560. util.coerce_kw_type(dialect_opts, opt, bool)
  561. setattr(self, opt, dialect_opts[opt])
  562. if url.database:
  563. # if we have a database, then we have a remote host
  564. port = url.port
  565. if port:
  566. port = int(port)
  567. else:
  568. port = 1521
  569. dsn = self.dbapi.makedsn(url.host, port, url.database)
  570. else:
  571. # we have a local tnsname
  572. dsn = url.host
  573. opts = dict(
  574. user=url.username,
  575. password=url.password,
  576. dsn=dsn,
  577. threaded=self.threaded,
  578. twophase=self.allow_twophase,
  579. )
  580. # Py2K
  581. if self._cx_oracle_with_unicode:
  582. for k, v in opts.items():
  583. if isinstance(v, str):
  584. opts[k] = unicode(v)
  585. else:
  586. for k, v in opts.items():
  587. if isinstance(v, unicode):
  588. opts[k] = str(v)
  589. # end Py2K
  590. if 'mode' in url.query:
  591. opts['mode'] = url.query['mode']
  592. if isinstance(opts['mode'], basestring):
  593. mode = opts['mode'].upper()
  594. if mode == 'SYSDBA':
  595. opts['mode'] = self.dbapi.SYSDBA
  596. elif mode == 'SYSOPER':
  597. opts['mode'] = self.dbapi.SYSOPER
  598. else:
  599. util.coerce_kw_type(opts, 'mode', int)
  600. return ([], opts)
  601. def _get_server_version_info(self, connection):
  602. return tuple(
  603. int(x)
  604. for x in connection.connection.version.split('.')
  605. )
  606. def is_disconnect(self, e, connection, cursor):
  607. error, = e.args
  608. if isinstance(e, self.dbapi.InterfaceError):
  609. return "not connected" in str(e)
  610. elif hasattr(error, 'code'):
  611. # ORA-00028: your session has been killed
  612. # ORA-03114: not connected to ORACLE
  613. # ORA-03113: end-of-file on communication channel
  614. # ORA-03135: connection lost contact
  615. # ORA-01033: ORACLE initialization or shutdown in progress
  616. # TODO: Others ?
  617. return error.code in (28, 3114, 3113, 3135, 1033)
  618. else:
  619. return False
  620. def create_xid(self):
  621. """create a two-phase transaction ID.
  622. this id will be passed to do_begin_twophase(), do_rollback_twophase(),
  623. do_commit_twophase(). its format is unspecified."""
  624. id = random.randint(0, 2 ** 128)
  625. return (0x1234, "%032x" % id, "%032x" % 9)
  626. def do_begin_twophase(self, connection, xid):
  627. connection.connection.begin(*xid)
  628. def do_prepare_twophase(self, connection, xid):
  629. connection.connection.prepare()
  630. def do_rollback_twophase(self, connection, xid, is_prepared=True, recover=False):
  631. self.do_rollback(connection.connection)
  632. def do_commit_twophase(self, connection, xid, is_prepared=True, recover=False):
  633. self.do_commit(connection.connection)
  634. def do_recover_twophase(self, connection):
  635. pass
  636. dialect = OracleDialect_cx_oracle