1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +00:00

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
This commit is contained in:
Malcolm Tredinnick 2007-07-02 07:43:36 +00:00
parent 5eb53a6397
commit 9bf4bacf13
2 changed files with 54 additions and 20 deletions

View File

@ -6,13 +6,18 @@ Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
from django.conf import settings from django.conf import settings
from django.db.backends import util 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: try:
import cx_Oracle as Database import cx_Oracle as Database
except ImportError, e: except ImportError, e:
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e
import datetime
from django.utils.datastructures import SortedDict
DatabaseError = Database.Error DatabaseError = Database.Error
@ -45,9 +50,9 @@ class DatabaseWrapper(local):
conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME) conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
self.connection = Database.connect(conn_string, **self.options) self.connection = Database.connect(conn_string, **self.options)
cursor = FormatStylePlaceholderCursor(self.connection) cursor = FormatStylePlaceholderCursor(self.connection)
# default arraysize of 1 is highly sub-optimal # Default arraysize of 1 is highly sub-optimal.
cursor.arraysize = 100 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_DATE_FORMAT = 'YYYY-MM-DD'")
cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'") cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
if settings.DEBUG: if settings.DEBUG:
@ -78,23 +83,22 @@ uses_case_insensitive_names = True
class FormatStylePlaceholderCursor(Database.Cursor): class FormatStylePlaceholderCursor(Database.Cursor):
""" """
Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" style. Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var"
This fixes it -- but note that if you want to use a literal "%s" in a query, style. This fixes it -- but note that if you want to use a literal "%s" in
you'll need to use "%%s". 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): def _rewrite_args(self, query, params=None):
if params is None: if params is None:
params = [] params = []
else: else:
# cx_Oracle can't handle unicode parameters, so cast to str for now params = self._format_params(params)
for i, param in enumerate(params):
if type(param) == unicode:
try:
params[i] = param.encode('utf-8')
except UnicodeError:
params[i] = str(param)
args = [(':arg%d' % i) for i in range(len(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 # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
# it does want a trailing ';' but not a trailing '/'. However, these # it does want a trailing ';' but not a trailing '/'. However, these
# characters must be included in the original query in case the query # characters must be included in the original query in case the query
@ -103,6 +107,16 @@ class FormatStylePlaceholderCursor(Database.Cursor):
query = query[:-1] query = query[:-1]
return query, params 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): def execute(self, query, params=None):
query, params = self._rewrite_args(query, params) query, params = self._rewrite_args(query, params)
return Database.Cursor.execute(self, query, params) return Database.Cursor.execute(self, query, params)
@ -111,6 +125,26 @@ class FormatStylePlaceholderCursor(Database.Cursor):
query, params = self._rewrite_args(query, params) query, params = self._rewrite_args(query, params)
return Database.Cursor.executemany(self, 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): def quote_name(name):
# SQL92 requires delimited (quoted) names to be case-sensitive. When # SQL92 requires delimited (quoted) names to be case-sensitive. When
# not quoted, Oracle has case-insensitive behavior for identifiers, but # not quoted, Oracle has case-insensitive behavior for identifiers, but

View File

@ -8,15 +8,15 @@ from django.core import management
DATA_TYPES = { DATA_TYPES = {
'AutoField': 'NUMBER(11)', 'AutoField': 'NUMBER(11)',
'BooleanField': 'NUMBER(1) CHECK (%(column)s IN (0,1))', 'BooleanField': 'NUMBER(1) CHECK (%(column)s IN (0,1))',
'CharField': 'VARCHAR2(%(maxlength)s)', 'CharField': 'NVARCHAR2(%(maxlength)s)',
'CommaSeparatedIntegerField': 'VARCHAR2(%(maxlength)s)', 'CommaSeparatedIntegerField': 'VARCHAR2(%(maxlength)s)',
'DateField': 'DATE', 'DateField': 'DATE',
'DateTimeField': 'TIMESTAMP', 'DateTimeField': 'TIMESTAMP',
'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)', 'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
'FileField': 'VARCHAR2(100)', 'FileField': 'NVARCHAR2(100)',
'FilePathField': 'VARCHAR2(100)', 'FilePathField': 'NVARCHAR2(100)',
'FloatField': 'DOUBLE PRECISION', 'FloatField': 'DOUBLE PRECISION',
'ImageField': 'VARCHAR2(100)', 'ImageField': 'NVARCHAR2(100)',
'IntegerField': 'NUMBER(11)', 'IntegerField': 'NUMBER(11)',
'IPAddressField': 'VARCHAR2(15)', 'IPAddressField': 'VARCHAR2(15)',
'ManyToManyField': None, 'ManyToManyField': None,
@ -25,7 +25,7 @@ DATA_TYPES = {
'PhoneNumberField': 'VARCHAR2(20)', 'PhoneNumberField': 'VARCHAR2(20)',
'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', 'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)',
'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)',
'SlugField': 'VARCHAR2(50)', 'SlugField': 'NVARCHAR2(50)',
'SmallIntegerField': 'NUMBER(11)', 'SmallIntegerField': 'NUMBER(11)',
'TextField': 'NCLOB', 'TextField': 'NCLOB',
'TimeField': 'TIMESTAMP', 'TimeField': 'TIMESTAMP',