Преглед изворни кода

Refs #25759 -- Documented customizing expressions' SQL on other databases.

Kai Feldhoff пре 9 година
родитељ
комит
5ca08f7cab
2 измењених фајлова са 41 додато и 7 уклоњено
  1. 36 7
      docs/ref/models/expressions.txt
  2. 5 0
      docs/ref/models/lookups.txt

+ 36 - 7
docs/ref/models/expressions.txt

@@ -261,6 +261,28 @@ The ``Func`` API is as follows:
         different number of expressions, ``TypeError`` will be raised. Defaults
         to ``None``.
 
+    .. method:: as_sql(compiler, connection, function=None, template=None)
+
+        Generates the SQL for the database function.
+
+        The ``as_vendor()`` methods should use the ``function`` and
+        ``template`` parameters to customize the SQL as needed. For example:
+
+        .. snippet::
+            :filename: django/db/models/functions.py
+
+            class ConcatPair(Func):
+                ...
+                function = 'CONCAT'
+                ...
+
+                def as_mysql(self, compiler, connection):
+                    return super(ConcatPair, self).as_sql(
+                        compiler, connection,
+                        function='CONCAT_WS',
+                        template="%(function)s('', %(expressions)s)",
+                    )
+
 The ``*expressions`` argument is a list of positional expressions that the
 function will be applied to. The expressions will be converted to strings,
 joined together with ``arg_joiner``, and then interpolated into the ``template``
@@ -560,7 +582,7 @@ an ``__init__()`` method to set some attributes::
   class Coalesce(Expression):
       template = 'COALESCE( %(expressions)s )'
 
-      def __init__(self, expressions, output_field, **extra):
+      def __init__(self, expressions, output_field):
         super(Coalesce, self).__init__(output_field=output_field)
         if len(expressions) < 2:
             raise ValueError('expressions must have at least 2 elements')
@@ -568,7 +590,6 @@ an ``__init__()`` method to set some attributes::
             if not hasattr(expression, 'resolve_expression'):
                 raise TypeError('%r is not an Expression' % expression)
         self.expressions = expressions
-        self.extra = extra
 
 We do some basic validation on the parameters, including requiring at least
 2 columns or values, and ensuring they are expressions. We are requiring
@@ -588,22 +609,30 @@ expressions::
 
 Next, we write the method responsible for generating the SQL::
 
-    def as_sql(self, compiler, connection):
+    def as_sql(self, compiler, connection, template=None):
         sql_expressions, sql_params = [], []
         for expression in self.expressions:
             sql, params = compiler.compile(expression)
             sql_expressions.append(sql)
             sql_params.extend(params)
-        self.extra['expressions'] = ','.join(sql_expressions)
-        return self.template % self.extra, sql_params
+        template = template or self.template
+        data = {'expressions': ','.join(sql_expressions)}
+        return template % data, params
 
     def as_oracle(self, compiler, connection):
         """
         Example of vendor specific handling (Oracle in this case).
         Let's make the function name lowercase.
         """
-        self.template = 'coalesce( %(expressions)s )'
-        return self.as_sql(compiler, connection)
+        return self.as_sql(compiler, connection, template='coalesce( %(expressions)s )')
+
+``as_sql()`` methods can support custom keyword arguments, allowing
+``as_vendorname()`` methods to override data used to generate the SQL string.
+Using ``as_sql()`` keyword arguments for customization is preferable to
+mutating ``self`` within ``as_vendorname()`` methods as the latter can lead to
+errors when running on different database backends. If your class relies on
+class attributes to define data, consider allowing overrides in your
+``as_sql()`` method.
 
 We generate the SQL for each of the ``expressions`` by using the
 ``compiler.compile()`` method, and join the result together with commas.

+ 5 - 0
docs/ref/models/lookups.txt

@@ -94,6 +94,11 @@ following methods:
     ``compiler.compile(expression)`` should be used. The ``compiler.compile()``
     method will take care of calling vendor-specific methods of the expression.
 
+    Custom keyword arguments may be defined on this method if it's likely that
+    ``as_vendorname()`` methods or subclasses will need to supply data to
+    override the generation of the SQL string. See :meth:`Func.as_sql` for
+    example usage.
+
 .. method:: as_vendorname(self, compiler, connection)
 
     Works like ``as_sql()`` method. When an expression is compiled by