1
0
mirror of https://github.com/django/django.git synced 2025-10-21 04:39:17 +00:00

boulder-oracle-sprint: Changed Oracle CLOB to NCLOB for i18n.

Fixed Oracle backend's get_date_trunc_sql() function.


git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@4064 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Boulder Sprinters 2006-11-10 17:16:45 +00:00
parent b47c350b56
commit 2ef60b546b
7 changed files with 125 additions and 106 deletions

View File

@ -203,16 +203,15 @@ def _get_sql_model_create(model, known_models=set()):
sequence_statement = 'CREATE SEQUENCE %s;' % sequence_name sequence_statement = 'CREATE SEQUENCE %s;' % sequence_name
final_output.append(sequence_statement) final_output.append(sequence_statement)
trigger_statement = '' + \ trigger_statement = '' + \
'CREATE OR REPLACE trigger %s\n' % truncate_name('%s_tr' % opts.db_table, backend.get_max_name_length()) + \ 'CREATE OR REPLACE TRIGGER %s\n' % truncate_name('%s_tr' % opts.db_table, backend.get_max_name_length()) + \
' before insert on %s\n' % backend.quote_name(opts.db_table) + \ ' BEFORE INSERT ON %s\n' % backend.quote_name(opts.db_table) + \
' for each row\n' + \ ' FOR EACH ROW\n' + \
' when (new.id is NULL)\n' + \ ' WHEN (new.id IS NULL)\n' + \
' begin\n' + \ ' BEGIN\n' + \
' select %s.NEXTVAL into :new.id from DUAL;\n' % sequence_name + \ ' SELECT %s.nextval INTO :new.id FROM dual;\n' % sequence_name + \
' end;\n' ' END;\n'
final_output.append(trigger_statement) final_output.append(trigger_statement)
return final_output, pending_references return final_output, pending_references
def _get_sql_for_pending_references(model, pending_references): def _get_sql_for_pending_references(model, pending_references):
@ -283,13 +282,13 @@ def _get_many_to_many_sql_for_model(model):
sequence_statement = 'CREATE SEQUENCE %s;' % sequence_name sequence_statement = 'CREATE SEQUENCE %s;' % sequence_name
final_output.append(sequence_statement) final_output.append(sequence_statement)
trigger_statement = '' + \ trigger_statement = '' + \
'CREATE OR REPLACE trigger %s\n' % truncate_name('%s_tr' % m_table, backend.get_max_name_length()) + \ 'CREATE OR REPLACE TRIGGER %s\n' % truncate_name('%s_tr' % m_table, backend.get_max_name_length()) + \
' before insert on %s\n' % backend.quote_name(m_table) + \ ' BEFORE INSERT ON %s\n' % backend.quote_name(m_table) + \
' for each row\n' + \ ' FOR EACH ROW\n' + \
' when (new.id is NULL)\n' + \ ' WHEN (new.id IS NULL)\n' + \
' begin\n' + \ ' BEGIN\n' + \
' select %s.NEXTVAL into :new.id from DUAL;\n' % sequence_name + \ ' SELECT %s.nextval INTO :new.id FROM dual;\n' % sequence_name + \
' end;\n' ' END;\n'
final_output.append(trigger_statement) final_output.append(trigger_statement)
return final_output return final_output

View File

