Power the optimization process, where you provide a list of Operations and you are returned a list of equal or shorter length - operations are merged into one if possible. For example, a CreateModel and an AddField can be optimized into a new CreateModel, and CreateModel and De
| 1 | class MigrationOptimizer: |
| 2 | """ |
| 3 | Power the optimization process, where you provide a list of Operations |
| 4 | and you are returned a list of equal or shorter length - operations |
| 5 | are merged into one if possible. |
| 6 | |
| 7 | For example, a CreateModel and an AddField can be optimized into a |
| 8 | new CreateModel, and CreateModel and DeleteModel can be optimized into |
| 9 | nothing. |
| 10 | """ |
| 11 | |
| 12 | def optimize(self, operations, app_label): |
| 13 | """ |
| 14 | Main optimization entry point. Pass in a list of Operation instances, |
| 15 | get out a new list of Operation instances. |
| 16 | |
| 17 | Unfortunately, due to the scope of the optimization (two combinable |
| 18 | operations might be separated by several hundred others), this can't be |
| 19 | done as a peephole optimization with checks/output implemented on |
| 20 | the Operations themselves; instead, the optimizer looks at each |
| 21 | individual operation and scans forwards in the list to see if there |
| 22 | are any matches, stopping at boundaries - operations which can't |
| 23 | be optimized over (RunSQL, operations on the same field/model, etc.) |
| 24 | |
| 25 | The inner loop is run until the starting list is the same as the result |
| 26 | list, and then the result is returned. This means that operation |
| 27 | optimization must be stable and always return an equal or shorter list. |
| 28 | """ |
| 29 | # Internal tracking variable for test assertions about # of loops |
| 30 | if app_label is None: |
| 31 | raise TypeError("app_label must be a str.") |
| 32 | self._iterations = 0 |
| 33 | while True: |
| 34 | result = self.optimize_inner(operations, app_label) |
| 35 | self._iterations += 1 |
| 36 | if result == operations: |
| 37 | return result |
| 38 | operations = result |
| 39 | |
| 40 | def optimize_inner(self, operations, app_label): |
| 41 | """Inner optimization loop.""" |
| 42 | new_operations = [] |
| 43 | for i, operation in enumerate(operations): |
| 44 | right = True # Should we reduce on the right or on the left. |
| 45 | # Compare it to each operation after it |
| 46 | for j, other in enumerate(operations[i + 1 :]): |
| 47 | result = operation.reduce(other, app_label) |
| 48 | if isinstance(result, list): |
| 49 | in_between = operations[i + 1 : i + j + 1] |
| 50 | if right: |
| 51 | new_operations.extend(in_between) |
| 52 | new_operations.extend(result) |
| 53 | elif all(op.reduce(other, app_label) is True for op in in_between): |
| 54 | # Perform a left reduction if all of the in-between |
| 55 | # operations can optimize through other. |
| 56 | new_operations.extend(result) |
| 57 | new_operations.extend(in_between) |
| 58 | else: |
| 59 | # Otherwise keep trying. |
| 60 | new_operations.append(operation) |
no outgoing calls