Source code for django_spanner.compiler

# Copyright 2020 Google LLC
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd

from django.core.exceptions import EmptyResultSet
from django.db.models.sql.compiler import (
    SQLAggregateCompiler as BaseSQLAggregateCompiler,
    SQLCompiler as BaseSQLCompiler,
    SQLDeleteCompiler as BaseSQLDeleteCompiler,
    SQLInsertCompiler as BaseSQLInsertCompiler,
    SQLUpdateCompiler as BaseSQLUpdateCompiler,
)
from django.db.utils import DatabaseError


[docs]class SQLCompiler(BaseSQLCompiler): """ A variation of the Django SQL compiler, adjusted for Spanner-specific functionality. """
[docs] def get_combinator_sql(self, combinator, all): """Override the native Django method. Copied from the base class except for: combinator_sql += ' ALL' if all else ' DISTINCT' Cloud Spanner requires ALL or DISTINCT. :type combinator: str :param combinator: A type of the combinator for the operation. :type all: bool :param all: Bool option for the SQL statement. :rtype: tuple :returns: A tuple containing SQL statement(s) with some additional parameters. """ features = self.connection.features compilers = [ query.get_compiler(self.using, self.connection) for query in self.query.combined_queries if not query.is_empty() ] if not features.supports_slicing_ordering_in_compound: for query, compiler in zip(self.query.combined_queries, compilers): if query.low_mark or query.high_mark: raise DatabaseError( "LIMIT/OFFSET not allowed in subqueries of compound " "statements." ) if compiler.get_order_by(): raise DatabaseError( "ORDER BY not allowed in subqueries of compound " "statements." ) parts = () for compiler in compilers: try: # If the columns list is limited, then all combined queries # must have the same columns list. Set the selects defined on # the query on all combined queries, if not already set. if ( not compiler.query.values_select and self.query.values_select ): compiler.query.set_values( ( *self.query.extra_select, *self.query.values_select, *self.query.annotation_select, ) ) part_sql, part_args = compiler.as_sql() if compiler.query.combinator: # Wrap in a subquery if wrapping in parentheses isn't # supported. if not features.supports_parentheses_in_compound: part_sql = "SELECT * FROM ({})".format(part_sql) # Add parentheses when combining with compound query if not # already added for all compound queries. elif not features.supports_slicing_ordering_in_compound: part_sql = "({})".format(part_sql) parts += ((part_sql, part_args),) except EmptyResultSet: # Omit the empty queryset with UNION and with DIFFERENCE if the # first queryset is nonempty. if combinator == "union" or ( combinator == "difference" and parts ): continue raise if not parts: raise EmptyResultSet combinator_sql = self.connection.ops.set_operators[combinator] combinator_sql += " ALL" if all else " DISTINCT" braces = ( "({})" if features.supports_slicing_ordering_in_compound else "{}" ) sql_parts, args_parts = zip( *((braces.format(sql), args) for sql, args in parts) ) result = [" {} ".format(combinator_sql).join(sql_parts)] params = [] for part in args_parts: params.extend(part) return result, params
[docs]class SQLInsertCompiler(BaseSQLInsertCompiler, SQLCompiler): """A wrapper class for compatibility with Django specifications.""" pass
[docs]class SQLDeleteCompiler(BaseSQLDeleteCompiler, SQLCompiler): """A wrapper class for compatibility with Django specifications.""" pass
[docs]class SQLUpdateCompiler(BaseSQLUpdateCompiler, SQLCompiler): """A wrapper class for compatibility with Django specifications.""" pass
[docs]class SQLAggregateCompiler(BaseSQLAggregateCompiler, SQLCompiler): """A wrapper class for compatibility with Django specifications.""" pass