From eade315da1c8372ac1dfcf1fd20ea87f454d71ac Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Fri, 6 Sep 2013 12:18:16 -0400 Subject: [PATCH] Fixed #10164 -- Made AutoField increase monotonically on SQLite Thanks malte for the report. --- django/db/backends/creation.py | 4 ++++ django/db/backends/sqlite3/base.py | 1 + django/db/backends/sqlite3/creation.py | 3 +++ django/db/backends/sqlite3/introspection.py | 2 +- django/db/models/fields/__init__.py | 3 +++ docs/releases/1.7.txt | 8 ++++++++ tests/backends/tests.py | 20 ++++++++++++++++++++ 7 files changed, 40 insertions(+), 1 deletion(-) diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 1d90f03425..eba3952d0f 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -23,6 +23,7 @@ class BaseDatabaseCreation(object): destruction of test databases. """ data_types = {} + data_types_suffix = {} data_type_check_constraints = {} def __init__(self, connection): @@ -53,6 +54,7 @@ class BaseDatabaseCreation(object): qn = self.connection.ops.quote_name for f in opts.local_fields: col_type = f.db_type(connection=self.connection) + col_type_suffix = f.db_type_suffix(connection=self.connection) tablespace = f.db_tablespace or opts.db_tablespace if col_type is None: # Skip ManyToManyFields, because they're not represented as @@ -88,6 +90,8 @@ class BaseDatabaseCreation(object): (model, f)) else: field_output.extend(ref_output) + if col_type_suffix: + field_output.append(style.SQL_KEYWORD(col_type_suffix)) table_output.append(' '.join(field_output)) for field_constraints in opts.unique_together: table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 385733cb8d..889630faf9 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -106,6 +106,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_check_constraints = False autocommits_when_autocommit_is_off = True supports_paramstyle_pyformat = False + supports_sequence_reset = False @cached_property def uses_savepoints(self): diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index 435cee3436..bdd4560516 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -34,6 +34,9 @@ class DatabaseCreation(BaseDatabaseCreation): 'TextField': 'text', 'TimeField': 'time', } + data_types_suffix = { + 'AutoField': 'AUTOINCREMENT', + } def sql_for_pending_references(self, model, style, pending_references): "SQLite3 doesn't support constraints" diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py index f1d433fe0f..55e68c34b9 100644 --- a/django/db/backends/sqlite3/introspection.py +++ b/django/db/backends/sqlite3/introspection.py @@ -175,7 +175,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): results = results[results.index('(') + 1:results.rindex(')')] for field_desc in results.split(','): field_desc = field_desc.strip() - m = re.search('"(.*)".*PRIMARY KEY$', field_desc) + m = re.search('"(.*)".*PRIMARY KEY( AUTOINCREMENT)?$', field_desc) if m: return m.groups()[0] return None diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index fe24fab589..91046f98c6 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -395,6 +395,9 @@ class Field(object): "check": check_string, } + def db_type_suffix(self, connection): + return connection.creation.data_types_suffix.get(self.get_internal_type()) + @property def unique(self): return self._unique or self.primary_key diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 1390317875..32b5e910a1 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -336,6 +336,14 @@ Miscellaneous when called on an instance without a primary key value. This is done to avoid mutable ``__hash__`` values in containers. +* The :meth:`django.db.backends.sqlite3.DatabaseCreation.sql_create_model` + will now create :class:`~django.db.models.AutoField` columns in SQLite + databases using the ``AUTOINCREMENT`` option, which guarantees monotonic + increments. This will cause primary key numbering behavior to change on + SQLite, becoming consistent with most other SQL databases. If you have a + database created with an older version of Django, you will need to migrate + it to take advantage of this feature. See ticket #10164 for details. + * ``django.contrib.auth.models.AbstractUser`` no longer defines a :meth:`~django.db.models.Model.get_absolute_url()` method. The old definition returned ``"/users/%s/" % urlquote(self.username)`` which was arbitrary diff --git a/tests/backends/tests.py b/tests/backends/tests.py index e00cdf94ce..25df8ea139 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import datetime from decimal import Decimal +import re import threading import unittest @@ -108,6 +109,25 @@ class OracleChecks(unittest.TestCase): self.assertEqual(c.fetchone()[0], 1) +class SQLiteTests(TestCase): + longMessage = True + + @unittest.skipUnless(connection.vendor == 'sqlite', + "Test valid only for SQLite") + def test_autoincrement(self): + """ + Check that auto_increment fields are created with the AUTOINCREMENT + keyword in order to be monotonically increasing. Refs #10164. + """ + statements = connection.creation.sql_create_model(models.Square, + style=no_style()) + match = re.search('"id" ([^,]+),', statements[0][0]) + self.assertIsNotNone(match) + self.assertEqual('integer NOT NULL PRIMARY KEY AUTOINCREMENT', + match.group(1), "Wrong SQL used to create an auto-increment " + "column on SQLite") + + class MySQLTests(TestCase): @unittest.skipUnless(connection.vendor == 'mysql', "Test valid only for MySQL")