Skip to content

Commit fe1f639

Browse files
AnexenPhotonios
authored andcommitted
Prevent migration optimization for partioned models
1 parent 3bcea35 commit fe1f639

File tree

5 files changed

+81
-9
lines changed

5 files changed

+81
-9
lines changed

psqlextra/backend/introspection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"h": PostgresPartitioningMethod.HASH,
1212
}
1313

14+
1415
@dataclass
1516
class PostgresIntrospectedPartitionTable:
1617
"""Data container for information about a partition."""
@@ -151,6 +152,7 @@ def get_partition_key(self, cursor, table_name: str) -> List[str]:
151152
CASE partstrat
152153
WHEN 'l' THEN 'list'
153154
WHEN 'r' THEN 'range'
155+
WHEN 'h' THEN 'hash'
154156
END AS partition_strategy,
155157
Unnest(partattrs) column_index
156158
FROM pg_partitioned_table) pt

psqlextra/backend/migrations/operations/add_hash_partition.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55

66
class PostgresAddHashPartition(PostgresPartitionOperation):
7-
"""
8-
Adds a new hash partition to a :see:PartitionedPostgresModel.
9-
Each partition will hold the rows for which the hash value of the partition
10-
key divided by the specified modulus will produce the specified remainder.
7+
"""Adds a new hash partition to a :see:PartitionedPostgresModel.
8+
9+
Each partition will hold the rows for which the hash value of the
10+
partition key divided by the specified modulus will produce the
11+
specified remainder.
1112
"""
1213

13-
def __init__(self, model_name: str, name: str, modulus: int, remainder: int):
14+
def __init__(
15+
self, model_name: str, name: str, modulus: int, remainder: int
16+
):
1417
"""Initializes new instance of :see:AddHashPartition.
1518
Arguments:
1619
model_name:

psqlextra/backend/migrations/operations/create_partitioned_model.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,19 @@ def describe(self):
6969
description = super().describe()
7070
description = description.replace("model", "partitioned model")
7171
return description
72+
73+
def reduce(self, *args, **kwargs):
74+
result = super().reduce(*args, **kwargs)
75+
76+
# replace CreateModel operation with PostgresCreatePartitionedModel
77+
if isinstance(result, list) and result:
78+
for i, op in enumerate(result):
79+
if isinstance(op, CreateModel):
80+
_, args, kwargs = op.deconstruct()
81+
result[i] = PostgresCreatePartitionedModel(
82+
*args,
83+
**kwargs,
84+
partitioning_options=self.partitioning_options
85+
)
86+
87+
return result

psqlextra/backend/migrations/operations/partition.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@ def state_forwards(self, *args, **kwargs):
2626

2727
def state_backwards(self, *args, **kwargs):
2828
pass
29+
30+
def reduce(self, *args, **kwargs):
31+
# PartitionOperation doesn't break migrations optimizations
32+
return True

tests/test_make_migrations.py

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import django
12
import pytest
23

34
from django.apps import apps
@@ -15,6 +16,7 @@
1516

1617
from .fake_model import (
1718
define_fake_materialized_view_model,
19+
define_fake_model,
1820
define_fake_partitioned_model,
1921
define_fake_view_model,
2022
get_fake_model,
@@ -41,8 +43,8 @@
4143
fields={"artist_id": models.IntegerField()},
4244
partitioning_options=dict(
4345
method=PostgresPartitioningMethod.HASH, key="artist_id"
44-
)
45-
)
46+
),
47+
),
4648
],
4749
)
4850
@postgres_patched_migrations()
@@ -54,10 +56,11 @@ def test_make_migration_create_partitioned_model(fake_app, model_config):
5456
**model_config, meta_options=dict(app_label=fake_app.name)
5557
)
5658

57-
migration = make_migration(model._meta.app_label)
59+
migration = make_migration(fake_app.name)
5860
ops = migration.operations
61+
method = model_config["partitioning_options"]["method"]
5962

60-
if model_config["partitioning_options"]["method"] == PostgresPartitioningMethod.HASH:
63+
if method == PostgresPartitioningMethod.HASH:
6164
# should have one operation to create the partitioned model
6265
# and no default partition
6366
assert len(ops) == 1
@@ -194,3 +197,47 @@ def test_make_migration_field_operations_view_models(
194197
)
195198
assert isinstance(migration.operations[0], operations.ApplyState)
196199
assert isinstance(migration.operations[0].state_operation, RemoveField)
200+
201+
202+
@pytest.mark.skipif(
203+
django.VERSION < (2, 2),
204+
reason="Django < 2.2 doesn't implement left-to-right migration optimizations",
205+
)
206+
@pytest.mark.parametrize("method", PostgresPartitioningMethod.all())
207+
@postgres_patched_migrations()
208+
def test_autodetect_fk_issue(fake_app, method):
209+
"""Test whether Django can perform ForeignKey optimization.
210+
211+
Fixes https://github.com/SectorLabs/django-postgres-extra/issues/123 for Django >= 2.2
212+
"""
213+
meta_options = {"app_label": fake_app.name}
214+
partitioning_options = {"method": method, "key": "artist_id"}
215+
216+
artist_model_fields = {"name": models.TextField()}
217+
Artist = define_fake_model(artist_model_fields, meta_options=meta_options)
218+
219+
from_state = ProjectState.from_apps(apps)
220+
221+
album_model_fields = {
222+
"name": models.TextField(),
223+
"artist": models.ForeignKey(
224+
to=Artist.__name__, on_delete=models.CASCADE
225+
),
226+
}
227+
228+
define_fake_partitioned_model(
229+
album_model_fields,
230+
partitioning_options=partitioning_options,
231+
meta_options=meta_options,
232+
)
233+
234+
migration = make_migration(fake_app.name, from_state=from_state)
235+
ops = migration.operations
236+
237+
if method == PostgresPartitioningMethod.HASH:
238+
assert len(ops) == 1
239+
assert isinstance(ops[0], operations.PostgresCreatePartitionedModel)
240+
else:
241+
assert len(ops) == 2
242+
assert isinstance(ops[0], operations.PostgresCreatePartitionedModel)
243+
assert isinstance(ops[1], operations.PostgresAddDefaultPartition)

0 commit comments

Comments
 (0)