2006-05-26 13:37:21 +08:00
|
|
|
"""
|
|
|
|
Oracle database backend for Django.
|
|
|
|
|
2008-12-12 04:12:00 +08:00
|
|
|
Requires cx_Oracle: http://cx-oracle.sourceforge.net/
|
2006-05-26 13:37:21 +08:00
|
|
|
"""
|
|
|
|
|
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
|
|
|
import os
|
2008-07-29 13:09:29 +08:00
|
|
|
import datetime
|
|
|
|
import time
|
2009-01-16 04:39:09 +08:00
|
|
|
from decimal import Decimal
|
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
|
|
|
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
# Oracle takes client-side character set encoding from the environment.
|
|
|
|
os.environ['NLS_LANG'] = '.UTF8'
|
2006-05-27 02:58:46 +08:00
|
|
|
try:
|
|
|
|
import cx_Oracle as Database
|
|
|
|
except ImportError, e:
|
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
2007-08-20 11:32:06 +08:00
|
|
|
raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e)
|
2007-06-23 22:16:00 +08:00
|
|
|
|
2008-08-22 22:18:53 +08:00
|
|
|
from django.db.backends import *
|
|
|
|
from django.db.backends.oracle import query
|
|
|
|
from django.db.backends.oracle.client import DatabaseClient
|
|
|
|
from django.db.backends.oracle.creation import DatabaseCreation
|
|
|
|
from django.db.backends.oracle.introspection import DatabaseIntrospection
|
|
|
|
from django.utils.encoding import smart_str, force_unicode
|
|
|
|
|
2008-09-02 03:58:41 +08:00
|
|
|
DatabaseError = Database.DatabaseError
|
2007-04-25 18:18:56 +08:00
|
|
|
IntegrityError = Database.IntegrityError
|
2006-05-26 13:37:21 +08:00
|
|
|
|
2008-08-25 04:16:56 +08:00
|
|
|
|
2007-08-20 10:20:33 +08:00
|
|
|
class DatabaseFeatures(BaseDatabaseFeatures):
|
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
|
|
|
empty_fetchmany_value = ()
|
2007-08-20 10:20:33 +08:00
|
|
|
needs_datetime_string_cast = False
|
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
|
|
|
uses_custom_query_class = True
|
2008-06-16 11:15:04 +08:00
|
|
|
interprets_empty_strings_as_nulls = True
|
2007-08-20 10:20:33 +08:00
|
|
|
|
2008-08-25 04:16:56 +08:00
|
|
|
|
2007-08-20 06:29:57 +08:00
|
|
|
class DatabaseOperations(BaseDatabaseOperations):
|
2008-12-12 04:12:00 +08:00
|
|
|
|
2007-09-15 02:12:36 +08:00
|
|
|
def autoinc_sql(self, table, column):
|
2007-08-20 06:29:57 +08:00
|
|
|
# To simulate auto-incrementing primary keys in Oracle, we have to
|
|
|
|
# create a sequence and a trigger.
|
|
|
|
sq_name = get_sequence_name(table)
|
|
|
|
tr_name = get_trigger_name(table)
|
2007-09-15 02:12:36 +08:00
|
|
|
tbl_name = self.quote_name(table)
|
|
|
|
col_name = self.quote_name(column)
|
2008-08-25 04:16:56 +08:00
|
|
|
sequence_sql = """
|
2008-12-12 04:12:00 +08:00
|
|
|
DECLARE
|
|
|
|
i INTEGER;
|
|
|
|
BEGIN
|
|
|
|
SELECT COUNT(*) INTO i FROM USER_CATALOG
|
|
|
|
WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE';
|
|
|
|
IF i = 0 THEN
|
|
|
|
EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"';
|
|
|
|
END IF;
|
|
|
|
END;
|
|
|
|
/""" % locals()
|
2007-08-20 06:29:57 +08:00
|
|
|
trigger_sql = """
|
2008-12-12 04:12:00 +08:00
|
|
|
CREATE OR REPLACE TRIGGER "%(tr_name)s"
|
|
|
|
BEFORE INSERT ON %(tbl_name)s
|
|
|
|
FOR EACH ROW
|
|
|
|
WHEN (new.%(col_name)s IS NULL)
|
|
|
|
BEGIN
|
|
|
|
SELECT "%(sq_name)s".nextval
|
|
|
|
INTO :new.%(col_name)s FROM dual;
|
|
|
|
END;
|
|
|
|
/""" % locals()
|
2007-08-20 06:29:57 +08:00
|
|
|
return sequence_sql, trigger_sql
|
|
|
|
|
2007-08-20 06:40:06 +08:00
|
|
|
def date_extract_sql(self, lookup_type, field_name):
|
|
|
|
# http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163
|
|
|
|
return "EXTRACT(%s FROM %s)" % (lookup_type, field_name)
|
|
|
|
|
2007-08-20 06:47:43 +08:00
|
|
|
def date_trunc_sql(self, lookup_type, field_name):
|
|
|
|
# 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
|
|
|
|
|
2007-08-20 06:55:05 +08:00
|
|
|
def datetime_cast_sql(self):
|
|
|
|
return "TO_TIMESTAMP(%s, 'YYYY-MM-DD HH24:MI:SS.FF')"
|
|
|
|
|
2007-08-20 07:03:38 +08:00
|
|
|
def deferrable_sql(self):
|
|
|
|
return " DEFERRABLE INITIALLY DEFERRED"
|
|
|
|
|
2007-08-20 11:08:32 +08:00
|
|
|
def drop_sequence_sql(self, table):
|
|
|
|
return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table))
|
|
|
|
|
2007-08-20 11:03:40 +08:00
|
|
|
def field_cast_sql(self, db_type):
|
2007-08-22 01:06:06 +08:00
|
|
|
if db_type and db_type.endswith('LOB'):
|
2007-08-20 11:03:40 +08:00
|
|
|
return "DBMS_LOB.SUBSTR(%s)"
|
|
|
|
else:
|
|
|
|
return "%s"
|
|
|
|
|
2007-08-20 07:18:43 +08:00
|
|
|
def last_insert_id(self, cursor, table_name, pk_name):
|
2008-12-09 02:36:22 +08:00
|
|
|
sq_name = get_sequence_name(table_name)
|
|
|
|
cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
|
2007-08-20 07:18:43 +08:00
|
|
|
return cursor.fetchone()[0]
|
|
|
|
|
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
|
|
|
def lookup_cast(self, lookup_type):
|
|
|
|
if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'):
|
|
|
|
return "UPPER(%s)"
|
|
|
|
return "%s"
|
|
|
|
|
2007-08-20 07:53:39 +08:00
|
|
|
def max_name_length(self):
|
|
|
|
return 30
|
|
|
|
|
2008-09-02 03:37:48 +08:00
|
|
|
def prep_for_iexact_query(self, x):
|
|
|
|
return x
|
|
|
|
|
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
|
|
|
def query_class(self, DefaultQueryClass):
|
|
|
|
return query.query_class(DefaultQueryClass, Database)
|
2007-08-20 10:39:05 +08:00
|
|
|
|
2007-08-20 09:03:33 +08:00
|
|
|
def quote_name(self, name):
|
|
|
|
# SQL92 requires delimited (quoted) names to be case-sensitive. When
|
|
|
|
# not quoted, Oracle has case-insensitive behavior for identifiers, but
|
|
|
|
# always defaults to uppercase.
|
|
|
|
# We simplify things by making Oracle identifiers always uppercase.
|
|
|
|
if not name.startswith('"') and not name.endswith('"'):
|
2008-12-12 04:12:00 +08:00
|
|
|
name = '"%s"' % util.truncate_name(name.upper(),
|
|
|
|
self.max_name_length())
|
2007-08-20 09:03:33 +08:00
|
|
|
return name.upper()
|
|
|
|
|
2007-08-20 08:04:20 +08:00
|
|
|
def random_function_sql(self):
|
|
|
|
return "DBMS_RANDOM.RANDOM"
|
|
|
|
|
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
|
|
|
def regex_lookup_9(self, lookup_type):
|
|
|
|
raise NotImplementedError("Regexes are not supported in Oracle before version 10g.")
|
|
|
|
|
|
|
|
def regex_lookup_10(self, lookup_type):
|
|
|
|
if lookup_type == 'regex':
|
|
|
|
match_option = "'c'"
|
|
|
|
else:
|
|
|
|
match_option = "'i'"
|
|
|
|
return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option
|
|
|
|
|
|
|
|
def regex_lookup(self, lookup_type):
|
|
|
|
# If regex_lookup is called before it's been initialized, then create
|
|
|
|
# a cursor to initialize it and recur.
|
|
|
|
from django.db import connection
|
|
|
|
connection.cursor()
|
|
|
|
return connection.ops.regex_lookup(lookup_type)
|
|
|
|
|
2007-08-20 08:15:53 +08:00
|
|
|
def sql_flush(self, style, tables, sequences):
|
|
|
|
# Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
|
|
|
|
# 'TRUNCATE z;'... style SQL statements
|
|
|
|
if tables:
|
|
|
|
# Oracle does support TRUNCATE, but it seems to get us into
|
|
|
|
# FK referential trouble, whereas DELETE FROM table works.
|
|
|
|
sql = ['%s %s %s;' % \
|
|
|
|
(style.SQL_KEYWORD('DELETE'),
|
|
|
|
style.SQL_KEYWORD('FROM'),
|
2008-12-12 04:12:00 +08:00
|
|
|
style.SQL_FIELD(self.quote_name(table)))
|
|
|
|
for table in tables]
|
2007-08-20 08:15:53 +08:00
|
|
|
# Since we've just deleted all the rows, running our sequence
|
|
|
|
# ALTER code will reset the sequence to 0.
|
|
|
|
for sequence_info in sequences:
|
2008-07-04 03:21:54 +08:00
|
|
|
sequence_name = get_sequence_name(sequence_info['table'])
|
|
|
|
table_name = self.quote_name(sequence_info['table'])
|
2007-09-15 06:07:54 +08:00
|
|
|
column_name = self.quote_name(sequence_info['column'] or 'id')
|
2008-07-04 03:21:54 +08:00
|
|
|
query = _get_sequence_reset_sql() % {'sequence': sequence_name,
|
|
|
|
'table': table_name,
|
2007-09-15 06:07:54 +08:00
|
|
|
'column': column_name}
|
2007-08-20 08:15:53 +08:00
|
|
|
sql.append(query)
|
|
|
|
return sql
|
|
|
|
else:
|
|
|
|
return []
|
|
|
|
|
2007-08-20 08:21:10 +08:00
|
|
|
def sequence_reset_sql(self, style, model_list):
|
|
|
|
from django.db import models
|
|
|
|
output = []
|
|
|
|
query = _get_sequence_reset_sql()
|
|
|
|
for model in model_list:
|
2008-07-04 02:42:47 +08:00
|
|
|
for f in model._meta.local_fields:
|
2007-08-20 08:21:10 +08:00
|
|
|
if isinstance(f, models.AutoField):
|
2008-07-04 02:42:47 +08:00
|
|
|
table_name = self.quote_name(model._meta.db_table)
|
2007-08-20 08:21:10 +08:00
|
|
|
sequence_name = get_sequence_name(model._meta.db_table)
|
2008-07-04 02:42:47 +08:00
|
|
|
column_name = self.quote_name(f.column)
|
2007-09-15 06:07:54 +08:00
|
|
|
output.append(query % {'sequence': sequence_name,
|
2008-07-04 02:42:47 +08:00
|
|
|
'table': table_name,
|
2007-09-15 06:07:54 +08:00
|
|
|
'column': column_name})
|
2008-12-12 04:12:00 +08:00
|
|
|
# Only one AutoField is allowed per model, so don't
|
|
|
|
# continue to loop
|
|
|
|
break
|
2007-08-20 08:21:10 +08:00
|
|
|
for f in model._meta.many_to_many:
|
2008-07-04 02:42:47 +08:00
|
|
|
table_name = self.quote_name(f.m2m_db_table())
|
2007-08-20 08:21:10 +08:00
|
|
|
sequence_name = get_sequence_name(f.m2m_db_table())
|
2008-07-04 03:21:54 +08:00
|
|
|
column_name = self.quote_name('id')
|
2007-09-15 06:07:54 +08:00
|
|
|
output.append(query % {'sequence': sequence_name,
|
2008-07-04 02:42:47 +08:00
|
|
|
'table': table_name,
|
2008-07-04 03:21:54 +08:00
|
|
|
'column': column_name})
|
2007-08-20 08:21:10 +08:00
|
|
|
return output
|
|
|
|
|
2007-08-20 08:24:03 +08:00
|
|
|
def start_transaction_sql(self):
|
|
|
|
return ''
|
|
|
|
|
2007-08-20 08:30:19 +08:00
|
|
|
def tablespace_sql(self, tablespace, inline=False):
|
2008-12-12 04:12:00 +08:00
|
|
|
return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""),
|
|
|
|
self.quote_name(tablespace))
|
2007-08-20 08:30:19 +08:00
|
|
|
|
2008-07-29 13:09:29 +08:00
|
|
|
def value_to_db_time(self, value):
|
|
|
|
if value is None:
|
|
|
|
return None
|
|
|
|
if isinstance(value, basestring):
|
|
|
|
return datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
|
|
|
|
return datetime.datetime(1900, 1, 1, value.hour, value.minute,
|
|
|
|
value.second, value.microsecond)
|
|
|
|
|
|
|
|
def year_lookup_bounds_for_date_field(self, value):
|
|
|
|
first = '%s-01-01'
|
|
|
|
second = '%s-12-31'
|
|
|
|
return [first % value, second % value]
|
|
|
|
|
|
|
|
|
2007-08-20 05:30:57 +08:00
|
|
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
2008-08-25 04:16:56 +08:00
|
|
|
|
2007-08-20 11:26:55 +08:00
|
|
|
operators = {
|
|
|
|
'exact': '= %s',
|
|
|
|
'iexact': '= UPPER(%s)',
|
2008-04-11 01:44:40 +08:00
|
|
|
'contains': "LIKEC %s ESCAPE '\\'",
|
|
|
|
'icontains': "LIKEC UPPER(%s) ESCAPE '\\'",
|
2007-08-20 11:26:55 +08:00
|
|
|
'gt': '> %s',
|
|
|
|
'gte': '>= %s',
|
|
|
|
'lt': '< %s',
|
|
|
|
'lte': '<= %s',
|
2008-04-11 01:44:40 +08:00
|
|
|
'startswith': "LIKEC %s ESCAPE '\\'",
|
|
|
|
'endswith': "LIKEC %s ESCAPE '\\'",
|
|
|
|
'istartswith': "LIKEC UPPER(%s) ESCAPE '\\'",
|
|
|
|
'iendswith': "LIKEC UPPER(%s) ESCAPE '\\'",
|
2007-08-20 11:26:55 +08:00
|
|
|
}
|
2007-09-15 02:26:07 +08:00
|
|
|
oracle_version = None
|
2007-08-20 06:29:57 +08:00
|
|
|
|
2008-08-25 04:16:56 +08:00
|
|
|
def __init__(self, *args, **kwargs):
|
2008-08-11 20:11:25 +08:00
|
|
|
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
self.features = DatabaseFeatures()
|
|
|
|
self.ops = DatabaseOperations()
|
|
|
|
self.client = DatabaseClient()
|
|
|
|
self.creation = DatabaseCreation(self)
|
|
|
|
self.introspection = DatabaseIntrospection(self)
|
|
|
|
self.validation = BaseDatabaseValidation()
|
|
|
|
|
2006-05-26 13:37:21 +08:00
|
|
|
def _valid_connection(self):
|
|
|
|
return self.connection is not None
|
|
|
|
|
2009-01-08 13:12:14 +08:00
|
|
|
def _connect_string(self, settings):
|
|
|
|
if len(settings.DATABASE_HOST.strip()) == 0:
|
|
|
|
settings.DATABASE_HOST = 'localhost'
|
|
|
|
if len(settings.DATABASE_PORT.strip()) != 0:
|
|
|
|
dsn = '%s:%s/%s' % (settings.DATABASE_HOST,
|
|
|
|
settings.DATABASE_PORT,
|
|
|
|
settings.DATABASE_NAME)
|
|
|
|
else:
|
|
|
|
dsn = settings.DATABASE_NAME
|
|
|
|
return "%s/%s@%s" % (settings.DATABASE_USER,
|
|
|
|
settings.DATABASE_PASSWORD, dsn)
|
|
|
|
|
2007-08-20 05:30:57 +08:00
|
|
|
def _cursor(self, settings):
|
2007-12-21 07:06:30 +08:00
|
|
|
cursor = None
|
2006-05-26 13:37:21 +08:00
|
|
|
if not self._valid_connection():
|
2009-01-08 13:12:14 +08:00
|
|
|
conn_string = self._connect_string(settings)
|
|
|
|
self.connection = Database.connect(conn_string, **self.options)
|
2007-12-21 07:06:30 +08:00
|
|
|
cursor = FormatStylePlaceholderCursor(self.connection)
|
|
|
|
# Set oracle date to ansi date format. This only needs to execute
|
|
|
|
# once when we create a new connection.
|
|
|
|
cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD' "
|
|
|
|
"NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
|
2007-09-15 02:26:07 +08:00
|
|
|
try:
|
|
|
|
self.oracle_version = int(self.connection.version.split('.')[0])
|
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
|
|
|
# There's no way for the DatabaseOperations class to know the
|
|
|
|
# currently active Oracle version, so we do some setups here.
|
|
|
|
# TODO: Multi-db support will need a better solution (a way to
|
|
|
|
# communicate the current version).
|
|
|
|
if self.oracle_version <= 9:
|
|
|
|
self.ops.regex_lookup = self.ops.regex_lookup_9
|
|
|
|
else:
|
|
|
|
self.ops.regex_lookup = self.ops.regex_lookup_10
|
2007-09-15 02:26:07 +08:00
|
|
|
except ValueError:
|
|
|
|
pass
|
2007-12-22 02:49:07 +08:00
|
|
|
try:
|
|
|
|
self.connection.stmtcachesize = 20
|
|
|
|
except:
|
|
|
|
# Django docs specify cx_Oracle version 4.3.1 or higher, but
|
|
|
|
# stmtcachesize is available only in 4.3.2 and up.
|
|
|
|
pass
|
2007-12-21 07:06:30 +08:00
|
|
|
if not cursor:
|
|
|
|
cursor = FormatStylePlaceholderCursor(self.connection)
|
2009-01-16 04:39:09 +08:00
|
|
|
# Necessary to retrieve decimal values without rounding error.
|
|
|
|
cursor.numbersAsStrings = True
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
# Default arraysize of 1 is highly sub-optimal.
|
2007-06-23 22:16:00 +08:00
|
|
|
cursor.arraysize = 100
|
|
|
|
return cursor
|
2006-05-26 13:37:21 +08:00
|
|
|
|
2008-08-25 04:16:56 +08:00
|
|
|
|
2008-07-30 09:29:31 +08:00
|
|
|
class OracleParam(object):
|
|
|
|
"""
|
|
|
|
Wrapper object for formatting parameters for Oracle. If the string
|
|
|
|
representation of the value is large enough (greater than 4000 characters)
|
2008-12-12 04:12:00 +08:00
|
|
|
the input size needs to be set as NCLOB. Alternatively, if the parameter
|
|
|
|
has an `input_size` attribute, then the value of the `input_size` attribute
|
|
|
|
will be used instead. Otherwise, no input size will be set for the
|
|
|
|
parameter when executing the query.
|
2008-07-30 09:29:31 +08:00
|
|
|
"""
|
2008-12-12 04:12:00 +08:00
|
|
|
|
2008-07-30 09:29:31 +08:00
|
|
|
def __init__(self, param, charset, strings_only=False):
|
|
|
|
self.smart_str = smart_str(param, charset, strings_only)
|
|
|
|
if hasattr(param, 'input_size'):
|
|
|
|
# If parameter has `input_size` attribute, use that.
|
|
|
|
self.input_size = param.input_size
|
|
|
|
elif isinstance(param, basestring) and len(param) > 4000:
|
2008-12-12 04:12:00 +08:00
|
|
|
# Mark any string param greater than 4000 characters as an NCLOB.
|
2008-07-30 09:29:31 +08:00
|
|
|
self.input_size = Database.NCLOB
|
|
|
|
else:
|
|
|
|
self.input_size = None
|
|
|
|
|
2008-08-25 04:16:56 +08:00
|
|
|
|
2006-05-26 13:37:21 +08:00
|
|
|
class FormatStylePlaceholderCursor(Database.Cursor):
|
|
|
|
"""
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
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.
|
2006-05-26 13:37:21 +08:00
|
|
|
"""
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
charset = 'utf-8'
|
|
|
|
|
|
|
|
def _format_params(self, params):
|
2008-11-14 08:54:16 +08:00
|
|
|
return tuple([OracleParam(p, self.charset, True) for p in params])
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
|
2007-12-11 10:22:40 +08:00
|
|
|
def _guess_input_sizes(self, params_list):
|
2008-11-14 08:54:16 +08:00
|
|
|
sizes = [None] * len(params_list[0])
|
|
|
|
for params in params_list:
|
|
|
|
for i, value in enumerate(params):
|
2008-12-12 04:12:00 +08:00
|
|
|
if value.input_size:
|
|
|
|
sizes[i] = value.input_size
|
2008-11-14 08:54:16 +08:00
|
|
|
self.setinputsizes(*sizes)
|
2007-12-11 10:22:40 +08:00
|
|
|
|
2008-07-30 09:29:31 +08:00
|
|
|
def _param_generator(self, params):
|
2008-11-14 08:54:16 +08:00
|
|
|
return [p.smart_str for p in params]
|
2008-07-30 09:29:31 +08:00
|
|
|
|
2006-06-03 21:37:34 +08:00
|
|
|
def execute(self, query, params=None):
|
2007-09-15 05:32:25 +08:00
|
|
|
if params is None:
|
|
|
|
params = []
|
|
|
|
else:
|
|
|
|
params = self._format_params(params)
|
|
|
|
args = [(':arg%d' % i) for i in range(len(params))]
|
|
|
|
# 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
|
|
|
|
# is being passed to SQL*Plus.
|
|
|
|
if query.endswith(';') or query.endswith('/'):
|
|
|
|
query = query[:-1]
|
|
|
|
query = smart_str(query, self.charset) % tuple(args)
|
2007-12-11 10:22:40 +08:00
|
|
|
self._guess_input_sizes([params])
|
2008-08-26 01:20:28 +08:00
|
|
|
try:
|
2008-12-12 04:12:00 +08:00
|
|
|
return Database.Cursor.execute(self, query,
|
|
|
|
self._param_generator(params))
|
2008-08-26 01:20:28 +08:00
|
|
|
except DatabaseError, e:
|
|
|
|
# cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400.
|
2008-09-05 00:41:59 +08:00
|
|
|
if e.args[0].code == 1400 and not isinstance(e, IntegrityError):
|
|
|
|
e = IntegrityError(e.args[0])
|
2008-08-26 01:20:28 +08:00
|
|
|
raise e
|
2006-05-26 13:37:21 +08:00
|
|
|
|
2006-06-03 21:37:34 +08:00
|
|
|
def executemany(self, query, params=None):
|
2007-09-15 05:32:25 +08:00
|
|
|
try:
|
2008-12-12 04:12:00 +08:00
|
|
|
args = [(':arg%d' % i) for i in range(len(params[0]))]
|
2007-09-15 05:32:25 +08:00
|
|
|
except (IndexError, TypeError):
|
2008-12-12 04:12:00 +08:00
|
|
|
# No params given, nothing to do
|
|
|
|
return None
|
2007-09-15 05:32:25 +08:00
|
|
|
# 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
|
|
|
|
# is being passed to SQL*Plus.
|
|
|
|
if query.endswith(';') or query.endswith('/'):
|
|
|
|
query = query[:-1]
|
|
|
|
query = smart_str(query, self.charset) % tuple(args)
|
2008-07-30 09:29:31 +08:00
|
|
|
formatted = [self._format_params(i) for i in params]
|
|
|
|
self._guess_input_sizes(formatted)
|
2008-08-26 01:20:28 +08:00
|
|
|
try:
|
2008-12-12 04:12:00 +08:00
|
|
|
return Database.Cursor.executemany(self, query,
|
|
|
|
[self._param_generator(p) for p in formatted])
|
2008-08-26 01:20:28 +08:00
|
|
|
except DatabaseError, e:
|
|
|
|
# cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400.
|
2008-09-05 00:41:59 +08:00
|
|
|
if e.args[0].code == 1400 and not isinstance(e, IntegrityError):
|
|
|
|
e = IntegrityError(e.args[0])
|
2008-08-26 01:20:28 +08:00
|
|
|
raise e
|
2006-05-26 13:37:21 +08:00
|
|
|
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
def fetchone(self):
|
2008-01-05 06:51:22 +08:00
|
|
|
row = Database.Cursor.fetchone(self)
|
|
|
|
if row is None:
|
|
|
|
return row
|
2009-01-16 04:39:09 +08:00
|
|
|
return self._rowfactory(row)
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
|
|
|
|
def fetchmany(self, size=None):
|
|
|
|
if size is None:
|
|
|
|
size = self.arraysize
|
2009-01-16 04:39:09 +08:00
|
|
|
return tuple([self._rowfactory(r)
|
2008-12-12 04:12:00 +08:00
|
|
|
for r in Database.Cursor.fetchmany(self, size)])
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
|
|
|
|
def fetchall(self):
|
2009-01-16 04:39:09 +08:00
|
|
|
return tuple([self._rowfactory(r)
|
2008-12-12 04:12:00 +08:00
|
|
|
for r in Database.Cursor.fetchall(self)])
|
|
|
|
|
2009-01-16 04:39:09 +08:00
|
|
|
def _rowfactory(self, row):
|
|
|
|
# Cast numeric values as the appropriate Python type based upon the
|
|
|
|
# cursor description, and convert strings to unicode.
|
|
|
|
casted = []
|
|
|
|
for value, desc in zip(row, self.description):
|
|
|
|
if value is not None and desc[1] is Database.NUMBER:
|
|
|
|
precision, scale = desc[4:6]
|
|
|
|
if scale == -127:
|
|
|
|
if precision == 0:
|
|
|
|
# NUMBER column: decimal-precision floating point
|
|
|
|
# This will normally be an integer from a sequence,
|
|
|
|
# but it could be a decimal value.
|
|
|
|
if '.' in value:
|
|
|
|
value = Decimal(value)
|
|
|
|
else:
|
|
|
|
value = int(value)
|
|
|
|
else:
|
|
|
|
# FLOAT column: binary-precision floating point.
|
|
|
|
# This comes from FloatField columns.
|
|
|
|
value = float(value)
|
|
|
|
elif precision > 0:
|
|
|
|
# NUMBER(p,s) column: decimal-precision fixed point.
|
|
|
|
# This comes from IntField and DecimalField columns.
|
|
|
|
if scale == 0:
|
|
|
|
value = int(value)
|
|
|
|
else:
|
|
|
|
value = Decimal(value)
|
|
|
|
elif '.' in value:
|
|
|
|
# No type information. This normally comes from a
|
|
|
|
# mathematical expression in the SELECT list. Guess int
|
|
|
|
# or Decimal based on whether it has a decimal point.
|
|
|
|
value = Decimal(value)
|
|
|
|
else:
|
|
|
|
value = int(value)
|
|
|
|
else:
|
|
|
|
value = to_unicode(value)
|
|
|
|
casted.append(value)
|
|
|
|
return tuple(casted)
|
|
|
|
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2008-12-12 04:12:00 +08:00
|
|
|
|
2007-06-23 22:16:00 +08:00
|
|
|
def _get_sequence_reset_sql():
|
|
|
|
# TODO: colorize this SQL code with style.SQL_KEYWORD(), etc.
|
|
|
|
return """
|
2008-12-12 04:12:00 +08:00
|
|
|
DECLARE
|
|
|
|
startvalue integer;
|
|
|
|
cval integer;
|
|
|
|
BEGIN
|
|
|
|
LOCK TABLE %(table)s IN SHARE MODE;
|
|
|
|
SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s;
|
|
|
|
SELECT "%(sequence)s".nextval INTO cval FROM dual;
|
|
|
|
cval := startvalue - cval;
|
|
|
|
IF cval != 0 THEN
|
|
|
|
EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" MINVALUE 0 INCREMENT BY '||cval;
|
|
|
|
SELECT "%(sequence)s".nextval INTO cval FROM dual;
|
|
|
|
EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" INCREMENT BY 1';
|
|
|
|
END IF;
|
|
|
|
COMMIT;
|
|
|
|
END;
|
|
|
|
/"""
|
|
|
|
|
2007-06-23 22:16:00 +08:00
|
|
|
|
|
|
|
def get_sequence_name(table):
|
2007-08-20 07:53:39 +08:00
|
|
|
name_length = DatabaseOperations().max_name_length() - 3
|
2007-06-23 22:16:00 +08:00
|
|
|
return '%s_SQ' % util.truncate_name(table, name_length).upper()
|
2007-03-01 21:11:08 +08:00
|
|
|
|
2008-12-12 04:12:00 +08:00
|
|
|
|
2007-06-23 22:16:00 +08:00
|
|
|
def get_trigger_name(table):
|
2007-08-20 07:53:39 +08:00
|
|
|
name_length = DatabaseOperations().max_name_length() - 3
|
2007-06-23 22:16:00 +08:00
|
|
|
return '%s_TR' % util.truncate_name(table, name_length).upper()
|