Made tzname a parameter of datetime_extract/trunc_sql.
This is required to work around a bug in Oracle.
This commit is contained in:
parent
924a144ef8
commit
01b6366f3c
|
@ -542,25 +542,20 @@ class BaseDatabaseOperations(object):
|
|||
"""
|
||||
return "%s"
|
||||
|
||||
def datetime_extract_sql(self, lookup_type, field_name):
|
||||
def datetime_extract_sql(self, lookup_type, field_name, tzname):
|
||||
"""
|
||||
Given a lookup_type of 'year', 'month', 'day', 'hour', 'minute' or
|
||||
'second', returns the SQL that extracts a value from the given
|
||||
datetime field field_name.
|
||||
|
||||
When time zone support is enabled, the SQL should include a '%s'
|
||||
placeholder for the time zone's name.
|
||||
datetime field field_name, and a tuple of parameters.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def datetime_trunc_sql(self, lookup_type, field_name):
|
||||
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
||||
"""
|
||||
Given a lookup_type of 'year', 'month', 'day', 'hour', 'minute' or
|
||||
'second', returns the SQL that truncates the given datetime field
|
||||
field_name to a datetime object with only the given specificity.
|
||||
|
||||
When time zone support is enabled, the SQL should include a '%s'
|
||||
placeholder for the time zone's name.
|
||||
field_name to a datetime object with only the given specificity, and
|
||||
a tuple of parameters.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
|
@ -225,20 +225,27 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
|
||||
return sql
|
||||
|
||||
def datetime_extract_sql(self, lookup_type, field_name):
|
||||
def datetime_extract_sql(self, lookup_type, field_name, tzname):
|
||||
if settings.USE_TZ:
|
||||
field_name = "CONVERT_TZ(%s, 'UTC', %%s)" % field_name
|
||||
params = [tzname]
|
||||
else:
|
||||
params = []
|
||||
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
|
||||
if lookup_type == 'week_day':
|
||||
# DAYOFWEEK() returns an integer, 1-7, Sunday=1.
|
||||
# Note: WEEKDAY() returns 0-6, Monday=0.
|
||||
return "DAYOFWEEK(%s)" % field_name
|
||||
sql = "DAYOFWEEK(%s)" % field_name
|
||||
else:
|
||||
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
||||
sql = "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
||||
return sql, params
|
||||
|
||||
def datetime_trunc_sql(self, lookup_type, field_name):
|
||||
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
||||
if settings.USE_TZ:
|
||||
field_name = "CONVERT_TZ(%s, 'UTC', %%s)" % field_name
|
||||
params = [tzname]
|
||||
else:
|
||||
params = []
|
||||
fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
|
||||
format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
|
||||
format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
|
||||
|
@ -249,7 +256,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
else:
|
||||
format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
|
||||
sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
|
||||
return sql
|
||||
return sql, params
|
||||
|
||||
def date_interval_sql(self, sql, connector, timedelta):
|
||||
return "(%s %s INTERVAL '%d 0:0:%d:%d' DAY_MICROSECOND)" % (sql, connector,
|
||||
|
|
|
@ -7,6 +7,7 @@ from __future__ import unicode_literals
|
|||
|
||||
import datetime
|
||||
import decimal
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
|
@ -131,6 +132,7 @@ WHEN (new.%(col_name)s IS NULL)
|
|||
if lookup_type == 'week_day':
|
||||
# TO_CHAR(field, 'D') returns an integer from 1-7, where 1=Sunday.
|
||||
return "TO_CHAR(%s, 'D')" % field_name
|
||||
else:
|
||||
# http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions050.htm
|
||||
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
||||
|
||||
|
@ -155,29 +157,52 @@ WHEN (new.%(col_name)s IS NULL)
|
|||
else:
|
||||
return "TRUNC(%s)" % field_name
|
||||
|
||||
def datetime_extract_sql(self, lookup_type, field_name):
|
||||
# Oracle crashes with "ORA-03113: end-of-file on communication channel"
|
||||
# if the time zone name is passed in parameter. Use interpolation instead.
|
||||
# https://groups.google.com/forum/#!msg/django-developers/zwQju7hbG78/9l934yelwfsJ
|
||||
# This regexp matches all time zone names from the zoneinfo database.
|
||||
_tzname_re = re.compile(r'^[\w/+-]+$')
|
||||
|
||||
def _convert_field_to_tz(self, field_name, tzname):
|
||||
if not self._tzname_re.match(tzname):
|
||||
raise ValueError("Invalid time zone name: %s" % tzname)
|
||||
# Convert from UTC to local time, returning TIMESTAMP WITH TIME ZONE.
|
||||
result = "(FROM_TZ(%s, 'UTC') AT TIME ZONE '%s')" % (field_name, tzname)
|
||||
# Extracting from a TIMESTAMP WITH TIME ZONE ignore the time zone.
|
||||
# Convert to a DATETIME, which is called DATE by Oracle. There's no
|
||||
# built-in function to do that; the easiest is to go through a string.
|
||||
result = "TO_CHAR(%s, 'YYYY-MM-DD HH24:MI:SS')" % result
|
||||
result = "TO_DATE(%s, 'YYYY-MM-DD HH24:MI:SS')" % result
|
||||
# Re-convert to a TIMESTAMP because EXTRACT only handles the date part
|
||||
# on DATE values, even though they actually store the time part.
|
||||
return "CAST(%s AS TIMESTAMP)" % result
|
||||
|
||||
def datetime_extract_sql(self, lookup_type, field_name, tzname):
|
||||
if settings.USE_TZ:
|
||||
field_name = "CAST((FROM_TZ(%s, 'UTC') AT TIME ZONE (%%s)) AS DATE)" % field_name
|
||||
field_name = self._convert_field_to_tz(field_name, tzname)
|
||||
if lookup_type == 'week_day':
|
||||
# TO_CHAR(field, 'D') returns an integer from 1-7, where 1=Sunday.
|
||||
return "TO_CHAR(%s, 'D')" % field_name
|
||||
sql = "TO_CHAR(%s, 'D')" % field_name
|
||||
else:
|
||||
# http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions050.htm
|
||||
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
||||
sql = "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
||||
return sql, []
|
||||
|
||||
def datetime_trunc_sql(self, lookup_type, field_name):
|
||||
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
||||
if settings.USE_TZ:
|
||||
field_name = "CAST((FROM_TZ(%s, 'UTC') AT TIME ZONE (%%s)) AS DATE)" % field_name
|
||||
field_name = self._convert_field_to_tz(field_name, tzname)
|
||||
# http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions230.htm#i1002084
|
||||
if lookup_type in ('year', 'month'):
|
||||
return "TRUNC(%s, '%s')" % (field_name, lookup_type.upper())
|
||||
sql = "TRUNC(%s, '%s')" % (field_name, lookup_type.upper())
|
||||
elif lookup_type == 'day':
|
||||
return "TRUNC(%s)" % field_name
|
||||
sql = "TRUNC(%s)" % field_name
|
||||
elif lookup_type == 'hour':
|
||||
return "TRUNC(%s, 'HH24')" % field_name
|
||||
sql = "TRUNC(%s, 'HH24')" % field_name
|
||||
elif lookup_type == 'minute':
|
||||
return "TRUNC(%s, 'MI')" % field_name
|
||||
sql = "TRUNC(%s, 'MI')" % field_name
|
||||
else:
|
||||
return field_name
|
||||
sql = field_name # Cast to DATE removes sub-second precision.
|
||||
return sql, []
|
||||
|
||||
def convert_values(self, value, field):
|
||||
if isinstance(value, Database.LOB):
|
||||
|
|
|
@ -37,21 +37,29 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
# http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
|
||||
return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
|
||||
|
||||
def datetime_extract_sql(self, lookup_type, field_name):
|
||||
def datetime_extract_sql(self, lookup_type, field_name, tzname):
|
||||
if settings.USE_TZ:
|
||||
field_name = "%s AT TIME ZONE %%s" % field_name
|
||||
params = [tzname]
|
||||
else:
|
||||
params = []
|
||||
# http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
|
||||
if lookup_type == 'week_day':
|
||||
# For consistency across backends, we return Sunday=1, Saturday=7.
|
||||
return "EXTRACT('dow' FROM %s) + 1" % field_name
|
||||
sql = "EXTRACT('dow' FROM %s) + 1" % field_name
|
||||
else:
|
||||
return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name)
|
||||
sql = "EXTRACT('%s' FROM %s)" % (lookup_type, field_name)
|
||||
return sql, params
|
||||
|
||||
def datetime_trunc_sql(self, lookup_type, field_name):
|
||||
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
||||
if settings.USE_TZ:
|
||||
field_name = "%s AT TIME ZONE %%s" % field_name
|
||||
params = [tzname]
|
||||
else:
|
||||
params = []
|
||||
# http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
|
||||
return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
|
||||
sql = "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
|
||||
return sql, params
|
||||
|
||||
def deferrable_sql(self):
|
||||
return " DEFERRABLE INITIALLY DEFERRED"
|
||||
|
|
|
@ -172,7 +172,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
# cause a collision with a field name).
|
||||
return "django_date_trunc('%s', %s)" % (lookup_type.lower(), field_name)
|
||||
|
||||
def datetime_extract_sql(self, lookup_type, field_name):
|
||||
def datetime_extract_sql(self, lookup_type, field_name, tzname):
|
||||
# Same comment as in date_extract_sql.
|
||||
if settings.USE_TZ:
|
||||
if pytz is None:
|
||||
|
@ -180,12 +180,9 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
raise ImproperlyConfigured("This query requires pytz, "
|
||||
"but it isn't installed.")
|
||||
return "django_datetime_extract('%s', %s, %%s)" % (
|
||||
lookup_type.lower(), field_name)
|
||||
else:
|
||||
return "django_datetime_extract('%s', %s, NULL)" % (
|
||||
lookup_type.lower(), field_name)
|
||||
lookup_type.lower(), field_name), [tzname]
|
||||
|
||||
def datetime_trunc_sql(self, lookup_type, field_name):
|
||||
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
||||
# Same comment as in date_trunc_sql.
|
||||
if settings.USE_TZ:
|
||||
if pytz is None:
|
||||
|
@ -193,10 +190,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
raise ImproperlyConfigured("This query requires pytz, "
|
||||
"but it isn't installed.")
|
||||
return "django_datetime_trunc('%s', %s, %%s)" % (
|
||||
lookup_type.lower(), field_name)
|
||||
else:
|
||||
return "django_datetime_trunc('%s', %s, NULL)" % (
|
||||
lookup_type.lower(), field_name)
|
||||
lookup_type.lower(), field_name), [tzname]
|
||||
|
||||
def drop_foreignkey_sql(self):
|
||||
return ""
|
||||
|
|
|
@ -1034,13 +1034,6 @@ class SQLDateCompiler(SQLCompiler):
|
|||
yield date
|
||||
|
||||
class SQLDateTimeCompiler(SQLCompiler):
|
||||
def as_sql(self):
|
||||
sql, params = super(SQLDateTimeCompiler, self).as_sql()
|
||||
if settings.USE_TZ:
|
||||
tzname = timezone._get_timezone_name(self.query.tzinfo)
|
||||
params = (tzname,) + params
|
||||
return sql, params
|
||||
|
||||
def results_iter(self):
|
||||
"""
|
||||
Returns an iterator over the results from executing this query.
|
||||
|
|
|
@ -26,8 +26,6 @@ class Date(object):
|
|||
"""
|
||||
Add a date selection column.
|
||||
"""
|
||||
trunc_func = 'date_trunc_sql'
|
||||
|
||||
def __init__(self, col, lookup_type):
|
||||
self.col = col
|
||||
self.lookup_type = lookup_type
|
||||
|
@ -42,10 +40,25 @@ class Date(object):
|
|||
col = '%s.%s' % tuple([qn(c) for c in self.col])
|
||||
else:
|
||||
col = self.col
|
||||
return getattr(connection.ops, self.trunc_func)(self.lookup_type, col), []
|
||||
return connection.ops.date_trunc_sql(self.lookup_type, col), []
|
||||
|
||||
class DateTime(Date):
|
||||
class DateTime(object):
|
||||
"""
|
||||
Add a datetime selection column.
|
||||
"""
|
||||
trunc_func = 'datetime_trunc_sql'
|
||||
def __init__(self, col, lookup_type, tzname):
|
||||
self.col = col
|
||||
self.lookup_type = lookup_type
|
||||
self.tzname = tzname
|
||||
|
||||
def relabel_aliases(self, change_map):
|
||||
c = self.col
|
||||
if isinstance(c, (list, tuple)):
|
||||
self.col = (change_map.get(c[0], c[0]), c[1])
|
||||
|
||||
def as_sql(self, qn, connection):
|
||||
if isinstance(self.col, (list, tuple)):
|
||||
col = '%s.%s' % tuple([qn(c) for c in self.col])
|
||||
else:
|
||||
col = self.col
|
||||
return connection.ops.datetime_trunc_sql(self.lookup_type, col, self.tzname)
|
||||
|
|
|
@ -14,6 +14,7 @@ from django.db.models.sql.where import AND, Constraint
|
|||
from django.utils.functional import Promise
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils import six
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
__all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery',
|
||||
|
@ -222,7 +223,6 @@ class DateQuery(Query):
|
|||
"""
|
||||
|
||||
compiler = 'SQLDateCompiler'
|
||||
select_type = Date
|
||||
|
||||
def add_select(self, field_name, lookup_type, order='ASC'):
|
||||
"""
|
||||
|
@ -241,7 +241,7 @@ class DateQuery(Query):
|
|||
field = result[0]
|
||||
self._check_field(field) # overridden in DateTimeQuery
|
||||
alias = result[3][-1]
|
||||
select = self.select_type((alias, field.column), lookup_type)
|
||||
select = self._get_select((alias, field.column), lookup_type)
|
||||
self.clear_select_clause()
|
||||
self.select = [SelectInfo(select, None)]
|
||||
self.distinct = True
|
||||
|
@ -257,6 +257,9 @@ class DateQuery(Query):
|
|||
assert not isinstance(field, DateTimeField), \
|
||||
"%r is a DateTimeField, not a DateField." % field.name
|
||||
|
||||
def _get_select(self, col, lookup_type):
|
||||
return Date(col, lookup_type)
|
||||
|
||||
class DateTimeQuery(DateQuery):
|
||||
"""
|
||||
A DateTimeQuery is like a DateQuery but for a datetime field. If time zone
|
||||
|
@ -265,12 +268,18 @@ class DateTimeQuery(DateQuery):
|
|||
"""
|
||||
|
||||
compiler = 'SQLDateTimeCompiler'
|
||||
select_type = DateTime
|
||||
|
||||
def _check_field(self, field):
|
||||
assert isinstance(field, DateTimeField), \
|
||||
"%r isn't a DateTimeField." % field.name
|
||||
|
||||
def _get_select(self, col, lookup_type):
|
||||
if self.tzinfo is None:
|
||||
tzname = None
|
||||
else:
|
||||
tzname = timezone._get_timezone_name(self.tzinfo)
|
||||
return DateTime(col, lookup_type, tzname)
|
||||
|
||||
class AggregateQuery(Query):
|
||||
"""
|
||||
An AggregateQuery takes another query as a parameter to the FROM
|
||||
|
|
|
@ -226,10 +226,9 @@ class WhereNode(tree.Node):
|
|||
return ('%s BETWEEN %%s and %%s' % field_sql, params)
|
||||
elif is_datetime_field and lookup_type in ('month', 'day', 'week_day',
|
||||
'hour', 'minute', 'second'):
|
||||
if settings.USE_TZ:
|
||||
params = [timezone.get_current_timezone_name()] + params
|
||||
return ('%s = %%s'
|
||||
% connection.ops.datetime_extract_sql(lookup_type, field_sql), params)
|
||||
tzname = timezone.get_current_timezone_name() if settings.USE_TZ else None
|
||||
sql, tz_params = connection.ops.datetime_extract_sql(lookup_type, field_sql, tzname)
|
||||
return ('%s = %%s' % sql, tz_params + params)
|
||||
elif lookup_type in ('month', 'day', 'week_day'):
|
||||
return ('%s = %%s'
|
||||
% connection.ops.date_extract_sql(lookup_type, field_sql), params)
|
||||
|
|
Loading…
Reference in New Issue