Skip to content

Commit

Permalink
Fixed #7560 -- Moved a lot of the value conversion preparation for
Browse files Browse the repository at this point in the history
loading/saving interactions with the databases into django.db.backend. This
helps external db backend writers and removes a bunch of database-specific
if-tests in django.db.models.fields.

Great work from Leo Soto.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@8131 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
malcolmt committed Jul 29, 2008
1 parent 7bc728c commit b3b71a0
Show file tree
Hide file tree
Showing 11 changed files with 318 additions and 135 deletions.
70 changes: 66 additions & 4 deletions django/db/backends/__init__.py
Expand Up @@ -5,6 +5,9 @@
# Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local

from django.db.backends import util
from django.utils import datetime_safe

class BaseDatabaseWrapper(local):
"""
Represents a database connection.
Expand Down Expand Up @@ -36,23 +39,21 @@ def cursor(self):
return cursor

def make_debug_cursor(self, cursor):
from django.db.backends import util
return util.CursorDebugWrapper(cursor, self)

class BaseDatabaseFeatures(object):
allows_group_by_ordinal = True
inline_fk_references = True
# True if django.db.backend.utils.typecast_timestamp is used on values
# returned from dates() calls.
needs_datetime_string_cast = True
supports_constraints = True
supports_tablespaces = False
uses_case_insensitive_names = False
uses_custom_query_class = False
empty_fetchmany_value = []
update_can_self_select = True
supports_usecs = True
time_field_needs_date = False
interprets_empty_strings_as_nulls = False
date_field_supports_time_value = True
can_use_chunked_reads = True

class BaseDatabaseOperations(object):
Expand Down Expand Up @@ -263,3 +264,64 @@ def prep_for_like_query(self, x):
"""Prepares a value for use in a LIKE query."""
from django.utils.encoding import smart_unicode
return smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")

def value_to_db_date(self, value):
"""
Transform a date value to an object compatible with what is expected
by the backend driver for date columns.
"""
if value is None:
return None
return datetime_safe.new_date(value).strftime('%Y-%m-%d')

def value_to_db_datetime(self, value):
"""
Transform a datetime value to an object compatible with what is expected
by the backend driver for date columns.
"""
if value is None:
return None
return unicode(value)

def value_to_db_time(self, value):
"""
Transform a datetime value to an object compatible with what is expected
by the backend driver for date columns.
"""
if value is None:
return None
return unicode(value)

def value_to_db_decimal(self, value, max_digits, decimal_places):
"""
Transform a decimal.Decimal value to an object compatible with what is
expected by the backend driver for decimal (numeric) columns.
"""
if value is None:
return None
return util.format_number(value, max_digits, decimal_places)

def year_lookup_bounds(self, value):
"""
Returns a two-elements list with the lower and upper bound to be used
with a BETWEEN operator to query a field value using a year lookup
`value` is an int, containing the looked-up year.
"""
first = '%s-01-01 00:00:00'
second = '%s-12-31 23:59:59.999999'
return [first % value, second % value]

def year_lookup_bounds_for_date_field(self, value):
"""
Returns a two-elements list with the lower and upper bound to be used
with a BETWEEN operator to query a DateField value using a year lookup
`value` is an int, containing the looked-up year.
By default, it just calls `self.year_lookup_bounds`. Some backends need
this hook because on their DB date fields can't be compared to values
which include a time part.
"""
return self.year_lookup_bounds(value)

19 changes: 18 additions & 1 deletion django/db/backends/mysql/base.py
Expand Up @@ -63,7 +63,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
inline_fk_references = False
empty_fetchmany_value = ()
update_can_self_select = False
supports_usecs = False

class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):
Expand Down Expand Up @@ -124,6 +123,24 @@ def sql_flush(self, style, tables, sequences):
else:
return []

def value_to_db_datetime(self, value):
# MySQL doesn't support microseconds
if value is None:
return None
return unicode(value.replace(microsecond=0))

def value_to_db_time(self, value):
# MySQL doesn't support microseconds
if value is None:
return None
return unicode(value.replace(microsecond=0))

def year_lookup_bounds(self, value):
# Again, no microseconds
first = '%s-01-01 00:00:00'
second = '%s-12-31 23:59:59.99'
return [first % value, second % value]

class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
Expand Down
19 changes: 17 additions & 2 deletions django/db/backends/oracle/base.py
Expand Up @@ -5,6 +5,8 @@
"""

import os
import datetime
import time

from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
from django.db.backends.oracle import query
Expand All @@ -28,9 +30,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_tablespaces = True
uses_case_insensitive_names = True
uses_custom_query_class = True
time_field_needs_date = True
interprets_empty_strings_as_nulls = True
date_field_supports_time_value = False

class DatabaseOperations(BaseDatabaseOperations):
def autoinc_sql(self, table, column):
Expand Down Expand Up @@ -180,6 +180,21 @@ def start_transaction_sql(self):
def tablespace_sql(self, tablespace, inline=False):
return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), self.quote_name(tablespace))

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]



class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
Expand Down
8 changes: 7 additions & 1 deletion django/db/backends/sqlite3/base.py
Expand Up @@ -84,6 +84,12 @@ def sql_flush(self, style, tables, sequences):
# sql_flush() implementations). Just return SQL at this point
return sql

def year_lookup_bounds(self, value):
first = '%s-01-01'
second = '%s-12-31 23:59:59.999999'
return [first % value, second % value]


class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
Expand Down Expand Up @@ -159,7 +165,7 @@ def _sqlite_extract(lookup_type, dt):
dt = util.typecast_timestamp(dt)
except (ValueError, TypeError):
return None
return str(getattr(dt, lookup_type))
return getattr(dt, lookup_type)

def _sqlite_date_trunc(lookup_type, dt):
try:
Expand Down
7 changes: 7 additions & 0 deletions django/db/backends/util.py
Expand Up @@ -117,3 +117,10 @@ def truncate_name(name, length=None):
hash = md5.md5(name).hexdigest()[:4]

return '%s%s' % (name[:length-4], hash)

def format_number(value, max_digits, decimal_places):
"""
Formats a number into a string with the requisite number of digits and
decimal places.
"""
return u"%.*f" % (decimal_places, value)

0 comments on commit b3b71a0

Please sign in to comment.