refactor generation of selectable changes

This commit is contained in:
Robert Lechte 2018-08-29 12:34:27 +10:00
parent 46e9ed7aff
commit 1b475b0569
12 changed files with 155 additions and 52 deletions

View File

@ -5,6 +5,7 @@ from .statements import Statements
from functools import partial
from collections import OrderedDict as od
THINGS = [
"schemas",
"enums",
@ -29,12 +30,27 @@ def statements_for_changes(
add_dependents_for_modified=False,
):
added, removed, modified, unmodified = differences(things_from, things_target)
if add_dependents_for_modified:
for k, m in list(modified.items()):
for d in m.dependents_all:
if d in unmodified:
modified[d] = unmodified.pop(d)
modified = od(sorted(modified.items()))
return statements_from_differences(
added,
removed,
modified,
creations_only,
drops_only,
modifications,
dependency_ordering,
)
def statements_from_differences(
added,
removed,
modified,
creations_only=False,
drops_only=False,
modifications=True,
dependency_ordering=False,
):
statements = Statements()
if not creations_only:
pending_drops = set(removed)
@ -124,8 +140,9 @@ def get_enum_modifications(tables_from, tables_target, enums_from, enums_target)
return pre + recreate + post
def get_schema_changes(tables_from, tables_target, enums_from, enums_target):
def get_table_changes(tables_from, tables_target, enums_from, enums_target):
added, removed, modified, _ = differences(tables_from, tables_target)
statements = Statements()
for t, v in removed.items():
statements.append(v.drop_statement)
@ -148,22 +165,84 @@ def get_schema_changes(tables_from, tables_target, enums_from, enums_target):
return statements
def get_selectable_changes(
selectables_from,
selectables_target,
enums_from,
enums_target,
add_dependents_for_modified=True,
):
tables_from = od(
(k, v) for k, v in selectables_from.items() if v.relationtype == "r"
)
tables_target = od(
(k, v) for k, v in selectables_target.items() if v.relationtype == "r"
)
other_from = od(
(k, v) for k, v in selectables_from.items() if v.relationtype != "r"
)
other_target = od(
(k, v) for k, v in selectables_target.items() if v.relationtype != "r"
)
added_tables, removed_tables, modified_tables, unmodified_tables = differences(
tables_from, tables_target
)
added_other, removed_other, modified_other, unmodified_other = differences(
other_from, other_target
)
m_all = {}
m_all.update(modified_tables)
m_all.update(modified_other)
m_all.update(removed_tables)
m_all.update(removed_other)
if add_dependents_for_modified:
for k, m in m_all.items():
for d in m.dependents_all:
if d in unmodified_other:
modified_other[d] = unmodified_other.pop(d)
modified_other = od(sorted(modified_other.items()))
statements = Statements()
def functions(d):
return {k: v for k, v in d.items() if v.relationtype == "f"}
statements += statements_from_differences(
added_other,
removed_other,
modified_other,
drops_only=True,
dependency_ordering=True,
)
statements += get_table_changes(
tables_from, tables_target, enums_from, enums_target
)
if any([functions(added_other), functions(modified_other)]):
statements += ["set check_function_bodies = off;"]
statements += statements_from_differences(
added_other,
removed_other,
modified_other,
creations_only=True,
dependency_ordering=True,
)
return statements
class Changes(object):
def __init__(self, i_from, i_target):
self.i_from = i_from
self.i_target = i_target
def __getattr__(self, name):
if name == "schema":
return partial(
get_schema_changes,
self.i_from.tables,
self.i_target.tables,
self.i_from.enums,
self.i_target.enums,
)
elif name == "non_pk_constraints":
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)
@ -177,19 +256,13 @@ class Changes(object):
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 == "views_and_functions":
av = self.i_from.views.items()
bv = self.i_target.views.items()
am = self.i_from.materialized_views.items()
bm = self.i_target.materialized_views.items()
af = self.i_from.functions.items()
bf = self.i_target.functions.items()
avf = list(av) + list(am) + list(af)
bvf = list(bv) + list(bm) + list(bf)
avf = od(sorted(avf))
bvf = od(sorted(bvf))
elif name == "selectables":
return partial(
statements_for_changes, avf, bvf, add_dependents_for_modified=True
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,
)
elif name in THINGS:

