summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarkus Holtermann <info@markusholtermann.eu>2015-01-02 20:22:28 +0100
committerTim Graham <timograham@gmail.com>2015-01-07 08:54:46 -0500
commit12bf42ae0db752bf4a4387d6be7276cd145f59d1 (patch)
treec8eeb8bc207b4c33ecb1b8aab462479765e4e3d3
parent391bb09bb09e8208b4711c21e850b9351dd62989 (diff)
Refs #22608 -- Optimized migration optimizer
Moved list constants instantiation into optimizer's __init__.
-rw-r--r--django/db/migrations/optimizer.py148
1 files changed, 75 insertions, 73 deletions
diff --git a/django/db/migrations/optimizer.py b/django/db/migrations/optimizer.py
index be3983719e..1cade67457 100644
--- a/django/db/migrations/optimizer.py
+++ b/django/db/migrations/optimizer.py
@@ -15,65 +15,18 @@ class MigrationOptimizer(object):
nothing.
"""
- def optimize(self, operations, app_label=None):
- """
- Main optimization entry point. Pass in a list of Operation instances,
- get out a new list of Operation instances.
-
- Unfortunately, due to the scope of the optimization (two combinable
- operations might be separated by several hundred others), this can't be
- done as a peephole optimization with checks/output implemented on
- the Operations themselves; instead, the optimizer looks at each
- individual operation and scans forwards in the list to see if there
- are any matches, stopping at boundaries - operations which can't
- be optimized over (RunSQL, operations on the same field/model, etc.)
-
- The inner loop is run until the starting list is the same as the result
- list, and then the result is returned. This means that operation
- optimization must be stable and always return an equal or shorter list.
-
- The app_label argument is optional, but if you pass it you'll get more
- efficient optimization.
- """
- # Internal tracking variable for test assertions about # of loops
- self._iterations = 0
- while True:
- result = self.optimize_inner(operations, app_label)
- self._iterations += 1
- if result == operations:
- return result
- operations = result
-
- def optimize_inner(self, operations, app_label=None):
- """
- Inner optimization loop.
- """
- new_operations = []
- for i, operation in enumerate(operations):
- # Compare it to each operation after it
- for j, other in enumerate(operations[i + 1:]):
- result = self.reduce(operation, other, operations[i + 1:i + j + 1])
- if result is not None:
- # Optimize! Add result, then remaining others, then return
- new_operations.extend(result)
- new_operations.extend(operations[i + 1:i + 1 + j])
- new_operations.extend(operations[i + j + 2:])
- return new_operations
- if not self.can_optimize_through(operation, other, app_label):
- new_operations.append(operation)
- break
- else:
- new_operations.append(operation)
- return new_operations
-
- #### REDUCTION ####
-
- def reduce(self, operation, other, in_between=None):
- """
- Either returns a list of zero, one or two operations,
- or None, meaning this pair cannot be optimized.
- """
- submethods = [
+ def __init__(self):
+ self.model_level_operations = (
+ migrations.CreateModel,
+ migrations.AlterModelTable,
+ migrations.AlterUniqueTogether,
+ migrations.AlterIndexTogether,
+ )
+ self.field_level_operations = (
+ migrations.AddField,
+ migrations.AlterField,
+ )
+ self.reduce_methods = [
(
migrations.CreateModel,
migrations.DeleteModel,
@@ -155,7 +108,66 @@ class MigrationOptimizer(object):
self.reduce_rename_field_self,
),
]
- for ia, ib, om in submethods:
+
+ def optimize(self, operations, app_label=None):
+ """
+ Main optimization entry point. Pass in a list of Operation instances,
+ get out a new list of Operation instances.
+
+ Unfortunately, due to the scope of the optimization (two combinable
+ operations might be separated by several hundred others), this can't be
+ done as a peephole optimization with checks/output implemented on
+ the Operations themselves; instead, the optimizer looks at each
+ individual operation and scans forwards in the list to see if there
+ are any matches, stopping at boundaries - operations which can't
+ be optimized over (RunSQL, operations on the same field/model, etc.)
+
+ The inner loop is run until the starting list is the same as the result
+ list, and then the result is returned. This means that operation
+ optimization must be stable and always return an equal or shorter list.
+
+ The app_label argument is optional, but if you pass it you'll get more
+ efficient optimization.
+ """
+ # Internal tracking variable for test assertions about # of loops
+ self._iterations = 0
+ while True:
+ result = self.optimize_inner(operations, app_label)
+ self._iterations += 1
+ if result == operations:
+ return result
+ operations = result
+
+ def optimize_inner(self, operations, app_label=None):
+ """
+ Inner optimization loop.
+ """
+ new_operations = []
+ for i, operation in enumerate(operations):
+ # Compare it to each operation after it
+ for j, other in enumerate(operations[i + 1:]):
+ result = self.reduce(operation, other, operations[i + 1:i + j + 1])
+ if result is not None:
+ # Optimize! Add result, then remaining others, then return
+ new_operations.extend(result)
+ new_operations.extend(operations[i + 1:i + 1 + j])
+ new_operations.extend(operations[i + j + 2:])
+ return new_operations
+ if not self.can_optimize_through(operation, other, app_label):
+ new_operations.append(operation)
+ break
+ else:
+ new_operations.append(operation)
+ return new_operations
+
+ # REDUCTION
+
+ def reduce(self, operation, other, in_between=None):
+ """
+ Either returns a list of zero, one or two operations,
+ or None, meaning this pair cannot be optimized.
+ """
+ for ia, ib, om in self.reduce_methods:
if isinstance(operation, ia) and isinstance(other, ib):
return om(operation, other, in_between or [])
return None
@@ -336,7 +348,7 @@ class MigrationOptimizer(object):
),
]
- #### THROUGH CHECKS ####
+ # THROUGH CHECKS
def can_optimize_through(self, operation, other, app_label=None):
"""
@@ -344,24 +356,14 @@ class MigrationOptimizer(object):
the other side of 'other'. This is possible if, for example, they
affect different models.
"""
- MODEL_LEVEL_OPERATIONS = (
- migrations.CreateModel,
- migrations.AlterModelTable,
- migrations.AlterUniqueTogether,
- migrations.AlterIndexTogether,
- )
- FIELD_LEVEL_OPERATIONS = (
- migrations.AddField,
- migrations.AlterField,
- )
# If it's a model level operation, let it through if there's
# nothing that looks like a reference to us in 'other'.
- if isinstance(operation, MODEL_LEVEL_OPERATIONS):
+ if isinstance(operation, self.model_level_operations):
if not other.references_model(operation.name, app_label):
return True
# If it's field level, only let it through things that don't reference
# the field (which includes not referencing the model)
- if isinstance(operation, FIELD_LEVEL_OPERATIONS):
+ if isinstance(operation, self.field_level_operations):
if not other.references_field(operation.model_name, operation.name, app_label):
return True
return False