Permalink
Browse files

unicode: Added Unicode support for the Oracle backend. All tests pass.

git-svn-id: http://code.djangoproject.com/svn/django/branches/unicode@5584 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 5eb53a6 commit 9bf4bacf1346d9f8e3ffff85ece5d52c1d24b902 @malcolmt malcolmt committed Jul 2, 2007
Showing with 54 additions and 20 deletions.
  1. +49 −15 django/db/backends/oracle/base.py
  2. +5 −5 django/db/backends/oracle/creation.py
@@ -6,13 +6,18 @@
from django.conf import settings
from django.db.backends import util
+from django.utils.datastructures import SortedDict
+from django.utils.encoding import smart_str, force_unicode
+import datetime
+import os
+
+# Oracle takes client-side character set encoding from the environment.
+os.environ['NLS_LANG'] = '.UTF8'
try:
import cx_Oracle as Database
except ImportError, e:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e
-import datetime
-from django.utils.datastructures import SortedDict
DatabaseError = Database.Error
@@ -45,9 +50,9 @@ def cursor(self):
conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
self.connection = Database.connect(conn_string, **self.options)
cursor = FormatStylePlaceholderCursor(self.connection)
- # default arraysize of 1 is highly sub-optimal
+ # Default arraysize of 1 is highly sub-optimal.
cursor.arraysize = 100
- # set oracle date to ansi date format
+ # Set oracle date to ansi date format.
cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'")
cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
if settings.DEBUG:
@@ -78,23 +83,22 @@ def close(self):
class FormatStylePlaceholderCursor(Database.Cursor):
"""
- Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" style.
- This fixes it -- but note that if you want to use a literal "%s" in a query,
- you'll need to use "%%s".
+ Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var"
+ style. This fixes it -- but note that if you want to use a literal "%s" in
+ a query, you'll need to use "%%s".
+
+ We also do automatic conversion between Unicode on the Python side and
+ UTF-8 -- for talking to Oracle -- in here.
"""
+ charset = 'utf-8'
+
def _rewrite_args(self, query, params=None):
if params is None:
params = []
else:
- # cx_Oracle can't handle unicode parameters, so cast to str for now
- for i, param in enumerate(params):
- if type(param) == unicode:
- try:
- params[i] = param.encode('utf-8')
- except UnicodeError:
- params[i] = str(param)
+ params = self._format_params(params)
args = [(':arg%d' % i) for i in range(len(params))]
- query = query % tuple(args)
+ query = smart_str(query, self.charset) % tuple(args)
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
# it does want a trailing ';' but not a trailing '/'. However, these
# characters must be included in the original query in case the query
@@ -103,6 +107,16 @@ def _rewrite_args(self, query, params=None):
query = query[:-1]
return query, params
+ def _format_params(self, params):
+ if isinstance(params, dict):
+ result = {}
+ charset = self.charset
+ for key, value in params.items():
+ result[smart_str(key, charset)] = smart_str(value, charset)
+ return result
+ else:
+ return tuple([smart_str(p, self.charset, True) for p in params])
+
def execute(self, query, params=None):
query, params = self._rewrite_args(query, params)
return Database.Cursor.execute(self, query, params)
@@ -111,6 +125,26 @@ def executemany(self, query, params=None):
query, params = self._rewrite_args(query, params)
return Database.Cursor.executemany(self, query, params)
+ def fetchone(self):
+ return to_unicode(Database.Cursor.fetchone(self))
+
+ def fetchmany(self, size=None):
+ if size is None:
+ size = self.arraysize
+ return tuple([tuple([to_unicode(e) for e in r]) for r in Database.Cursor.fetchmany(self, size)])
+
+ def fetchall(self):
+ return tuple([tuple([to_unicode(e) for e in r]) for r in Database.Cursor.fetchall(self)])
+
+def to_unicode(s):
+ """
+ Convert strings to Unicode objects (and return all other data types
+ unchanged).
+ """
+ if isinstance(s, basestring):
+ return force_unicode(s)
+ return s
+
def quote_name(name):
# SQL92 requires delimited (quoted) names to be case-sensitive. When
# not quoted, Oracle has case-insensitive behavior for identifiers, but
@@ -8,15 +8,15 @@
DATA_TYPES = {
'AutoField': 'NUMBER(11)',
'BooleanField': 'NUMBER(1) CHECK (%(column)s IN (0,1))',
- 'CharField': 'VARCHAR2(%(maxlength)s)',
+ 'CharField': 'NVARCHAR2(%(maxlength)s)',
'CommaSeparatedIntegerField': 'VARCHAR2(%(maxlength)s)',
'DateField': 'DATE',
'DateTimeField': 'TIMESTAMP',
'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
- 'FileField': 'VARCHAR2(100)',
- 'FilePathField': 'VARCHAR2(100)',
+ 'FileField': 'NVARCHAR2(100)',
+ 'FilePathField': 'NVARCHAR2(100)',
'FloatField': 'DOUBLE PRECISION',
- 'ImageField': 'VARCHAR2(100)',
+ 'ImageField': 'NVARCHAR2(100)',
'IntegerField': 'NUMBER(11)',
'IPAddressField': 'VARCHAR2(15)',
'ManyToManyField': None,
@@ -25,7 +25,7 @@
'PhoneNumberField': 'VARCHAR2(20)',
'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)',
'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)',
- 'SlugField': 'VARCHAR2(50)',
+ 'SlugField': 'NVARCHAR2(50)',
'SmallIntegerField': 'NUMBER(11)',
'TextField': 'NCLOB',
'TimeField': 'TIMESTAMP',

0 comments on commit 9bf4bac

Please sign in to comment.