From 022e8124ca6e1d032b7ac370692a0c7027aa8bdc Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 12 Mar 2010 21:05:53 +0000 Subject: [PATCH] - Added preliminary support for Oracle's WITH_UNICODE mode. At the very least this establishes initial support for cx_Oracle with Python 3. [ticket:1670] --- CHANGES | 6 +++ lib/sqlalchemy/dialects/oracle/base.py | 29 +++++++++---- lib/sqlalchemy/dialects/oracle/cx_oracle.py | 45 ++++++++++++++++----- lib/sqlalchemy/engine/default.py | 10 ++++- lib/sqlalchemy/test/config.py | 1 + 5 files changed, 73 insertions(+), 18 deletions(-) diff --git a/CHANGES b/CHANGES index 4335efd8a3..b8ca5a0a7e 100644 --- a/CHANGES +++ b/CHANGES @@ -306,12 +306,18 @@ CHANGES - "out" parameters require a type that is supported by cx_oracle. An error will be raised if no cx_oracle type can be found. + - Oracle 'DATE' now does not perform any result processing, as the DATE type in Oracle stores full date+time objects, that's what you'll get. Note that the generic types.Date type *will* still call value.date() on incoming values, however. When reflecting a table, the reflected type will be 'DATE'. + + - Added preliminary support for Oracle's WITH_UNICODE + mode. At the very least this establishes initial + support for cx_Oracle with Python 3. + [ticket:1670] - sqlite - Added "native_datetime=True" flag to create_engine(). diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py index 3107c8b6c9..ffadc84f87 100644 --- a/lib/sqlalchemy/dialects/oracle/base.py +++ b/lib/sqlalchemy/dialects/oracle/base.py @@ -590,22 +590,31 @@ class OracleDialect(default.DefaultDialect): def normalize_name(self, name): if name is None: return None - elif (name.upper() == name and - not self.identifier_preparer._requires_quotes(name.lower().decode(self.encoding))): - return name.lower().decode(self.encoding) + # Py2K + if isinstance(name, str): + name = name.decode(self.encoding) + # end Py2K + if name.upper() == name and \ + not self.identifier_preparer._requires_quotes(name.lower()): + return name.lower() else: - return name.decode(self.encoding) + return name def denormalize_name(self, name): if name is None: return None elif name.lower() == name and not self.identifier_preparer._requires_quotes(name.lower()): - return name.upper().encode(self.encoding) + name = name.upper() + # Py2K + if not self.supports_unicode_binds: + name = name.encode(self.encoding) else: - return name.encode(self.encoding) + name = unicode(name) + # end Py2K + return name def _get_default_schema_name(self, connection): - return self.normalize_name(connection.execute('SELECT USER FROM DUAL').scalar()) + return self.normalize_name(connection.execute(u'SELECT USER FROM DUAL').scalar()) def table_names(self, connection, schema): # note that table_names() isnt loading DBLINKed or synonym'ed tables @@ -664,7 +673,11 @@ class OracleDialect(default.DefaultDialect): resolve_synonyms=False, dblink='', **kw): if resolve_synonyms: - actual_name, owner, dblink, synonym = self._resolve_synonym(connection, desired_owner=self.denormalize_name(schema), desired_synonym=self.denormalize_name(table_name)) + actual_name, owner, dblink, synonym = self._resolve_synonym( + connection, + desired_owner=self.denormalize_name(schema), + desired_synonym=self.denormalize_name(table_name) + ) else: actual_name, owner, dblink, synonym = None, None, None, None if not actual_name: diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py index f4c2e295f2..c5e24cbb35 100644 --- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py +++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py @@ -119,7 +119,9 @@ class _NativeUnicodeMixin(object): def result_processor(self, dialect, coltype): # if we know cx_Oracle will return unicode, # don't process results - if self.convert_unicode != 'force' and \ + if dialect._cx_oracle_with_unicode: + return None + elif self.convert_unicode != 'force' and \ dialect._cx_oracle_native_nvarchar and \ coltype == dialect.dbapi.UNICODE: return None @@ -227,9 +229,8 @@ class Oracle_cx_oracleExecutionContext(OracleExecutionContext): # on String, including that outparams/RETURNING # breaks for varchars self.set_input_sizes(quoted_bind_names, - exclude_types=[ - self.dialect.dbapi.STRING, - self.dialect.dbapi.UNICODE]) + exclude_types=self.dialect._cx_oracle_string_types + ) if len(self.compiled_parameters) == 1: for key in self.compiled.binds: @@ -266,7 +267,7 @@ class Oracle_cx_oracleExecutionContext(OracleExecutionContext): if self.cursor.description is not None: for column in self.cursor.description: type_code = column[1] - if type_code in self.dialect.ORACLE_BINARY_TYPES: + if type_code in self.dialect._cx_oracle_binary_types: result = base.BufferedColumnResultProxy(self) if result is None: @@ -347,10 +348,26 @@ class Oracle_cx_oracle(OracleDialect): cx_oracle_ver = vers(self.dbapi.version) self.supports_unicode_binds = cx_oracle_ver >= (5, 0) self._cx_oracle_native_nvarchar = cx_oracle_ver >= (5, 0) - - if self.dbapi is None or not self.auto_convert_lobs or not 'CLOB' in self.dbapi.__dict__: + + if self.dbapi is not None and not hasattr(self.dbapi, 'UNICODE'): + # cx_Oracle WITH_UNICODE mode. *only* python + # unicode objects accepted for anything + self._cx_oracle_string_types = set([self.dbapi.STRING]) + self.supports_unicode_statements = True + self.supports_unicode_binds = True + self._cx_oracle_with_unicode = True + else: + self._cx_oracle_with_unicode = False + if self.dbapi is not None: + self._cx_oracle_string_types = set([self.dbapi.UNICODE, self.dbapi.STRING]) + else: + self._cx_oracle_string_types = set() + + if self.dbapi is None or \ + not self.auto_convert_lobs or \ + not hasattr(self.dbapi, 'CLOB'): self.dbapi_type_map = {} - self.ORACLE_BINARY_TYPES = [] + self._cx_oracle_binary_types = set() else: # only use this for LOB objects. using it for strings, dates # etc. leads to a little too much magic, reflection doesn't know if it should @@ -361,7 +378,9 @@ class Oracle_cx_oracle(OracleDialect): self.dbapi.BLOB: oracle.BLOB(), self.dbapi.BINARY: oracle.RAW(), } - self.ORACLE_BINARY_TYPES = [getattr(self.dbapi, k) for k in ["BFILE", "CLOB", "NCLOB", "BLOB"] if hasattr(self.dbapi, k)] + self._cx_oracle_binary_types = set([getattr(self.dbapi, k) for k in + ["BFILE", "CLOB", "NCLOB", "BLOB"] + if hasattr(self.dbapi, k)]) @classmethod def dbapi(cls): @@ -395,6 +414,14 @@ class Oracle_cx_oracle(OracleDialect): threaded=self.threaded, twophase=self.allow_twophase, ) + + # Py2K + if self._cx_oracle_with_unicode: + for k, v in opts.items(): + if isinstance(v, str): + opts[k] = unicode(v) + # end Py2K + if 'mode' in url.query: opts['mode'] = url.query['mode'] if isinstance(opts['mode'], basestring): diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 0776279490..cfab01dc42 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -138,9 +138,17 @@ class DefaultDialect(base.Dialect): def _check_unicode_returns(self, connection): cursor = connection.connection.cursor() + # Py2K + if self.supports_unicode_statements: + cast_to = unicode + else: + cast_to = str + # end Py2K + # Py3K + #cast_to = str def check_unicode(type_): cursor.execute( - str( + cast_to( expression.select( [expression.cast( expression.literal_column("'test unicode returns'"), type_) diff --git a/lib/sqlalchemy/test/config.py b/lib/sqlalchemy/test/config.py index eec962d807..efbe00fef2 100644 --- a/lib/sqlalchemy/test/config.py +++ b/lib/sqlalchemy/test/config.py @@ -1,5 +1,6 @@ import optparse, os, sys, re, ConfigParser, time, warnings + # 2to3 import StringIO -- 2.47.3