mirror of
https://github.com/django/django.git
synced 2025-10-24 22:26:08 +00:00
Fixed #24793 -- Unified temporal difference support.
This commit is contained in:
@@ -64,6 +64,10 @@ class BaseDatabaseFeatures(object):
|
||||
# Is there a true datatype for timedeltas?
|
||||
has_native_duration_field = False
|
||||
|
||||
# Does the database driver supports same type temporal data subtraction
|
||||
# by returning the type used to store duration field?
|
||||
supports_temporal_subtraction = False
|
||||
|
||||
# Does the database driver support timedeltas as arguments?
|
||||
# This is only relevant when there is a native duration field.
|
||||
# Specifically, there is a bug with cx_Oracle:
|
||||
|
||||
@@ -590,3 +590,10 @@ class BaseDatabaseOperations(object):
|
||||
range of the column type bound to the field.
|
||||
"""
|
||||
return self.integer_field_ranges[internal_type]
|
||||
|
||||
def subtract_temporals(self, internal_type, lhs, rhs):
|
||||
if self.connection.features.supports_temporal_subtraction:
|
||||
lhs_sql, lhs_params = lhs
|
||||
rhs_sql, rhs_params = rhs
|
||||
return "(%s - %s)" % (lhs_sql, rhs_sql), lhs_params + rhs_params
|
||||
raise NotImplementedError("This backend does not support %s subtraction." % internal_type)
|
||||
|
||||
@@ -32,6 +32,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
atomic_transactions = False
|
||||
supports_column_check_constraints = False
|
||||
can_clone_databases = True
|
||||
supports_temporal_subtraction = True
|
||||
|
||||
@cached_property
|
||||
def _mysql_storage_engine(self):
|
||||
|
||||
@@ -210,3 +210,21 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||
if value is not None:
|
||||
value = uuid.UUID(value)
|
||||
return value
|
||||
|
||||
def subtract_temporals(self, internal_type, lhs, rhs):
|
||||
lhs_sql, lhs_params = lhs
|
||||
rhs_sql, rhs_params = rhs
|
||||
if self.connection.features.supports_microsecond_precision:
|
||||
if internal_type == 'TimeField':
|
||||
return (
|
||||
"((TIME_TO_SEC(%(lhs)s) * POW(10, 6) + MICROSECOND(%(lhs)s)) -"
|
||||
" (TIME_TO_SEC(%(rhs)s) * POW(10, 6) + MICROSECOND(%(rhs)s)))"
|
||||
) % {'lhs': lhs_sql, 'rhs': rhs_sql}, lhs_params * 2 + rhs_params * 2
|
||||
else:
|
||||
return "TIMESTAMPDIFF(MICROSECOND, %s, %s)" % (rhs_sql, lhs_sql), rhs_params + lhs_params
|
||||
elif internal_type == 'TimeField':
|
||||
return (
|
||||
"(TIME_TO_SEC(%s) * POW(10, 6) - TIME_TO_SEC(%s) * POW(10, 6))"
|
||||
) % (lhs_sql, rhs_sql), lhs_params + rhs_params
|
||||
else:
|
||||
return "(TIMESTAMPDIFF(SECOND, %s, %s) * POW(10, 6))" % (rhs_sql, lhs_sql), rhs_params + lhs_params
|
||||
|
||||
@@ -39,6 +39,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
uppercases_column_names = True
|
||||
# select for update with limit can be achieved on Oracle, but not with the current backend.
|
||||
supports_select_for_update_with_limit = False
|
||||
supports_temporal_subtraction = True
|
||||
|
||||
def introspected_boolean_field_type(self, field=None, created_separately=False):
|
||||
"""
|
||||
|
||||
@@ -449,3 +449,10 @@ WHEN (new.%(col_name)s IS NULL)
|
||||
"SELECT %s FROM DUAL" % ", ".join(row)
|
||||
for row in placeholder_rows
|
||||
)
|
||||
|
||||
def subtract_temporals(self, internal_type, lhs, rhs):
|
||||
if internal_type == 'DateField':
|
||||
lhs_sql, lhs_params = lhs
|
||||
rhs_sql, rhs_params = rhs
|
||||
return "NUMTODSINTERVAL(%s - %s, 'DAY')" % (lhs_sql, rhs_sql), lhs_params + rhs_params
|
||||
return super(DatabaseOperations, self).subtract_temporals(internal_type, lhs, rhs)
|
||||
|
||||
@@ -29,3 +29,4 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
requires_sqlparse_for_splitting = False
|
||||
greatest_least_ignores_nulls = True
|
||||
can_clone_databases = True
|
||||
supports_temporal_subtraction = True
|
||||
|
||||
@@ -239,3 +239,10 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||
if value:
|
||||
return Inet(value)
|
||||
return None
|
||||
|
||||
def subtract_temporals(self, internal_type, lhs, rhs):
|
||||
if internal_type == 'DateField':
|
||||
lhs_sql, lhs_params = lhs
|
||||
rhs_sql, rhs_params = rhs
|
||||
return "age(%s, %s)" % (lhs_sql, rhs_sql), lhs_params + rhs_params
|
||||
return super(DatabaseOperations, self).subtract_temporals(internal_type, lhs, rhs)
|
||||
|
||||
@@ -213,6 +213,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||
conn.create_function("django_datetime_extract", 3, _sqlite_datetime_extract)
|
||||
conn.create_function("django_datetime_trunc", 3, _sqlite_datetime_trunc)
|
||||
conn.create_function("django_time_extract", 2, _sqlite_time_extract)
|
||||
conn.create_function("django_time_diff", 2, _sqlite_time_diff)
|
||||
conn.create_function("django_timestamp_diff", 2, _sqlite_timestamp_diff)
|
||||
conn.create_function("regexp", 2, _sqlite_regexp)
|
||||
conn.create_function("django_format_dtdelta", 3, _sqlite_format_dtdelta)
|
||||
conn.create_function("django_power", 2, _sqlite_power)
|
||||
@@ -444,6 +446,27 @@ def _sqlite_format_dtdelta(conn, lhs, rhs):
|
||||
return str(out)
|
||||
|
||||
|
||||
def _sqlite_time_diff(lhs, rhs):
|
||||
left = backend_utils.typecast_time(lhs)
|
||||
right = backend_utils.typecast_time(rhs)
|
||||
return (
|
||||
(left.hour * 60 * 60 * 1000000) +
|
||||
(left.minute * 60 * 1000000) +
|
||||
(left.second * 1000000) +
|
||||
(left.microsecond) -
|
||||
(right.hour * 60 * 60 * 1000000) -
|
||||
(right.minute * 60 * 1000000) -
|
||||
(right.second * 1000000) -
|
||||
(right.microsecond)
|
||||
)
|
||||
|
||||
|
||||
def _sqlite_timestamp_diff(lhs, rhs):
|
||||
left = backend_utils.typecast_timestamp(lhs)
|
||||
right = backend_utils.typecast_timestamp(rhs)
|
||||
return (left - right).total_seconds() * 1000000
|
||||
|
||||
|
||||
def _sqlite_regexp(re_pattern, re_string):
|
||||
return bool(re.search(re_pattern, force_text(re_string))) if re_string is not None else False
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
supports_paramstyle_pyformat = False
|
||||
supports_sequence_reset = False
|
||||
can_clone_databases = True
|
||||
supports_temporal_subtraction = True
|
||||
|
||||
@cached_property
|
||||
def uses_savepoints(self):
|
||||
|
||||
@@ -263,3 +263,10 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||
def integer_field_range(self, internal_type):
|
||||
# SQLite doesn't enforce any integer constraints
|
||||
return (None, None)
|
||||
|
||||
def subtract_temporals(self, internal_type, lhs, rhs):
|
||||
lhs_sql, lhs_params = lhs
|
||||
rhs_sql, rhs_params = rhs
|
||||
if internal_type == 'TimeField':
|
||||
return "django_time_diff(%s, %s)" % (lhs_sql, rhs_sql), lhs_params + rhs_params
|
||||
return "django_timestamp_diff(%s, %s)" % (lhs_sql, rhs_sql), lhs_params + rhs_params
|
||||
|
||||
@@ -398,6 +398,10 @@ class CombinedExpression(Expression):
|
||||
((lhs_output and lhs_output.get_internal_type() == 'DurationField')
|
||||
or (rhs_output and rhs_output.get_internal_type() == 'DurationField'))):
|
||||
return DurationExpression(self.lhs, self.connector, self.rhs).as_sql(compiler, connection)
|
||||
if (lhs_output and rhs_output and self.connector == self.SUB and
|
||||
lhs_output.get_internal_type() in {'DateField', 'DateTimeField', 'TimeField'} and
|
||||
lhs_output.get_internal_type() == lhs_output.get_internal_type()):
|
||||
return TemporalSubtraction(self.lhs, self.rhs).as_sql(compiler, connection)
|
||||
expressions = []
|
||||
expression_params = []
|
||||
sql, params = compiler.compile(self.lhs)
|
||||
@@ -448,6 +452,17 @@ class DurationExpression(CombinedExpression):
|
||||
return expression_wrapper % sql, expression_params
|
||||
|
||||
|
||||
class TemporalSubtraction(CombinedExpression):
|
||||
def __init__(self, lhs, rhs):
|
||||
super(TemporalSubtraction, self).__init__(lhs, self.SUB, rhs, output_field=fields.DurationField())
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
connection.ops.check_expression_support(self)
|
||||
lhs = compiler.compile(self.lhs, connection)
|
||||
rhs = compiler.compile(self.rhs, connection)
|
||||
return connection.ops.subtract_temporals(self.lhs.output_field.get_internal_type(), lhs, rhs)
|
||||
|
||||
|
||||
class F(Combinable):
|
||||
"""
|
||||
An object capable of resolving references to existing query objects.
|
||||
|
||||
Reference in New Issue
Block a user