@ -180,9 +180,6 @@ def get_drop_foreignkey_sql():
def get_pk_default_value(): def get_pk_default_value():
return "DEFAULT" return "DEFAULT"
def get_max_name_length():
return 64
OPERATOR_MAPPING = { OPERATOR_MAPPING = {
'exact': '= %s', 'exact': '= %s',
'iexact': 'LIKE %s', 'iexact': 'LIKE %s',

View File

@ -40,8 +40,8 @@ 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.connection = Database.connect(conn_string)
# set oracle date to ansi date format # set oracle date to ansi date format
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute("alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'") cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'")
cursor.close() cursor.close()
return FormatStylePlaceholderCursor(self.connection) return FormatStylePlaceholderCursor(self.connection)
@ -68,54 +68,62 @@ 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" style.
This fixes it -- but note that if you want to use a literal "%s" in a query, This fixes it -- but note that if you want to use a literal "%s" in a query,
you'll need to use "%%s". you'll need to use "%%s".
""" """
def execute(self, query, params=None): def _rewrite_args(self, query, params=None):
if params is None: if params is None:
params = [] params = []
args = [(':arg%s' % i) for i in range(len(params))] args = [(':arg%d' % i) for i in range(len(params))]
query = query % tuple(args) query = query % tuple(args)
# cx_Oracle cannot execute a query with the closing ';'
# cx can not execute the query with the closing ';'
if query.endswith(';'): if query.endswith(';'):
query = query[:-1] query = query[:-1]
return query, params
def execute(self, query, params=None):
query, params = self._rewrite_args(query, params)
self.arraysize = 200
return Database.Cursor.execute(self, query, params) return Database.Cursor.execute(self, query, params)
def executemany(self, query, params=None): def executemany(self, query, params=None):
if params is None: params = [] query, params = self._rewrite_args(query, params)
query = self.convert_arguments(query, len(params[0])) self.arraysize = 200
# cx can not execute the query with the closing ';'
if query.endswith(';') :
query = query[0:len(query)-1]
return Database.Cursor.executemany(self, query, params) return Database.Cursor.executemany(self, query, params)
def quote_name(name): def quote_name(name):
if name.startswith('"') and name.endswith('"'): # Oracle requires that quoted names be uppercase.
return name # Quoting once is enough. name = name.upper()
if not name.startswith('"') and not name.endswith('"'):
# Oracle requires that quoted names be uppercase. name = '"%s"' % util.truncate_name(name.upper(), get_max_name_length())
return '"%s"' % name.upper() return name
dictfetchone = util.dictfetchone dictfetchone = util.dictfetchone
dictfetchmany = util.dictfetchmany dictfetchmany = util.dictfetchmany
dictfetchall = util.dictfetchall dictfetchall = util.dictfetchall
def get_last_insert_id(cursor, table_name, pk_name): def get_last_insert_id(cursor, table_name, pk_name):
query = "SELECT %s_sq.currval from dual" % table_name cursor.execute('SELECT %s_sq.currval FROM dual' % table_name)
cursor.execute(query)
return cursor.fetchone()[0] return cursor.fetchone()[0]
def get_date_extract_sql(lookup_type, table_name): def get_date_extract_sql(lookup_type, table_name):
# lookup_type is 'year', 'month', 'day' # lookup_type is 'year', 'month', 'day'
# http://www.psoug.org/reference/date_func.html # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163
return "EXTRACT(%s FROM %s)" % (lookup_type, table_name) return "EXTRACT(%s FROM %s)" % (lookup_type, table_name)
def get_date_trunc_sql(lookup_type, field_name): def get_date_trunc_sql(lookup_type, field_name):
return "EXTRACT(%s FROM TRUNC(%s))" % (lookup_type, field_name) # lookup_type is 'year', 'month', 'day'
# Oracle uses TRUNC() for both dates and numbers.
# http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151
if lookup_type == 'day':
sql = 'TRUNC(%s)' % (field_name,)
else:
sql = "TRUNC(%s, '%s')" % (field_name, lookup_type)
return sql
def get_limit_offset_sql(limit, offset=None): def get_limit_offset_sql(limit, offset=None):
# Limits and offset are too complicated to be handled here. # Limits and offset are too complicated to be handled here.
# Instead, they are handled in django/db/query.py. # Instead, they are handled in django/db/backends/oracle/query.py.
pass raise NotImplementedError
def get_random_function_sql(): def get_random_function_sql():
return "DBMS_RANDOM.RANDOM" return "DBMS_RANDOM.RANDOM"

View File

@ -1,34 +1,40 @@
import sys import sys, time
# This dictionary maps Field objects to their associated Oracle column
# types, as strings. Column-type strings can contain format strings; they'll
# be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
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': 'VARCHAR2(%(maxlength)s)',
'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)', 'CommaSeparatedIntegerField': 'VARCHAR2(%(maxlength)s)',
'DateField': 'date', 'DateField': 'DATE',
'DateTimeField': 'timestamp with time zone', 'DateTimeField': 'TIMESTAMP WITH TIME ZONE',
'FileField': 'varchar2(100)', 'FileField': 'VARCHAR2(100)',
'FilePathField': 'varchar2(100)', 'FilePathField': 'VARCHAR2(100)',
'FloatField': 'number(%(max_digits)s, %(decimal_places)s)', 'FloatField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
'ImageField': 'varchar2(100)', 'ImageField': 'VARCHAR2(100)',
'IntegerField': 'number(11)', 'IntegerField': 'NUMBER(11)',
'IPAddressField': 'char(15)', 'IPAddressField': 'CHAR(15)',
'ManyToManyField': None, 'ManyToManyField': None,
'NullBooleanField': 'number(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))', 'NullBooleanField': 'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))',
'OneToOneField': 'number(11)', 'OneToOneField': 'NUMBER(11)',
'PhoneNumberField': 'varchar2(20)', 'PhoneNumberField': 'VARCHAR2(20)',
'PositiveIntegerField': 'number(11) CHECK (%(column)s >= 1)', 'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 1)',
'PositiveSmallIntegerField': 'number(11) CHECK (%(column)s >= 1)', 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 1)',
'SlugField': 'varchar2(50)', 'SlugField': 'VARCHAR2(50)',
'SmallIntegerField': 'number(11)', 'SmallIntegerField': 'NUMBER(11)',
'TextField': 'clob', 'TextField': 'NCLOB',
'TimeField': 'timestamp', 'TimeField': 'TIMESTAMP',
'URLField': 'varchar2(200)', 'URLField': 'VARCHAR2(200)',
'USStateField': 'char(2)', 'USStateField': 'CHAR(2)',
} }
TEST_DATABASE_PREFIX = 'test_' TEST_DATABASE_PREFIX = 'test_'
PASSWORD = 'Im_a_lumberjack' PASSWORD = 'Im_a_lumberjack'
OLD_DATABASE_USER = None
OLD_DATABASE_PASSWORD = None
def create_test_db(settings, connection, backend, verbosity=1, autoclobber=False): def create_test_db(settings, connection, backend, verbosity=1, autoclobber=False):
if verbosity >= 1: if verbosity >= 1:
@ -46,7 +52,7 @@ def create_test_db(settings, connection, backend, verbosity=1, autoclobber=False
if autoclobber or confirm == 'yes': if autoclobber or confirm == 'yes':
try: try:
if verbosity >= 1: if verbosity >= 1:
print "Destroying old test database..." print "Destroying old test database..."
_destroy_test_db(cursor, TEST_DATABASE_NAME, verbosity) _destroy_test_db(cursor, TEST_DATABASE_NAME, verbosity)
if verbosity >= 1: if verbosity >= 1:
print "Creating test database..." print "Creating test database..."
@ -66,13 +72,15 @@ def create_test_db(settings, connection, backend, verbosity=1, autoclobber=False
# the side effect of initializing the test database. # the side effect of initializing the test database.
cursor = connection.cursor() cursor = connection.cursor()
def destroy_test_db(settings, connection, old_database_name, verbosity=1): def destroy_test_db(settings, connection, backend, old_database_name, verbosity=1):
if verbosity >= 1: if verbosity >= 1:
print "Destroying test database..." print "Destroying test database..."
connection.close() connection.close()
TEST_DATABASE_NAME = _test_database_name(settings) TEST_DATABASE_NAME = _test_database_name(settings)
settings.DATABASE_NAME = old_database_name settings.DATABASE_NAME = old_database_name
#settings.DATABASE_USER = 'old_user'
#settings.DATABASE_PASSWORD = 'old_password'
cursor = connection.cursor() cursor = connection.cursor()
time.sleep(1) # To avoid "database is being accessed by other users" errors. time.sleep(1) # To avoid "database is being accessed by other users" errors.
@ -83,19 +91,18 @@ def _create_test_db(cursor, dbname, verbosity):
if verbosity >= 2: if verbosity >= 2:
print "_create_test_db(): dbname = %s" % dbname print "_create_test_db(): dbname = %s" % dbname
statements = [ statements = [
"""create tablespace %(user)s """CREATE TABLESPACE %(user)s
datafile '%(user)s.dbf' size 10M autoextend on next 10M maxsize 20M DATAFILE '%(user)s.dbf' SIZE 10M AUTOEXTEND ON NEXT 10M MAXSIZE 20M
""", """,
"""create temporary tablespace %(user)s_temp """CREATE TEMPORARY TABLESPACE %(user)s_temp
tempfile '%(user)s_temp.dbf' size 10M autoextend on next 10M maxsize 20M TEMPFILE '%(user)s_temp.dbf' SIZE 10M AUTOEXTEND ON NEXT 10M MAXSIZE 20M
""", """,
"""create user %(user)s """CREATE USER %(user)s
identified by %(password)s IDENTIFIED BY %(password)s
default tablespace %(user)s DEFAULT TABLESPACE %(user)s
temporary tablespace %(user)s_temp TEMPORARY TABLESPACE %(user)s_temp
""", """,
"""grant resource to %(user)s""", """GRANT CONNECT, RESOURCE TO %(user)s""",
"""grant connect to %(user)s""",
] ]
_execute_statements(cursor, statements, dbname, verbosity) _execute_statements(cursor, statements, dbname, verbosity)
@ -103,9 +110,9 @@ def _destroy_test_db(cursor, dbname, verbosity):
if verbosity >= 2: if verbosity >= 2:
print "_destroy_test_db(): dbname=%s" % dbname print "_destroy_test_db(): dbname=%s" % dbname
statements = [ statements = [
"""drop user %(user)s cascade""", 'DROP USER %(user)s CASCADE',
"""drop tablespace %(user)s including contents and datafiles cascade constraints""", 'DROP TABLESPACE %(user)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
"""drop tablespace %(user)s_temp including contents and datafiles cascade constraints""", 'DROP TABLESPACE %(user)s_TEMP INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
] ]
_execute_statements(cursor, statements, dbname, verbosity) _execute_statements(cursor, statements, dbname, verbosity)
@ -119,8 +126,7 @@ def _execute_statements(cursor, statements, dbname, verbosity):
cursor.execute(stmt) cursor.execute(stmt)
except Exception, err: except Exception, err:
sys.stderr.write("Failed (%s)\n" % (err)) sys.stderr.write("Failed (%s)\n" % (err))
if required: raise
raise
def _test_database_name(settings): def _test_database_name(settings):
if settings.TEST_DATABASE_NAME: if settings.TEST_DATABASE_NAME:
@ -128,4 +134,3 @@ def _test_database_name(settings):
else: else:
name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
return name return name

View File

@ -28,16 +28,16 @@ def get_relations(cursor, table_name):
cursor.execute(""" cursor.execute("""
SELECT ta.column_id - 1, tb.table_name, tb.column_id - 1 SELECT ta.column_id - 1, tb.table_name, tb.column_id - 1
FROM user_constraints, USER_CONS_COLUMNS ca, USER_CONS_COLUMNS cb, FROM user_constraints, USER_CONS_COLUMNS ca, USER_CONS_COLUMNS cb,
user_tab_cols ta, user_tab_cols tb user_tab_cols ta, user_tab_cols tb
WHERE user_constraints.table_name = %s AND WHERE user_constraints.table_name = %s AND
ta.table_name = %s AND ta.table_name = %s AND
ta.column_name = ca.column_name AND ta.column_name = ca.column_name AND
ca.table_name = %s AND ca.table_name = %s AND
user_constraints.constraint_name = ca.constraint_name AND user_constraints.constraint_name = ca.constraint_name AND
user_constraints.r_constraint_name = cb.constraint_name AND user_constraints.r_constraint_name = cb.constraint_name AND
cb.table_name = tb.table_name AND cb.table_name = tb.table_name AND
cb.column_name = tb.column_name AND cb.column_name = tb.column_name AND
ca.position = cb.position""", [table_name, table_name, table_name]) ca.position = cb.position""", [table_name, table_name, table_name])
relations = {} relations = {}
for row in cursor.fetchall(): for row in cursor.fetchall():

View File

@ -67,7 +67,7 @@ def get_query_set_class(DefaultQuerySet):
# so here's the logic; # so here's the logic;
# 1. retrieve each row in turn # 1. retrieve each row in turn
# 2. convert CLOBs # 2. convert NCLOBs
def resolve_lobs(row): def resolve_lobs(row):
for field in row: for field in row:

View File

@ -171,7 +171,7 @@ class _QuerySet(object):
cursor = connection.cursor() cursor = connection.cursor()
full_query = None full_query = None
select, sql, params = self._get_sql_clause() select, sql, params, full_query = self._get_sql_clause()
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
fill_cache = self._select_related fill_cache = self._select_related
@ -196,7 +196,7 @@ class _QuerySet(object):
counter._offset = None counter._offset = None
counter._limit = None counter._limit = None
counter._select_related = False counter._select_related = False
select, sql, params = counter._get_sql_clause()[:3] select, sql, params, full_query = counter._get_sql_clause()
cursor = connection.cursor() cursor = connection.cursor()
if self._distinct: if self._distinct:
id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table),
@ -535,7 +535,7 @@ class ValuesQuerySet(QuerySet):
field_names = [f.attname for f in self.model._meta.fields] field_names = [f.attname for f in self.model._meta.fields]
cursor = connection.cursor() cursor = connection.cursor()
select, sql, params = self._get_sql_clause()[:3] select, sql, params, full_query = self._get_sql_clause()
select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
while 1: while 1:
@ -557,16 +557,26 @@ class DateQuerySet(QuerySet):
if self._field.null: if self._field.null:
self._where.append('%s.%s IS NOT NULL' % \ self._where.append('%s.%s IS NOT NULL' % \
(backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column)))
select, sql, params = self._get_sql_clause() select, sql, params, full_query = self._get_sql_clause()
sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ table_name = backend.quote_name(self.model._meta.db_table)
(backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), field_name = backend.quote_name(self._field.column)
backend.quote_name(self._field.column))), sql, self._order) date_trunc_sql = backend.get_date_trunc_sql(self._kind,
cursor = connection.cursor() '%s.%s' % (table_name, field_name))
cursor.execute(sql, params) fmt = 'SELECT %s %s GROUP BY %s ORDER BY 1 %s'
# We have to manually run typecast_timestamp(str()) on the results, because
# MySQL doesn't automatically cast the result of date functions as datetime if settings.DATABASE_ENGINE == 'oracle':
# objects -- MySQL returns the values as strings, instead. sql = fmt % (date_trunc_sql, sql, date_trunc_sql, self._order)
return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()] cursor = connection.cursor()
cursor.execute(sql, params)
return [row[0] for row in cursor.fetchall()]
else:
sql = fmt % (date_trunc_sql, sql, 1, self._order_by)
cursor = connection.cursor()
cursor.execute(sql, params)
# We have to manually run typecast_timestamp(str()) on the results, because
# MySQL doesn't automatically cast the result of date functions as datetime
# objects -- MySQL returns the values as strings, instead.
return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]
def _clone(self, klass=None, **kwargs): def _clone(self, klass=None, **kwargs):
c = super(DateQuerySet, self)._clone(klass, **kwargs) c = super(DateQuerySet, self)._clone(klass, **kwargs)