1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +00:00

boulder-oracle-sprint: Made the iterator methods of _QuerySet, DateQuerySet and ValueQuerySet each call a resolve_columns method on each row, which may be implemented in the backend to do custom processing on the result rows. Oracle uses this to resolve lob objects and to correctly cast datetime objects.

git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@5013 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Boulder Sprinters 2007-04-16 22:31:55 +00:00
parent 531235c3bf
commit 9699399eee
2 changed files with 72 additions and 43 deletions

View File

@ -296,46 +296,12 @@ def get_query_set_class(DefaultQuerySet):
# 1. retrieve each row in turn # 1. retrieve each row in turn
# 2. convert NCLOBs # 2. convert NCLOBs
def resolve_cols(row):
for field in row:
value = field
if isinstance(field, Database.LOB):
value = field.read()
# cx_Oracle always returns datetime.datetime objects for
# DATE and TIMESTAMP columns, but Django wants to see a
# python datetime.date, .time, or .datetime.
# As a workaround, we cast to date if all the time-related
# fields are 0, or to time if the date is 1/1/1900.
# A better fix would involve either patching cx_Oracle
# or checking the Model here, neither of which is good.
elif isinstance(field, datetime.datetime):
if field.hour == field.minute == field.second == field.microsecond == 0:
value = field.date()
elif field.year == 1900 and field.month == field.day == 1:
value = field.time()
# In Python 2.3, the cx_Oracle driver returns its own
# Timestamp object that we must convert to a datetime class.
elif isinstance(field, Database.Timestamp):
if field.hour == field.minute == field.second == field.fsecond == 0:
value = datetime.date(field.year, field.month, field.day)
elif field.year == 1900 and field.month == field.day == 1:
value = datetime.time(field.hour, field.minute, field.second, field.fsecond)
else:
value = datetime.datetime(field.year, field.month, field.day, field.hour,
field.minute, field.second, field.fsecond)
# Since Oracle won't distinguish between NULL and an empty
# string (''), we store empty strings as a space. Here is
# where we undo that treachery.
if value == ' ':
value = ''
yield value
while 1: while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows: if not rows:
raise StopIteration raise StopIteration
for row in rows: for row in rows:
row = list(resolve_cols(row)) row = self.resolve_columns(row)
if fill_cache: if fill_cache:
obj, index_end = get_cached_row(klass=self.model, row=row, obj, index_end = get_cached_row(klass=self.model, row=row,
index_start=0, max_depth=self._max_related_depth) index_start=0, max_depth=self._max_related_depth)
@ -479,6 +445,46 @@ def get_query_set_class(DefaultQuerySet):
else: else:
return select, " ".join(sql), params return select, " ".join(sql), params
def resolve_columns(self, row, fields=()):
from django.db.models.fields import DateField, DateTimeField, TimeField
values = []
for value, field in map(None, row, fields):
if isinstance(value, Database.LOB):
value = value.read()
# Since Oracle won't distinguish between NULL and an empty
# string (''), we store empty strings as a space. Here is
# where we undo that treachery.
if value == ' ':
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):
value = value.time()
elif value.hour == value.minute == value.second == value.microsecond == 0:
value = value.date()
elif value.year == 1900 and value.month == value.day == 1:
value = value.time()
values.append(value)
return values
return OracleQuerySet return OracleQuerySet

View File

@ -184,11 +184,14 @@ class _QuerySet(object):
fill_cache = self._select_related fill_cache = self._select_related
index_end = len(self.model._meta.fields) index_end = len(self.model._meta.fields)
has_resolve_columns = hasattr(self, 'resolve_columns')
while 1: while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows: if not rows:
raise StopIteration raise StopIteration
for row in rows: for row in rows:
if has_resolve_columns:
row = self.resolve_columns(row)
if fill_cache: if fill_cache:
obj, index_end = get_cached_row(klass=self.model, row=row, obj, index_end = get_cached_row(klass=self.model, row=row,
index_start=0, max_depth=self._max_related_depth) index_start=0, max_depth=self._max_related_depth)
@ -573,20 +576,24 @@ class ValuesQuerySet(QuerySet):
# self._fields is a list of field names to fetch. # self._fields is a list of field names to fetch.
if self._fields: if self._fields:
columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields] fields = [self.model._meta.get_field(f, many_to_many=False) for f in self._fields]
field_names = self._fields
else: # Default to all fields. else: # Default to all fields.
columns = [f.column for f in self.model._meta.fields] fields = self.model._meta.fields
field_names = [f.attname for f in self.model._meta.fields] columns = [f.column for f in fields]
field_names = [f.attname for f in fields]
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 = connection.cursor() cursor = connection.cursor()
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
has_resolve_columns = hasattr(self, 'resolve_columns')
while 1: while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows: if not rows:
raise StopIteration raise StopIteration
for row in rows: for row in rows:
if has_resolve_columns:
row = self.resolve_columns(row, fields)
yield dict(zip(field_names, row)) yield dict(zip(field_names, row))
def _clone(self, klass=None, **kwargs): def _clone(self, klass=None, **kwargs):
@ -597,6 +604,7 @@ class ValuesQuerySet(QuerySet):
class DateQuerySet(QuerySet): class DateQuerySet(QuerySet):
def iterator(self): def iterator(self):
from django.db.backends.util import typecast_timestamp from django.db.backends.util import typecast_timestamp
from django.db.models.fields import DateTimeField
self._order_by = () # Clear this because it'll mess things up otherwise. self._order_by = () # Clear this because it'll mess things up otherwise.
if self._field.null: if self._field.null:
self._where.append('%s.%s IS NOT NULL' % \ self._where.append('%s.%s IS NOT NULL' % \
@ -620,10 +628,25 @@ class DateQuerySet(QuerySet):
backend.quote_name(self._field.column))), sql, group_by, self._order) backend.quote_name(self._field.column))), sql, group_by, self._order)
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute(sql, params) cursor.execute(sql, params)
if backend.needs_datetime_string_cast:
return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()] has_resolve_columns = hasattr(self, 'resolve_columns')
else: needs_datetime_string_cast = backend.needs_datetime_string_cast
return [row[0] for row in cursor.fetchall()] dates = []
# It would be better to use self._field here instead of DateTimeField(),
# but in Oracle that will result in a list of datetime.date instead of
# datetime.datetime.
fields = [DateTimeField()]
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows:
return dates
for row in rows:
date = row[0]
if has_resolve_columns:
date = self.resolve_columns([date], fields)[0]
elif needs_datetime_string_cast:
date = typecast_timestamp(str(date))
dates.append(date)
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)