Skip to content

Commit

Permalink
Added a stealth option to flush to allow cascades.
Browse files Browse the repository at this point in the history
This allows using flush on a subset of the tables without having to
manually cascade to all tables with foreign keys to the tables being
truncated, when they're known to be empty.

On databases where truncate is implemented with DELETE FROM, this
doesn't make a difference. The cascade is allowed, not mandatory.
  • Loading branch information
aaugustin committed Jun 10, 2013
1 parent 7a65c95 commit 13b7f29
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 25 deletions.
7 changes: 5 additions & 2 deletions django/core/management/commands/flush.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ def handle_noargs(self, **options):
connection = connections[db]
verbosity = int(options.get('verbosity'))
interactive = options.get('interactive')
# 'reset_sequences' is a stealth option
# 'reset_sequences' and 'allow_cascade' are stealth options
reset_sequences = options.get('reset_sequences', True)
allow_cascade = options.get('allow_cascade', False)

self.style = no_style()

Expand All @@ -45,7 +46,9 @@ def handle_noargs(self, **options):
except ImportError:
pass

sql_list = sql_flush(self.style, connection, only_django=True, reset_sequences=reset_sequences)
sql_list = sql_flush(self.style, connection, only_django=True,
reset_sequences=reset_sequences,
allow_cascade=allow_cascade)

if interactive:
confirm = input("""You have requested a flush of the database.
Expand Down
4 changes: 2 additions & 2 deletions django/core/management/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def sql_delete(app, style, connection):
return output[::-1] # Reverse it, to deal with table dependencies.


def sql_flush(style, connection, only_django=False, reset_sequences=True):
def sql_flush(style, connection, only_django=False, reset_sequences=True, allow_cascade=False):
"""
Returns a list of the SQL statements used to flush the database.
Expand All @@ -114,7 +114,7 @@ def sql_flush(style, connection, only_django=False, reset_sequences=True):
else:
tables = connection.introspection.table_names()
seqs = connection.introspection.sequence_list() if reset_sequences else ()
statements = connection.ops.sql_flush(style, tables, seqs)
statements = connection.ops.sql_flush(style, tables, seqs, allow_cascade)
return statements


Expand Down
8 changes: 6 additions & 2 deletions django/db/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ def constraint_checks_disabled(self):
def disable_constraint_checking(self):
"""
Backends can implement as needed to temporarily disable foreign key
constraint checking. Should return True if the constraints were
constraint checking. Should return True if the constraints were
disabled and will need to be reenabled.
"""
return False
Expand Down Expand Up @@ -947,7 +947,7 @@ def set_time_zone_sql(self):
"""
return ''

def sql_flush(self, style, tables, sequences):
def sql_flush(self, style, tables, sequences, allow_cascade=False):
"""
Returns a list of SQL statements required to remove all data from
the given database tables (without actually removing the tables
Expand All @@ -958,6 +958,10 @@ def sql_flush(self, style, tables, sequences):
The `style` argument is a Style object as returned by either
color_style() or no_style() in django.core.management.color.
The `allow_cascade` argument determines whether truncation may cascade
to tables with foreign keys pointing the tables being truncated.
PostgreSQL requires a cascade even if these tables are empty.
"""
raise NotImplementedError()

Expand Down
7 changes: 5 additions & 2 deletions django/db/backends/mysql/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,14 +298,17 @@ def quote_name(self, name):
def random_function_sql(self):
return 'RAND()'

def sql_flush(self, style, tables, sequences):
def sql_flush(self, style, tables, sequences, allow_cascade=False):
# NB: The generated SQL below is specific to MySQL
# 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
# to clear all tables of all data
if tables:
sql = ['SET FOREIGN_KEY_CHECKS = 0;']
for table in tables:
sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
sql.append('%s %s;' % (
style.SQL_KEYWORD('TRUNCATE'),
style.SQL_FIELD(self.quote_name(table)),
))
sql.append('SET FOREIGN_KEY_CHECKS = 1;')
sql.extend(self.sequence_reset_by_name_sql(style, sequences))
return sql
Expand Down
12 changes: 6 additions & 6 deletions django/db/backends/oracle/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,17 +339,17 @@ def savepoint_create_sql(self, sid):
def savepoint_rollback_sql(self, sid):
return convert_unicode("ROLLBACK TO SAVEPOINT " + self.quote_name(sid))

def sql_flush(self, style, tables, sequences):
def sql_flush(self, style, tables, sequences, allow_cascade=False):
# Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
# 'TRUNCATE z;'... style SQL statements
if tables:
# Oracle does support TRUNCATE, but it seems to get us into
# FK referential trouble, whereas DELETE FROM table works.
sql = ['%s %s %s;' % \
(style.SQL_KEYWORD('DELETE'),
style.SQL_KEYWORD('FROM'),
style.SQL_FIELD(self.quote_name(table)))
for table in tables]
sql = ['%s %s %s;' % (
style.SQL_KEYWORD('DELETE'),
style.SQL_KEYWORD('FROM'),
style.SQL_FIELD(self.quote_name(table))
) for table in tables]
# Since we've just deleted all the rows, running our sequence
# ALTER code will reset the sequence to 0.
sql.extend(self.sequence_reset_by_name_sql(style, sequences))
Expand Down
19 changes: 14 additions & 5 deletions django/db/backends/postgresql_psycopg2/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,24 @@ def quote_name(self, name):
def set_time_zone_sql(self):
return "SET TIME ZONE %s"

def sql_flush(self, style, tables, sequences):
def sql_flush(self, style, tables, sequences, allow_cascade=False):
if tables:
# Perform a single SQL 'TRUNCATE x, y, z...;' statement. It allows
# us to truncate tables referenced by a foreign key in any other
# table.
sql = ['%s %s;' % \
(style.SQL_KEYWORD('TRUNCATE'),
style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables]))
)]
tables_sql = ', '.join(
style.SQL_FIELD(self.quote_name(table)) for table in tables)
if allow_cascade:
sql = ['%s %s %s;' % (
style.SQL_KEYWORD('TRUNCATE'),
tables_sql,
style.SQL_KEYWORD('CASCADE'),
)]
else:
sql = ['%s %s;' % (
style.SQL_KEYWORD('TRUNCATE'),
tables_sql,
)]
sql.extend(self.sequence_reset_by_name_sql(style, sequences))
return sql
else:
Expand Down
12 changes: 6 additions & 6 deletions django/db/backends/sqlite3/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,15 +209,15 @@ def quote_name(self, name):
def no_limit_value(self):
return -1

def sql_flush(self, style, tables, sequences):
def sql_flush(self, style, tables, sequences, allow_cascade=False):
# NB: The generated SQL below is specific to SQLite
# Note: The DELETE FROM... SQL generated below works for SQLite databases
# because constraints don't exist
sql = ['%s %s %s;' % \
(style.SQL_KEYWORD('DELETE'),
style.SQL_KEYWORD('FROM'),
style.SQL_FIELD(self.quote_name(table))
) for table in tables]
sql = ['%s %s %s;' % (
style.SQL_KEYWORD('DELETE'),
style.SQL_KEYWORD('FROM'),
style.SQL_FIELD(self.quote_name(table))
) for table in tables]
# Note: No requirement for reset of auto-incremented indices (cf. other
# sql_flush() implementations). Just return SQL at this point
return sql
Expand Down

0 comments on commit 13b7f29

Please sign in to comment.