Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Made tzname a parameter of datetime_extract/trunc_sql.

This is required to work around a bug in Oracle.
  • Loading branch information...
commit 01b6366f3ce67d57a58ca8f25e5be77911748638 1 parent 924a144
@aaugustin aaugustin authored
View
15 django/db/backends/__init__.py
@@ -542,25 +542,20 @@ def datetime_cast_sql(self):
"""
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()
View
17 django/db/backends/mysql/base.py
@@ -225,20 +225,27 @@ def date_trunc_sql(self, lookup_type, field_name):
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 @@ def datetime_trunc_sql(self, lookup_type, field_name):
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,
View
53 django/db/backends/oracle/base.py
@@ -7,6 +7,7 @@
import datetime
import decimal
+import re
import sys
import warnings
@@ -131,8 +132,9 @@ def date_extract_sql(self, lookup_type, field_name):
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
- # http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions050.htm
- return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), 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)
def date_interval_sql(self, sql, connector, timedelta):
"""
@@ -155,29 +157,52 @@ def date_trunc_sql(self, lookup_type, field_name):
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
- # http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions050.htm
- return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
+ sql = "TO_CHAR(%s, 'D')" % field_name
+ else:
+ # http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions050.htm
+ 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):
View
18 django/db/backends/postgresql_psycopg2/operations.py
@@ -37,21 +37,29 @@ def date_trunc_sql(self, lookup_type, field_name):
# 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"
View
18 django/db/backends/sqlite3/base.py
@@ -172,31 +172,25 @@ def date_trunc_sql(self, lookup_type, field_name):
# 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:
from django.core.exceptions import ImproperlyConfigured
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)
+ return "django_datetime_extract('%s', %s, %%s)" % (
+ 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:
from django.core.exceptions import ImproperlyConfigured
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)
+ return "django_datetime_trunc('%s', %s, %%s)" % (
+ lookup_type.lower(), field_name), [tzname]
def drop_foreignkey_sql(self):
return ""
View
7 django/db/models/sql/compiler.py
@@ -1034,13 +1034,6 @@ def results_iter(self):
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.
View
23 django/db/models/sql/datastructures.py
@@ -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 @@ def as_sql(self, qn, connection):
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)
View
15 django/db/models/sql/subqueries.py
@@ -14,6 +14,7 @@
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 @@ def add_select(self, field_name, lookup_type, order='ASC'):
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 @@ def _check_field(self, field):
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
View
7 django/db/models/sql/where.py
@@ -226,10 +226,9 @@ def make_atom(self, child, qn, connection):
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)
Please sign in to comment.
Something went wrong with that request. Please try again.