1
0
mirror of https://github.com/django/django.git synced 2025-07-05 18:29:11 +00:00

queryset-refactor: The Oracle changes necessary for [7426]. I can't test these

at the moment, but they should be close to correct.

Refs #6956.


git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7427 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2008-04-16 02:58:26 +00:00
parent 1add6fb366
commit f2b64c0c4e
3 changed files with 137 additions and 117 deletions

View File

@ -49,7 +49,7 @@ class BaseDatabaseFeatures(object):
supports_constraints = True supports_constraints = True
supports_tablespaces = False supports_tablespaces = False
uses_case_insensitive_names = False uses_case_insensitive_names = False
uses_custom_queryset = False uses_custom_query_class = False
empty_fetchmany_value = [] empty_fetchmany_value = []
class BaseDatabaseOperations(object): class BaseDatabaseOperations(object):
@ -199,11 +199,11 @@ class BaseDatabaseOperations(object):
""" """
return 'DEFAULT' return 'DEFAULT'
def query_set_class(self, DefaultQuerySet): def query_class(self, DefaultQueryClass):
""" """
Given the default QuerySet class, returns a custom QuerySet class Given the default QuerySet class, returns a custom QuerySet class
to use for this backend. Returns None if a custom QuerySet isn't used. to use for this backend. Returns None if a custom QuerySet isn't used.
See also BaseDatabaseFeatures.uses_custom_queryset, which regulates See also BaseDatabaseFeatures.uses_custom_query_class, which regulates
whether this method is called at all. whether this method is called at all.
""" """
return None return None

View File

@ -4,10 +4,10 @@ Oracle database backend for Django.
Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/ Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
""" """
import datetime
import os import os
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
from django.db.backends.oracle import query
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.utils.encoding import smart_str, force_unicode from django.utils.encoding import smart_str, force_unicode
@ -30,7 +30,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
needs_upper_for_iops = True needs_upper_for_iops = True
supports_tablespaces = True supports_tablespaces = True
uses_case_insensitive_names = True uses_case_insensitive_names = True
uses_custom_queryset = True uses_custom_query_class = True
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
def autoinc_sql(self, table, column): def autoinc_sql(self, table, column):
@ -99,118 +99,8 @@ class DatabaseOperations(BaseDatabaseOperations):
def max_name_length(self): def max_name_length(self):
return 30 return 30
def query_set_class(self, DefaultQuerySet): def query_class(self, DefaultQueryClass):
# Getting the base default `Query` object. return query.query_class(DefaultQueryClass, Database)
DefaultQuery = DefaultQuerySet().query.__class__
class OracleQuery(DefaultQuery):
def resolve_columns(self, row, fields=()):
from django.db.models.fields import DateField, DateTimeField, \
TimeField, BooleanField, NullBooleanField, DecimalField, Field
values = []
for value, field in map(None, row, fields):
if isinstance(value, Database.LOB):
value = value.read()
# Oracle stores empty strings as null. We need to undo this in
# order to adhere to the Django convention of using the empty
# string instead of null, but only if the field accepts the
# empty string.
if value is None and isinstance(field, Field) and field.empty_strings_allowed:
value = u''
# Convert 1 or 0 to True or False
elif value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
value = bool(value)
# Convert floats to decimals
elif value is not None and isinstance(field, DecimalField):
value = util.typecast_decimal(field.format_number(value))
# cx_Oracle always returns datetime.datetime objects for
# DATE and TIMESTAMP columns, but Django wants to see a
# python datetime.date, .time, or .datetime. We use the type
# of the Field to determine which to cast to, but it's not
# always available.
# As a workaround, we cast to date if all the time-related
# values are 0, or to time if the date is 1/1/1900.
# This could be cleaned a bit by adding a method to the Field
# classes to normalize values from the database (the to_python
# method is used for validation and isn't what we want here).
elif isinstance(value, Database.Timestamp):
# In Python 2.3, the cx_Oracle driver returns its own
# Timestamp object that we must convert to a datetime class.
if not isinstance(value, datetime.datetime):
value = datetime.datetime(value.year, value.month, value.day, value.hour,
value.minute, value.second, value.fsecond)
if isinstance(field, DateTimeField):
pass # DateTimeField subclasses DateField so must be checked first.
elif isinstance(field, DateField):
value = value.date()
elif isinstance(field, TimeField) or (value.year == 1900 and value.month == value.day == 1):
value = value.time()
elif value.hour == value.minute == value.second == value.microsecond == 0:
value = value.date()
values.append(value)
return values
def as_sql(self, with_limits=True):
"""
Creates the SQL for this query. Returns the SQL string and list
of parameters. This is overriden from the original Query class
to accommodate Oracle's limit/offset SQL.
If 'with_limits' is False, any limit/offset information is not
included in the query.
"""
# The `do_offset` flag indicates whether we need to construct
# the SQL needed to use limit/offset w/Oracle.
do_offset = with_limits and (self.high_mark or self.low_mark)
# If no offsets, just return the result of the base class
# `as_sql`.
if not do_offset:
return super(OracleQuery, self).as_sql(with_limits=False)
# `get_columns` needs to be called before `get_ordering` to
# populate `_select_alias`.
self.pre_sql_setup()
out_cols = self.get_columns()
ordering = self.get_ordering()
# Getting the "ORDER BY" SQL for the ROW_NUMBER() result.
if ordering:
rn_orderby = ', '.join(ordering)
else:
# Oracle's ROW_NUMBER() function always requires an
# order-by clause. So we need to define a default
# order-by, since none was provided.
qn = self.quote_name_unless_alias
opts = self.model._meta
rn_orderby = '%s.%s' % (qn(opts.db_table), qn(opts.fields[0].db_column or opts.fields[0].column))
# Getting the selection SQL and the params, which has the `rn`
# extra selection SQL; we pop `rn` after this completes so we do
# not get the attribute on the returned models.
self.extra_select['rn'] = 'ROW_NUMBER() OVER (ORDER BY %s )' % rn_orderby
sql, params= super(OracleQuery, self).as_sql(with_limits=False)
self.extra_select.pop('rn')
# Constructing the result SQL, using the initial select SQL
# obtained above.
result = ['SELECT * FROM (%s)' % sql]
# Place WHERE condition on `rn` for the desired range.
result.append('WHERE rn > %d' % self.low_mark)
if self.high_mark:
result.append('AND rn <= %d' % self.high_mark)
# Returning the SQL w/params.
return ' '.join(result), params
from django.db import connection
class OracleQuerySet(DefaultQuerySet):
"The OracleQuerySet is overriden to use OracleQuery."
def __init__(self, model=None, query=None):
super(OracleQuerySet, self).__init__(model=model, query=query)
self.query = query or OracleQuery(self.model, connection)
return OracleQuerySet
def quote_name(self, name): def quote_name(self, name):
# SQL92 requires delimited (quoted) names to be case-sensitive. When # SQL92 requires delimited (quoted) names to be case-sensitive. When

