Ticket #7210: 7210-F-Syntax.patch

File 7210-F-Syntax.patch, 20.0 KB (added by nicolas, 7 years ago)

Re-write of the patch. Added docs.

  • django/db/models/sql/where.py

     
    2626    relabel_aliases() methods.
    2727    """
    2828    default = AND
    29 
     29   
    3030    def add(self, data, connector):
    3131        """
    3232        Add a node to the where-tree. If the data is a list or tuple, it is
     
    4343            return
    4444
    4545        alias, col, field, lookup_type, value = data
     46
     47        #If the object defined its own value trust it's sql fragment to allow
     48        #not inclusion of columns and avoid quoting
     49        #import pdb; pdb.set_trace()
     50        if hasattr(value, 'make_value'):
     51            self.fragment, params = value.as_sql()
     52            db_type = None
     53        elif field:
     54            params = field.get_db_prep_lookup(lookup_type, value)
     55            db_type = field.db_type()
     56        else:
     57            # This is possible when we add a comparison to NULL sometimes (we
     58            # don't really need to waste time looking up the associated field
     59            # object).
     60            params = Field().get_db_prep_lookup(lookup_type, value)
     61            db_type = None
     62
    4663        try:
    4764            if field:
    4865                params = field.get_db_prep_lookup(lookup_type, value)
     
    5976            # match.
    6077            super(WhereNode, self).add(NothingNode(), connector)
    6178            return
     79
    6280        if isinstance(value, datetime.datetime):
    6381            annotation = datetime.datetime
    6482        else:
    6583            annotation = bool(value)
     84
    6685        super(WhereNode, self).add((alias, col, db_type, lookup_type,
    6786                annotation, params), connector)
    6887
    69     def as_sql(self, qn=None):
     88    def as_sql(self, qn=None, trust_content=False):
    7089        """
    7190        Returns the SQL version of the where clause and the value to be
    7291        substituted in. Returns None, None if this node is empty.
     
    83102        result_params = []
    84103        empty = True
    85104        for child in self.children:
     105            #import pdb; pdb.set_trace()
    86106            try:
    87107                if hasattr(child, 'as_sql'):
    88                     sql, params = child.as_sql(qn=qn)
     108                    try:
     109                        sql, params = child.as_sql(qn=qn,
     110                                                   trust_content=hasattr(self, 'fragment')
     111                                                   or trust_content)
     112                    except:
     113                        sql, params = child.as_sql(qn=qn)
    89114                else:
    90115                    # A leaf node in the tree.
    91                     sql, params = self.make_atom(child, qn)
     116                    sql, params = self.make_atom(child, qn,
     117                                                 trust_content=hasattr(self, 'fragment')
     118                                                 or trust_content)
     119
    92120            except EmptyResultSet:
    93121                if self.connector == AND and not self.negated:
    94122                    # We can bail out early in this particular case (only).
     
    106134                if self.negated:
    107135                    empty = True
    108136                continue
     137            if hasattr(self, 'fragment') and '%s' in sql:
     138                sql = sql % self.fragment
     139               
    109140            empty = False
    110141            if sql:
    111142                result.append(sql)
     
    122153                sql_string = '(%s)' % sql_string
    123154        return sql_string, result_params
    124155
    125     def make_atom(self, child, qn):
     156    def make_atom(self, child, qn, trust_content=False):
    126157        """
    127158        Turn a tuple (table_alias, column_name, db_type, lookup_type,
    128159        value_annot, params) into valid SQL.
     
    141172            cast_sql = connection.ops.datetime_cast_sql()
    142173        else:
    143174            cast_sql = '%s'
    144 
     175           
    145176        if isinstance(params, QueryWrapper):
    146177            extra, params = params.data
    147178        else:
    148179            extra = ''
    149180
     181        if trust_content:
     182            extra = ''
     183
    150184        if lookup_type in connection.operators:
     185            #REm0ove %s
    151186            format = "%s %%s %s" % (connection.ops.lookup_cast(lookup_type),
    152187                    extra)
    153188            return (format % (field_sql,
     
    175210
    176211        raise TypeError('Invalid lookup_type: %r' % lookup_type)
    177212
     213
    178214    def relabel_aliases(self, change_map, node=None):
    179215        """
    180216        Relabels the alias values of any children. 'change_map' is a dictionary
  • django/db/models/sql/expressions.py

     
     1from copy import deepcopy
     2from datetime import datetime
     3
     4from django.utils import tree
     5from django.core.exceptions import FieldError
     6from django.db import connection
     7from django.db.models.fields import Field, FieldDoesNotExist
     8from django.db.models.query_utils import QueryWrapper
     9from constants import LOOKUP_SEP
     10
     11class Expression(object):
     12    """
     13    Base class for all sql expressions, expected by QuerySet.update.
     14    """
     15    # Arithmetic connection types
     16    ADD = '+'
     17    SUB = '-'
     18    MUL = '*'
     19    DIV = '/'
     20    MOD = '%%'
     21
     22    # Bitwise connection types
     23    AND = '&'
     24    OR = '|'
     25
     26    def _combine(self, other, conn, reversed, node=None):
     27        if reversed:
     28            obj = ExpressionNode([other], conn)
     29            obj.add(node or self, conn)
     30        else:
     31            obj = node or ExpressionNode([self], conn)
     32            if isinstance(other, Expression):
     33                obj.add(other, conn)
     34            else:
     35                obj.add(other, conn)
     36        return obj
     37
     38    def __add__(self, other):
     39        return self._combine(other, self.ADD, False)
     40
     41    def __sub__(self, other):
     42        return self._combine(other, self.SUB, False)
     43
     44    def __mul__(self, other):
     45        return self._combine(other, self.MUL, False)
     46
     47    def __div__(self, other):
     48        return self._combine(other, self.DIV, False)
     49
     50    def __mod__(self, other):
     51        return self._combine(other, self.MOD, False)
     52
     53    def __and__(self, other):
     54        return self._combine(other, self.AND, False)
     55
     56    def __or__(self, other):
     57        return self._combine(other, self.OR, False)
     58
     59    def __radd__(self, other):
     60        return self._combine(other, self.ADD, True)
     61
     62    def __rsub__(self, other):
     63        return self._combine(other, self.SUB, True)
     64
     65    def __rmul__(self, other):
     66        return self._combine(other, self.MUL, True)
     67
     68    def __rdiv__(self, other):
     69        return self._combine(other, self.DIV, True)
     70
     71    def __rmod__(self, other):
     72        return self._combine(other, self.MOD, True)
     73
     74    def __rand__(self, other):
     75        return self._combine(other, self.AND, True)
     76
     77    def __ror__(self, other):
     78        return self._combine(other, self.OR, True)
     79
     80    def as_sql(self, prep_func=None, qn=None):
     81        raise NotImplementedError
     82
     83class ExpressionNode(Expression, tree.Node):
     84    default = None
     85
     86    def __init__(self, children=None, connector=None, negated=False):
     87        if children is not None and len(children) > 1 and connector is None:
     88            raise TypeError('You have to specify a connector.')
     89        super(ExpressionNode, self).__init__(children, connector, negated)
     90
     91    def _combine(self, *args, **kwargs):
     92        return super(ExpressionNode, self)._combine(node=deepcopy(self), *args, **kwargs)
     93
     94    def make_value(self, data, qn=None):
     95        opts, select = data
     96
     97        if not qn:
     98            qn = connection.ops.quote_name
     99
     100        result = []
     101        result_params = []
     102        for child in self.children:
     103            if hasattr(child, 'as_sql'):
     104                child.make_value(data)
     105                sql, params = child.as_sql(None, qn)
     106            else:
     107                try:
     108                    sql, params = qn(child), []
     109                except:
     110                    sql, params = str(child), []
     111                   
     112            if hasattr(child, 'children') > 1:
     113                format = '(%s)'
     114            else:
     115                format = '%s'
     116
     117
     118            if sql:
     119                result.append(format % sql)
     120                result_params.extend(params)
     121        conn = ' %s ' % self.connector
     122        self.sql_value = conn.join(result)
     123        self.result_params = result_params
     124   
     125    def as_sql(self, prep_func=None, qn=None):
     126        return self.sql_value, tuple(self.result_params)
     127
     128class F(Expression):
     129    """
     130    An expression representing the value of the given field.
     131    """
     132    def __init__(self, name):
     133        self.name = name
     134        self.sql_value = ''
     135
     136    def make_value(self, data, qn=None):
     137        opts, select = data
     138
     139        if not qn:
     140            qn = connection.ops.quote_name
     141
     142        try:
     143            column = opts.get_field(self.name).column
     144            self.sql_value = '%s.%s' % (qn(opts.db_table), qn(column))
     145        except FieldDoesNotExist:
     146            raise FieldError("Cannot resolve keyword %r into field. "
     147                             "Choices are: %s" % (self.name,
     148                                                  [f.name for f in opts.fields]))
     149           
     150
     151    def as_sql(self, prep_func=None, qn=None):
     152        return self.sql_value, ()
  • django/db/models/sql/query.py

     
    10391039        opts = self.get_meta()
    10401040        alias = self.get_initial_alias()
    10411041        allow_many = trim or not negate
    1042 
     1042       
    10431043        try:
    10441044            field, target, opts, join_list, last = self.setup_joins(parts, opts,
    10451045                    alias, True, allow_many, can_reuse=can_reuse)
     
    11141114                if self.promote_alias(table, table_promote):
    11151115                    table_promote = True
    11161116
     1117        #Manage special objects through their make_value attribute
     1118        if hasattr(value, 'make_value'):
     1119            value.make_value((opts, self.select))
    11171120        self.where.add((alias, col, field, lookup_type, value), connector)
    1118 
     1121       
    11191122        if negate:
    11201123            for alias in join_list:
    11211124                self.promote_alias(alias)
     
    11371140                    entry.add((alias, col, None, 'isnull', True), AND)
    11381141                    entry.negate()
    11391142                    self.where.add(entry, AND)
    1140 
     1143                   
    11411144        if can_reuse is not None:
    11421145            can_reuse.update(join_list)
    11431146
     
    16251628                return
    16261629
    16271630        cursor = self.connection.cursor()
     1631        #import pdb; pdb.set_trace()
    16281632        cursor.execute(sql, params)
    16291633
    16301634        if not result_type:
  • django/db/models/sql/subqueries.py

     
    137137        for name, val, placeholder in self.values:
    138138            if val is not None:
    139139                values.append('%s = %s' % (qn(name), placeholder))
    140                 update_params.append(val)
     140                if not hasattr(val, 'make_value'):
     141                    update_params.append(val)
    141142            else:
    142143                values.append('%s = NULL' % qn(name))
    143144        result.append(', '.join(values))
     
    239240        saving models.
    240241        """
    241242        from django.db.models.base import Model
     243        #import pdb; pdb.set_trace()
    242244        for field, model, val in values_seq:
    243245            # FIXME: Some sort of db_prep_* is probably more appropriate here.
    244246            if field.rel and isinstance(val, Model):
     
    250252            else:
    251253                placeholder = '%s'
    252254
     255            if hasattr(val, 'make_value'):
     256                val.make_value((self.get_meta(),self.select))
     257                fragment, _ = val.as_sql()
     258                placeholder = placeholder % fragment
     259               
    253260            if model:
    254261                self.add_related_update(model, field.column, val, placeholder)
    255262            else:
  • django/db/models/__init__.py

     
    44from django.db import connection
    55from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
    66from django.db.models.query import Q
     7from django.db.models.sql.expressions import F
    78from django.db.models.manager import Manager
    89from django.db.models.base import Model
    910from django.db.models.fields import *
  • django/db/models/query_utils.py

     
    66"""
    77
    88from copy import deepcopy
    9 
     9from django.db import connection
    1010from django.utils import tree
    1111
    1212class QueryWrapper(object):
  • tests/modeltests/expressions/models.py

     
     1"""
     2Tests for the update() queryset method that allows in-place, multi-object
     3updates.
     4"""
     5
     6from django.db import models
     7
     8#
     9# Model for testing arithmetic expressions.
     10#
     11
     12class Number(models.Model):
     13    integer = models.IntegerField()
     14    float = models.FloatField(null=True)
     15
     16    def __unicode__(self):
     17        return u'%i, %.3f' % (self.integer, self.float)
     18
     19#
     20# A more ordinary use case.
     21#
     22
     23class Employee(models.Model):
     24    firstname = models.CharField(max_length=50)
     25    lastname = models.CharField(max_length=50)
     26
     27    def __unicode__(self):
     28        return u'%s %s' % (self.firstname, self.lastname)
     29
     30class Company(models.Model):
     31    name = models.CharField(max_length=100)
     32    num_employees = models.PositiveIntegerField()
     33    num_chairs = models.PositiveIntegerField()
     34    ceo = models.ForeignKey(
     35        Employee,
     36        related_name='company_ceo_set')
     37    point_of_contact = models.ForeignKey(
     38        Employee,
     39        related_name='company_point_of_contact_set',
     40        null=True)
     41
     42    def __unicode__(self):
     43        return self.name
     44
     45
     46__test__ = {'API_TESTS': """
     47>>> from django.db.models import F
     48
     49>>> Number(integer=-1).save()
     50>>> Number(integer=42).save()
     51>>> Number(integer=1337).save()
     52
     53We can fill a value in all objects with an other value of the same object.
     54
     55>>> Number.objects.update(float=F('integer'))
     563
     57>>> Number.objects.all()
     58[<Number: -1, -1.000>, <Number: 42, 42.000>, <Number: 1337, 1337.000>]
     59
     60We can increment a value of all objects in a query set.
     61
     62>>> Number.objects.filter(integer__gt=0).update(integer=F('integer') + 1)
     632
     64>>> Number.objects.all()
     65[<Number: -1, -1.000>, <Number: 43, 42.000>, <Number: 1338, 1337.000>]
     66
     67We can filter for objects, where a value is not equals the value of an other field.
     68
     69>>> Number.objects.exclude(float=F('integer'))
     70[<Number: 43, 42.000>, <Number: 1338, 1337.000>]
     71
     72Complex expressions of different connection types are possible.
     73
     74>>> n = Number.objects.create(integer=10, float=123.45)
     75
     76>>> Number.objects.filter(pk=n.pk).update(float=F('integer') + F('float') * 2)
     771
     78>>> Number.objects.get(pk=n.pk)
     79<Number: 10, 256.900>
     80
     81All supported operators, work as expected in native and reverse order.
     82
     83>>> from operator import add, sub, mul, div, mod, and_, or_
     84>>> for op in (add, sub, mul, div, mod, and_, or_):
     85...     n = Number.objects.create(integer=42, float=15.)
     86...     Number.objects.filter(pk=n.pk).update(
     87...         integer=op(F('integer'), 15), float=op(42., F('float')))
     88...     Number.objects.get(pk=n.pk)
     891
     90<Number: 57, 57.000>
     911
     92<Number: 27, 27.000>
     931
     94<Number: 630, 630.000>
     951
     96<Number: 2, 2.800>
     971
     98<Number: 12, 12.000>
     991
     100<Number: 10, 10.000>
     1011
     102<Number: 47, 47.000>
     103
     104
     105>>> Company(name='Example Inc.', num_employees=2300, num_chairs=5,
     106...     ceo=Employee.objects.create(firstname='Joe', lastname='Smith')).save()
     107>>> Company(name='Foobar Ltd.', num_employees=3, num_chairs=3,
     108...     ceo=Employee.objects.create(firstname='Frank', lastname='Meyer')).save()
     109>>> Company(name='Test GmbH', num_employees=32, num_chairs=1,
     110...     ceo=Employee.objects.create(firstname='Max', lastname='Mustermann')).save()
     111
     112We can filter for companies where the number of employees is greater than the
     113number of chairs.
     114
     115>>> Company.objects.filter(num_employees__gt=F('num_chairs'))
     116[<Company: Example Inc.>, <Company: Test GmbH>]
     117
     118The relation of a foreign key can become copied over to an other foreign key.
     119
     120>>> Company.objects.update(point_of_contact=F('ceo'))
     1213
     122>>> [c.point_of_contact for c in Company.objects.all()]
     123[<Employee: Joe Smith>, <Employee: Frank Meyer>, <Employee: Max Mustermann>]
     124
     125"""}
  • docs/db-api.txt

     
    3030        headline = models.CharField(max_length=255)
    3131        body_text = models.TextField()
    3232        pub_date = models.DateTimeField()
     33        num_comments = models.IntegerField()
     34        comment_threshold = models.IntegerField()
    3335        authors = models.ManyToManyField(Author)
    3436
    3537        def __unicode__(self):
     
    17441746    Blog.objetcs.filter(entry__author__isnull=False,
    17451747            entry__author__name__isnull=True)
    17461748
     1749Using other model attributes in your lookups with F objects
     1750-----------------------------------------------------------
     1751
     1752**New in Django development version**
     1753
     1754When filtering you can refer to you can refer to other attributes of
     1755your model by using F objects and expressions.  For example if you
     1756want to obtain the entries that have more comments than they were
     1757suposed to you could do::
     1758
     1759    Entry.objects.filter(num_comments__gt=F('comment_threshold'))
     1760
     1761You could also use aritmethic expressions so you could get those that
     1762have doubled the expected threshold::
     1763
     1764    Entry.objects.filter(num_comments__gt=2*F('comment_threshold'))
     1765
     1766The available operands for arithmetic expressions are::
     1767
     1768 * Addition using ``+``
     1769
     1770 * Substraction using ``-``
     1771
     1772 * Multiplication using ``*``
     1773
     1774 * Division using ``/``
     1775
     1776 * Modulo using ``%%``
     1777 
     1778 * Bitwise AND using ``&``
     1779
     1780 * Bitwise OR using ``|``
     1781
     1782.. note::
     1783   F objects can only access one database table, the model's main
     1784   table. If you need to access more than this you can use extra or
     1785   `fall back to SQL`_.
     1786
     1787   .. _`fall back to SQL`: ../model-api/#falling-back-to--raw-sql
     1788
    17471789Spanning multi-valued relationships
    17481790~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    17491791
     
    22832325    # Change every Entry so that it belongs to this Blog.
    22842326    Entry.objects.all().update(blog=b)
    22852327
    2286 The ``update()`` method is applied instantly and doesn't return anything
    2287 (similar to ``delete()``). The only restriction on the ``QuerySet`` that is
    2288 updated is that it can only access one database table, the model's main
    2289 table. So don't try to filter based on related fields or anything like that;
    2290 it won't work.
     2328The ``update()`` method is applied instantly and returns the number of
     2329objects that were affected. The only restriction on the ``QuerySet``
     2330that is updated is that it can only access one database table, the
     2331model's main table. So don't try to filter based on related fields or
     2332anything like that; it won't work.
    22912333
    22922334Be aware that the ``update()`` method is converted directly to an SQL
    22932335statement. It is a bulk operation for direct updates. It doesn't run any
     
    23002342    for item in my_queryset:
    23012343        item.save()
    23022344
     2345When using ``update()`` you can also refer to other fields in the
     2346model by using F objects and expressions. Updating the comment
     2347threshold of all entries for it to be the same as the current number
     2348of comments could be done like this::
    23032349
     2350    Entry.objects.all().update(comment_threshold=F('num_comments'))
     2351
     2352
    23042353Extra instance methods
    23052354======================
    23062355
Back to Top