Refs #28643 -- Reorganized database functions.

Thanks Tim Graham for the review.
This commit is contained in:
Mariusz Felisiak 2017-10-13 21:23:00 +02:00 committed by GitHub
parent 8c538871bd
commit 4f27e475b3
4 changed files with 108 additions and 107 deletions

View File

@ -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',

View File

@ -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')

View File

@ -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

View File

@ -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