Ticket #7210: 7210-F-Syntax.patch
File 7210-F-Syntax.patch, 20.0 KB (added by , 16 years ago) |
---|
-
django/db/models/sql/where.py
26 26 relabel_aliases() methods. 27 27 """ 28 28 default = AND 29 29 30 30 def add(self, data, connector): 31 31 """ 32 32 Add a node to the where-tree. If the data is a list or tuple, it is … … 43 43 return 44 44 45 45 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 46 63 try: 47 64 if field: 48 65 params = field.get_db_prep_lookup(lookup_type, value) … … 59 76 # match. 60 77 super(WhereNode, self).add(NothingNode(), connector) 61 78 return 79 62 80 if isinstance(value, datetime.datetime): 63 81 annotation = datetime.datetime 64 82 else: 65 83 annotation = bool(value) 84 66 85 super(WhereNode, self).add((alias, col, db_type, lookup_type, 67 86 annotation, params), connector) 68 87 69 def as_sql(self, qn=None ):88 def as_sql(self, qn=None, trust_content=False): 70 89 """ 71 90 Returns the SQL version of the where clause and the value to be 72 91 substituted in. Returns None, None if this node is empty. … … 83 102 result_params = [] 84 103 empty = True 85 104 for child in self.children: 105 #import pdb; pdb.set_trace() 86 106 try: 87 107 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) 89 114 else: 90 115 # 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 92 120 except EmptyResultSet: 93 121 if self.connector == AND and not self.negated: 94 122 # We can bail out early in this particular case (only). … … 106 134 if self.negated: 107 135 empty = True 108 136 continue 137 if hasattr(self, 'fragment') and '%s' in sql: 138 sql = sql % self.fragment 139 109 140 empty = False 110 141 if sql: 111 142 result.append(sql) … … 122 153 sql_string = '(%s)' % sql_string 123 154 return sql_string, result_params 124 155 125 def make_atom(self, child, qn ):156 def make_atom(self, child, qn, trust_content=False): 126 157 """ 127 158 Turn a tuple (table_alias, column_name, db_type, lookup_type, 128 159 value_annot, params) into valid SQL. … … 141 172 cast_sql = connection.ops.datetime_cast_sql() 142 173 else: 143 174 cast_sql = '%s' 144 175 145 176 if isinstance(params, QueryWrapper): 146 177 extra, params = params.data 147 178 else: 148 179 extra = '' 149 180 181 if trust_content: 182 extra = '' 183 150 184 if lookup_type in connection.operators: 185 #REm0ove %s 151 186 format = "%s %%s %s" % (connection.ops.lookup_cast(lookup_type), 152 187 extra) 153 188 return (format % (field_sql, … … 175 210 176 211 raise TypeError('Invalid lookup_type: %r' % lookup_type) 177 212 213 178 214 def relabel_aliases(self, change_map, node=None): 179 215 """ 180 216 Relabels the alias values of any children. 'change_map' is a dictionary -
django/db/models/sql/expressions.py
1 from copy import deepcopy 2 from datetime import datetime 3 4 from django.utils import tree 5 from django.core.exceptions import FieldError 6 from django.db import connection 7 from django.db.models.fields import Field, FieldDoesNotExist 8 from django.db.models.query_utils import QueryWrapper 9 from constants import LOOKUP_SEP 10 11 class 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 83 class 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 128 class 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
1039 1039 opts = self.get_meta() 1040 1040 alias = self.get_initial_alias() 1041 1041 allow_many = trim or not negate 1042 1042 1043 1043 try: 1044 1044 field, target, opts, join_list, last = self.setup_joins(parts, opts, 1045 1045 alias, True, allow_many, can_reuse=can_reuse) … … 1114 1114 if self.promote_alias(table, table_promote): 1115 1115 table_promote = True 1116 1116 1117 #Manage special objects through their make_value attribute 1118 if hasattr(value, 'make_value'): 1119 value.make_value((opts, self.select)) 1117 1120 self.where.add((alias, col, field, lookup_type, value), connector) 1118 1121 1119 1122 if negate: 1120 1123 for alias in join_list: 1121 1124 self.promote_alias(alias) … … 1137 1140 entry.add((alias, col, None, 'isnull', True), AND) 1138 1141 entry.negate() 1139 1142 self.where.add(entry, AND) 1140 1143 1141 1144 if can_reuse is not None: 1142 1145 can_reuse.update(join_list) 1143 1146 … … 1625 1628 return 1626 1629 1627 1630 cursor = self.connection.cursor() 1631 #import pdb; pdb.set_trace() 1628 1632 cursor.execute(sql, params) 1629 1633 1630 1634 if not result_type: -
django/db/models/sql/subqueries.py
137 137 for name, val, placeholder in self.values: 138 138 if val is not None: 139 139 values.append('%s = %s' % (qn(name), placeholder)) 140 update_params.append(val) 140 if not hasattr(val, 'make_value'): 141 update_params.append(val) 141 142 else: 142 143 values.append('%s = NULL' % qn(name)) 143 144 result.append(', '.join(values)) … … 239 240 saving models. 240 241 """ 241 242 from django.db.models.base import Model 243 #import pdb; pdb.set_trace() 242 244 for field, model, val in values_seq: 243 245 # FIXME: Some sort of db_prep_* is probably more appropriate here. 244 246 if field.rel and isinstance(val, Model): … … 250 252 else: 251 253 placeholder = '%s' 252 254 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 253 260 if model: 254 261 self.add_related_update(model, field.column, val, placeholder) 255 262 else: -
django/db/models/__init__.py
4 4 from django.db import connection 5 5 from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models 6 6 from django.db.models.query import Q 7 from django.db.models.sql.expressions import F 7 8 from django.db.models.manager import Manager 8 9 from django.db.models.base import Model 9 10 from django.db.models.fields import * -
django/db/models/query_utils.py
6 6 """ 7 7 8 8 from copy import deepcopy 9 9 from django.db import connection 10 10 from django.utils import tree 11 11 12 12 class QueryWrapper(object): -
tests/modeltests/expressions/models.py
1 """ 2 Tests for the update() queryset method that allows in-place, multi-object 3 updates. 4 """ 5 6 from django.db import models 7 8 # 9 # Model for testing arithmetic expressions. 10 # 11 12 class 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 23 class 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 30 class 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 53 We can fill a value in all objects with an other value of the same object. 54 55 >>> Number.objects.update(float=F('integer')) 56 3 57 >>> Number.objects.all() 58 [<Number: -1, -1.000>, <Number: 42, 42.000>, <Number: 1337, 1337.000>] 59 60 We can increment a value of all objects in a query set. 61 62 >>> Number.objects.filter(integer__gt=0).update(integer=F('integer') + 1) 63 2 64 >>> Number.objects.all() 65 [<Number: -1, -1.000>, <Number: 43, 42.000>, <Number: 1338, 1337.000>] 66 67 We 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 72 Complex 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) 77 1 78 >>> Number.objects.get(pk=n.pk) 79 <Number: 10, 256.900> 80 81 All 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) 89 1 90 <Number: 57, 57.000> 91 1 92 <Number: 27, 27.000> 93 1 94 <Number: 630, 630.000> 95 1 96 <Number: 2, 2.800> 97 1 98 <Number: 12, 12.000> 99 1 100 <Number: 10, 10.000> 101 1 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 112 We can filter for companies where the number of employees is greater than the 113 number of chairs. 114 115 >>> Company.objects.filter(num_employees__gt=F('num_chairs')) 116 [<Company: Example Inc.>, <Company: Test GmbH>] 117 118 The relation of a foreign key can become copied over to an other foreign key. 119 120 >>> Company.objects.update(point_of_contact=F('ceo')) 121 3 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
30 30 headline = models.CharField(max_length=255) 31 31 body_text = models.TextField() 32 32 pub_date = models.DateTimeField() 33 num_comments = models.IntegerField() 34 comment_threshold = models.IntegerField() 33 35 authors = models.ManyToManyField(Author) 34 36 35 37 def __unicode__(self): … … 1744 1746 Blog.objetcs.filter(entry__author__isnull=False, 1745 1747 entry__author__name__isnull=True) 1746 1748 1749 Using other model attributes in your lookups with F objects 1750 ----------------------------------------------------------- 1751 1752 **New in Django development version** 1753 1754 When filtering you can refer to you can refer to other attributes of 1755 your model by using F objects and expressions. For example if you 1756 want to obtain the entries that have more comments than they were 1757 suposed to you could do:: 1758 1759 Entry.objects.filter(num_comments__gt=F('comment_threshold')) 1760 1761 You could also use aritmethic expressions so you could get those that 1762 have doubled the expected threshold:: 1763 1764 Entry.objects.filter(num_comments__gt=2*F('comment_threshold')) 1765 1766 The 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 1747 1789 Spanning multi-valued relationships 1748 1790 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1749 1791 … … 2283 2325 # Change every Entry so that it belongs to this Blog. 2284 2326 Entry.objects.all().update(blog=b) 2285 2327 2286 The ``update()`` method is applied instantly and doesn't return anything2287 (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.2328 The ``update()`` method is applied instantly and returns the number of 2329 objects that were affected. The only restriction on the ``QuerySet`` 2330 that is updated is that it can only access one database table, the 2331 model's main table. So don't try to filter based on related fields or 2332 anything like that; it won't work. 2291 2333 2292 2334 Be aware that the ``update()`` method is converted directly to an SQL 2293 2335 statement. It is a bulk operation for direct updates. It doesn't run any … … 2300 2342 for item in my_queryset: 2301 2343 item.save() 2302 2344 2345 When using ``update()`` you can also refer to other fields in the 2346 model by using F objects and expressions. Updating the comment 2347 threshold of all entries for it to be the same as the current number 2348 of comments could be done like this:: 2303 2349 2350 Entry.objects.all().update(comment_threshold=F('num_comments')) 2351 2352 2304 2353 Extra instance methods 2305 2354 ====================== 2306 2355