| 1 | diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
|
|---|
| 2 | index 9966849..876d84d 100644
|
|---|
| 3 | --- a/django/contrib/auth/management/__init__.py
|
|---|
| 4 | +++ b/django/contrib/auth/management/__init__.py
|
|---|
| 5 | @@ -46,16 +46,14 @@ def create_permissions(app, created_models, verbosity, **kwargs):
|
|---|
| 6 | "content_type", "codename"
|
|---|
| 7 | ))
|
|---|
| 8 |
|
|---|
| 9 | - for ctype, (codename, name) in searched_perms:
|
|---|
| 10 | - # If the permissions exists, move on.
|
|---|
| 11 | - if (ctype.pk, codename) in all_perms:
|
|---|
| 12 | - continue
|
|---|
| 13 | - p = auth_app.Permission.objects.create(
|
|---|
| 14 | - codename=codename,
|
|---|
| 15 | - name=name,
|
|---|
| 16 | - content_type=ctype
|
|---|
| 17 | - )
|
|---|
| 18 | - if verbosity >= 2:
|
|---|
| 19 | + objs = [
|
|---|
| 20 | + auth_app.Permission(codename=codename, name=name, content_type=ctype)
|
|---|
| 21 | + for ctype, (codename, name) in searched_perms
|
|---|
| 22 | + if (ctype.pk, codename) not in all_perms
|
|---|
| 23 | + ]
|
|---|
| 24 | + auth_app.Permission.objects.bulk_create(objs)
|
|---|
| 25 | + if verbosity >= 2:
|
|---|
| 26 | + for obj in objs:
|
|---|
| 27 | print "Adding permission '%s'" % p
|
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 | diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
|
|---|
| 31 | index b64fb01..8ed6d6b 100644
|
|---|
| 32 | --- a/django/db/backends/__init__.py
|
|---|
| 33 | +++ b/django/db/backends/__init__.py
|
|---|
| 34 | @@ -272,8 +272,10 @@ class BaseDatabaseFeatures(object):
|
|---|
| 35 |
|
|---|
| 36 | can_use_chunked_reads = True
|
|---|
| 37 | can_return_id_from_insert = False
|
|---|
| 38 | + has_bulk_insert = False
|
|---|
| 39 | uses_autocommit = False
|
|---|
| 40 | uses_savepoints = False
|
|---|
| 41 | + can_combine_inserts_with_and_without_auto_increment_pk = False
|
|---|
| 42 |
|
|---|
| 43 | # If True, don't use integer foreign keys referring to, e.g., positive
|
|---|
| 44 | # integer primary keys.
|
|---|
| 45 | diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
|
|---|
| 46 | index 67e2877..ad355b8 100644
|
|---|
| 47 | --- a/django/db/backends/postgresql_psycopg2/base.py
|
|---|
| 48 | +++ b/django/db/backends/postgresql_psycopg2/base.py
|
|---|
| 49 | @@ -72,6 +72,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|---|
| 50 | can_defer_constraint_checks = True
|
|---|
| 51 | has_select_for_update = True
|
|---|
| 52 | has_select_for_update_nowait = True
|
|---|
| 53 | + has_bulk_insert = True
|
|---|
| 54 |
|
|---|
| 55 |
|
|---|
| 56 | class DatabaseWrapper(BaseDatabaseWrapper):
|
|---|
| 57 | diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py
|
|---|
| 58 | index 3315913..7a9c406 100644
|
|---|
| 59 | --- a/django/db/backends/postgresql_psycopg2/operations.py
|
|---|
| 60 | +++ b/django/db/backends/postgresql_psycopg2/operations.py
|
|---|
| 61 | @@ -208,3 +208,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|---|
| 62 |
|
|---|
| 63 | def return_insert_id(self):
|
|---|
| 64 | return "RETURNING %s", ()
|
|---|
| 65 | +
|
|---|
| 66 | + def bulk_insert_sql(self, fields, num_values):
|
|---|
| 67 | + items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
|
|---|
| 68 | + return "VALUES " + ", ".join([items_sql] * num_values)
|
|---|
| 69 | \ No newline at end of file
|
|---|
| 70 | diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
|
|---|
| 71 | index 5b4a1c2..ea65865 100644
|
|---|
| 72 | --- a/django/db/backends/sqlite3/base.py
|
|---|
| 73 | +++ b/django/db/backends/sqlite3/base.py
|
|---|
| 74 | @@ -57,6 +57,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|---|
| 75 | supports_unspecified_pk = True
|
|---|
| 76 | supports_1000_query_parameters = False
|
|---|
| 77 | supports_mixed_date_datetime_comparisons = False
|
|---|
| 78 | + has_bulk_insert = True
|
|---|
| 79 | + can_combine_inserts_with_and_without_auto_increment_pk = True
|
|---|
| 80 |
|
|---|
| 81 | def _supports_stddev(self):
|
|---|
| 82 | """Confirm support for STDDEV and related stats functions
|
|---|
| 83 | @@ -105,7 +107,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|---|
| 84 | return ""
|
|---|
| 85 |
|
|---|
| 86 | def pk_default_value(self):
|
|---|
| 87 | - return 'NULL'
|
|---|
| 88 | + return "NULL"
|
|---|
| 89 |
|
|---|
| 90 | def quote_name(self, name):
|
|---|
| 91 | if name.startswith('"') and name.endswith('"'):
|
|---|
| 92 | @@ -153,6 +155,14 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|---|
| 93 | # No field, or the field isn't known to be a decimal or integer
|
|---|
| 94 | return value
|
|---|
| 95 |
|
|---|
| 96 | + def bulk_insert_sql(self, fields, num_values):
|
|---|
| 97 | + res = []
|
|---|
| 98 | + res.append("SELECT %s" % ", ".join(
|
|---|
| 99 | + "%%s AS %s" % self.quote_name(f.column) for f in fields
|
|---|
| 100 | + ))
|
|---|
| 101 | + res.extend(["UNION SELECT %s" % ", ".join(["%s"] * len(fields))] * (num_values - 1))
|
|---|
| 102 | + return " ".join(res)
|
|---|
| 103 | +
|
|---|
| 104 | class DatabaseWrapper(BaseDatabaseWrapper):
|
|---|
| 105 | vendor = 'sqlite'
|
|---|
| 106 | # SQLite requires LIKE statements to include an ESCAPE clause if the value
|
|---|
| 107 | diff --git a/django/db/models/base.py b/django/db/models/base.py
|
|---|
| 108 | index 31310ea..f71f13e 100644
|
|---|
| 109 | --- a/django/db/models/base.py
|
|---|
| 110 | +++ b/django/db/models/base.py
|
|---|
| 111 | @@ -539,24 +539,16 @@ class Model(object):
|
|---|
| 112 | order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
|
|---|
| 113 | self._order = order_value
|
|---|
| 114 |
|
|---|
| 115 | + fields = meta.local_fields
|
|---|
| 116 | if not pk_set:
|
|---|
| 117 | if force_update:
|
|---|
| 118 | raise ValueError("Cannot force an update in save() with no primary key.")
|
|---|
| 119 | - values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
|
|---|
| 120 | - for f in meta.local_fields if not isinstance(f, AutoField)]
|
|---|
| 121 | - else:
|
|---|
| 122 | - values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
|
|---|
| 123 | - for f in meta.local_fields]
|
|---|
| 124 | + fields = [f for f in fields if not isinstance(f, AutoField)]
|
|---|
| 125 |
|
|---|
| 126 | record_exists = False
|
|---|
| 127 |
|
|---|
| 128 | update_pk = bool(meta.has_auto_field and not pk_set)
|
|---|
| 129 | - if values:
|
|---|
| 130 | - # Create a new record.
|
|---|
| 131 | - result = manager._insert(values, return_id=update_pk, using=using)
|
|---|
| 132 | - else:
|
|---|
| 133 | - # Create a new record with defaults for everything.
|
|---|
| 134 | - result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True, using=using)
|
|---|
| 135 | + result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
|
|---|
| 136 |
|
|---|
| 137 | if update_pk:
|
|---|
| 138 | setattr(self, meta.pk.attname, result)
|
|---|
| 139 | diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
|
|---|
| 140 | index cedf308..4d6a8a7 100644
|
|---|
| 141 | --- a/django/db/models/fields/related.py
|
|---|
| 142 | +++ b/django/db/models/fields/related.py
|
|---|
| 143 | @@ -432,7 +432,7 @@ class ForeignRelatedObjectsDescriptor(object):
|
|---|
| 144 | add.alters_data = True
|
|---|
| 145 |
|
|---|
| 146 | def create(self, **kwargs):
|
|---|
| 147 | - kwargs.update({rel_field.name: instance})
|
|---|
| 148 | + kwargs[rel_field.name] = instance
|
|---|
| 149 | db = router.db_for_write(rel_model, instance=instance)
|
|---|
| 150 | return super(RelatedManager, self.db_manager(db)).create(**kwargs)
|
|---|
| 151 | create.alters_data = True
|
|---|
| 152 | @@ -440,7 +440,7 @@ class ForeignRelatedObjectsDescriptor(object):
|
|---|
| 153 | def get_or_create(self, **kwargs):
|
|---|
| 154 | # Update kwargs with the related object that this
|
|---|
| 155 | # ForeignRelatedObjectsDescriptor knows about.
|
|---|
| 156 | - kwargs.update({rel_field.name: instance})
|
|---|
| 157 | + kwargs[rel_field.name] = instance
|
|---|
| 158 | db = router.db_for_write(rel_model, instance=instance)
|
|---|
| 159 | return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
|
|---|
| 160 | get_or_create.alters_data = True
|
|---|
| 161 | @@ -580,11 +580,13 @@ def create_many_related_manager(superclass, rel=False):
|
|---|
| 162 | instance=self.instance, reverse=self.reverse,
|
|---|
| 163 | model=self.model, pk_set=new_ids, using=db)
|
|---|
| 164 | # Add the ones that aren't there already
|
|---|
| 165 | - for obj_id in new_ids:
|
|---|
| 166 | - self.through._default_manager.using(db).create(**{
|
|---|
| 167 | + self.through._default_manager.using(db).bulk_create([
|
|---|
| 168 | + self.through(**{
|
|---|
| 169 | '%s_id' % source_field_name: self._pk_val,
|
|---|
| 170 | '%s_id' % target_field_name: obj_id,
|
|---|
| 171 | })
|
|---|
| 172 | + for obj_id in new_ids
|
|---|
| 173 | + ])
|
|---|
| 174 | if self.reverse or source_field_name == self.source_field_name:
|
|---|
| 175 | # Don't send the signal when we are inserting the
|
|---|
| 176 | # duplicate data row for symmetrical reverse entries.
|
|---|
| 177 | @@ -703,12 +705,12 @@ class ReverseManyRelatedObjectsDescriptor(object):
|
|---|
| 178 | def __init__(self, m2m_field):
|
|---|
| 179 | self.field = m2m_field
|
|---|
| 180 |
|
|---|
| 181 | - def _through(self):
|
|---|
| 182 | + @property
|
|---|
| 183 | + def through(self):
|
|---|
| 184 | # through is provided so that you have easy access to the through
|
|---|
| 185 | # model (Book.authors.through) for inlines, etc. This is done as
|
|---|
| 186 | # a property to ensure that the fully resolved value is returned.
|
|---|
| 187 | return self.field.rel.through
|
|---|
| 188 | - through = property(_through)
|
|---|
| 189 |
|
|---|
| 190 | def __get__(self, instance, instance_type=None):
|
|---|
| 191 | if instance is None:
|
|---|
| 192 | diff --git a/django/db/models/manager.py b/django/db/models/manager.py
|
|---|
| 193 | index 4fa4c4a..92090e0 100644
|
|---|
| 194 | --- a/django/db/models/manager.py
|
|---|
| 195 | +++ b/django/db/models/manager.py
|
|---|
| 196 | @@ -137,6 +137,9 @@ class Manager(object):
|
|---|
| 197 | def create(self, **kwargs):
|
|---|
| 198 | return self.get_query_set().create(**kwargs)
|
|---|
| 199 |
|
|---|
| 200 | + def bulk_create(self, *args, **kwargs):
|
|---|
| 201 | + return self.get_query_set().bulk_create(*args, **kwargs)
|
|---|
| 202 | +
|
|---|
| 203 | def filter(self, *args, **kwargs):
|
|---|
| 204 | return self.get_query_set().filter(*args, **kwargs)
|
|---|
| 205 |
|
|---|
| 206 | @@ -194,8 +197,8 @@ class Manager(object):
|
|---|
| 207 | def exists(self, *args, **kwargs):
|
|---|
| 208 | return self.get_query_set().exists(*args, **kwargs)
|
|---|
| 209 |
|
|---|
| 210 | - def _insert(self, values, **kwargs):
|
|---|
| 211 | - return insert_query(self.model, values, **kwargs)
|
|---|
| 212 | + def _insert(self, objs, fields, **kwargs):
|
|---|
| 213 | + return insert_query(self.model, objs, fields, **kwargs)
|
|---|
| 214 |
|
|---|
| 215 | def _update(self, values, **kwargs):
|
|---|
| 216 | return self.get_query_set()._update(values, **kwargs)
|
|---|
| 217 | diff --git a/django/db/models/query.py b/django/db/models/query.py
|
|---|
| 218 | index 6a6a829..86060cd 100644
|
|---|
| 219 | --- a/django/db/models/query.py
|
|---|
| 220 | +++ b/django/db/models/query.py
|
|---|
| 221 | @@ -7,7 +7,7 @@ from itertools import izip
|
|---|
| 222 |
|
|---|
| 223 | from django.db import connections, router, transaction, IntegrityError
|
|---|
| 224 | from django.db.models.aggregates import Aggregate
|
|---|
| 225 | -from django.db.models.fields import DateField
|
|---|
| 226 | +from django.db.models.fields import DateField, AutoField
|
|---|
| 227 | from django.db.models.query_utils import (Q, select_related_descend,
|
|---|
| 228 | deferred_class_factory, InvalidQuery)
|
|---|
| 229 | from django.db.models.deletion import Collector
|
|---|
| 230 | @@ -360,6 +360,39 @@ class QuerySet(object):
|
|---|
| 231 | obj.save(force_insert=True, using=self.db)
|
|---|
| 232 | return obj
|
|---|
| 233 |
|
|---|
| 234 | + def bulk_create(self, objs):
|
|---|
| 235 | + """
|
|---|
| 236 | + Inserts each of the instances into the database. This does *not* call
|
|---|
| 237 | + save() on each of the instances, does not send any pre/post save
|
|---|
| 238 | + signals, and does not set the primary key attribute if it is an
|
|---|
| 239 | + autoincrement field.
|
|---|
| 240 | + """
|
|---|
| 241 | + # So this case is fun. When you bulk insert you don't get the primary
|
|---|
| 242 | + # keys back (if it's an autoincrement), so you can't insert into the
|
|---|
| 243 | + # child tables which references this. There are two workarounds, 1)
|
|---|
| 244 | + # this could be implemented if you didn't have an autoincrement pk,
|
|---|
| 245 | + # and 2) you could do it by doing O(n) normal inserts into the parent
|
|---|
| 246 | + # tables to get the primary keys back, and then doing a single bulk
|
|---|
| 247 | + # insert into the childmost table. We're punting on these for now
|
|---|
| 248 | + # because they are relatively rare cases.
|
|---|
| 249 | + if self.model._meta.parents:
|
|---|
| 250 | + raise ValueError("Can't bulk create an inherited model")
|
|---|
| 251 | + if not objs:
|
|---|
| 252 | + return
|
|---|
| 253 | + self._for_write = True
|
|---|
| 254 | + connection = connections[self.db]
|
|---|
| 255 | + fields = self.model._meta.local_fields
|
|---|
| 256 | + if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
|
|---|
| 257 | + and self.model._meta.has_auto_field):
|
|---|
| 258 | + self.model._base_manager._insert(objs, fields=fields, using=self.db)
|
|---|
| 259 | + else:
|
|---|
| 260 | + objs_with_pk = [o for o in objs if o.pk]
|
|---|
| 261 | + objs_without_pk = [o for o in objs if not o.pk]
|
|---|
| 262 | + if objs_with_pk:
|
|---|
| 263 | + self.model._base_manager._insert(objs_with_pk, fields=fields, using=self.db)
|
|---|
| 264 | + if objs_without_pk:
|
|---|
| 265 | + self.model._base_manager._insert(objs_without_pk, fields=[f for f in fields if not isinstance(f, AutoField)], using=self.db)
|
|---|
| 266 | +
|
|---|
| 267 | def get_or_create(self, **kwargs):
|
|---|
| 268 | """
|
|---|
| 269 | Looks up an object with the given kwargs, creating one if necessary.
|
|---|
| 270 | @@ -1445,12 +1478,12 @@ class RawQuerySet(object):
|
|---|
| 271 | self._model_fields[converter(column)] = field
|
|---|
| 272 | return self._model_fields
|
|---|
| 273 |
|
|---|
| 274 | -def insert_query(model, values, return_id=False, raw_values=False, using=None):
|
|---|
| 275 | +def insert_query(model, objs, fields, return_id=False, raw=False, using=None):
|
|---|
| 276 | """
|
|---|
| 277 | Inserts a new record for the given model. This provides an interface to
|
|---|
| 278 | the InsertQuery class and is how Model.save() is implemented. It is not
|
|---|
| 279 | part of the public API.
|
|---|
| 280 | """
|
|---|
| 281 | query = sql.InsertQuery(model)
|
|---|
| 282 | - query.insert_values(values, raw_values)
|
|---|
| 283 | + query.insert_values(fields, objs, raw=raw)
|
|---|
| 284 | return query.get_compiler(using=using).execute_sql(return_id)
|
|---|
| 285 | diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
|
|---|
| 286 | index 841ec12..84dd7ed 100644
|
|---|
| 287 | --- a/django/db/models/sql/compiler.py
|
|---|
| 288 | +++ b/django/db/models/sql/compiler.py
|
|---|
| 289 | @@ -1,3 +1,5 @@
|
|---|
| 290 | +from itertools import izip
|
|---|
| 291 | +
|
|---|
| 292 | from django.core.exceptions import FieldError
|
|---|
| 293 | from django.db import connections
|
|---|
| 294 | from django.db import transaction
|
|---|
| 295 | @@ -9,6 +11,7 @@ from django.db.models.sql.query import get_proxied_model, get_order_dir, \
|
|---|
| 296 | select_related_descend, Query
|
|---|
| 297 | from django.db.utils import DatabaseError
|
|---|
| 298 |
|
|---|
| 299 | +
|
|---|
| 300 | class SQLCompiler(object):
|
|---|
| 301 | def __init__(self, query, connection, using):
|
|---|
| 302 | self.query = query
|
|---|
| 303 | @@ -794,20 +797,55 @@ class SQLInsertCompiler(SQLCompiler):
|
|---|
| 304 | qn = self.connection.ops.quote_name
|
|---|
| 305 | opts = self.query.model._meta
|
|---|
| 306 | result = ['INSERT INTO %s' % qn(opts.db_table)]
|
|---|
| 307 | - result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns]))
|
|---|
| 308 | - values = [self.placeholder(*v) for v in self.query.values]
|
|---|
| 309 | - result.append('VALUES (%s)' % ', '.join(values))
|
|---|
| 310 | - params = self.query.params
|
|---|
| 311 | +
|
|---|
| 312 | + has_fields = bool(self.query.fields)
|
|---|
| 313 | + fields = self.query.fields if has_fields else [opts.pk]
|
|---|
| 314 | + result.append('(%s)' % ', '.join([qn(f.column) for f in fields]))
|
|---|
| 315 | +
|
|---|
| 316 | + if has_fields:
|
|---|
| 317 | + params = values = [
|
|---|
| 318 | + [
|
|---|
| 319 | + f.get_db_prep_save(getattr(obj, f.attname) if self.query.raw else f.pre_save(obj, True), connection=self.connection)
|
|---|
| 320 | + for f in fields
|
|---|
| 321 | + ]
|
|---|
| 322 | + for obj in self.query.objs
|
|---|
| 323 | + ]
|
|---|
| 324 | + else:
|
|---|
| 325 | + values = [[self.connection.ops.pk_default_value()] for obj in self.query.objs]
|
|---|
| 326 | + params = [[]]
|
|---|
| 327 | + fields = [None]
|
|---|
| 328 | + can_bulk = not any(hasattr(field, "get_placeholder") for field in fields) and not self.return_id
|
|---|
| 329 | +
|
|---|
| 330 | + if can_bulk:
|
|---|
| 331 | + placeholders = [["%s"] * len(fields)]
|
|---|
| 332 | + else:
|
|---|
| 333 | + placeholders = [
|
|---|
| 334 | + [self.placeholder(field, v) for field, v in izip(fields, val)]
|
|---|
| 335 | + for val in values
|
|---|
| 336 | + ]
|
|---|
| 337 | if self.return_id and self.connection.features.can_return_id_from_insert:
|
|---|
| 338 | - col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
|
|---|
| 339 | + params = values[0]
|
|---|
| 340 | + col = "%s.%s" % (qn(opst.db_table), qn(opts.pk.column))
|
|---|
| 341 | + result.append("VALUES (%s)" % ", ".join(placeholders[0]))
|
|---|
| 342 | r_fmt, r_params = self.connection.ops.return_insert_id()
|
|---|
| 343 | result.append(r_fmt % col)
|
|---|
| 344 | - params = params + r_params
|
|---|
| 345 | - return ' '.join(result), params
|
|---|
| 346 | + params += r_params
|
|---|
| 347 | + return [(" ".join(result), tuple(param))]
|
|---|
| 348 | + if can_bulk and self.connection.features.has_bulk_insert:
|
|---|
| 349 | + result.append(self.connection.ops.bulk_insert_sql(fields, len(values)))
|
|---|
| 350 | + return [(" ".join(result), tuple([v for val in values for v in val]))]
|
|---|
| 351 | + else:
|
|---|
| 352 | + return [
|
|---|
| 353 | + (" ".join(result + ["VALUES (%s)" % ", ".join(p)]), vals)
|
|---|
| 354 | + for p, vals in izip(placeholders, params)
|
|---|
| 355 | + ]
|
|---|
| 356 |
|
|---|
| 357 | def execute_sql(self, return_id=False):
|
|---|
| 358 | + assert not (return_id and len(self.query.objs) != 1)
|
|---|
| 359 | self.return_id = return_id
|
|---|
| 360 | - cursor = super(SQLInsertCompiler, self).execute_sql(None)
|
|---|
| 361 | + cursor = self.connection.cursor()
|
|---|
| 362 | + for sql, params in self.as_sql():
|
|---|
| 363 | + cursor.execute(sql, params)
|
|---|
| 364 | if not (return_id and cursor):
|
|---|
| 365 | return
|
|---|
| 366 | if self.connection.features.can_return_id_from_insert:
|
|---|
| 367 | diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
|
|---|
| 368 | index 99663b6..12b13a7 100644
|
|---|
| 369 | --- a/django/db/models/sql/query.py
|
|---|
| 370 | +++ b/django/db/models/sql/query.py
|
|---|
| 371 | @@ -8,9 +8,10 @@ all about the internals of models in order to get the information it needs.
|
|---|
| 372 | """
|
|---|
| 373 |
|
|---|
| 374 | import copy
|
|---|
| 375 | -from django.utils.tree import Node
|
|---|
| 376 | +
|
|---|
| 377 | from django.utils.datastructures import SortedDict
|
|---|
| 378 | from django.utils.encoding import force_unicode
|
|---|
| 379 | +from django.utils.tree import Node
|
|---|
| 380 | from django.db import connections, DEFAULT_DB_ALIAS
|
|---|
| 381 | from django.db.models import signals
|
|---|
| 382 | from django.db.models.fields import FieldDoesNotExist
|
|---|
| 383 | diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
|
|---|
| 384 | index 003bf43..39cfc03 100644
|
|---|
| 385 | --- a/django/db/models/sql/subqueries.py
|
|---|
| 386 | +++ b/django/db/models/sql/subqueries.py
|
|---|
| 387 | @@ -138,20 +138,19 @@ class InsertQuery(Query):
|
|---|
| 388 |
|
|---|
| 389 | def __init__(self, *args, **kwargs):
|
|---|
| 390 | super(InsertQuery, self).__init__(*args, **kwargs)
|
|---|
| 391 | - self.columns = []
|
|---|
| 392 | - self.values = []
|
|---|
| 393 | - self.params = ()
|
|---|
| 394 | + self.fields = []
|
|---|
| 395 | + self.objs = []
|
|---|
| 396 |
|
|---|
| 397 | def clone(self, klass=None, **kwargs):
|
|---|
| 398 | extras = {
|
|---|
| 399 | - 'columns': self.columns[:],
|
|---|
| 400 | - 'values': self.values[:],
|
|---|
| 401 | - 'params': self.params
|
|---|
| 402 | + 'fields': self.fields[:],
|
|---|
| 403 | + 'objs': self.objs[:],
|
|---|
| 404 | + 'raw': self.raw,
|
|---|
| 405 | }
|
|---|
| 406 | extras.update(kwargs)
|
|---|
| 407 | return super(InsertQuery, self).clone(klass, **extras)
|
|---|
| 408 |
|
|---|
| 409 | - def insert_values(self, insert_values, raw_values=False):
|
|---|
| 410 | + def insert_values(self, fields, objs, raw=False):
|
|---|
| 411 | """
|
|---|
| 412 | Set up the insert query from the 'insert_values' dictionary. The
|
|---|
| 413 | dictionary gives the model field names and their target values.
|
|---|
| 414 | @@ -161,16 +160,9 @@ class InsertQuery(Query):
|
|---|
| 415 | parameters. This provides a way to insert NULL and DEFAULT keywords
|
|---|
| 416 | into the query, for example.
|
|---|
| 417 | """
|
|---|
| 418 | - placeholders, values = [], []
|
|---|
| 419 | - for field, val in insert_values:
|
|---|
| 420 | - placeholders.append((field, val))
|
|---|
| 421 | - self.columns.append(field.column)
|
|---|
| 422 | - values.append(val)
|
|---|
| 423 | - if raw_values:
|
|---|
| 424 | - self.values.extend([(None, v) for v in values])
|
|---|
| 425 | - else:
|
|---|
| 426 | - self.params += tuple(values)
|
|---|
| 427 | - self.values.extend(placeholders)
|
|---|
| 428 | + self.fields = fields
|
|---|
| 429 | + self.objs = objs
|
|---|
| 430 | + self.raw = raw
|
|---|
| 431 |
|
|---|
| 432 | class DateQuery(Query):
|
|---|
| 433 | """
|
|---|
| 434 | diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
|
|---|
| 435 | index 2bd813d..b7a84cd 100644
|
|---|
| 436 | --- a/docs/ref/models/querysets.txt
|
|---|
| 437 | +++ b/docs/ref/models/querysets.txt
|
|---|
| 438 | @@ -139,7 +139,7 @@ Though you usually won't create one manually -- you'll go through a
|
|---|
| 439 | clause or a default ordering on the model. ``False`` otherwise.
|
|---|
| 440 |
|
|---|
| 441 | .. attribute:: db
|
|---|
| 442 | -
|
|---|
| 443 | +
|
|---|
| 444 | The database that will be used if this query is executed now.
|
|---|
| 445 |
|
|---|
| 446 | .. note::
|
|---|
| 447 | @@ -1139,6 +1139,29 @@ has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec.
|
|---|
| 448 |
|
|---|
| 449 | .. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
|
|---|
| 450 |
|
|---|
| 451 | +bulk_create
|
|---|
| 452 | +~~~~~~~~~~~
|
|---|
| 453 | +
|
|---|
| 454 | +.. method:: bulk_create(objs)
|
|---|
| 455 | +
|
|---|
| 456 | +This method inserts the provided list of objects into the database in an
|
|---|
| 457 | +efficient manner (generally only 1 query, no matter how many objects there
|
|---|
| 458 | +are)::
|
|---|
| 459 | +
|
|---|
| 460 | + >>> Entry.objects.bulk_create([
|
|---|
| 461 | + ... Entry(headline="Django 1.0 Released"),
|
|---|
| 462 | + ... Entry(headline="Django 1.1 Announced"),
|
|---|
| 463 | + ... Entry(headline="Breaking: Django is awesome")
|
|---|
| 464 | + ... ])
|
|---|
| 465 | +
|
|---|
| 466 | +This has a number of caveats though:
|
|---|
| 467 | +
|
|---|
| 468 | + * The model's ``save()`` method will not be called, and the ``pre_save`` and
|
|---|
| 469 | + ``post_save`` signals will not be sent.
|
|---|
| 470 | + * It does not work with child models in a multi-table inheritance scenario.
|
|---|
| 471 | + * If the model's primary key is an :class:`AutoField` it does not retrieve
|
|---|
| 472 | + and set the primary key attribute, as ``save()`` does.
|
|---|
| 473 | +
|
|---|
| 474 | count
|
|---|
| 475 | ~~~~~
|
|---|
| 476 |
|
|---|
| 477 | diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt
|
|---|
| 478 | index 265ef55..24830aa 100644
|
|---|
| 479 | --- a/docs/topics/db/optimization.txt
|
|---|
| 480 | +++ b/docs/topics/db/optimization.txt
|
|---|
| 481 | @@ -268,3 +268,29 @@ instead of::
|
|---|
| 482 |
|
|---|
| 483 | entry.blog.id
|
|---|
| 484 |
|
|---|
| 485 | +Insert in bulk
|
|---|
| 486 | +==============
|
|---|
| 487 | +
|
|---|
| 488 | +When creating objects, where possible, use the :meth:`QuerySet.bulk_create()`
|
|---|
| 489 | +method to reduce the number of SQL queries. For example::
|
|---|
| 490 | +
|
|---|
| 491 | + Entry.objects.bulk_create([
|
|---|
| 492 | + Entry(headline="Python 3.0 Released"),
|
|---|
| 493 | + Entry(headline="Python 3.1 Planned")
|
|---|
| 494 | + ])
|
|---|
| 495 | +
|
|---|
| 496 | +Is preferable to::
|
|---|
| 497 | +
|
|---|
| 498 | + Entry.objects.create(headline="Python 3.0 Released")
|
|---|
| 499 | + Entry.objects.create(headline="Python 3.1 Planned")
|
|---|
| 500 | +
|
|---|
| 501 | +This also applies to :class:`ManyToManyFields`, doing::
|
|---|
| 502 | +
|
|---|
| 503 | + my_band.members.add(me, my_friend)
|
|---|
| 504 | +
|
|---|
| 505 | +Is preferable to::
|
|---|
| 506 | +
|
|---|
| 507 | + my_band.members.add(me)
|
|---|
| 508 | + my_band.members.add(my_friend)
|
|---|
| 509 | +
|
|---|
| 510 | +Where ``Bands`` and ``Artists`` have a many-to-many relationship.
|
|---|
| 511 | \ No newline at end of file
|
|---|
| 512 | diff --git a/tests/regressiontests/bulk_create/models.py b/tests/regressiontests/bulk_create/models.py
|
|---|
| 513 | index 1f98a40..a4c611d 100644
|
|---|
| 514 | --- a/tests/regressiontests/bulk_create/models.py
|
|---|
| 515 | +++ b/tests/regressiontests/bulk_create/models.py
|
|---|
| 516 | @@ -3,4 +3,19 @@ from django.db import models
|
|---|
| 517 |
|
|---|
| 518 | class Country(models.Model):
|
|---|
| 519 | name = models.CharField(max_length=255)
|
|---|
| 520 | - iso_two_letter = models.CharField(max_length=2)
|
|---|
| 521 | \ No newline at end of file
|
|---|
| 522 | + iso_two_letter = models.CharField(max_length=2)
|
|---|
| 523 | +
|
|---|
| 524 | +class Place(models.Model):
|
|---|
| 525 | + name = models.CharField(max_length=100)
|
|---|
| 526 | +
|
|---|
| 527 | + class Meta:
|
|---|
| 528 | + abstract = True
|
|---|
| 529 | +
|
|---|
| 530 | +class Restaurant(Place):
|
|---|
| 531 | + pass
|
|---|
| 532 | +
|
|---|
| 533 | +class Pizzeria(Restaurant):
|
|---|
| 534 | + pass
|
|---|
| 535 | +
|
|---|
| 536 | +class State(models.Model):
|
|---|
| 537 | + two_letter_code = models.CharField(max_length=2, primary_key=True)
|
|---|
| 538 | \ No newline at end of file
|
|---|
| 539 | diff --git a/tests/regressiontests/bulk_create/tests.py b/tests/regressiontests/bulk_create/tests.py
|
|---|
| 540 | index 42ba095..020841c 100644
|
|---|
| 541 | --- a/tests/regressiontests/bulk_create/tests.py
|
|---|
| 542 | +++ b/tests/regressiontests/bulk_create/tests.py
|
|---|
| 543 | @@ -1,8 +1,10 @@
|
|---|
| 544 | +from __future__ import with_statement
|
|---|
| 545 | +
|
|---|
| 546 | from operator import attrgetter
|
|---|
| 547 |
|
|---|
| 548 | from django.test import TestCase, skipUnlessDBFeature
|
|---|
| 549 |
|
|---|
| 550 | -from models import Country
|
|---|
| 551 | +from models import Country, Restaurant, Pizzeria, State
|
|---|
| 552 |
|
|---|
| 553 |
|
|---|
| 554 | class BulkCreateTests(TestCase):
|
|---|
| 555 | @@ -23,4 +25,29 @@ class BulkCreateTests(TestCase):
|
|---|
| 556 | @skipUnlessDBFeature("has_bulk_insert")
|
|---|
| 557 | def test_efficiency(self):
|
|---|
| 558 | with self.assertNumQueries(1):
|
|---|
| 559 | - Country.objects.bulk_create(self.data)
|
|---|
| 560 | \ No newline at end of file
|
|---|
| 561 | + Country.objects.bulk_create(self.data)
|
|---|
| 562 | +
|
|---|
| 563 | + def test_inheritance(self):
|
|---|
| 564 | + Restaurant.objects.bulk_create([
|
|---|
| 565 | + Restaurant(name="Nicholas's")
|
|---|
| 566 | + ])
|
|---|
| 567 | + self.assertQuerysetEqual(Restaurant.objects.all(), [
|
|---|
| 568 | + "Nicholas's",
|
|---|
| 569 | + ], attrgetter("name"))
|
|---|
| 570 | + with self.assertRaises(ValueError):
|
|---|
| 571 | + Pizzeria.objects.bulk_create([
|
|---|
| 572 | + Pizzeria(name="The Art of Pizza")
|
|---|
| 573 | + ])
|
|---|
| 574 | + self.assertQuerysetEqual(Pizzeria.objects.all(), [])
|
|---|
| 575 | + self.assertQuerysetEqual(Restaurant.objects.all(), [
|
|---|
| 576 | + "Nicholas's",
|
|---|
| 577 | + ], attrgetter("name"))
|
|---|
| 578 | +
|
|---|
| 579 | + def test_non_auto_increment_pk(self):
|
|---|
| 580 | + State.objects.bulk_create([
|
|---|
| 581 | + State(two_letter_code=s)
|
|---|
| 582 | + for s in ["IL", "NY", "CA", "ME"]
|
|---|
| 583 | + ])
|
|---|
| 584 | + self.assertQuerysetEqual(State.objects.order_by("two_letter_code"), [
|
|---|
| 585 | + "CA", "IL", "ME", "NY",
|
|---|
| 586 | + ], attrgetter("two_letter_code"))
|
|---|
| 587 | \ No newline at end of file
|
|---|
| 588 | diff --git a/tests/regressiontests/db_typecasts/tests.py b/tests/regressiontests/db_typecasts/tests.py
|
|---|
| 589 | index 8c71c8f..1d3bbfa 100644
|
|---|
| 590 | --- a/tests/regressiontests/db_typecasts/tests.py
|
|---|
| 591 | +++ b/tests/regressiontests/db_typecasts/tests.py
|
|---|
| 592 | @@ -53,10 +53,10 @@ TEST_CASES = {
|
|---|
| 593 |
|
|---|
| 594 | class DBTypeCasts(unittest.TestCase):
|
|---|
| 595 | def test_typeCasts(self):
|
|---|
| 596 | - for k, v in TEST_CASES.items():
|
|---|
| 597 | + for k, v in TEST_CASES.iteritems():
|
|---|
| 598 | for inpt, expected in v:
|
|---|
| 599 | got = getattr(typecasts, k)(inpt)
|
|---|
| 600 | - assert got == expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got)
|
|---|
| 601 | + self.assertEqual(got, expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got))
|
|---|
| 602 |
|
|---|
| 603 | if __name__ == '__main__':
|
|---|
| 604 | unittest.main()
|
|---|