Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.d/173-preflight-extension-arrays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- The data copy preflight now names an extension that a selected table needs through an array or a domain of the extension's type, such as a `citext[]` column or a column of a domain over `citext` (chained domains included), under `extensions` and passes it to `pg_dump --extension`; before, only a direct use of the extension's type was seen, so the copy skipped the extension and the restore failed (#173)
28 changes: 22 additions & 6 deletions engine/ansible/roles/cloudfall_data_migration/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@ cloudfall_data_migration_target_tables_query: >-
# extensions: an extension the selected schemas need: one installed in a
# selected schema, one whose member object an object of a selected schema
# depends on in pg_depend (a column type, a default, an index operator
# class, a view, a trigger, a policy), or, with no schema named, every
# extension but plpgsql, since the whole dump creates them all
# class, a view, a trigger, a policy), following an array type to its
# element type and a domain to its base type, chained, since a citext[]
# column depends on _citext and only citext is the extension's member,
# or, with no schema named, every extension but plpgsql, since the whole
# dump creates them all
# policyRoles: a role a row-level security policy on a table of a selected
# schema names (pg_policy.polroles; 0 is PUBLIC)
# authReferences: when auth isn't selected, a function of a selected schema
# whose source mentions auth. or that depends on an object in auth, and a
# policy on a table of a selected schema whose USING or WITH CHECK does
cloudfall_data_migration_source_preflight_query: >-
WITH selected AS (
WITH RECURSIVE selected AS (
SELECT jsonb_array_elements_text(:'cloudfall_data_schemas'::jsonb)
AS nspname
),
Expand Down Expand Up @@ -83,15 +86,28 @@ cloudfall_data_migration_source_preflight_query: >-
WHERE d.refclassid = 'pg_catalog.pg_extension'::regclass
AND d.deptype = 'e'
),
referenced(classid, objid) AS (
SELECT d.refclassid, d.refobjid
FROM pg_catalog.pg_depend d
JOIN selected_objects o ON o.classid = d.classid AND o.objid = d.objid
UNION
SELECT 'pg_catalog.pg_type'::regclass::oid, under.oid
FROM referenced r JOIN pg_catalog.pg_type t
ON r.classid = 'pg_catalog.pg_type'::regclass AND t.oid = r.objid
CROSS JOIN LATERAL (VALUES
(CASE WHEN t.typcategory = 'A' THEN t.typelem END),
(CASE WHEN t.typtype = 'd' THEN t.typbasetype END)
) AS under(oid)
WHERE under.oid <> 0
),
needed AS (
SELECT e.extname FROM pg_catalog.pg_extension e
WHERE (:'cloudfall_data_every_schema'::boolean AND e.extname <> 'plpgsql')
OR e.extnamespace IN (SELECT oid FROM selected_ns)
OR e.oid IN (
SELECT m.extoid
FROM pg_catalog.pg_depend d
JOIN selected_objects o ON o.classid = d.classid AND o.objid = d.objid
JOIN members m ON m.classid = d.refclassid AND m.objid = d.refobjid
FROM referenced r JOIN members m
ON m.classid = r.classid AND m.objid = r.objid
UNION
SELECT d.refobjid
FROM pg_catalog.pg_depend d
Expand Down
17 changes: 17 additions & 0 deletions engine/tests/test_data_migration_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,23 @@ def test_s007_7_an_extension_the_target_lacks_is_refused() -> None:
assert "e.extname <> 'plpgsql'" in sql


def test_s007_7_an_array_or_domain_of_an_extension_type_names_it() -> None:
sql = _source_preflight_sql(["public"], every_schema=False)
needed = sql[sql.index("needed AS (") : sql.index("missing AS (")]

# A citext[] column depends on _citext, which depends on citext with
# deptype 'i', not on the extension: the walk follows an array to its
# element type and a domain to its base type, chained, before it
# matches extension members, in the one query that feeds both the
# extensions finding and pg_dump --extension.
assert sql.startswith("WITH RECURSIVE selected AS (")
assert "referenced(classid, objid) AS (" in sql
assert "FROM referenced r JOIN pg_catalog.pg_type t" in sql
assert "WHEN t.typcategory = 'A' THEN t.typelem" in sql
assert "WHEN t.typtype = 'd' THEN t.typbasetype" in sql
assert "FROM referenced r JOIN members m" in needed


def test_s007_7_named_schemas_dump_their_needed_extensions() -> None:
shell = _module(
"Dump the source database on the target host", "ansible.builtin.shell"
Expand Down
Loading