View File

@ -0,0 +1,130 @@
"""
Custom Query class for this backend (a derivative of
django.db.models.sql.query.Query).
"""
import datetime
from django.db.backends import util
# Cache. Maps default query class to new Oracle query class.
_classes = {}
def query_class(QueryClass, Database):
"""
Returns a custom djang.db.models.sql.query.Query subclass that is
appropraite for Oracle.
The 'Database' module (cx_Oracle) is passed in here so that all the setup
required to import it only needs to be done by the calling module.
"""
global _classes
try:
return _classes[QueryClass]
except KeyError:
pass
class OracleQuery(QueryClass):
def resolve_columns(self, row, fields=()):
from django.db.models.fields import DateField, DateTimeField, \
TimeField, BooleanField, NullBooleanField, DecimalField, Field
values = []
for value, field in map(None, row, fields):
if isinstance(value, Database.LOB):
value = value.read()
# Oracle stores empty strings as null. We need to undo this in
# order to adhere to the Django convention of using the empty
# string instead of null, but only if the field accepts the
# empty string.
if value is None and isinstance(field, Field) and field.empty_strings_allowed:
value = u''
# Convert 1 or 0 to True or False
elif value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
value = bool(value)
# Convert floats to decimals
elif value is not None and isinstance(field, DecimalField):
value = util.typecast_decimal(field.format_number(value))
# cx_Oracle always returns datetime.datetime objects for
# DATE and TIMESTAMP columns, but Django wants to see a
# python datetime.date, .time, or .datetime. We use the type
# of the Field to determine which to cast to, but it's not
# always available.
# As a workaround, we cast to date if all the time-related
# values are 0, or to time if the date is 1/1/1900.
# This could be cleaned a bit by adding a method to the Field
# classes to normalize values from the database (the to_python
# method is used for validation and isn't what we want here).
elif isinstance(value, Database.Timestamp):
# In Python 2.3, the cx_Oracle driver returns its own
# Timestamp object that we must convert to a datetime class.
if not isinstance(value, datetime.datetime):
value = datetime.datetime(value.year, value.month, value.day, value.hour,
value.minute, value.second, value.fsecond)
if isinstance(field, DateTimeField):
pass # DateTimeField subclasses DateField so must be checked first.
elif isinstance(field, DateField):
value = value.date()
elif isinstance(field, TimeField) or (value.year == 1900 and value.month == value.day == 1):
value = value.time()
elif value.hour == value.minute == value.second == value.microsecond == 0:
value = value.date()
values.append(value)
return values
def as_sql(self, with_limits=True):
"""
Creates the SQL for this query. Returns the SQL string and list
of parameters. This is overriden from the original Query class
to accommodate Oracle's limit/offset SQL.
If 'with_limits' is False, any limit/offset information is not
included in the query.
"""
# The `do_offset` flag indicates whether we need to construct
# the SQL needed to use limit/offset w/Oracle.
do_offset = with_limits and (self.high_mark or self.low_mark)
# If no offsets, just return the result of the base class
# `as_sql`.
if not do_offset:
return super(OracleQuery, self).as_sql(with_limits=False)
# `get_columns` needs to be called before `get_ordering` to
# populate `_select_alias`.
self.pre_sql_setup()
out_cols = self.get_columns()
ordering = self.get_ordering()
# Getting the "ORDER BY" SQL for the ROW_NUMBER() result.
if ordering:
rn_orderby = ', '.join(ordering)
else:
# Oracle's ROW_NUMBER() function always requires an
# order-by clause. So we need to define a default
# order-by, since none was provided.
qn = self.quote_name_unless_alias
opts = self.model._meta
rn_orderby = '%s.%s' % (qn(opts.db_table), qn(opts.fields[0].db_column or opts.fields[0].column))
# Getting the selection SQL and the params, which has the `rn`
# extra selection SQL; we pop `rn` after this completes so we do
# not get the attribute on the returned models.
self.extra_select['rn'] = 'ROW_NUMBER() OVER (ORDER BY %s )' % rn_orderby
sql, params= super(OracleQuery, self).as_sql(with_limits=False)
self.extra_select.pop('rn')
# Constructing the result SQL, using the initial select SQL
# obtained above.
result = ['SELECT * FROM (%s)' % sql]
# Place WHERE condition on `rn` for the desired range.
result.append('WHERE rn > %d' % self.low_mark)
if self.high_mark:
result.append('AND rn <= %d' % self.high_mark)
# Returning the SQL w/params.
return ' '.join(result), params
_classes[QueryClass] = OracleQuery
return OracleQuery