704 lines
27 KiB
Python
704 lines
27 KiB
Python
import datetime
|
|
import decimal
|
|
from importlib import import_module
|
|
|
|
import sqlparse
|
|
|
|
from django.conf import settings
|
|
from django.db import NotSupportedError, transaction
|
|
from django.db.backends import utils
|
|
from django.utils import timezone
|
|
from django.utils.encoding import force_str
|
|
|
|
|
|
class BaseDatabaseOperations:
|
|
"""
|
|
Encapsulate backend-specific differences, such as the way a backend
|
|
performs ordering or calculates the ID of a recently-inserted row.
|
|
"""
|
|
compiler_module = "django.db.models.sql.compiler"
|
|
|
|
# Integer field safe ranges by `internal_type` as documented
|
|
# in docs/ref/models/fields.txt.
|
|
integer_field_ranges = {
|
|
'SmallIntegerField': (-32768, 32767),
|
|
'IntegerField': (-2147483648, 2147483647),
|
|
'BigIntegerField': (-9223372036854775808, 9223372036854775807),
|
|
'PositiveBigIntegerField': (0, 9223372036854775807),
|
|
'PositiveSmallIntegerField': (0, 32767),
|
|
'PositiveIntegerField': (0, 2147483647),
|
|
'SmallAutoField': (-32768, 32767),
|
|
'AutoField': (-2147483648, 2147483647),
|
|
'BigAutoField': (-9223372036854775808, 9223372036854775807),
|
|
}
|
|
set_operators = {
|
|
'union': 'UNION',
|
|
'intersection': 'INTERSECT',
|
|
'difference': 'EXCEPT',
|
|
}
|
|
# Mapping of Field.get_internal_type() (typically the model field's class
|
|
# name) to the data type to use for the Cast() function, if different from
|
|
# DatabaseWrapper.data_types.
|
|
cast_data_types = {}
|
|
# CharField data type if the max_length argument isn't provided.
|
|
cast_char_field_without_max_length = None
|
|
|
|
# Start and end points for window expressions.
|
|
PRECEDING = 'PRECEDING'
|
|
FOLLOWING = 'FOLLOWING'
|
|
UNBOUNDED_PRECEDING = 'UNBOUNDED ' + PRECEDING
|
|
UNBOUNDED_FOLLOWING = 'UNBOUNDED ' + FOLLOWING
|
|
CURRENT_ROW = 'CURRENT ROW'
|
|
|
|
# Prefix for EXPLAIN queries, or None EXPLAIN isn't supported.
|
|
explain_prefix = None
|
|
|
|
def __init__(self, connection):
|
|
self.connection = connection
|
|
self._cache = None
|
|
|
|
def autoinc_sql(self, table, column):
|
|
"""
|
|
Return any SQL needed to support auto-incrementing primary keys, or
|
|
None if no SQL is necessary.
|
|
|
|
This SQL is executed when a table is created.
|
|
"""
|
|
return None
|
|
|
|
def bulk_batch_size(self, fields, objs):
|
|
"""
|
|
Return the maximum allowed batch size for the backend. The fields
|
|
are the fields going to be inserted in the batch, the objs contains
|
|
all the objects to be inserted.
|
|
"""
|
|
return len(objs)
|
|
|
|
def cache_key_culling_sql(self):
|
|
"""
|
|
Return an SQL query that retrieves the first cache key greater than the
|
|
n smallest.
|
|
|
|
This is used by the 'db' cache backend to determine where to start
|
|
culling.
|
|
"""
|
|
return "SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s"
|
|
|
|
def unification_cast_sql(self, output_field):
|
|
"""
|
|
Given a field instance, return the SQL that casts the result of a union
|
|
to that type. The resulting string should contain a '%s' placeholder
|
|
for the expression being cast.
|
|
"""
|
|
return '%s'
|
|
|
|
def date_extract_sql(self, lookup_type, field_name):
|
|
"""
|
|
Given a lookup_type of 'year', 'month', or 'day', return the SQL that
|
|
extracts a value from the given date field field_name.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a date_extract_sql() method')
|
|
|
|
def date_interval_sql(self, timedelta):
|
|
"""
|
|
Implement the date interval functionality for expressions.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a date_interval_sql() method')
|
|
|
|
def date_trunc_sql(self, lookup_type, field_name):
|
|
"""
|
|
Given a lookup_type of 'year', 'month', or 'day', return the SQL that
|
|
truncates the given date field field_name to a date object with only
|
|
the given specificity.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a date_trunc_sql() method.')
|
|
|
|
def datetime_cast_date_sql(self, field_name, tzname):
|
|
"""
|
|
Return the SQL to cast a datetime value to date value.
|
|
"""
|
|
raise NotImplementedError(
|
|
'subclasses of BaseDatabaseOperations may require a '
|
|
'datetime_cast_date_sql() method.'
|
|
)
|
|
|
|
def datetime_cast_time_sql(self, field_name, tzname):
|
|
"""
|
|
Return the SQL to cast a datetime value to time value.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_cast_time_sql() method')
|
|
|
|
def datetime_extract_sql(self, lookup_type, field_name, tzname):
|
|
"""
|
|
Given a lookup_type of 'year', 'month', 'day', 'hour', 'minute', or
|
|
'second', return the SQL that extracts a value from the given
|
|
datetime field field_name.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_extract_sql() method')
|
|
|
|
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
|
"""
|
|
Given a lookup_type of 'year', 'month', 'day', 'hour', 'minute', or
|
|
'second', return the SQL that truncates the given datetime field
|
|
field_name to a datetime object with only the given specificity.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_trunc_sql() method')
|
|
|
|
def time_trunc_sql(self, lookup_type, field_name):
|
|
"""
|
|
Given a lookup_type of 'hour', 'minute' or 'second', return the SQL
|
|
that truncates the given time field field_name to a time object with
|
|
only the given specificity.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a time_trunc_sql() method')
|
|
|
|
def time_extract_sql(self, lookup_type, field_name):
|
|
"""
|
|
Given a lookup_type of 'hour', 'minute', or 'second', return the SQL
|
|
that extracts a value from the given time field field_name.
|
|
"""
|
|
return self.date_extract_sql(lookup_type, field_name)
|
|
|
|
def json_cast_text_sql(self, field_name):
|
|
"""Return the SQL to cast a JSON value to text value."""
|
|
raise NotImplementedError(
|
|
'subclasses of BaseDatabaseOperations may require a '
|
|
'json_cast_text_sql() method'
|
|
)
|
|
|
|
def deferrable_sql(self):
|
|
"""
|
|
Return the SQL to make a constraint "initially deferred" during a
|
|
CREATE TABLE statement.
|
|
"""
|
|
return ''
|
|
|
|
def distinct_sql(self, fields, params):
|
|
"""
|
|
Return an SQL DISTINCT clause which removes duplicate rows from the
|
|
result set. If any fields are given, only check the given fields for
|
|
duplicates.
|
|
"""
|
|
if fields:
|
|
raise NotSupportedError('DISTINCT ON fields is not supported by this database backend')
|
|
else:
|
|
return ['DISTINCT'], []
|
|
|
|
def fetch_returned_insert_columns(self, cursor, returning_params):
|
|
"""
|
|
Given a cursor object that has just performed an INSERT...RETURNING
|
|
statement into a table, return the newly created data.
|
|
"""
|
|
return cursor.fetchone()
|
|
|
|
def field_cast_sql(self, db_type, internal_type):
|
|
"""
|
|
Given a column type (e.g. 'BLOB', 'VARCHAR') and an internal type
|
|
(e.g. 'GenericIPAddressField'), return the SQL to cast it before using
|
|
it in a WHERE statement. The resulting string should contain a '%s'
|
|
placeholder for the column being searched against.
|
|
"""
|
|
return '%s'
|
|
|
|
def force_no_ordering(self):
|
|
"""
|
|
Return a list used in the "ORDER BY" clause to force no ordering at
|
|
all. Return an empty list to include nothing in the ordering.
|
|
"""
|
|
return []
|
|
|
|
def for_update_sql(self, nowait=False, skip_locked=False, of=()):
|
|
"""
|
|
Return the FOR UPDATE SQL clause to lock rows for an update operation.
|
|
"""
|
|
return 'FOR UPDATE%s%s%s' % (
|
|
' OF %s' % ', '.join(of) if of else '',
|
|
' NOWAIT' if nowait else '',
|
|
' SKIP LOCKED' if skip_locked else '',
|
|
)
|
|
|
|
def _get_limit_offset_params(self, low_mark, high_mark):
|
|
offset = low_mark or 0
|
|
if high_mark is not None:
|
|
return (high_mark - offset), offset
|
|
elif offset:
|
|
return self.connection.ops.no_limit_value(), offset
|
|
return None, offset
|
|
|
|
def limit_offset_sql(self, low_mark, high_mark):
|
|
"""Return LIMIT/OFFSET SQL clause."""
|
|
limit, offset = self._get_limit_offset_params(low_mark, high_mark)
|
|
return ' '.join(sql for sql in (
|
|
('LIMIT %d' % limit) if limit else None,
|
|
('OFFSET %d' % offset) if offset else None,
|
|
) if sql)
|
|
|
|
def last_executed_query(self, cursor, sql, params):
|
|
"""
|
|
Return a string of the query last executed by the given cursor, with
|
|
placeholders replaced with actual values.
|
|
|
|
`sql` is the raw query containing placeholders and `params` is the
|
|
sequence of parameters. These are used by default, but this method
|
|
exists for database backends to provide a better implementation
|
|
according to their own quoting schemes.
|
|
"""
|
|
# Convert params to contain string values.
|
|
def to_string(s):
|
|
return force_str(s, strings_only=True, errors='replace')
|
|
if isinstance(params, (list, tuple)):
|
|
u_params = tuple(to_string(val) for val in params)
|
|
elif params is None:
|
|
u_params = ()
|
|
else:
|
|
u_params = {to_string(k): to_string(v) for k, v in params.items()}
|
|
|
|
return "QUERY = %r - PARAMS = %r" % (sql, u_params)
|
|
|
|
def last_insert_id(self, cursor, table_name, pk_name):
|
|
"""
|
|
Given a cursor object that has just performed an INSERT statement into
|
|
a table that has an auto-incrementing ID, return the newly created ID.
|
|
|
|
`pk_name` is the name of the primary-key column.
|
|
"""
|
|
return cursor.lastrowid
|
|
|
|
def lookup_cast(self, lookup_type, internal_type=None):
|
|
"""
|
|
Return the string to use in a query when performing lookups
|
|
("contains", "like", etc.). It should contain a '%s' placeholder for
|
|
the column being searched against.
|
|
"""
|
|
return "%s"
|
|
|
|
def max_in_list_size(self):
|
|
"""
|
|
Return the maximum number of items that can be passed in a single 'IN'
|
|
list condition, or None if the backend does not impose a limit.
|
|
"""
|
|
return None
|
|
|
|
def max_name_length(self):
|
|
"""
|
|
Return the maximum length of table and column names, or None if there
|
|
is no limit.
|
|
"""
|
|
return None
|
|
|
|
def no_limit_value(self):
|
|
"""
|
|
Return the value to use for the LIMIT when we are wanting "LIMIT
|
|
infinity". Return None if the limit clause can be omitted in this case.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a no_limit_value() method')
|
|
|
|
def pk_default_value(self):
|
|
"""
|
|
Return the value to use during an INSERT statement to specify that
|
|
the field should use its default value.
|
|
"""
|
|
return 'DEFAULT'
|
|
|
|
def prepare_sql_script(self, sql):
|
|
"""
|
|
Take an SQL script that may contain multiple lines and return a list
|
|
of statements to feed to successive cursor.execute() calls.
|
|
|
|
Since few databases are able to process raw SQL scripts in a single
|
|
cursor.execute() call and PEP 249 doesn't talk about this use case,
|
|
the default implementation is conservative.
|
|
"""
|
|
return [
|
|
sqlparse.format(statement, strip_comments=True)
|
|
for statement in sqlparse.split(sql) if statement
|
|
]
|
|
|
|
def process_clob(self, value):
|
|
"""
|
|
Return the value of a CLOB column, for backends that return a locator
|
|
object that requires additional processing.
|
|
"""
|
|
return value
|
|
|
|
def return_insert_columns(self, fields):
|
|
"""
|
|
For backends that support returning columns as part of an insert query,
|
|
return the SQL and params to append to the INSERT query. The returned
|
|
fragment should contain a format string to hold the appropriate column.
|
|
"""
|
|
pass
|
|
|
|
def compiler(self, compiler_name):
|
|
"""
|
|
Return the SQLCompiler class corresponding to the given name,
|
|
in the namespace corresponding to the `compiler_module` attribute
|
|
on this backend.
|
|
"""
|
|
if self._cache is None:
|
|
self._cache = import_module(self.compiler_module)
|
|
return getattr(self._cache, compiler_name)
|
|
|
|
def quote_name(self, name):
|
|
"""
|
|
Return a quoted version of the given table, index, or column name. Do
|
|
not quote the given name if it's already been quoted.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a quote_name() method')
|
|
|
|
def random_function_sql(self):
|
|
"""Return an SQL expression that returns a random value."""
|
|
return 'RANDOM()'
|
|
|
|
def regex_lookup(self, lookup_type):
|
|
"""
|
|
Return the string to use in a query when performing regular expression
|
|
lookups (using "regex" or "iregex"). It should contain a '%s'
|
|
placeholder for the column being searched against.
|
|
|
|
If the feature is not supported (or part of it is not supported), raise
|
|
NotImplementedError.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a regex_lookup() method')
|
|
|
|
def savepoint_create_sql(self, sid):
|
|
"""
|
|
Return the SQL for starting a new savepoint. Only required if the
|
|
"uses_savepoints" feature is True. The "sid" parameter is a string
|
|
for the savepoint id.
|
|
"""
|
|
return "SAVEPOINT %s" % self.quote_name(sid)
|
|
|
|
def savepoint_commit_sql(self, sid):
|
|
"""
|
|
Return the SQL for committing the given savepoint.
|
|
"""
|
|
return "RELEASE SAVEPOINT %s" % self.quote_name(sid)
|
|
|
|
def savepoint_rollback_sql(self, sid):
|
|
"""
|
|
Return the SQL for rolling back the given savepoint.
|
|
"""
|
|
return "ROLLBACK TO SAVEPOINT %s" % self.quote_name(sid)
|
|
|
|
def set_time_zone_sql(self):
|
|
"""
|
|
Return the SQL that will set the connection's time zone.
|
|
|
|
Return '' if the backend doesn't support time zones.
|
|
"""
|
|
return ''
|
|
|
|
def sql_flush(self, style, tables, *, reset_sequences=False, allow_cascade=False):
|
|
"""
|
|
Return a list of SQL statements required to remove all data from
|
|
the given database tables (without actually removing the tables
|
|
themselves).
|
|
|
|
The `style` argument is a Style object as returned by either
|
|
color_style() or no_style() in django.core.management.color.
|
|
|
|
If `reset_sequences` is True, the list includes SQL statements required
|
|
to reset the sequences.
|
|
|
|
The `allow_cascade` argument determines whether truncation may cascade
|
|
to tables with foreign keys pointing the tables being truncated.
|
|
PostgreSQL requires a cascade even if these tables are empty.
|
|
"""
|
|
raise NotImplementedError('subclasses of BaseDatabaseOperations must provide an sql_flush() method')
|
|
|
|
def execute_sql_flush(self, sql_list):
|
|
"""Execute a list of SQL statements to flush the database."""
|
|
with transaction.atomic(
|
|
using=self.connection.alias,
|
|
savepoint=self.connection.features.can_rollback_ddl,
|
|
):
|
|
with self.connection.cursor() as cursor:
|
|
for sql in sql_list:
|
|
cursor.execute(sql)
|
|
|
|
def sequence_reset_by_name_sql(self, style, sequences):
|
|
"""
|
|
Return a list of the SQL statements required to reset sequences
|
|
passed in `sequences`.
|
|
|
|
The `style` argument is a Style object as returned by either
|
|
color_style() or no_style() in django.core.management.color.
|
|
"""
|
|
return []
|
|
|
|
def sequence_reset_sql(self, style, model_list):
|
|
"""
|
|
Return a list of the SQL statements required to reset sequences for
|
|
the given models.
|
|
|
|
The `style` argument is a Style object as returned by either
|
|
color_style() or no_style() in django.core.management.color.
|
|
"""
|
|
return [] # No sequence reset required by default.
|
|
|
|
def start_transaction_sql(self):
|
|
"""Return the SQL statement required to start a transaction."""
|
|
return "BEGIN;"
|
|
|
|
def end_transaction_sql(self, success=True):
|
|
"""Return the SQL statement required to end a transaction."""
|
|
if not success:
|
|
return "ROLLBACK;"
|
|
return "COMMIT;"
|
|
|
|
def tablespace_sql(self, tablespace, inline=False):
|
|
"""
|
|
Return the SQL that will be used in a query to define the tablespace.
|
|
|
|
Return '' if the backend doesn't support tablespaces.
|
|
|
|
If `inline` is True, append the SQL to a row; otherwise append it to
|
|
the entire CREATE TABLE or CREATE INDEX statement.
|
|
"""
|
|
return ''
|
|
|
|
def prep_for_like_query(self, x):
|
|
"""Prepare a value for use in a LIKE query."""
|
|
return str(x).replace("\\", "\\\\").replace("%", r"\%").replace("_", r"\_")
|
|
|
|
# Same as prep_for_like_query(), but called for "iexact" matches, which
|
|
# need not necessarily be implemented using "LIKE" in the backend.
|
|
prep_for_iexact_query = prep_for_like_query
|
|
|
|
def validate_autopk_value(self, value):
|
|
"""
|
|
Certain backends do not accept some values for "serial" fields
|
|
(for example zero in MySQL). Raise a ValueError if the value is
|
|
invalid, otherwise return the validated value.
|
|
"""
|
|
return value
|
|
|
|
def adapt_unknown_value(self, value):
|
|
"""
|
|
Transform a value to something compatible with the backend driver.
|
|
|
|
This method only depends on the type of the value. It's designed for
|
|
cases where the target type isn't known, such as .raw() SQL queries.
|
|
As a consequence it may not work perfectly in all circumstances.
|
|
"""
|
|
if isinstance(value, datetime.datetime): # must be before date
|
|
return self.adapt_datetimefield_value(value)
|
|
elif isinstance(value, datetime.date):
|
|
return self.adapt_datefield_value(value)
|
|
elif isinstance(value, datetime.time):
|
|
return self.adapt_timefield_value(value)
|
|
elif isinstance(value, decimal.Decimal):
|
|
return self.adapt_decimalfield_value(value)
|
|
else:
|
|
return value
|
|
|
|
def adapt_datefield_value(self, value):
|
|
"""
|
|
Transform a date value to an object compatible with what is expected
|
|
by the backend driver for date columns.
|
|
"""
|
|
if value is None:
|
|
return None
|
|
return str(value)
|
|
|
|
def adapt_datetimefield_value(self, value):
|
|
"""
|
|
Transform a datetime value to an object compatible with what is expected
|
|
by the backend driver for datetime columns.
|
|
"""
|
|
if value is None:
|
|
return None
|
|
return str(value)
|
|
|
|
def adapt_timefield_value(self, value):
|
|
"""
|
|
Transform a time value to an object compatible with what is expected
|
|
by the backend driver for time columns.
|
|
"""
|
|
if value is None:
|
|
return None
|
|
if timezone.is_aware(value):
|
|
raise ValueError("Django does not support timezone-aware times.")
|
|
return str(value)
|
|
|
|
def adapt_decimalfield_value(self, value, max_digits=None, decimal_places=None):
|
|
"""
|
|
Transform a decimal.Decimal value to an object compatible with what is
|
|
expected by the backend driver for decimal (numeric) columns.
|
|
"""
|
|
return utils.format_number(value, max_digits, decimal_places)
|
|
|
|
def adapt_ipaddressfield_value(self, value):
|
|
"""
|
|
Transform a string representation of an IP address into the expected
|
|
type for the backend driver.
|
|
"""
|
|
return value or None
|
|
|
|
def year_lookup_bounds_for_date_field(self, value):
|
|
"""
|
|
Return a two-elements list with the lower and upper bound to be used
|
|
with a BETWEEN operator to query a DateField value using a year
|
|
lookup.
|
|
|
|
`value` is an int, containing the looked-up year.
|
|
"""
|
|
first = datetime.date(value, 1, 1)
|
|
second = datetime.date(value, 12, 31)
|
|
first = self.adapt_datefield_value(first)
|
|
second = self.adapt_datefield_value(second)
|
|
return [first, second]
|
|
|
|
def year_lookup_bounds_for_datetime_field(self, value):
|
|
"""
|
|
Return a two-elements list with the lower and upper bound to be used
|
|
with a BETWEEN operator to query a DateTimeField value using a year
|
|
lookup.
|
|
|
|
`value` is an int, containing the looked-up year.
|
|
"""
|
|
first = datetime.datetime(value, 1, 1)
|
|
second = datetime.datetime(value, 12, 31, 23, 59, 59, 999999)
|
|
if settings.USE_TZ:
|
|
tz = timezone.get_current_timezone()
|
|
first = timezone.make_aware(first, tz)
|
|
second = timezone.make_aware(second, tz)
|
|
first = self.adapt_datetimefield_value(first)
|
|
second = self.adapt_datetimefield_value(second)
|
|
return [first, second]
|
|
|
|
def get_db_converters(self, expression):
|
|
"""
|
|
Return a list of functions needed to convert field data.
|
|
|
|
Some field types on some backends do not provide data in the correct
|
|
format, this is the hook for converter functions.
|
|
"""
|
|
return []
|
|
|
|
def convert_durationfield_value(self, value, expression, connection):
|
|
if value is not None:
|
|
return datetime.timedelta(0, 0, value)
|
|
|
|
def check_expression_support(self, expression):
|
|
"""
|
|
Check that the backend supports the provided expression.
|
|
|
|
This is used on specific backends to rule out known expressions
|
|
that have problematic or nonexistent implementations. If the
|
|
expression has a known problem, the backend should raise
|
|
NotSupportedError.
|
|
"""
|
|
pass
|
|
|
|
def conditional_expression_supported_in_where_clause(self, expression):
|
|
"""
|
|
Return True, if the conditional expression is supported in the WHERE
|
|
clause.
|
|
"""
|
|
return True
|
|
|
|
def combine_expression(self, connector, sub_expressions):
|
|
"""
|
|
Combine a list of subexpressions into a single expression, using
|
|
the provided connecting operator. This is required because operators
|
|
can vary between backends (e.g., Oracle with %% and &) and between
|
|
subexpression types (e.g., date expressions).
|
|
"""
|
|
conn = ' %s ' % connector
|
|
return conn.join(sub_expressions)
|
|
|
|
def combine_duration_expression(self, connector, sub_expressions):
|
|
return self.combine_expression(connector, sub_expressions)
|
|
|
|
def binary_placeholder_sql(self, value):
|
|
"""
|
|
Some backends require special syntax to insert binary content (MySQL
|
|
for example uses '_binary %s').
|
|
"""
|
|
return '%s'
|
|
|
|
def modify_insert_params(self, placeholder, params):
|
|
"""
|
|
Allow modification of insert parameters. Needed for Oracle Spatial
|
|
backend due to #10888.
|
|
"""
|
|
return params
|
|
|
|
def integer_field_range(self, internal_type):
|
|
"""
|
|
Given an integer field internal type (e.g. 'PositiveIntegerField'),
|
|
return a tuple of the (min_value, max_value) form representing the
|
|
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 NotSupportedError("This backend does not support %s subtraction." % internal_type)
|
|
|
|
def window_frame_start(self, start):
|
|
if isinstance(start, int):
|
|
if start < 0:
|
|
return '%d %s' % (abs(start), self.PRECEDING)
|
|
elif start == 0:
|
|
return self.CURRENT_ROW
|
|
elif start is None:
|
|
return self.UNBOUNDED_PRECEDING
|
|
raise ValueError("start argument must be a negative integer, zero, or None, but got '%s'." % start)
|
|
|
|
def window_frame_end(self, end):
|
|
if isinstance(end, int):
|
|
if end == 0:
|
|
return self.CURRENT_ROW
|
|
elif end > 0:
|
|
return '%d %s' % (end, self.FOLLOWING)
|
|
elif end is None:
|
|
return self.UNBOUNDED_FOLLOWING
|
|
raise ValueError("end argument must be a positive integer, zero, or None, but got '%s'." % end)
|
|
|
|
def window_frame_rows_start_end(self, start=None, end=None):
|
|
"""
|
|
Return SQL for start and end points in an OVER clause window frame.
|
|
"""
|
|
if not self.connection.features.supports_over_clause:
|
|
raise NotSupportedError('This backend does not support window expressions.')
|
|
return self.window_frame_start(start), self.window_frame_end(end)
|
|
|
|
def window_frame_range_start_end(self, start=None, end=None):
|
|
start_, end_ = self.window_frame_rows_start_end(start, end)
|
|
if (
|
|
self.connection.features.only_supports_unbounded_with_preceding_and_following and
|
|
((start and start < 0) or (end and end > 0))
|
|
):
|
|
raise NotSupportedError(
|
|
'%s only supports UNBOUNDED together with PRECEDING and '
|
|
'FOLLOWING.' % self.connection.display_name
|
|
)
|
|
return start_, end_
|
|
|
|
def explain_query_prefix(self, format=None, **options):
|
|
if not self.connection.features.supports_explaining_query_execution:
|
|
raise NotSupportedError('This backend does not support explaining query execution.')
|
|
if format:
|
|
supported_formats = self.connection.features.supported_explain_formats
|
|
normalized_format = format.upper()
|
|
if normalized_format not in supported_formats:
|
|
msg = '%s is not a recognized format.' % normalized_format
|
|
if supported_formats:
|
|
msg += ' Allowed formats: %s' % ', '.join(sorted(supported_formats))
|
|
raise ValueError(msg)
|
|
if options:
|
|
raise ValueError('Unknown options: %s' % ', '.join(sorted(options.keys())))
|
|
return self.explain_prefix
|
|
|
|
def insert_statement(self, ignore_conflicts=False):
|
|
return 'INSERT INTO'
|
|
|
|
def ignore_conflicts_suffix_sql(self, ignore_conflicts=None):
|
|
return ''
|