diff --git a/migra/changes.py b/migra/changes.py index 6daaeb7..38b19dd 100644 --- a/migra/changes.py +++ b/migra/changes.py @@ -28,9 +28,11 @@ def statements_for_changes( things_target, creations_only=False, drops_only=False, + modifications_only=False, modifications=True, dependency_ordering=False, add_dependents_for_modified=False, + modifications_as_alters=False ): added, removed, modified, unmodified = differences(things_from, things_target) @@ -41,9 +43,11 @@ def statements_for_changes( replaceable=None, creations_only=creations_only, drops_only=drops_only, + modifications_only=modifications_only, modifications=modifications, dependency_ordering=dependency_ordering, old=things_from, + modifications_as_alters=modifications_as_alters ) @@ -57,21 +61,38 @@ def statements_from_differences( modifications=True, dependency_ordering=False, old=None, + modifications_only=False, + modifications_as_alters=False ): replaceable = replaceable or set() statements = Statements() - if not creations_only: - pending_drops = set(removed) - if modifications: + + pending_creations = set() + pending_drops = set() + + creations = not (drops_only or modifications_only) + drops = not (creations_only or modifications_only) + modifications = modifications or modifications_only and not (creations_only or drops_only) + + drop_and_recreate = modifications and not modifications_as_alters + alters = modifications and modifications_as_alters + + if drops: + pending_drops |= set(removed) + + if creations: + pending_creations |= set(added) + + if drop_and_recreate: + if drops: pending_drops |= set(modified) - replaceable - else: - pending_drops = set() - if not drops_only: - pending_creations = set(added) - if modifications: + + if creations: pending_creations |= set(modified) - else: - pending_creations = set() + + if alters: + for k, v in modified.items(): + statements += v.alter_statements(old[k]) def has_remaining_dependents(v, pending_drops): if not dependency_ordering: @@ -87,13 +108,13 @@ def statements_from_differences( while True: before = pending_drops | pending_creations - if not creations_only: + if drops: for k, v in removed.items(): if not has_remaining_dependents(v, pending_drops): if k in pending_drops: statements.append(old[k].drop_statement) pending_drops.remove(k) - if not drops_only: + if creations: for k, v in added.items(): if not has_uncreated_dependencies(v, pending_creations): if k in pending_creations: @@ -101,12 +122,12 @@ def statements_from_differences( pending_creations.remove(k) if modifications: for k, v in modified.items(): - if not creations_only: + if drops: if not has_remaining_dependents(v, pending_drops): if k in pending_drops: statements.append(old[k].drop_statement) pending_drops.remove(k) - if not drops_only: + if creations: if not has_uncreated_dependencies(v, pending_creations): if k in pending_creations: statements.append(v.create_statement) @@ -443,52 +464,68 @@ class Changes(object): self.i_from = i_from self.i_target = i_target + @property + def extensions(self): + return partial( + statements_for_changes, + self.i_from.extensions, + self.i_target.extensions, + modifications_as_alters=True + ) + + @property + def selectables(self): + return partial( + get_selectable_changes, + od(sorted(self.i_from.selectables.items())), + od(sorted(self.i_target.selectables.items())), + self.i_from.enums, + self.i_target.enums, + self.i_from.sequences, + self.i_target.sequences, + ) + + + @property + def non_pk_constraints(self): + a = self.i_from.constraints.items() + b = self.i_target.constraints.items() + a_od = od((k, v) for k, v in a if v.constraint_type != PK) + b_od = od((k, v) for k, v in b if v.constraint_type != PK) + return partial(statements_for_changes, a_od, b_od) + + + @property + def pk_constraints(self): + a = self.i_from.constraints.items() + b = self.i_target.constraints.items() + a_od = od((k, v) for k, v in a if v.constraint_type == PK) + b_od = od((k, v) for k, v in b if v.constraint_type == PK) + return partial(statements_for_changes, a_od, b_od) + + @property + def triggers(self): + return partial( + get_trigger_changes, + od(sorted(self.i_from.triggers.items())), + od(sorted(self.i_target.triggers.items())), + od(sorted(self.i_from.selectables.items())), + od(sorted(self.i_target.selectables.items())), + self.i_from.enums, + self.i_target.enums, + ) + + @property + def sequences(self): + return partial( + statements_for_changes, + self.i_from.sequences, + self.i_target.sequences, + modifications=False, + ) + def __getattr__(self, name): - if name == "non_pk_constraints": - a = self.i_from.constraints.items() - b = self.i_target.constraints.items() - a_od = od((k, v) for k, v in a if v.constraint_type != PK) - b_od = od((k, v) for k, v in b if v.constraint_type != PK) - return partial(statements_for_changes, a_od, b_od) - - elif name == "pk_constraints": - a = self.i_from.constraints.items() - b = self.i_target.constraints.items() - a_od = od((k, v) for k, v in a if v.constraint_type == PK) - b_od = od((k, v) for k, v in b if v.constraint_type == PK) - return partial(statements_for_changes, a_od, b_od) - - elif name == "selectables": - return partial( - get_selectable_changes, - od(sorted(self.i_from.selectables.items())), - od(sorted(self.i_target.selectables.items())), - self.i_from.enums, - self.i_target.enums, - self.i_from.sequences, - self.i_target.sequences, - ) - - elif name == "triggers": - return partial( - get_trigger_changes, - od(sorted(self.i_from.triggers.items())), - od(sorted(self.i_target.triggers.items())), - od(sorted(self.i_from.selectables.items())), - od(sorted(self.i_target.selectables.items())), - self.i_from.enums, - self.i_target.enums, - ) - - elif name == "sequences": - return partial( - statements_for_changes, - getattr(self.i_from, name), - getattr(self.i_target, name), - modifications=False, - ) - - elif name in THINGS: + if name in THINGS: return partial( statements_for_changes, getattr(self.i_from, name), diff --git a/migra/migra.py b/migra/migra.py index ac04acb..c71183d 100644 --- a/migra/migra.py +++ b/migra/migra.py @@ -64,7 +64,8 @@ class Migration(object): def add_all_changes(self, privileges=False): self.add(self.changes.schemas(creations_only=True)) - self.add(self.changes.extensions(creations_only=True)) + self.add(self.changes.extensions(creations_only=True, modifications=False)) + self.add(self.changes.extensions(modifications_only=True, modifications=True)) self.add(self.changes.collations(creations_only=True)) self.add(self.changes.enums(creations_only=True, modifications=False)) self.add(self.changes.sequences(creations_only=True)) @@ -80,7 +81,7 @@ class Migration(object): self.add(self.changes.sequences(drops_only=True)) self.add(self.changes.enums(drops_only=True, modifications=False)) - self.add(self.changes.extensions(drops_only=True)) + self.add(self.changes.extensions(drops_only=True, modifications=False)) self.add(self.changes.indexes(creations_only=True)) self.add(self.changes.pk_constraints(creations_only=True)) self.add(self.changes.non_pk_constraints(creations_only=True)) diff --git a/pyproject.toml b/pyproject.toml index 12c8bc3..ff53c69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,8 +13,8 @@ homepage = "https://migra.djrobstep.com/" python = "*" sqlbag = "*" six = "*" -#schemainspect = {path="../schemainspect"} -schemainspect = ">=0.1.1595570852" +# schemainspect = {path="../schemainspect"} +schemainspect = ">=0.1.1595673757" psycopg2-binary = { version="*", optional = true } [tool.poetry.dev-dependencies] diff --git a/tests/FIXTURES/extversions/a.sql b/tests/FIXTURES/extversions/a.sql new file mode 100644 index 0000000..dabb926 --- /dev/null +++ b/tests/FIXTURES/extversions/a.sql @@ -0,0 +1,3 @@ +create extension pg_trgm version '1.3'; + +create extension hstore; \ No newline at end of file diff --git a/tests/FIXTURES/extversions/additions.sql b/tests/FIXTURES/extversions/additions.sql new file mode 100644 index 0000000..e69de29 diff --git a/tests/FIXTURES/extversions/b.sql b/tests/FIXTURES/extversions/b.sql new file mode 100644 index 0000000..5d8c276 --- /dev/null +++ b/tests/FIXTURES/extversions/b.sql @@ -0,0 +1,3 @@ +create extension citext; + +create extension pg_trgm version '1.4'; diff --git a/tests/FIXTURES/extversions/expected.sql b/tests/FIXTURES/extversions/expected.sql new file mode 100644 index 0000000..3e80ffc --- /dev/null +++ b/tests/FIXTURES/extversions/expected.sql @@ -0,0 +1,5 @@ +create extension if not exists "citext" with schema "public" version '1.6'; + +alter extension "pg_trgm" update to '1.4'; + +drop extension if exists "hstore"; \ No newline at end of file diff --git a/tests/FIXTURES/extversions/expected2.sql b/tests/FIXTURES/extversions/expected2.sql new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_migra.py b/tests/test_migra.py index 806f20c..bc99421 100644 --- a/tests/test_migra.py +++ b/tests/test_migra.py @@ -94,6 +94,11 @@ def test_enumdeps(): do_fixture_test(FIXTURE_NAME, with_privileges=True) +def test_extversions(): + for FIXTURE_NAME in ["extversions"]: + do_fixture_test(FIXTURE_NAME, with_privileges=True) + + def test_sequences(): for FIXTURE_NAME in ["seq"]: do_fixture_test(FIXTURE_NAME, with_privileges=True)