During the review I wanted to see if a few changes in approach were viable, but ended up going further than expected and wound up with a solution that is structured a bit differently, and doesn't use any dynamic queries at all: https://paste.ubuntu.com/p/SQzFYMBBqZ/
Thoughts on that? Diff comments: > diff --git a/database/schema/patch-2210-06-0.sql > b/database/schema/patch-2210-06-0.sql > new file mode 100644 > index 0000000..b411d69 > --- /dev/null > +++ b/database/schema/patch-2210-06-0.sql > @@ -0,0 +1,251 @@ > +-- Copyright 2019 Canonical Ltd. This software is licensed under the > +-- GNU Affero General Public License version 3 (see the file LICENSE). > + > +SET client_min_messages=ERROR; > + > +-- Rewrite the BugSummaryJournal maintenance triggers to make use of the new > +-- transition tables provided to AFTER ... FOR EACH STATEMENT triggers as of > +-- PostgreSQL 10. Instead of using row-level triggers which accumulate > +-- changes in a temporary table and flush it into the journal, we now write > +-- directly to the journal at the end of each statement. > + > +DROP TRIGGER bugtaskflat_maintain_bug_summary ON bugtaskflat; > +DROP TRIGGER bugtag_maintain_bug_summary_before_trigger ON bugtag; > +DROP TRIGGER bugtag_maintain_bug_summary_after_trigger ON bugtag; > + > +-- Similar to the previous bugsummary_tags, but returns an array of tags on > +-- the given bug plus NULL; this can be passed to other functions, and is > +-- suitable for constructing a set of rows to insert into BugSummaryJournal > +-- when handling changes to BugTaskFlat. > +CREATE OR REPLACE FUNCTION bugsummary_tag_names(bug integer) > + RETURNS text[] > + LANGUAGE sql STABLE > + AS $_$ > + SELECT array_agg(tag) > + FROM ( > + SELECT BugTag.tag FROM BugTag WHERE BugTag.bug = $1 > + UNION ALL > + SELECT NULL::text > + ) AS tag; > +$_$; > + > +COMMENT ON FUNCTION bugsummary_tag_names IS TIL that this is valid syntax as of PostgreSQL 10: "Allow the specification of a function name without arguments in DDL commands, if it is unique (Peter Eisentraut)" > + 'Return an array of the tag names on the given bug, plus NULL; this is > suitable for constructing BugSummaryJournal rows when handling changes to > BugTaskFlat.'; > + > +-- Modify the existing bugsummary_locations to accept an array of tags > +-- rather than looking them up for itself. > +DROP FUNCTION bugsummary_locations; > +CREATE FUNCTION bugsummary_locations(btf_row bugtaskflat, tags text[]) > + RETURNS SETOF bugsummary > + LANGUAGE plpgsql > + AS $$ > +BEGIN > + IF btf_row.duplicateof IS NOT NULL THEN > + RETURN; > + END IF; > + RETURN QUERY > + SELECT > + CAST(NULL AS integer) AS id, > + CAST(1 AS integer) AS count, > + bug_targets.product, bug_targets.productseries, > + bug_targets.distribution, bug_targets.distroseries, > + bug_targets.sourcepackagename, > + bug_viewers.viewed_by, bug_tags.tag, btf_row.status, > + btf_row.milestone, btf_row.importance, > + btf_row.latest_patch_uploaded IS NOT NULL AS has_patch, > + bug_viewers.access_policy > + FROM > + bugsummary_targets(btf_row) AS bug_targets, > + unnest(tags) AS bug_tags (tag), > + bugsummary_viewers(btf_row) AS bug_viewers; > +END; > +$$; > + > +-- Modify the existing bugsummary_journal_bugtaskflat to accept an array of > +-- tags, and to return a set of rows to the caller rather than inserting > +-- them into a temporary table. This can now be just SQL rather than > +-- PL/pgSQL. > +DROP FUNCTION bugsummary_journal_bugtaskflat; > +CREATE FUNCTION bugsummary_journal_bugtaskflat(btf_row bugtaskflat, tags > text[], _count integer) > + RETURNS SETOF bugsummaryjournal > + LANGUAGE sql > + AS $$ > + SELECT > + CAST(NULL AS integer) as id, > + _count, product, productseries, distribution, > + distroseries, sourcepackagename, viewed_by, tag, > + status, milestone, importance, has_patch, access_policy > + FROM bugsummary_locations(btf_row, tags); > +$$; > + > +-- Rewrite the existing bugtaskflat_maintain_bug_summary as a > +-- statement-level trigger. > +CREATE OR REPLACE FUNCTION bugtaskflat_maintain_bug_summary() > + RETURNS trigger > + LANGUAGE plpgsql SECURITY DEFINER > + SET search_path TO 'public' > + AS $$ > +DECLARE > + summary_queries text[] DEFAULT '{}'; > +BEGIN > + -- Work out the subqueries we need to compute the set of > + -- BugSummaryJournal rows that should be inserted. (We have to use > + -- dynamic commands for this, because the transition tables are not > + -- visible in functions called by this trigger function.) > + IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN > + summary_queries := array_append(summary_queries, $_$ > + SELECT summary.* > + FROM > + old_bugtaskflat btf_row, > + LATERAL bugsummary_journal_bugtaskflat( > + btf_row, bugsummary_tag_names(btf_row.bug), -1 > + ) AS summary > + $_$); > + END IF; > + IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN > + summary_queries := array_append(summary_queries, $_$ > + SELECT summary.* > + FROM > + new_bugtaskflat btf_row, > + LATERAL bugsummary_journal_bugtaskflat( > + btf_row, bugsummary_tag_names(btf_row.bug), 1 > + ) AS summary > + $_$); > + END IF; > + -- We sum the rows here to minimise the number of inserts into the > + -- journal, as in the case of UPDATE statement we may have -1s and +1s > + -- cancelling each other out. > + EXECUTE ($_$ > + INSERT INTO BugSummaryJournal( > + count, product, productseries, distribution, > + distroseries, sourcepackagename, viewed_by, tag, > + status, milestone, importance, has_patch, access_policy) > + SELECT > + SUM(count), product, productseries, distribution, > + distroseries, sourcepackagename, viewed_by, tag, > + status, milestone, importance, has_patch, access_policy > + FROM ( > + $_$ || array_to_string(summary_queries, 'UNION ALL') || $_$ > + ) AS summary > + GROUP BY > + product, productseries, distribution, > + distroseries, sourcepackagename, viewed_by, tag, > + status, milestone, importance, has_patch, access_policy > + HAVING SUM(count) != 0; > + $_$); > + RETURN NULL; > +END; > +$$; > + > +CREATE TRIGGER bugtaskflat_maintain_bug_summary_insert > + AFTER INSERT ON bugtaskflat > + REFERENCING NEW TABLE AS new_bugtaskflat > + FOR EACH STATEMENT EXECUTE PROCEDURE bugtaskflat_maintain_bug_summary(); > + > +CREATE TRIGGER bugtaskflat_maintain_bug_summary_update > + AFTER UPDATE ON bugtaskflat > + REFERENCING OLD TABLE AS old_bugtaskflat NEW TABLE AS new_bugtaskflat > + FOR EACH STATEMENT EXECUTE PROCEDURE bugtaskflat_maintain_bug_summary(); > + > +CREATE TRIGGER bugtaskflat_maintain_bug_summary_delete > + AFTER DELETE ON bugtaskflat > + REFERENCING OLD TABLE AS old_bugtaskflat > + FOR EACH STATEMENT EXECUTE PROCEDURE bugtaskflat_maintain_bug_summary(); > + > +-- Modify the existing bugsummary_journal_bug to accept an array of tags, > +-- and to return a set of rows to the caller rather than inserting them into > +-- a temporary table. Rephrasing the loop using LATERAL allows this to be > +-- just SQL rather than PL/pgSQL. > +DROP FUNCTION bugsummary_journal_bug; > +CREATE FUNCTION bugsummary_journal_bug(bug_row bug, tags text[], _count > integer) > + RETURNS SETOF bugsummaryjournal > + LANGUAGE sql > + AS $$ > + SELECT summary.* > + FROM > + bugtaskflat btf_row, > + LATERAL bugsummary_journal_bugtaskflat( > + btf_row, tags, _count > + ) AS summary > + WHERE bug = bug_row.id; > +$$; > + > +-- Rewrite the existing bugtag_maintain_bug_summary as a statement-level > +-- trigger. > +CREATE OR REPLACE FUNCTION bugtag_maintain_bug_summary() > + RETURNS trigger > + LANGUAGE plpgsql SECURITY DEFINER > + SET search_path TO 'public' > + AS $$ > +DECLARE > + summary_queries text[] DEFAULT '{}'; > +BEGIN > + -- Work out the subqueries we need to compute the set of > + -- BugSummaryJournal rows that should be inserted. (We have to use > + -- dynamic commands for this, because the transition tables are not > + -- visible in functions called by this trigger function.) > + IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN > + summary_queries := array_append(summary_queries, $_$ > + SELECT summary.* > + FROM > + (SELECT bug, array_agg(tag) AS tags > + FROM old_bugtag > + GROUP BY bug) AS grouped_bugtags, > + LATERAL bugsummary_journal_bug( > + bug_row(grouped_bugtags.bug), grouped_bugtags.tags, -1 > + ) AS summary > + $_$); > + END IF; > + IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN > + summary_queries := array_append(summary_queries, $_$ > + SELECT summary.* > + FROM > + (SELECT bug, array_agg(tag) AS tags > + FROM new_bugtag > + GROUP BY bug) AS grouped_bugtags, > + LATERAL bugsummary_journal_bug( > + bug_row(grouped_bugtags.bug), grouped_bugtags.tags, 1 > + ) AS summary > + $_$); > + END IF; > + -- We sum the rows here to minimise the number of inserts into the > + -- journal, as in the case of UPDATE statement we may have -1s and +1s > + -- cancelling each other out. > + EXECUTE ($_$ > + INSERT INTO BugSummaryJournal( > + count, product, productseries, distribution, > + distroseries, sourcepackagename, viewed_by, tag, > + status, milestone, importance, has_patch, access_policy) > + SELECT > + SUM(count), product, productseries, distribution, > + distroseries, sourcepackagename, viewed_by, tag, > + status, milestone, importance, has_patch, access_policy > + FROM ( > + $_$ || array_to_string(summary_queries, 'UNION ALL') || $_$ > + ) AS summary > + GROUP BY > + product, productseries, distribution, > + distroseries, sourcepackagename, viewed_by, tag, > + status, milestone, importance, has_patch, access_policy > + HAVING SUM(count) != 0; > + $_$); > + RETURN NULL; > +END; > +$$; > + > +CREATE TRIGGER bugtag_maintain_bug_summary_insert > + AFTER INSERT ON bugtag > + REFERENCING NEW TABLE AS new_bugtag > + FOR EACH STATEMENT EXECUTE PROCEDURE bugtag_maintain_bug_summary(); > + > +CREATE TRIGGER bugtag_maintain_bug_summary_update > + AFTER UPDATE ON bugtag > + REFERENCING OLD TABLE AS old_bugtag NEW TABLE AS new_bugtag > + FOR EACH STATEMENT EXECUTE PROCEDURE bugtag_maintain_bug_summary(); > + > +CREATE TRIGGER bugtag_maintain_bug_summary_delete > + AFTER DELETE ON bugtag > + REFERENCING OLD TABLE AS old_bugtag > + FOR EACH STATEMENT EXECUTE PROCEDURE bugtag_maintain_bug_summary(); > + > +INSERT INTO LaunchpadDatabaseRevision VALUES (2210, 06, 0); -- https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/373765 Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:db-bugsummary-statement-triggers into launchpad:db-devel. _______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp