Code

Ticket #11863: 11863-rc3.diff

File 11863-rc3.diff, 27.4 KB (added by russellm, 4 years ago)

Same as RC2, but with deferred fields.

Line 
1diff -r 277cb8fbef51 django/db/models/manager.py
2--- a/django/db/models/manager.py       Sat Dec 19 14:27:26 2009 +0000
3+++ b/django/db/models/manager.py       Sat Dec 19 22:34:47 2009 +0800
4@@ -1,5 +1,5 @@
5 import django.utils.copycompat as copy
6-from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
7+from django.db.models.query import QuerySet, EmptyQuerySet, insert_query, RawQuerySet
8 from django.db.models import signals
9 from django.db.models.fields import FieldDoesNotExist
10 
11@@ -181,6 +181,9 @@
12     def _update(self, values, **kwargs):
13         return self.get_query_set()._update(values, **kwargs)
14 
15+    def raw(self, query, params=None, *args, **kwargs):
16+        return RawQuerySet(model=self.model, query=query, params=params, *args, **kwargs)
17+
18 class ManagerDescriptor(object):
19     # This class ensures managers aren't accessible via model instances.
20     # For example, Poll.objects works, but poll_obj.objects raises AttributeError.
21diff -r 277cb8fbef51 django/db/models/query.py
22--- a/django/db/models/query.py Sat Dec 19 14:27:26 2009 +0000
23+++ b/django/db/models/query.py Sat Dec 19 22:34:47 2009 +0800
24@@ -5,7 +5,7 @@
25 from django.db import connection, transaction, IntegrityError
26 from django.db.models.aggregates import Aggregate
27 from django.db.models.fields import DateField
28-from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory
29+from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory, InvalidQuery
30 from django.db.models import signals, sql
31 from django.utils.copycompat import deepcopy
32 
33@@ -287,7 +287,7 @@
34         Returns a dictionary containing the calculations (aggregation)
35         over the current queryset
36 
37-        If args is present the expression is passed as a kwarg ussing
38+        If args is present the expression is passed as a kwarg using
39         the Aggregate object's default alias.
40         """
41         for arg in args:
42@@ -1105,6 +1105,89 @@
43         if forced_managed:
44             transaction.leave_transaction_management()
45 
46+class RawQuerySet(object):
47+    """
48+    Provides an iterator which converts the results of raw SQL queries into
49+    annotated model instances.
50+    """
51+    def __init__(self, query, model=None, query_obj=None, params=None, translations=None):
52+        self.model = model
53+        self.query = query_obj or sql.RawQuery(sql=query, connection=connection, params=params)
54+        self.params = params or ()
55+        self.translations = translations or {}
56+
57+    def __iter__(self):
58+        for row in self.query:
59+            yield self.transform_results(row)
60+
61+    def __repr__(self):
62+        return "<RawQuerySet: %r>" % (self.query.sql % self.params)
63+
64+    @property
65+    def columns(self):
66+        """
67+        A list of model field names in the order they'll appear in the
68+        query results.
69+        """
70+        if not hasattr(self, '_columns'):
71+            self._columns = self.query.get_columns()
72+
73+            # Adjust any column names which don't match field names
74+            for (query_name, model_name) in self.translations.items():
75+                try:
76+                    index = self._columns.index(query_name)
77+                    self._columns[index] = model_name
78+                except ValueError:
79+                    # Ignore translations for non-existant column names
80+                    pass
81+
82+        return self._columns
83+
84+    @property
85+    def model_fields(self):
86+        """
87+        A dict mapping column names to model field names.
88+        """
89+        if not hasattr(self, '_model_fields'):
90+            self._model_fields = {}
91+            for field in self.model._meta.fields:
92+                name, column = field.get_attname_column()
93+                self._model_fields[column] = name
94+        return self._model_fields
95+
96+    def transform_results(self, values):
97+        model_init_kwargs = {}
98+        annotations = ()
99+
100+        # Associate fields to values
101+        for pos, value in enumerate(values):
102+            column = self.columns[pos]
103+
104+            # Separate properties from annotations
105+            if column in self.model_fields.keys():
106+                model_init_kwargs[self.model_fields[column]] = value
107+            else:
108+                annotations += (column, value),
109+
110+        # Construct model instance and apply annotations
111+        skip = set()
112+        for field in self.model._meta.fields:
113+            if field.name not in model_init_kwargs.keys():
114+                skip.add(field.attname)
115+
116+        if skip:
117+            if self.model._meta.pk.attname in skip:
118+                raise InvalidQuery('Raw query must include the primary key')
119+            model_cls = deferred_class_factory(self.model, skip)
120+        else:
121+            model_cls = self.model
122+
123+        instance = model_cls(**model_init_kwargs)
124+
125+        for field, value in annotations:
126+            setattr(instance, field, value)
127+
128+        return instance
129 
130 def insert_query(model, values, return_id=False, raw_values=False):
131     """
132diff -r 277cb8fbef51 django/db/models/query_utils.py
133--- a/django/db/models/query_utils.py   Sat Dec 19 14:27:26 2009 +0000
134+++ b/django/db/models/query_utils.py   Sat Dec 19 22:34:47 2009 +0800
135@@ -20,6 +20,13 @@
136     """
137     pass
138 
139+class InvalidQuery(Exception):
140+    """
141+    The query passed to raw isn't a safe query to use with raw.
142+    """
143+    pass
144+
145+
146 class CollectedObjects(object):
147     """
148     A container that stores keys and lists of values along with remembering the
149diff -r 277cb8fbef51 django/db/models/sql/query.py
150--- a/django/db/models/sql/query.py     Sat Dec 19 14:27:26 2009 +0000
151+++ b/django/db/models/sql/query.py     Sat Dec 19 22:34:47 2009 +0800
152@@ -15,7 +15,7 @@
153 from django.db import connection
154 from django.db.models import signals
155 from django.db.models.fields import FieldDoesNotExist
156-from django.db.models.query_utils import select_related_descend
157+from django.db.models.query_utils import select_related_descend, InvalidQuery
158 from django.db.models.sql import aggregates as base_aggregates_module
159 from django.db.models.sql.expressions import SQLEvaluator
160 from django.db.models.sql.where import WhereNode, Constraint, EverythingNode, AND, OR
161@@ -23,7 +23,42 @@
162 from datastructures import EmptyResultSet, Empty, MultiJoin
163 from constants import *
164 
165-__all__ = ['Query', 'BaseQuery']
166+__all__ = ['Query', 'BaseQuery', 'RawQuery']
167+
168+class RawQuery(object):
169+    """
170+    A single raw SQL query
171+    """
172+
173+    def __init__(self, sql, connection, params=None):
174+        self.validate_sql(sql)
175+        self.params = params or ()
176+        self.sql = sql
177+        self.connection = connection
178+        self.cursor = None
179+
180+    def get_columns(self):
181+        if self.cursor is None:
182+            self._execute_query()
183+        return [column_meta[0] for column_meta in self.cursor.description]
184+
185+    def validate_sql(self, sql):
186+        if not sql.lower().strip().startswith('select'):
187+            raise InvalidQuery('Raw queries are limited to SELECT queries. Use '
188+                               'connection.cursor directly for types of queries.')
189+
190+    def __iter__(self):
191+        # Always execute a new query for a new iterator.
192+        # This could be optomized with a cache at the expense of RAM.
193+        self._execute_query()
194+        return self.cursor
195+
196+    def __repr__(self):
197+        return "<RawQuery: %r>" % (self.sql % self.params)
198+
199+    def _execute_query(self):
200+        self.cursor = self.connection.cursor()
201+        self.cursor.execute(self.sql, self.params)
202 
203 class BaseQuery(object):
204     """
205diff -r 277cb8fbef51 docs/topics/db/queries.txt
206--- a/docs/topics/db/queries.txt        Sat Dec 19 14:27:26 2009 +0000
207+++ b/docs/topics/db/queries.txt        Sat Dec 19 22:34:47 2009 +0800
208@@ -1059,14 +1059,9 @@
209 =======================
210 
211 If you find yourself needing to write an SQL query that is too complex for
212-Django's database-mapper to handle, you can fall back into raw-SQL statement
213-mode.
214-
215-The preferred way to do this is by giving your model custom methods or custom
216-manager methods that execute queries. Although there's nothing in Django that
217-*requires* database queries to live in the model layer, this approach keeps all
218-your data-access logic in one place, which is smart from an code-organization
219-standpoint. For instructions, see :ref:`topics-db-sql`.
220+Django's database-mapper to handle, you can fall back on writing SQL by hand.
221+Django has a couple of options for writing raw SQL queries; see
222+:ref:`topics-db-sql`.
223 
224 Finally, it's important to note that the Django database layer is merely an
225 interface to your database. You can access your database via other tools,
226diff -r 277cb8fbef51 docs/topics/db/sql.txt
227--- a/docs/topics/db/sql.txt    Sat Dec 19 14:27:26 2009 +0000
228+++ b/docs/topics/db/sql.txt    Sat Dec 19 22:34:47 2009 +0800
229@@ -1,10 +1,183 @@
230 .. _topics-db-sql:
231 
232+==========================
233 Performing raw SQL queries
234 ==========================
235 
236-Feel free to write custom SQL statements in custom model methods and
237-module-level methods. The object ``django.db.connection`` represents the
238+.. currentmodule:: django.db.models
239+
240+When the :ref:`model query APIs <topics-db-queries>` don't go far enough, you
241+can fall back to writing raw SQL. Django gives you two ways of performing raw
242+SQL queries: you can use :meth:`Manager.raw()` to `perform raw queries and
243+return model instances`__, or you can avoid the model layer entirely and
244+`execute custom SQL directly`__.
245+
246+__ `performing raw queries`_
247+__ `executing custom SQL directly`_
248+
249+Performing raw queries
250+======================
251+
252+.. versionadded:: 1.2
253+
254+The ``raw()`` manager method can be used to perform raw SQL queries that
255+return model instances:
256+
257+.. method:: Manager.raw(query, params=None, translations=None)
258+
259+This method method takes a raw SQL query, executes it, and returns model
260+instances.
261+
262+This is best illustrated with an example. Suppose you've got the following model::
263+
264+    class Person(models.Model):
265+        first_name = models.CharField(...)
266+        last_name = models.CharField(...)
267+        birth_date = models.DateField(...)
268+
269+You could then execute custom SQL like so::
270+
271+    >>> Person.objects.raw('SELECT * from myapp_person')
272+    [<Person: John Doe>, <Person: Jane Doe>, ...]
273+
274+.. admonition:: Model table names
275+
276+    Where'd the name of the ``Person`` table come from in that example?
277+
278+    By default, Django figures out a database table name by joining the
279+    model's "app label" -- the name you used in ``manage.py startapp`` -- to
280+    the model's class name, with an underscore between them. In the example
281+    we've assumed that the ``Person`` model lives in an app named ``myapp``,
282+    so its table would be ``myapp_person``.
283+
284+    For more details check out the documentation for the
285+    :attr:`~Options.db_table` option, which also lets you manually set the
286+    database table name.
287+
288+Of course, this example isn't very exciting -- it's exactly the same as
289+running ``Person.objects.all()``. However, ``raw()`` has a bunch of other
290+options that make it very powerful.
291+
292+Mapping query fields to model fields
293+------------------------------------
294+
295+``raw()`` automatically maps fields in the query to fields on the model.
296+
297+The order of fields in your query doesn't matter. In other words, both
298+of the following queries work identically::
299+
300+    >>> Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_person')
301+    ...
302+    >>> Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_person')
303+    ...
304+
305+Matching is done by name. This means that you can use SQL's ``AS`` clauses to
306+map fields in the query to model fields. So if you had some other table that
307+had ``Person`` data in it, you could easily map it into ``Person`` instances::
308+
309+    >>> Person.objects.raw('''SELECT first AS first_name,
310+    ...                              last AS last_name,
311+    ...                              bd AS birth_date,
312+    ...                              pk as id,
313+    ...                       FROM some_other_table)
314+
315+As long as the names match, the model instances will be created correctly.
316+
317+Alternatively, you can map fields in the query to model fields using the
318+``translations`` argument to ``raw()``. This is a dictionary mapping names of
319+fields in the query to names of fields on the model. For example, the above
320+query could also be written::
321+
322+    >>> name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
323+    >>> Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
324+
325+Deferring model fields
326+----------------------
327+
328+Fields may also be left out::
329+
330+    >>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person'):
331+
332+The ``Person`` objects returned by this query will be :ref:`deferred
333+<queryset-defer>` model instances. This means that the fields that are omitted
334+from the query will be loaded on demand. For example::
335+
336+    >>> for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'):
337+    ...     print p.first_name, # This will be retrieved by the original query
338+    ...     print p.last_name # This will be retrieved on demand
339+    ...
340+    John Smith
341+    Jane Jones
342+
343+From outward appearances, this looks like the query has retrieved both
344+the first name and last name. However, this example actually issued 3
345+queries. Only the first names were retrieved by the raw() query -- the
346+last names were both retrieved on demand when they were printed.
347+
348+There is only one field that you can't leave out - the primary key
349+field. Django uses the primary key to identify model instances, so it
350+must always be included in a raw query. An ``InvalidQuery`` exception
351+will be raised if you forget to include the primary key.
352+
353+Adding annotations
354+------------------
355+
356+You can also execute queries containing fields that aren't defined on the
357+model. For example, we could use `PostgreSQL's age() function`__ to get a list
358+of people with their ages calculated by the database::
359+
360+    >>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM myapp_person')
361+    >>> for p in people:
362+    ...   print "%s is %s." % (p.first_name, p.age)
363+    John is 37.
364+    Jane is 42.
365+    ...
366+
367+__ http://www.postgresql.org/docs/8.4/static/functions-datetime.html
368+
369+Passing parameters into ``raw()``
370+---------------------------------
371+
372+If you need to perform parameterized queries, you can use the ``params``
373+argument to ``raw()``::
374+
375+    >>> lname = 'Doe'
376+    >>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])
377+
378+``params`` is a list of parameters. You'll use ``%s`` placeholders in the
379+query string (regardless of your database engine); they'll be replaced with
380+parameters from the ``params`` list.
381+
382+.. warning::
383+
384+    **Do not use string formatting on raw queries!**
385+
386+    It's tempting to write the above query as::
387+
388+        >>> query = 'SELECT * FROM myapp_person WHERE last_name = %s', % lname
389+        >>> Person.objects.raw(query)
390+
391+    **Don't.**
392+
393+    Using the ``params`` list completely protects you from `SQL injection
394+    attacks`__`, a common exploit where attackers inject arbitrary SQL into
395+    your database. If you use string interpolation, sooner or later you'll
396+    fall victim to SQL injection. As long as you remember to always use the
397+    ``params`` list you'll be protected.
398+
399+__ http://en.wikipedia.org/wiki/SQL_injection
400+
401+Executing custom SQL directly
402+=============================
403+
404+Sometimes even :meth:`Manager.raw` isn't quite enough: you might need to
405+perform queries that don't map cleanly to models, or directly execute
406+``UPDATE``, ``INSERT``, or ``DELETE`` queries.
407+
408+In these cases, you can always access the database directly, routing around
409+the model layer entirely.
410+
411+The object ``django.db.connection`` represents the
412 current database connection, and ``django.db.transaction`` represents the
413 current database transaction. To use the database connection, call
414 ``connection.cursor()`` to get a cursor object. Then, call
415@@ -15,7 +188,7 @@
416 to the database. If your query is purely a data retrieval operation, no commit
417 is required. For example::
418 
419-    def my_custom_sql(self):
420+    def my_custom_sql():
421         from django.db import connection, transaction
422         cursor = connection.cursor()
423 
424@@ -78,12 +251,5 @@
425 ``"?"`` placeholder, which is used by the SQLite Python bindings. This is for
426 the sake of consistency and sanity.)
427 
428-An easier option?
429------------------
430-
431-A final note: If all you want to do is a custom ``WHERE`` clause, you can just
432-use the ``where``, ``tables`` and ``params`` arguments to the
433-:ref:`extra clause <extra>` in the standard queryset API.
434-
435 .. _Python DB-API: http://www.python.org/peps/pep-0249.html
436 
437diff -r 277cb8fbef51 tests/modeltests/raw_query/fixtures/initial_data.json
438--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
439+++ b/tests/modeltests/raw_query/fixtures/initial_data.json     Sat Dec 19 22:34:47 2009 +0800
440@@ -0,0 +1,102 @@
441+[
442+    {
443+        "pk": 1,
444+        "model": "raw_query.author",
445+        "fields": {
446+            "dob": "1950-09-20",
447+            "first_name": "Joe",
448+            "last_name": "Smith"
449+        }
450+    },
451+    {
452+        "pk": 2,
453+        "model": "raw_query.author",
454+        "fields": {
455+            "dob": "1920-04-02",
456+            "first_name": "Jill",
457+            "last_name": "Doe"
458+        }
459+    },
460+    {
461+        "pk": 3,
462+        "model": "raw_query.author",
463+        "fields": {
464+            "dob": "1986-01-25",
465+            "first_name": "Bob",
466+            "last_name": "Smith"
467+        }
468+    },
469+    {
470+        "pk": 4,
471+        "model": "raw_query.author",
472+        "fields": {
473+            "dob": "1932-05-10",
474+            "first_name": "Bill",
475+            "last_name": "Jones"
476+        }
477+    },
478+    {
479+        "pk": 1,
480+        "model": "raw_query.book",
481+        "fields": {
482+            "author": 1,
483+            "title": "The awesome book"
484+        }
485+    },
486+    {
487+        "pk": 2,
488+        "model": "raw_query.book",
489+        "fields": {
490+            "author": 1,
491+            "title": "The horrible book"
492+        }
493+    },
494+    {
495+        "pk": 3,
496+        "model": "raw_query.book",
497+        "fields": {
498+            "author": 1,
499+            "title": "Another awesome book"
500+        }
501+    },
502+    {
503+        "pk": 4,
504+        "model": "raw_query.book",
505+        "fields": {
506+            "author": 3,
507+            "title": "Some other book"
508+        }
509+    },
510+    {
511+        "pk": 1,
512+        "model": "raw_query.coffee",
513+        "fields": {
514+            "brand": "dunkin doughnuts"
515+        }
516+    },
517+    {
518+        "pk": 2,
519+        "model": "raw_query.coffee",
520+        "fields": {
521+            "brand": "starbucks"
522+        }
523+    },
524+    {
525+        "pk": 1,
526+        "model": "raw_query.reviewer",
527+        "fields": {
528+            "reviewed": [
529+                2,
530+                3,
531+                4
532+            ]
533+        }
534+    },
535+    {
536+        "pk": 2,
537+        "model": "raw_query.reviewer",
538+        "fields": {
539+            "reviewed": []
540+        }
541+    }
542+]
543diff -r 277cb8fbef51 tests/modeltests/raw_query/models.py
544--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
545+++ b/tests/modeltests/raw_query/models.py      Sat Dec 19 22:34:47 2009 +0800
546@@ -0,0 +1,25 @@
547+from django.db import models
548+
549+class Author(models.Model):
550+    first_name = models.CharField(max_length=255)
551+    last_name = models.CharField(max_length=255)
552+    dob = models.DateField()
553+
554+    def __init__(self, *args, **kwargs):
555+        super(Author, self).__init__(*args, **kwargs)
556+        # Protect against annotations being passed to __init__ --
557+        # this'll make the test suite get angry if annotations aren't
558+        # treated differently than fields.
559+        for k in kwargs:
560+            assert k in [f.attname for f in self._meta.fields], \
561+                "Author.__init__ got an unexpected paramater: %s" % k
562+
563+class Book(models.Model):
564+    title = models.CharField(max_length=255)
565+    author = models.ForeignKey(Author)
566+
567+class Coffee(models.Model):
568+    brand = models.CharField(max_length=255, db_column="name")
569+
570+class Reviewer(models.Model):
571+    reviewed = models.ManyToManyField(Book)
572\ No newline at end of file
573diff -r 277cb8fbef51 tests/modeltests/raw_query/tests.py
574--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
575+++ b/tests/modeltests/raw_query/tests.py       Sat Dec 19 22:34:47 2009 +0800
576@@ -0,0 +1,188 @@
577+from django.test import TestCase
578+from datetime import datetime
579+from models import Author, Book, Coffee, Reviewer
580+from django.db.models.sql.query import InvalidQuery
581+
582+class RawQueryTests(TestCase):
583+
584+    def assertSuccessfulRawQuery(self, model, query, expected_results,
585+            expected_annotations=(), params=[], translations=None):
586+        """
587+        Execute the passed query against the passed model and check the output
588+        """
589+        results = list(model.objects.raw(query=query, params=params, translations=translations))
590+        self.assertProcessed(results, expected_results, expected_annotations)
591+        self.assertAnnotations(results, expected_annotations)
592+
593+    def assertProcessed(self, results, orig, expected_annotations=()):
594+        """
595+        Compare the results of a raw query against expected results
596+        """
597+        self.assertEqual(len(results), len(orig))
598+        for index, item in enumerate(results):
599+            orig_item = orig[index]
600+            for annotation in expected_annotations:
601+                setattr(orig_item, *annotation)
602+
603+            self.assertEqual(item.id, orig_item.id)
604+
605+    def assertNoAnnotations(self, results):
606+        """
607+        Check that the results of a raw query contain no annotations
608+        """
609+        self.assertAnnotations(results, ())
610+
611+    def assertAnnotations(self, results, expected_annotations):
612+        """
613+        Check that the passed raw query results contain the expected
614+        annotations
615+        """
616+        if expected_annotations:
617+            for index, result in enumerate(results):
618+                annotation, value = expected_annotations[index]
619+                self.assertTrue(hasattr(result, annotation))
620+                self.assertEqual(getattr(result, annotation), value)
621+
622+    def testSimpleRawQuery(self):
623+        """
624+        Basic test of raw query with a simple database query
625+        """
626+        query = "SELECT * FROM raw_query_author"
627+        authors = Author.objects.all()
628+        self.assertSuccessfulRawQuery(Author, query, authors)
629+
630+    def testRawQueryLazy(self):
631+        """
632+        Raw queries are lazy: they aren't actually executed until they're
633+        iterated over.
634+        """
635+        q = Author.objects.raw('SELECT * FROM raw_query_author')
636+        self.assert_(q.query.cursor is None)
637+        list(q)
638+        self.assert_(q.query.cursor is not None)
639+
640+    def testFkeyRawQuery(self):
641+        """
642+        Test of a simple raw query against a model containing a foreign key
643+        """
644+        query = "SELECT * FROM raw_query_book"
645+        books = Book.objects.all()
646+        self.assertSuccessfulRawQuery(Book, query, books)
647+
648+    def testDBColumnHandler(self):
649+        """
650+        Test of a simple raw query against a model containing a field with
651+        db_column defined.
652+        """
653+        query = "SELECT * FROM raw_query_coffee"
654+        coffees = Coffee.objects.all()
655+        self.assertSuccessfulRawQuery(Coffee, query, coffees)
656+
657+    def testOrderHandler(self):
658+        """
659+        Test of raw raw query's tolerance for columns being returned in any
660+        order
661+        """
662+        selects = (
663+            ('dob, last_name, first_name, id'),
664+            ('last_name, dob, first_name, id'),
665+            ('first_name, last_name, dob, id'),
666+        )
667+
668+        for select in selects:
669+            query = "SELECT %s FROM raw_query_author" % select
670+            authors = Author.objects.all()
671+            self.assertSuccessfulRawQuery(Author, query, authors)
672+
673+    def testTranslations(self):
674+        """
675+        Test of raw query's optional ability to translate unexpected result
676+        column names to specific model fields
677+        """
678+        query = "SELECT first_name AS first, last_name AS last, dob, id FROM raw_query_author"
679+        translations = {'first': 'first_name', 'last': 'last_name'}
680+        authors = Author.objects.all()
681+        self.assertSuccessfulRawQuery(Author, query, authors, translations=translations)
682+
683+    def testParams(self):
684+        """
685+        Test passing optional query parameters
686+        """
687+        query = "SELECT * FROM raw_query_author WHERE first_name = %s"
688+        author = Author.objects.all()[2]
689+        params = [author.first_name]
690+        results = list(Author.objects.raw(query=query, params=params))
691+        self.assertProcessed(results, [author])
692+        self.assertNoAnnotations(results)
693+        self.assertEqual(len(results), 1)
694+
695+    def testManyToMany(self):
696+        """
697+        Test of a simple raw query against a model containing a m2m field
698+        """
699+        query = "SELECT * FROM raw_query_reviewer"
700+        reviewers = Reviewer.objects.all()
701+        self.assertSuccessfulRawQuery(Reviewer, query, reviewers)
702+
703+    def testExtraConversions(self):
704+        """
705+        Test to insure that extra translations are ignored.
706+        """
707+        query = "SELECT * FROM raw_query_author"
708+        translations = {'something': 'else'}
709+        authors = Author.objects.all()
710+        self.assertSuccessfulRawQuery(Author, query, authors, translations=translations)
711+
712+    def testMissingFields(self):
713+        query = "SELECT id, first_name, dob FROM raw_query_author"
714+        for author in Author.objects.raw(query):
715+            self.assertNotEqual(author.first_name, None)
716+            # last_name isn't given, but it will be retrieved on demand
717+            self.assertNotEqual(author.last_name, None)
718+
719+    def testMissingFieldsWithoutPK(self):
720+        query = "SELECT first_name, dob FROM raw_query_author"
721+        try:
722+            list(Author.objects.raw(query))
723+            self.fail('Query without primary key should fail')
724+        except InvalidQuery:
725+            pass
726+
727+    def testAnnotations(self):
728+        query = "SELECT a.*, count(b.id) as book_count FROM raw_query_author a LEFT JOIN raw_query_book b ON a.id = b.author_id GROUP BY a.id, a.first_name, a.last_name, a.dob ORDER BY a.id"
729+        expected_annotations = (
730+            ('book_count', 3),
731+            ('book_count', 0),
732+            ('book_count', 1),
733+            ('book_count', 0),
734+        )
735+        authors = Author.objects.all()
736+        self.assertSuccessfulRawQuery(Author, query, authors, expected_annotations)
737+
738+    def testInvalidQuery(self):
739+        query = "UPDATE raw_query_author SET first_name='thing' WHERE first_name='Joe'"
740+        self.assertRaises(InvalidQuery, Author.objects.raw, query)
741+
742+    def testWhiteSpaceQuery(self):
743+        query = "    SELECT * FROM raw_query_author"
744+        authors = Author.objects.all()
745+        self.assertSuccessfulRawQuery(Author, query, authors)
746+
747+    def testMultipleIterations(self):
748+        query = "SELECT * FROM raw_query_author"
749+        normal_authors = Author.objects.all()
750+        raw_authors = Author.objects.raw(query)
751+
752+        # First Iteration
753+        first_iterations = 0
754+        for index, raw_author in enumerate(raw_authors):
755+            self.assertEqual(normal_authors[index], raw_author)
756+            first_iterations += 1
757+
758+        # Second Iteration
759+        second_iterations = 0
760+        for index, raw_author in enumerate(raw_authors):
761+            self.assertEqual(normal_authors[index], raw_author)
762+            second_iterations += 1
763+
764+        self.assertEqual(first_iterations, second_iterations)
765\ No newline at end of file