View File

@ -70,16 +70,9 @@ class Migration(object):
self.add(self.changes.non_pk_constraints(drops_only=True))
self.add(self.changes.pk_constraints(drops_only=True))
self.add(self.changes.indexes(drops_only=True))
self.add(
self.changes.views_and_functions(drops_only=True, dependency_ordering=True)
)
self.add(self.changes.schema())
v_and_f_changes = self.changes.views_and_functions(
creations_only=True, dependency_ordering=True
)
if v_and_f_changes:
self.add(["set check_function_bodies = off;"])
self.add(v_and_f_changes)
self.add(self.changes.selectables())
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))

View File

@ -6,7 +6,7 @@ from setuptools import setup, find_packages
readme = io.open("README.md").read()
setup(
name="migra",
version="1.0.1531741707",
version="1.0.1535509978",
url="https://github.com/djrobstep/migra",
description="Like diff but for PostgreSQL schemas",
long_description=readme,

View File

@ -10,8 +10,6 @@ drop view if exists "public"."ddd_changed" cascade;
drop view if exists "public"."aaa_view1" cascade;
set check_function_bodies = off;
create view "public"."ddd_changed" as SELECT basetable.name,
'x'::text AS x
FROM basetable;

View File

@ -16,8 +16,6 @@ drop view if exists "public"."ddd_changed" cascade;
drop view if exists "public"."aaa_view1" cascade;
set check_function_bodies = off;
create view "public"."ddd_changed" as SELECT basetable.name,
'x'::text AS x
FROM basetable;

View File

@ -0,0 +1,5 @@
create schema x;
create table x.data(id uuid, name text);
create view x.q as select * from x.data;

View File

@ -0,0 +1,7 @@
create schema x;
create table x.t_data(id uuid, name text);
create view x.data as select * from x.t_data;
create view x.q as select * from x.data;

View File

@ -0,0 +1,18 @@
drop view if exists "x"."q" cascade;
drop table "x"."data";
create table "x"."t_data" (
"id" uuid,
"name" text
);
create view "x"."data" as SELECT t_data.id,
t_data.name
FROM x.t_data;
create view "x"."q" as SELECT data.id,
data.name
FROM x.data;

View File

@ -8,6 +8,4 @@ create type "goodschema"."sdfasdfasdf" as enum ('not shipped', 'shipped', 'deliv
alter table "goodschema"."t" add column "name" text;
set check_function_bodies = off;
create view "goodschema"."v" as SELECT 2;

View File

@ -35,15 +35,27 @@ def outs():
return io.StringIO(), io.StringIO()
def test_with_fixtures():
for FIXTURE_NAME in ["dependencies"]:
def test_deps():
for FIXTURE_NAME in ["dependencies", "dependencies2"]:
do_fixture_test(FIXTURE_NAME)
def test_everything():
for FIXTURE_NAME in ["everything"]:
do_fixture_test(FIXTURE_NAME, with_privileges=True)
def test_singleschemea():
for FIXTURE_NAME in ["singleschema"]:
do_fixture_test(FIXTURE_NAME, schema="goodschema")
def test_singleschema_ext():
for FIXTURE_NAME in ["singleschema_ext"]:
do_fixture_test(FIXTURE_NAME, create_extensions_only=True)
def test_privs():
for FIXTURE_NAME in ["privileges"]:
do_fixture_test(FIXTURE_NAME, with_privileges=True)
@ -70,10 +82,11 @@ def do_fixture_test(
out, err = outs()
assert run(args, out=out, err=err) == 3
assert out.getvalue() == ""
assert (
err.getvalue()
== "-- ERROR: destructive statements generated. Use the --unsafe flag to suppress this error.\n"
)
DESTRUCTIVE = "-- ERROR: destructive statements generated. Use the --unsafe flag to suppress this error.\n"
assert err.getvalue() == DESTRUCTIVE
args = parse_args(flags + [d0, d1])
assert args.unsafe
assert args.schema == schema