From dabd96646cf04f0b4dc346f90baf8199e225f128 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Apr 2007 02:25:58 +0000 Subject: [PATCH] Fixed #3790 -- Fixed a problem with sequence resetting during fixture loads when using Postgres. Thanks to Jon Ballard and scott@staplefish.com for the report, and to Zach Thompson for suggesting a solution. git-svn-id: http://code.djangoproject.com/svn/django/trunk@4937 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/core/management.py | 38 +++++++------------ django/db/backends/ado_mssql/base.py | 5 +++ django/db/backends/dummy/base.py | 1 + django/db/backends/mysql/base.py | 5 +++ django/db/backends/mysql_old/base.py | 5 +++ django/db/backends/oracle/base.py | 4 ++ django/db/backends/postgresql/base.py | 24 ++++++++++++ .../db/backends/postgresql_psycopg2/base.py | 25 ++++++++++++ django/db/backends/sqlite3/base.py | 5 +++ docs/django-admin.txt | 2 +- .../fixtures_regress/__init__.py | 0 .../fixtures_regress/fixtures/sequence.json | 10 +++++ .../fixtures_regress/models.py | 22 +++++++++++ 14 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 tests/regressiontests/fixtures_regress/__init__.py create mode 100644 tests/regressiontests/fixtures_regress/fixtures/sequence.json create mode 100644 tests/regressiontests/fixtures_regress/models.py diff --git a/AUTHORS b/AUTHORS index 673976231a..cca3247981 100644 --- a/AUTHORS +++ b/AUTHORS @@ -192,6 +192,7 @@ answer newbie questions, and generally made Django that much better: Ville Säävuori Tyson Tate thebjorn + Zach Thompson Tom Tobin Joe Topjian torne-django@wolfpuppy.org.uk diff --git a/django/core/management.py b/django/core/management.py index fd37eb1e25..2a45ca106b 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -410,30 +410,10 @@ get_sql_initial_data.help_doc = "RENAMED: see 'sqlcustom'" get_sql_initial_data.args = '' def get_sql_sequence_reset(app): - "Returns a list of the SQL statements to reset PostgreSQL sequences for the given app." + "Returns a list of the SQL statements to reset sequences for the given app." from django.db import backend, models - output = [] - for model in models.get_models(app): - for f in model._meta.fields: - if isinstance(f, models.AutoField): - output.append("%s setval('%s', (%s max(%s) %s %s));" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), - style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD(backend.quote_name(f.column)), - style.SQL_KEYWORD('FROM'), - style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) - break # Only one AutoField is allowed per model, so don't bother continuing. - for f in model._meta.many_to_many: - output.append("%s setval('%s', (%s max(%s) %s %s));" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()), - style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD(backend.quote_name('id')), - style.SQL_KEYWORD('FROM'), - style.SQL_TABLE(f.m2m_db_table()))) - return output -get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app name(s)." + return backend.get_sql_sequence_reset(style, models.get_models(app)) +get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting sequences for the given app name(s)." get_sql_sequence_reset.args = APP_ARGS def get_sql_indexes(app): @@ -1333,13 +1313,14 @@ def load_data(fixture_labels, verbosity=1): "Installs the provided fixture file(s) as data in the database." from django.db.models import get_apps from django.core import serializers - from django.db import connection, transaction + from django.db import connection, transaction, backend from django.conf import settings import sys # Keep a count of the installed objects and fixtures count = [0,0] - + models = set() + humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' # Get a cursor (even though we don't need one yet). This has @@ -1401,6 +1382,7 @@ def load_data(fixture_labels, verbosity=1): objects = serializers.deserialize(format, fixture) for obj in objects: count[0] += 1 + models.add(obj.object.__class__) obj.save() label_found = True except Exception, e: @@ -1422,6 +1404,12 @@ def load_data(fixture_labels, verbosity=1): else: if verbosity > 0: print "Installed %d object(s) from %d fixture(s)" % tuple(count) + sequence_sql = backend.get_sql_sequence_reset(style, models) + if sequence_sql: + if verbosity > 1: + print "Resetting sequences" + for line in sequence_sql: + cursor.execute(line) transaction.commit() transaction.leave_transaction_management() diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py index 8dcb98ce61..e0f6379edf 100644 --- a/django/db/backends/ado_mssql/base.py +++ b/django/db/backends/ado_mssql/base.py @@ -151,6 +151,11 @@ def get_sql_flush(sql_styler, full_table_list): sql_styler.SQL_FIELD(quote_name(table)) ) for table in full_table_list] +def get_sql_sequence_reset(style, model_list): + "Returns a list of the SQL statements to reset sequences for the given models." + # No sequence reset required + return [] + OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py index e36a99e982..eb3c3867c2 100644 --- a/django/db/backends/dummy/base.py +++ b/django/db/backends/dummy/base.py @@ -40,5 +40,6 @@ get_deferrable_sql = complain get_fulltext_search_sql = complain get_drop_foreignkey_sql = complain get_sql_flush = complain +get_sql_sequence_reset = complain OPERATOR_MAPPING = {} diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 94718595cb..3c2431a982 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -215,6 +215,11 @@ def get_sql_flush(style, tables, sequences): else: return [] +def get_sql_sequence_reset(style, model_list): + "Returns a list of the SQL statements to reset sequences for the given models." + # No sequence reset required + return [] + OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py index 4bd87518e8..ded0b6cbcb 100644 --- a/django/db/backends/mysql_old/base.py +++ b/django/db/backends/mysql_old/base.py @@ -217,6 +217,11 @@ def get_sql_flush(style, tables, sequences): else: return [] +def get_sql_sequence_reset(style, model_list): + "Returns a list of the SQL statements to reset sequences for the given models." + # No sequence reset required + return [] + OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index d52ae33c2e..0f32407638 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -134,6 +134,10 @@ def get_sql_flush(style, tables, sequences): style.SQL_FIELD(quote_name(table)) ) for table in tables] +def get_sql_sequence_reset(style, model_list): + "Returns a list of the SQL statements to reset sequences for the given models." + # No sequence reset required + return [] OPERATOR_MAPPING = { 'exact': '= %s', diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 54be422ae2..0dab19ba0d 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -213,6 +213,30 @@ def get_sql_flush(style, tables, sequences): else: return [] +def get_sql_sequence_reset(style, model_list): + "Returns a list of the SQL statements to reset sequences for the given models." + from django.db import models + output = [] + for model in model_list: + for f in model._meta.fields: + if isinstance(f, models.AutoField): + output.append("%s setval('%s', (%s max(%s) %s %s));" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), + style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD(quote_name(f.column)), + style.SQL_KEYWORD('FROM'), + style.SQL_TABLE(quote_name(model._meta.db_table)))) + break # Only one AutoField is allowed per model, so don't bother continuing. + for f in model._meta.many_to_many: + output.append("%s setval('%s', (%s max(%s) %s %s));" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()), + style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD(quote_name('id')), + style.SQL_KEYWORD('FROM'), + style.SQL_TABLE(f.m2m_db_table()))) + return output # Register these custom typecasts, because Django expects dates/times to be # in Python's native (standard-library) datetime/time format, whereas psycopg diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index e4724e46fb..58e232df68 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -169,6 +169,31 @@ def get_sql_flush(style, tables, sequences): return sql else: return [] + +def get_sql_sequence_reset(style, model_list): + "Returns a list of the SQL statements to reset sequences for the given models." + from django.db import models + output = [] + for model in model_list: + for f in model._meta.fields: + if isinstance(f, models.AutoField): + output.append("%s setval('%s', (%s max(%s) %s %s));" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), + style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD(quote_name(f.column)), + style.SQL_KEYWORD('FROM'), + style.SQL_TABLE(quote_name(model._meta.db_table)))) + break # Only one AutoField is allowed per model, so don't bother continuing. + for f in model._meta.many_to_many: + output.append("%s setval('%s', (%s max(%s) %s %s));" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()), + style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD(quote_name('id')), + style.SQL_KEYWORD('FROM'), + style.SQL_TABLE(f.m2m_db_table()))) + return output OPERATOR_MAPPING = { 'exact': '= %s', diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 4b8a1c64a8..ec0f715491 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -170,6 +170,11 @@ def get_sql_flush(style, tables, sequences): # get_sql_flush() implementations). Just return SQL at this point return sql +def get_sql_sequence_reset(style, model_list): + "Returns a list of the SQL statements to reset sequences for the given models." + # No sequence reset required + return [] + def _sqlite_date_trunc(lookup_type, dt): try: dt = util.typecast_timestamp(dt) diff --git a/docs/django-admin.txt b/docs/django-admin.txt index b6028dc2a0..388eaf98ac 100644 --- a/docs/django-admin.txt +++ b/docs/django-admin.txt @@ -366,7 +366,7 @@ Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given appnames. sqlsequencereset [appname appname ...] ---------------------------------------------- -Prints the SQL statements for resetting PostgreSQL sequences for the given +Prints the SQL statements for resetting sequences for the given appnames. See http://simon.incutio.com/archive/2004/04/21/postgres for more information. diff --git a/tests/regressiontests/fixtures_regress/__init__.py b/tests/regressiontests/fixtures_regress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/fixtures_regress/fixtures/sequence.json b/tests/regressiontests/fixtures_regress/fixtures/sequence.json new file mode 100644 index 0000000000..ecaf637b9f --- /dev/null +++ b/tests/regressiontests/fixtures_regress/fixtures/sequence.json @@ -0,0 +1,10 @@ +[ + { + "pk": "1", + "model": "fixtures_regress.animal", + "fields": { + "name": "Lion", + "latin_name": "Panthera leo" + } + } +] \ No newline at end of file diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py new file mode 100644 index 0000000000..f9d4d35045 --- /dev/null +++ b/tests/regressiontests/fixtures_regress/models.py @@ -0,0 +1,22 @@ +from django.db import models + +class Animal(models.Model): + name = models.CharField(maxlength=150) + latin_name = models.CharField(maxlength=150) + + def __str__(self): + return self.common_name + +__test__ = {'API_TESTS':""" +>>> from django.core import management + +# Load a fixture that uses PK=1 +>>> management.load_data(['sequence'], verbosity=0) + +# Create a new animal. Without a sequence reset, this new object +# will take a PK of 1 (on Postgres), and the save will fail. +# This is a regression test for ticket #3790. +>>> animal = Animal(name='Platypus', latin_name='Ornithorhynchus anatinus') +>>> animal.save() + +"""} \ No newline at end of file