Refs #28643 -- Reorganized database functions.
Thanks Tim Graham for the review.
This commit is contained in:
parent
8c538871bd
commit
4f27e475b3
|
@ -1,27 +1,27 @@
|
|||
from .base import (
|
||||
Cast, Coalesce, Concat, ConcatPair, Greatest, Least, Length, Lower, Now,
|
||||
StrIndex, Substr, Upper,
|
||||
)
|
||||
from .comparison import Cast, Coalesce, Greatest, Least
|
||||
from .datetime import (
|
||||
Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
|
||||
ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear,
|
||||
Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
|
||||
Now, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
|
||||
TruncQuarter, TruncSecond, TruncTime, TruncYear,
|
||||
)
|
||||
from .text import Concat, ConcatPair, Length, Lower, StrIndex, Substr, Upper
|
||||
from .window import (
|
||||
CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
|
||||
PercentRank, Rank, RowNumber,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# base
|
||||
'Cast', 'Coalesce', 'Concat', 'ConcatPair', 'Greatest', 'Least', 'Length',
|
||||
'Lower', 'Now', 'StrIndex', 'Substr', 'Upper',
|
||||
# comparison and conversion
|
||||
'Cast', 'Coalesce', 'Greatest', 'Least',
|
||||
# datetime
|
||||
'Extract', 'ExtractDay', 'ExtractHour', 'ExtractMinute', 'ExtractMonth',
|
||||
'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractWeekDay',
|
||||
'ExtractYear', 'Trunc', 'TruncDate', 'TruncDay', 'TruncHour', 'TruncMinute',
|
||||
'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime', 'TruncYear',
|
||||
'ExtractYear', 'Now', 'Trunc', 'TruncDate', 'TruncDay', 'TruncHour',
|
||||
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
|
||||
'TruncYear',
|
||||
# text
|
||||
'Concat', 'ConcatPair', 'Length', 'Lower', 'StrIndex', 'Substr', 'Upper',
|
||||
# window
|
||||
'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
|
||||
'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber',
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
"""Database functions that do comparisons or type conversions."""
|
||||
from django.db.models import Func
|
||||
|
||||
|
||||
class Cast(Func):
|
||||
"""Coerce an expression to a new field type."""
|
||||
function = 'CAST'
|
||||
template = '%(function)s(%(expressions)s AS %(db_type)s)'
|
||||
|
||||
def __init__(self, expression, output_field):
|
||||
super().__init__(expression, output_field=output_field)
|
||||
|
||||
def as_sql(self, compiler, connection, **extra_context):
|
||||
extra_context['db_type'] = self.output_field.cast_db_type(connection)
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def as_postgresql(self, compiler, connection):
|
||||
# CAST would be valid too, but the :: shortcut syntax is more readable.
|
||||
return self.as_sql(compiler, connection, template='%(expressions)s::%(db_type)s')
|
||||
|
||||
|
||||
class Coalesce(Func):
|
||||
"""Return, from left to right, the first non-null expression."""
|
||||
function = 'COALESCE'
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('Coalesce must take at least two expressions')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_oracle(self, compiler, connection):
|
||||
# Oracle prohibits mixing TextField (NCLOB) and CharField (NVARCHAR2),
|
||||
# so convert all fields to NCLOB when that type is expected.
|
||||
if self.output_field.get_internal_type() == 'TextField':
|
||||
class ToNCLOB(Func):
|
||||
function = 'TO_NCLOB'
|
||||
|
||||
expressions = [
|
||||
ToNCLOB(expression) for expression in self.get_source_expressions()
|
||||
]
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions(expressions)
|
||||
return super(Coalesce, clone).as_sql(compiler, connection)
|
||||
return self.as_sql(compiler, connection)
|
||||
|
||||
|
||||
class Greatest(Func):
|
||||
"""
|
||||
Return the maximum expression.
|
||||
|
||||
If any expression is null the return value is database-specific:
|
||||
On PostgreSQL, the maximum not-null expression is returned.
|
||||
On MySQL, Oracle, and SQLite, if any expression is null, null is returned.
|
||||
"""
|
||||
function = 'GREATEST'
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('Greatest must take at least two expressions')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_sqlite(self, compiler, connection):
|
||||
"""Use the MAX function on SQLite."""
|
||||
return super().as_sqlite(compiler, connection, function='MAX')
|
||||
|
||||
|
||||
class Least(Func):
|
||||
"""
|
||||
Return the minimum expression.
|
||||
|
||||
If any expression is null the return value is database-specific:
|
||||
On PostgreSQL, return the minimum not-null expression.
|
||||
On MySQL, Oracle, and SQLite, if any expression is null, return null.
|
||||
"""
|
||||
function = 'LEAST'
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('Least must take at least two expressions')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_sqlite(self, compiler, connection):
|
||||
"""Use the MIN function on SQLite."""
|
||||
return super().as_sqlite(compiler, connection, function='MIN')
|
|
@ -2,8 +2,8 @@ from datetime import datetime
|
|||
|
||||
from django.conf import settings
|
||||
from django.db.models import (
|
||||
DateField, DateTimeField, DurationField, Field, IntegerField, TimeField,
|
||||
Transform,
|
||||
DateField, DateTimeField, DurationField, Field, Func, IntegerField,
|
||||
TimeField, Transform, fields,
|
||||
)
|
||||
from django.db.models.lookups import (
|
||||
YearExact, YearGt, YearGte, YearLt, YearLte,
|
||||
|
@ -143,6 +143,17 @@ ExtractYear.register_lookup(YearLt)
|
|||
ExtractYear.register_lookup(YearLte)
|
||||
|
||||
|
||||
class Now(Func):
|
||||
template = 'CURRENT_TIMESTAMP'
|
||||
output_field = fields.DateTimeField()
|
||||
|
||||
def as_postgresql(self, compiler, connection):
|
||||
# PostgreSQL's CURRENT_TIMESTAMP means "the time at the start of the
|
||||
# transaction". Use STATEMENT_TIMESTAMP to be cross-compatible with
|
||||
# other databases.
|
||||
return self.as_sql(compiler, connection, template='STATEMENT_TIMESTAMP()')
|
||||
|
||||
|
||||
class TruncBase(TimezoneMixin, Transform):
|
||||
kind = None
|
||||
tzinfo = None
|
||||
|
|
|
@ -1,48 +1,5 @@
|
|||
"""
|
||||
Classes that represent database functions.
|
||||
"""
|
||||
from django.db.models import Func, Transform, Value, fields
|
||||
|
||||
|
||||
class Cast(Func):
|
||||
"""Coerce an expression to a new field type."""
|
||||
function = 'CAST'
|
||||
template = '%(function)s(%(expressions)s AS %(db_type)s)'
|
||||
|
||||
def __init__(self, expression, output_field):
|
||||
super().__init__(expression, output_field=output_field)
|
||||
|
||||
def as_sql(self, compiler, connection, **extra_context):
|
||||
extra_context['db_type'] = self.output_field.cast_db_type(connection)
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def as_postgresql(self, compiler, connection):
|
||||
# CAST would be valid too, but the :: shortcut syntax is more readable.
|
||||
return self.as_sql(compiler, connection, template='%(expressions)s::%(db_type)s')
|
||||
|
||||
|
||||
class Coalesce(Func):
|
||||
"""Return, from left to right, the first non-null expression."""
|
||||
function = 'COALESCE'
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('Coalesce must take at least two expressions')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_oracle(self, compiler, connection):
|
||||
# we can't mix TextField (NCLOB) and CharField (NVARCHAR), so convert
|
||||
# all fields to NCLOB when we expect NCLOB
|
||||
if self.output_field.get_internal_type() == 'TextField':
|
||||
class ToNCLOB(Func):
|
||||
function = 'TO_NCLOB'
|
||||
|
||||
expressions = [
|
||||
ToNCLOB(expression) for expression in self.get_source_expressions()]
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions(expressions)
|
||||
return super(Coalesce, clone).as_sql(compiler, connection)
|
||||
return self.as_sql(compiler, connection)
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
|
||||
class ConcatPair(Func):
|
||||
|
@ -98,46 +55,6 @@ class Concat(Func):
|
|||
return ConcatPair(expressions[0], self._paired(expressions[1:]))
|
||||
|
||||
|
||||
class Greatest(Func):
|
||||
"""
|
||||
Return the maximum expression.
|
||||
|
||||
If any expression is null the return value is database-specific:
|
||||
On Postgres, the maximum not-null expression is returned.
|
||||
On MySQL, Oracle, and SQLite, if any expression is null, null is returned.
|
||||
"""
|
||||
function = 'GREATEST'
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('Greatest must take at least two expressions')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_sqlite(self, compiler, connection):
|
||||
"""Use the MAX function on SQLite."""
|
||||
return super().as_sqlite(compiler, connection, function='MAX')
|
||||
|
||||
|
||||
class Least(Func):
|
||||
"""
|
||||
Return the minimum expression.
|
||||
|
||||
If any expression is null the return value is database-specific:
|
||||
On Postgres, return the minimum not-null expression.
|
||||
On MySQL, Oracle, and SQLite, if any expression is null, return null.
|
||||
"""
|
||||
function = 'LEAST'
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
if len(expressions) < 2:
|
||||
raise ValueError('Least must take at least two expressions')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_sqlite(self, compiler, connection):
|
||||
"""Use the MIN function on SQLite."""
|
||||
return super().as_sqlite(compiler, connection, function='MIN')
|
||||
|
||||
|
||||
class Length(Transform):
|
||||
"""Return the number of characters in the expression."""
|
||||
function = 'LENGTH'
|
||||
|
@ -153,17 +70,6 @@ class Lower(Transform):
|
|||
lookup_name = 'lower'
|
||||
|
||||
|
||||
class Now(Func):
|
||||
template = 'CURRENT_TIMESTAMP'
|
||||
output_field = fields.DateTimeField()
|
||||
|
||||
def as_postgresql(self, compiler, connection):
|
||||
# Postgres' CURRENT_TIMESTAMP means "the time at the start of the
|
||||
# transaction". We use STATEMENT_TIMESTAMP to be cross-compatible with
|
||||
# other databases.
|
||||
return self.as_sql(compiler, connection, template='STATEMENT_TIMESTAMP()')
|
||||
|
||||
|
||||
class StrIndex(Func):
|
||||
"""
|
||||
Return a positive integer corresponding to the 1-indexed position of the
|
Loading…
Reference in New Issue