Fix duration-only expressions for SQLite and MySQL

Allow arithmetic with DurationField on SQLite/MySQL by converting
timedelta expressions to microseconds for proper backend handling.
Addresses errors with duration-only annotation queries.
This commit is contained in:
utkarsh.arya@zomato.com 2025-11-15 23:04:27 +00:00
parent ec5aa2161d
commit 9be35a85d0
2 changed files with 23 additions and 0 deletions

View file

@ -1,6 +1,7 @@
import uuid
from django.conf import settings
from django.db import DatabaseError
from django.db.backends.base.operations import BaseDatabaseOperations
from django.utils import timezone
from django.utils.duration import duration_microseconds
@ -288,6 +289,24 @@ class DatabaseOperations(BaseDatabaseOperations):
return 'FLOOR(%(lhs)s / POW(2, %(rhs)s))' % {'lhs': lhs, 'rhs': rhs}
return super().combine_expression(connector, sub_expressions)
def combine_duration_expression(self, connector, sub_expressions):
# MySQL doesn't support direct arithmetic on INTERVAL expressions.
# For duration-only operations, we need to extract the numeric values
# and perform integer arithmetic, then format the result.
if connector not in ['+', '-']:
raise DatabaseError('Invalid connector for timedelta: %s.' % connector)
# Remove INTERVAL ... MICROSECOND wrapping if present and do numeric arithmetic
unwrapped = []
for expr in sub_expressions:
if expr.startswith('INTERVAL ') and expr.endswith(' MICROSECOND'):
# Extract the numeric part
unwrapped.append(expr[9:-12]) # Remove 'INTERVAL ' and ' MICROSECOND'
else:
unwrapped.append(expr)
# Perform the arithmetic and return as microseconds (BIGINT)
conn = ' %s ' % connector
return conn.join(unwrapped)
def get_db_converters(self, expression):
converters = super().get_db_converters(expression)
internal_type = expression.output_field.get_internal_type()

View file

@ -561,6 +561,10 @@ def _sqlite_format_dtdelta(conn, lhs, rhs):
return None
# typecast_timestamp returns a date or a datetime without timezone.
# It will be formatted as "%Y-%m-%d" or "%Y-%m-%d %H:%M:%S[.%f]"
# For timedelta, return the value as microseconds to match the expected
# format for DurationField in databases without native duration support.
if isinstance(out, datetime.timedelta):
return duration_microseconds(out)
return str(out)