Merge lp:~benji/launchpad/bug-697735 into lp:launchpad

Proposed by Benji York
Status: Work in progress
Proposed branch: lp:~benji/launchpad/bug-697735
Merge into: lp:launchpad
Diff against target: 1661 lines (+145/-1310)
9 files modified
database/schema/patch-2208-63-0.sql (+0/-597)
database/schema/patch-2208-63-1.sql (+0/-460)
database/schema/patch-2208-63-2.sql (+0/-13)
database/schema/patch-2208-63-3.sql (+0/-55)
database/schema/patch-2208-63-4.sql (+0/-171)
lib/canonical/launchpad/webapp/errorlog.py (+14/-2)
lib/canonical/launchpad/webapp/interfaces.py (+22/-10)
lib/canonical/launchpad/webapp/tests/test_errorlog.py (+78/-2)
lib/lp_sitecustomize.py (+31/-0)
To merge this branch: bzr merge lp:~benji/launchpad/bug-697735
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+64535@code.launchpad.net

Description of the change

Bug 697735 is about OOPS reports that are generated when
zope.publisher.browser tries to apply a type conversion (e.g.,
?foo:int=7) and the conversion raises an exception. Generating an error
is fine, but we don't want OOPS reports logged when they occur.

This branch adds a marker interface that can be applied to an exception
that signals the OOPS reporting mechanism that no report should be
logged. Originally I added an underscore-prefixed attribute to
exceptions that shouldn't be recorded, but Gary suggested the
improvement of using a marker interface.

The several tests were added to
lib/canonical/launchpad/webapp/tests/test_errorlog.py cover the new
behavior.

A fair bit of mostly whitespace lint was fixed in
./lib/canonical/launchpad/webapp/interfaces.py as well.

The only slightly suboptimal part of this branch is the need to monkey
patch zope.publisher.browser (see lib/lp_sitecustomize.py) so that we
could wrap the conversion functions with a try/except to mark
ValueErrors as non-oops-report-worthy. Gary and I discussed this
aspect of the branch and felt like it was a reasonable compromise.

To post a comment you must log in.

Unmerged revisions

13204. By Benji York

freshen from devel

13203. By Benji York

freshen from devel

13202. By Benji York

fix lint

13201. By Benji York

checkpoint

13200. By Benji York

don't treat URL parameter type conversion failures as OOPS-worthy

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'database/schema/patch-2208-63-0.sql'
2--- database/schema/patch-2208-63-0.sql 2011-06-05 07:13:43 +0000
3+++ database/schema/patch-2208-63-0.sql 1970-01-01 00:00:00 +0000
4@@ -1,597 +0,0 @@
5--- Copyright 2011 Canonical Ltd. This software is licensed under the
6--- GNU Affero General Public License version 3 (see the file LICENSE).
7-
8-SET client_min_messages=ERROR;
9-
10-CREATE TABLE BugSummary(
11- -- Slony needs a primary key and there are no natural candidates.
12- id serial PRIMARY KEY,
13- count INTEGER NOT NULL default 0,
14- product INTEGER REFERENCES Product ON DELETE CASCADE,
15- productseries INTEGER REFERENCES ProductSeries ON DELETE CASCADE,
16- distribution INTEGER REFERENCES Distribution ON DELETE CASCADE,
17- distroseries INTEGER REFERENCES DistroSeries ON DELETE CASCADE,
18- sourcepackagename INTEGER REFERENCES SourcePackageName ON DELETE CASCADE,
19- viewed_by INTEGER, -- No REFERENCES because it is trigger maintained.
20- tag TEXT,
21- status INTEGER NOT NULL,
22- milestone INTEGER REFERENCES Milestone ON DELETE CASCADE,
23- CONSTRAINT bugtask_assignment_checks CHECK (
24- CASE
25- WHEN product IS NOT NULL THEN
26- productseries IS NULL
27- AND distribution IS NULL
28- AND distroseries IS NULL
29- AND sourcepackagename IS NULL
30- WHEN productseries IS NOT NULL THEN
31- distribution IS NULL
32- AND distroseries IS NULL
33- AND sourcepackagename IS NULL
34- WHEN distribution IS NOT NULL THEN
35- distroseries IS NULL
36- WHEN distroseries IS NOT NULL THEN
37- TRUE
38- ELSE
39- FALSE
40- END)
41-);
42-
43----- Bulk load into the table - after this it is maintained by trigger. Timed
44--- at 2-3 minutes on staging.
45--- basic theory: each bug *task* has some unary dimensions (like status) and
46--- some N-ary dimensions (like contexts [sourcepackage+distro, distro only], or
47--- subscriptions, or tags). For N-ary dimensions we record the bug against all
48--- positions in that dimension.
49--- Some tasks aggregate into the same dimension - e.g. two different source
50--- packages tasks in Ubuntu. At the time of writing we only want to count those
51--- once ( because we have had user confusion when two tasks of the same bug are
52--- both counted toward portal aggregates). So we add bug.id distinct.
53--- We don't map INCOMPLETE to INCOMPLETE_WITH_RESPONSE - instead we'll let that
54--- migration happen separately.
55--- So the rules the code below should be implementing are:
56--- once for each task in a different target
57--- once for each subscription (private bugs) (left join subscribers conditionally on privacy)
58--- once for each sourcepackage name + one with sourcepackagename=NULL (two queries unioned)
59--- once for each tag + one with tag=NULL (two queries unioned)
60--- bugs with duplicateof non null are excluded because we exclude them from all our aggregates.
61-INSERT INTO bugsummary (
62- count, product, productseries, distribution, distroseries,
63- sourcepackagename, viewed_by, tag, status, milestone)
64-WITH
65- -- kill dupes
66- relevant_bug AS (SELECT * FROM bug where duplicateof is NULL),
67-
68- -- (bug.id, tag) for all bug-tag pairs plus (bug.id, NULL) for all bugs
69- bug_tags AS (
70- SELECT relevant_bug.id, NULL::text AS tag FROM relevant_bug
71- UNION
72- SELECT relevant_bug.id, tag
73- FROM relevant_bug INNER JOIN bugtag ON relevant_bug.id=bugtag.bug),
74- -- (bug.id, NULL) for all public bugs + (bug.id, viewer) for all
75- -- (subscribers+assignee) on private bugs
76- bug_viewers AS (
77- SELECT relevant_bug.id, NULL::integer AS person
78- FROM relevant_bug WHERE NOT relevant_bug.private
79- UNION
80- SELECT relevant_bug.id, assignee AS person
81- FROM relevant_bug
82- INNER JOIN bugtask ON relevant_bug.id=bugtask.bug
83- WHERE relevant_bug.private and bugtask.assignee IS NOT NULL
84- UNION
85- SELECT relevant_bug.id, bugsubscription.person
86- FROM relevant_bug INNER JOIN bugsubscription
87- ON bugsubscription.bug=relevant_bug.id WHERE relevant_bug.private),
88-
89- -- (bugtask.(bug, product, productseries, distribution, distroseries,
90- -- sourcepackagename, status, milestone) for all bugs + the same with
91- -- sourcepackage squashed to NULL)
92- tasks AS (
93- SELECT
94- bug, product, productseries, distribution, distroseries,
95- sourcepackagename, status, milestone
96- FROM bugtask
97- UNION
98- SELECT DISTINCT ON (
99- bug, product, productseries, distribution, distroseries,
100- sourcepackagename, milestone)
101- bug, product, productseries, distribution, distroseries,
102- NULL::integer as sourcepackagename,
103- status, milestone
104- FROM bugtask where sourcepackagename IS NOT NULL)
105-
106- -- Now combine
107- SELECT
108- count(*), product, productseries, distribution, distroseries,
109- sourcepackagename, person, tag, status, milestone
110- FROM relevant_bug
111- INNER JOIN bug_tags ON relevant_bug.id=bug_tags.id
112- INNER JOIN bug_viewers ON relevant_bug.id=bug_viewers.id
113- INNER JOIN tasks on tasks.bug=relevant_bug.id
114- GROUP BY
115- product, productseries, distribution, distroseries,
116- sourcepackagename, person, tag, status, milestone;
117-
118--- Need indices for FK CASCADE DELETE to find any FK easily
119-CREATE INDEX bugsummary__distribution__idx ON BugSummary (distribution)
120- WHERE distribution IS NOT NULL;
121-
122-CREATE INDEX bugsummary__distroseries__idx ON BugSummary (distroseries)
123- WHERE distroseries IS NOT NULL;
124-
125-CREATE INDEX bugsummary__viewed_by__idx ON BugSummary (viewed_by)
126- WHERE viewed_by IS NOT NULL;
127-
128-CREATE INDEX bugsummary__product__idx ON BugSummary (product)
129- WHERE product IS NOT NULL;
130-
131-CREATE INDEX bugsummary__productseries__idx ON BugSummary (productseries)
132- WHERE productseries IS NOT NULL;
133-
134--- can only have one fact row per set of dimensions
135-CREATE UNIQUE INDEX bugsummary__dimensions__unique ON bugsummary (
136- status,
137- COALESCE(product, (-1)),
138- COALESCE(productseries, (-1)),
139- COALESCE(distribution, (-1)),
140- COALESCE(distroseries, (-1)),
141- COALESCE(sourcepackagename, (-1)),
142- COALESCE(viewed_by, (-1)),
143- COALESCE(milestone, (-1)),
144- COALESCE(tag, ('')));
145-
146--- While querying is tolerably fast with the base dimension indices,
147--- we want snappy:
148--- Distribution bug counts
149-CREATE INDEX bugsummary__distribution_count__idx
150-ON BugSummary (distribution)
151-WHERE sourcepackagename IS NULL AND tag IS NULL;
152-
153--- Distribution wide tag counts
154-CREATE INDEX bugsummary__distribution_tag_count__idx
155-ON BugSummary (distribution)
156-WHERE sourcepackagename IS NULL AND tag IS NOT NULL;
157-
158--- Everything (counts)
159-CREATE INDEX bugsummary__status_count__idx
160-ON BugSummary (status)
161-WHERE sourcepackagename IS NULL AND tag IS NULL;
162-
163--- Everything (tags)
164-CREATE INDEX bugsummary__tag_count__idx
165-ON BugSummary (status)
166-WHERE sourcepackagename IS NULL AND tag IS NOT NULL;
167-
168-
169---
170--- Functions exist here for pathalogical reasons.
171---
172--- They can't go in trusted.sql at the moment, because trusted.sql is
173--- run against an empty database. If these functions where in there,
174--- it would fail because they use BugSummary table as a useful
175--- composite type.
176--- I suspect we will need to leave these function definitions in here,
177--- and move them to trusted.sql after the baseline SQL script contains
178--- the BugSummary table definition.
179-
180--- We also considered switching from one 'trusted.sql' to two files -
181--- pre_patch.sql and post_patch.sql. But that doesn't gain us much
182--- as the functions need to be declared before the triggers can be
183--- created. It would work, but we would still need stub 'forward
184--- declarations' of the functions in here, with the functions recreated
185--- with the real implementation in post_patch.sql.
186-
187-CREATE OR REPLACE FUNCTION bug_summary_inc(d bugsummary) RETURNS VOID
188-LANGUAGE plpgsql AS
189-$$
190-BEGIN
191- -- Shameless adaption from postgresql manual
192- LOOP
193- -- first try to update the row
194- UPDATE BugSummary SET count = count + 1
195- WHERE
196- product IS NOT DISTINCT FROM d.product
197- AND productseries IS NOT DISTINCT FROM d.productseries
198- AND distribution IS NOT DISTINCT FROM d.distribution
199- AND distroseries IS NOT DISTINCT FROM d.distroseries
200- AND sourcepackagename IS NOT DISTINCT FROM d.sourcepackagename
201- AND viewed_by IS NOT DISTINCT FROM d.viewed_by
202- AND tag IS NOT DISTINCT FROM d.tag
203- AND status IS NOT DISTINCT FROM d.status
204- AND milestone IS NOT DISTINCT FROM d.milestone;
205- IF found THEN
206- RETURN;
207- END IF;
208- -- not there, so try to insert the key
209- -- if someone else inserts the same key concurrently,
210- -- we could get a unique-key failure
211- BEGIN
212- INSERT INTO BugSummary(
213- count, product, productseries, distribution,
214- distroseries, sourcepackagename, viewed_by, tag,
215- status, milestone)
216- VALUES (
217- 1, d.product, d.productseries, d.distribution,
218- d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
219- d.status, d.milestone);
220- RETURN;
221- EXCEPTION WHEN unique_violation THEN
222- -- do nothing, and loop to try the UPDATE again
223- END;
224- END LOOP;
225-END;
226-$$;
227-
228-COMMENT ON FUNCTION bug_summary_inc(bugsummary) IS
229-'UPSERT into bugsummary incrementing one row';
230-
231-CREATE OR REPLACE FUNCTION bug_summary_dec(bugsummary) RETURNS VOID
232-LANGUAGE SQL AS
233-$$
234- -- We own the row reference, so in the absence of bugs this cannot
235- -- fail - just decrement the row.
236- UPDATE BugSummary SET count = count - 1
237- WHERE
238- product IS NOT DISTINCT FROM $1.product
239- AND productseries IS NOT DISTINCT FROM $1.productseries
240- AND distribution IS NOT DISTINCT FROM $1.distribution
241- AND distroseries IS NOT DISTINCT FROM $1.distroseries
242- AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename
243- AND viewed_by IS NOT DISTINCT FROM $1.viewed_by
244- AND tag IS NOT DISTINCT FROM $1.tag
245- AND status IS NOT DISTINCT FROM $1.status
246- AND milestone IS NOT DISTINCT FROM $1.milestone;
247- -- gc the row (perhaps should be garbo but easy enough to add here:
248- DELETE FROM bugsummary
249- WHERE
250- count=0
251- AND product IS NOT DISTINCT FROM $1.product
252- AND productseries IS NOT DISTINCT FROM $1.productseries
253- AND distribution IS NOT DISTINCT FROM $1.distribution
254- AND distroseries IS NOT DISTINCT FROM $1.distroseries
255- AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename
256- AND viewed_by IS NOT DISTINCT FROM $1.viewed_by
257- AND tag IS NOT DISTINCT FROM $1.tag
258- AND status IS NOT DISTINCT FROM $1.status
259- AND milestone IS NOT DISTINCT FROM $1.milestone;
260- -- If its not found then someone else also dec'd and won concurrently.
261-$$;
262-
263-COMMENT ON FUNCTION bug_summary_inc(bugsummary) IS
264-'UPSERT into bugsummary incrementing one row';
265-
266-
267-CREATE OR REPLACE FUNCTION bug_row(bug_id integer)
268-RETURNS bug LANGUAGE SQL STABLE AS
269-$$
270- SELECT * FROM Bug WHERE id=$1;
271-$$;
272-COMMENT ON FUNCTION bug_row(integer) IS
273-'Helper for manually testing functions requiring a bug row as input. eg. SELECT * FROM bugsummary_tags(bug_row(1))';
274-
275-
276-CREATE OR REPLACE FUNCTION bugsummary_viewers(BUG_ROW bug)
277-RETURNS SETOF bugsubscription LANGUAGE SQL STABLE AS
278-$$
279- SELECT *
280- FROM BugSubscription
281- WHERE
282- bugsubscription.bug=$1.id
283- AND $1.private IS TRUE;
284-$$;
285-
286-COMMENT ON FUNCTION bugsummary_viewers(bug) IS
287-'Return (bug, viewer) for all viewers if private, nothing otherwise';
288-
289-
290-CREATE OR REPLACE FUNCTION bugsummary_tags(BUG_ROW bug)
291-RETURNS SETOF bugtag LANGUAGE SQL STABLE AS
292-$$
293- SELECT * FROM BugTag WHERE BugTag.bug = $1.id
294- UNION ALL
295- SELECT NULL::integer, $1.id, NULL::text;
296-$$;
297-
298-COMMENT ON FUNCTION bugsummary_tags(bug) IS
299-'Return (bug, tag) for all tags + (bug, NULL::text)';
300-
301-
302-CREATE OR REPLACE FUNCTION bugsummary_tasks(BUG_ROW bug)
303-RETURNS SETOF bugtask LANGUAGE plpgsql STABLE AS
304-$$
305-DECLARE
306- bt bugtask%ROWTYPE;
307- r record;
308-BEGIN
309- bt.bug = BUG_ROW.id;
310-
311- -- One row only for each target permutation - need to ignore other fields
312- -- like date last modified to deal with conjoined masters and multiple
313- -- sourcepackage tasks in a distro.
314- FOR r IN
315- SELECT
316- product, productseries, distribution, distroseries,
317- sourcepackagename, status, milestone
318- FROM BugTask WHERE bug=BUG_ROW.id
319- UNION
320- SELECT
321- product, productseries, distribution, distroseries,
322- NULL, status, milestone
323- FROM BugTask WHERE bug=BUG_ROW.id AND sourcepackagename IS NOT NULL
324- LOOP
325- bt.product = r.product;
326- bt.productseries = r.productseries;
327- bt.distribution = r.distribution;
328- bt.distroseries = r.distroseries;
329- bt.sourcepackagename = r.sourcepackagename;
330- bt.status = r.status;
331- bt.milestone = r.milestone;
332- RETURN NEXT bt;
333- END LOOP;
334-END;
335-$$;
336-
337-COMMENT ON FUNCTION bugsummary_tasks(bug) IS
338-'Return all tasks for the bug + all sourcepackagename tasks again with the sourcepackagename squashed';
339-
340-
341-CREATE OR REPLACE FUNCTION bugsummary_locations(BUG_ROW bug)
342-RETURNS SETOF bugsummary LANGUAGE plpgsql AS
343-$$
344-BEGIN
345- IF BUG_ROW.duplicateof IS NOT NULL THEN
346- RETURN;
347- END IF;
348- RETURN QUERY
349- SELECT
350- CAST(NULL AS integer) AS id,
351- CAST(1 AS integer) AS count,
352- product, productseries, distribution, distroseries,
353- sourcepackagename, person AS viewed_by, tag, status, milestone
354- FROM bugsummary_tasks(BUG_ROW) AS tasks
355- JOIN bugsummary_tags(BUG_ROW) AS bug_tags ON TRUE
356- LEFT OUTER JOIN bugsummary_viewers(BUG_ROW) AS bug_viewers ON TRUE;
357-END;
358-$$;
359-
360-COMMENT ON FUNCTION bugsummary_locations(bug) IS
361-'Calculate what BugSummary rows should exist for a given Bug.';
362-
363-
364-CREATE OR REPLACE FUNCTION summarise_bug(BUG_ROW bug) RETURNS VOID
365-LANGUAGE plpgsql VOLATILE AS
366-$$
367-DECLARE
368- d bugsummary%ROWTYPE;
369-BEGIN
370- -- Grab a suitable lock before we start calculating bug summary data
371- -- to avoid race conditions. This lock allows SELECT but blocks writes.
372- LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE;
373- FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
374- PERFORM bug_summary_inc(d);
375- END LOOP;
376-END;
377-$$;
378-
379-COMMENT ON FUNCTION summarise_bug(bug) IS
380-'AFTER summarise a bug row into bugsummary.';
381-
382-
383-CREATE OR REPLACE FUNCTION unsummarise_bug(BUG_ROW bug) RETURNS VOID
384-LANGUAGE plpgsql VOLATILE AS
385-$$
386-DECLARE
387- d bugsummary%ROWTYPE;
388-BEGIN
389- -- Grab a suitable lock before we start calculating bug summary data
390- -- to avoid race conditions. This lock allows SELECT but blocks writes.
391- LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE;
392- FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
393- PERFORM bug_summary_dec(d);
394- END LOOP;
395-END;
396-$$;
397-
398-COMMENT ON FUNCTION unsummarise_bug(bug) IS
399-'AFTER unsummarise a bug row from bugsummary.';
400-
401-
402-CREATE OR REPLACE FUNCTION bug_maintain_bug_summary() RETURNS TRIGGER
403-LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
404-$$
405-BEGIN
406- -- There is no INSERT logic, as a bug will not have any summary
407- -- information until BugTask rows have been attached.
408- IF TG_OP = 'UPDATE' THEN
409- IF OLD.duplicateof IS DISTINCT FROM NEW.duplicateof
410- OR OLD.private IS DISTINCT FROM NEW.private THEN
411- PERFORM unsummarise_bug(OLD);
412- PERFORM summarise_bug(NEW);
413- END IF;
414-
415- ELSIF TG_OP = 'DELETE' THEN
416- PERFORM unsummarise_bug(OLD);
417- END IF;
418-
419- RETURN NULL; -- Ignored - this is an AFTER trigger
420-END;
421-$$;
422-
423-COMMENT ON FUNCTION bug_maintain_bug_summary() IS
424-'AFTER trigger on bug maintaining the bugs summaries in bugsummary.';
425-
426-
427-CREATE OR REPLACE FUNCTION bugtask_maintain_bug_summary() RETURNS TRIGGER
428-LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
429-$$
430-BEGIN
431- -- This trigger only works if we are inserting, updating or deleting
432- -- a single row per statement.
433-
434- -- Unlike bug_maintain_bug_summary, this trigger does not have access
435- -- to the old bug when invoked as an AFTER trigger. To work around this
436- -- we install this trigger as both a BEFORE and an AFTER trigger.
437- IF TG_OP = 'INSERT' THEN
438- IF TG_WHEN = 'BEFORE' THEN
439- PERFORM unsummarise_bug(bug_row(NEW.bug));
440- ELSE
441- PERFORM summarise_bug(bug_row(NEW.bug));
442- END IF;
443- RETURN NEW;
444-
445- ELSIF TG_OP = 'DELETE' THEN
446- IF TG_WHEN = 'BEFORE' THEN
447- PERFORM unsummarise_bug(bug_row(OLD.bug));
448- ELSE
449- PERFORM summarise_bug(bug_row(OLD.bug));
450- END IF;
451- RETURN OLD;
452-
453- ELSE
454- IF (OLD.product IS DISTINCT FROM NEW.product
455- OR OLD.productseries IS DISTINCT FROM NEW.productseries
456- OR OLD.distribution IS DISTINCT FROM NEW.distribution
457- OR OLD.distroseries IS DISTINCT FROM NEW.distroseries
458- OR OLD.sourcepackagename IS DISTINCT FROM NEW.sourcepackagename
459- OR OLD.status IS DISTINCT FROM NEW.status
460- OR OLD.milestone IS DISTINCT FROM NEW.milestone) THEN
461- IF TG_WHEN = 'BEFORE' THEN
462- PERFORM unsummarise_bug(bug_row(OLD.bug));
463- IF OLD.bug <> NEW.bug THEN
464- PERFORM unsummarise_bug(bug_row(NEW.bug));
465- END IF;
466- ELSE
467- PERFORM summarise_bug(bug_row(OLD.bug));
468- IF OLD.bug <> NEW.bug THEN
469- PERFORM summarise_bug(bug_row(NEW.bug));
470- END IF;
471- END IF;
472- END IF;
473- RETURN NEW;
474- END IF;
475-END;
476-$$;
477-
478-COMMENT ON FUNCTION bugtask_maintain_bug_summary() IS
479-'Both BEFORE & AFTER trigger on bugtask maintaining the bugs summaries in bugsummary.';
480-
481-
482-CREATE OR REPLACE FUNCTION bugsubscription_maintain_bug_summary()
483-RETURNS TRIGGER LANGUAGE plpgsql VOLATILE
484-SECURITY DEFINER SET search_path TO public AS
485-$$
486-BEGIN
487- -- This trigger only works if we are inserting, updating or deleting
488- -- a single row per statement.
489- IF TG_OP = 'INSERT' THEN
490- IF TG_WHEN = 'BEFORE' THEN
491- PERFORM unsummarise_bug(bug_row(NEW.bug));
492- ELSE
493- PERFORM summarise_bug(bug_row(NEW.bug));
494- END IF;
495- RETURN NEW;
496- ELSIF TG_OP = 'DELETE' THEN
497- IF TG_WHEN = 'BEFORE' THEN
498- PERFORM unsummarise_bug(bug_row(OLD.bug));
499- ELSE
500- PERFORM summarise_bug(bug_row(OLD.bug));
501- END IF;
502- RETURN OLD;
503- ELSE
504- IF (OLD.person IS DISTINCT FROM NEW.person
505- OR OLD.bug IS DISTINCT FROM NEW.bug) THEN
506- IF TG_WHEN = 'BEFORE' THEN
507- PERFORM unsummarise_bug(bug_row(OLD.bug));
508- IF OLD.bug <> NEW.bug THEN
509- PERFORM unsummarise_bug(bug_row(NEW.bug));
510- END IF;
511- ELSE
512- PERFORM summarise_bug(bug_row(OLD.bug));
513- IF OLD.bug <> NEW.bug THEN
514- PERFORM summarise_bug(bug_row(NEW.bug));
515- END IF;
516- END IF;
517- END IF;
518- RETURN NEW;
519- END IF;
520-END;
521-$$;
522-
523-COMMENT ON FUNCTION bugsubscription_maintain_bug_summary() IS
524-'AFTER trigger on bugsubscription maintaining the bugs summaries in bugsummary.';
525-
526-
527-CREATE OR REPLACE FUNCTION bugtag_maintain_bug_summary() RETURNS TRIGGER
528-LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
529-$$
530-BEGIN
531- IF TG_OP = 'INSERT' THEN
532- IF TG_WHEN = 'BEFORE' THEN
533- PERFORM unsummarise_bug(bug_row(NEW.bug));
534- ELSE
535- PERFORM summarise_bug(bug_row(NEW.bug));
536- END IF;
537- RETURN NEW;
538- ELSIF TG_OP = 'DELETE' THEN
539- IF TG_WHEN = 'BEFORE' THEN
540- PERFORM unsummarise_bug(bug_row(OLD.bug));
541- ELSE
542- PERFORM summarise_bug(bug_row(OLD.bug));
543- END IF;
544- RETURN OLD;
545- ELSE
546- IF TG_WHEN = 'BEFORE' THEN
547- PERFORM unsummarise_bug(bug_row(OLD.bug));
548- IF OLD.bug <> NEW.bug THEN
549- PERFORM unsummarise_bug(bug_row(NEW.bug));
550- END IF;
551- ELSE
552- PERFORM summarise_bug(bug_row(OLD.bug));
553- IF OLD.bug <> NEW.bug THEN
554- PERFORM summarise_bug(bug_row(NEW.bug));
555- END IF;
556- END IF;
557- RETURN NEW;
558- END IF;
559-END;
560-$$;
561-
562-COMMENT ON FUNCTION bugtag_maintain_bug_summary() IS
563-'AFTER trigger on bugtag maintaining the bugs summaries in bugsummary.';
564-
565-
566--- we need to maintain the summaries when things change. Each variable the
567--- population script above uses needs to be accounted for.
568-
569--- bug: duplicateof, private (not INSERT because a task is needed to be included in summaries.
570-CREATE TRIGGER bug_maintain_bug_summary_trigger
571-AFTER UPDATE OR DELETE ON bug
572-FOR EACH ROW EXECUTE PROCEDURE bug_maintain_bug_summary();
573-
574--- bugtask: target, status, milestone
575-CREATE TRIGGER bugtask_maintain_bug_summary_before_trigger
576-BEFORE INSERT OR UPDATE OR DELETE ON bugtask
577-FOR EACH ROW EXECUTE PROCEDURE bugtask_maintain_bug_summary();
578-
579-CREATE TRIGGER bugtask_maintain_bug_summary_after_trigger
580-AFTER INSERT OR UPDATE OR DELETE ON bugtask
581-FOR EACH ROW EXECUTE PROCEDURE bugtask_maintain_bug_summary();
582-
583--- bugsubscription: existence
584-CREATE TRIGGER bugsubscription_maintain_bug_summary_before_trigger
585-BEFORE INSERT OR UPDATE OR DELETE ON bugsubscription
586-FOR EACH ROW EXECUTE PROCEDURE bugsubscription_maintain_bug_summary();
587-
588-CREATE TRIGGER bugsubscription_maintain_bug_summary_after_trigger
589-AFTER INSERT OR UPDATE OR DELETE ON bugsubscription
590-FOR EACH ROW EXECUTE PROCEDURE bugsubscription_maintain_bug_summary();
591-
592--- bugtag: existence
593-CREATE TRIGGER bugtag_maintain_bug_summary_before_trigger
594-BEFORE INSERT OR UPDATE OR DELETE ON bugtag
595-FOR EACH ROW EXECUTE PROCEDURE bugtag_maintain_bug_summary();
596-
597-CREATE TRIGGER bugtag_maintain_bug_summary_after_trigger
598-AFTER INSERT OR UPDATE OR DELETE ON bugtag
599-FOR EACH ROW EXECUTE PROCEDURE bugtag_maintain_bug_summary();
600-
601-INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 0);
602
603=== removed file 'database/schema/patch-2208-63-1.sql'
604--- database/schema/patch-2208-63-1.sql 2011-06-09 12:55:22 +0000
605+++ database/schema/patch-2208-63-1.sql 1970-01-01 00:00:00 +0000
606@@ -1,460 +0,0 @@
607--- Copyright 2011 Canonical Ltd. This software is licensed under the
608--- GNU Affero General Public License version 3 (see the file LICENSE).
609-
610-SET client_min_messages=ERROR;
611-
612-CREATE OR REPLACE FUNCTION bug_summary_inc(d bugsummary) RETURNS VOID
613-LANGUAGE plpgsql AS
614-$$
615-BEGIN
616- -- Shameless adaption from postgresql manual
617- LOOP
618- -- first try to update the row
619- UPDATE BugSummary SET count = count + 1
620- WHERE
621- ((d.product IS NULL AND product IS NULL)
622- OR product = d.product)
623- AND ((d.productseries IS NULL AND productseries IS NULL)
624- OR productseries = d.productseries)
625- AND ((d.distribution IS NULL AND distribution IS NULL)
626- OR distribution = d.distribution)
627- AND ((d.distroseries IS NULL AND distroseries IS NULL)
628- OR distroseries = d.distroseries)
629- AND ((d.sourcepackagename IS NULL AND sourcepackagename IS NULL)
630- OR sourcepackagename = d.sourcepackagename)
631- AND ((d.viewed_by IS NULL AND viewed_by IS NULL)
632- OR viewed_by = d.viewed_by)
633- AND ((d.tag IS NULL AND tag IS NULL)
634- OR tag = d.tag)
635- AND ((d.status IS NULL AND status IS NULL)
636- OR status = d.status)
637- AND ((d.milestone IS NULL AND milestone IS NULL)
638- OR milestone = d.milestone);
639- IF found THEN
640- RETURN;
641- END IF;
642- -- not there, so try to insert the key
643- -- if someone else inserts the same key concurrently,
644- -- we could get a unique-key failure
645- BEGIN
646- INSERT INTO BugSummary(
647- count, product, productseries, distribution,
648- distroseries, sourcepackagename, viewed_by, tag,
649- status, milestone)
650- VALUES (
651- 1, d.product, d.productseries, d.distribution,
652- d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
653- d.status, d.milestone);
654- RETURN;
655- EXCEPTION WHEN unique_violation THEN
656- -- do nothing, and loop to try the UPDATE again
657- END;
658- END LOOP;
659-END;
660-$$;
661-
662-COMMENT ON FUNCTION bug_summary_inc(bugsummary) IS
663-'UPSERT into bugsummary incrementing one row';
664-
665-CREATE OR REPLACE FUNCTION bug_summary_dec(bugsummary) RETURNS VOID
666-LANGUAGE SQL AS
667-$$
668- -- We own the row reference, so in the absence of bugs this cannot
669- -- fail - just decrement the row.
670- UPDATE BugSummary SET count = count - 1
671- WHERE
672- (($1.product IS NULL AND product IS NULL)
673- OR product = $1.product)
674- AND (($1.productseries IS NULL AND productseries IS NULL)
675- OR productseries = $1.productseries)
676- AND (($1.distribution IS NULL AND distribution IS NULL)
677- OR distribution = $1.distribution)
678- AND (($1.distroseries IS NULL AND distroseries IS NULL)
679- OR distroseries = $1.distroseries)
680- AND (($1.sourcepackagename IS NULL AND sourcepackagename IS NULL)
681- OR sourcepackagename = $1.sourcepackagename)
682- AND (($1.viewed_by IS NULL AND viewed_by IS NULL)
683- OR viewed_by = $1.viewed_by)
684- AND (($1.tag IS NULL AND tag IS NULL)
685- OR tag = $1.tag)
686- AND (($1.status IS NULL AND status IS NULL)
687- OR status = $1.status)
688- AND (($1.milestone IS NULL AND milestone IS NULL)
689- OR milestone = $1.milestone);
690- -- gc the row (perhaps should be garbo but easy enough to add here:
691- DELETE FROM bugsummary
692- WHERE
693- count=0
694- AND (($1.product IS NULL AND product IS NULL)
695- OR product = $1.product)
696- AND (($1.productseries IS NULL AND productseries IS NULL)
697- OR productseries = $1.productseries)
698- AND (($1.distribution IS NULL AND distribution IS NULL)
699- OR distribution = $1.distribution)
700- AND (($1.distroseries IS NULL AND distroseries IS NULL)
701- OR distroseries = $1.distroseries)
702- AND (($1.sourcepackagename IS NULL AND sourcepackagename IS NULL)
703- OR sourcepackagename = $1.sourcepackagename)
704- AND (($1.viewed_by IS NULL AND viewed_by IS NULL)
705- OR viewed_by = $1.viewed_by)
706- AND (($1.tag IS NULL AND tag IS NULL)
707- OR tag = $1.tag)
708- AND (($1.status IS NULL AND status IS NULL)
709- OR status = $1.status)
710- AND (($1.milestone IS NULL AND milestone IS NULL)
711- OR milestone = $1.milestone);
712- -- If its not found then someone else also dec'd and won concurrently.
713-$$;
714-
715-
716--- bad comment fixup
717-COMMENT ON FUNCTION bug_summary_dec(bugsummary) IS
718-'UPSERT into bugsummary incrementing one row';
719-
720-CREATE OR REPLACE FUNCTION ensure_bugsummary_temp_journal() RETURNS VOID
721-LANGUAGE plpgsql VOLATILE AS
722-$$
723-DECLARE
724-BEGIN
725- CREATE TEMPORARY TABLE bugsummary_temp_journal (
726- LIKE bugsummary ) ON COMMIT DROP;
727- ALTER TABLE bugsummary_temp_journal ALTER COLUMN id DROP NOT NULL;
728- -- For safety use a unique index.
729- CREATE UNIQUE INDEX bugsummary__temp_journal__dimensions__unique ON bugsummary_temp_journal (
730- status,
731- COALESCE(product, (-1)),
732- COALESCE(productseries, (-1)),
733- COALESCE(distribution, (-1)),
734- COALESCE(distroseries, (-1)),
735- COALESCE(sourcepackagename, (-1)),
736- COALESCE(viewed_by, (-1)),
737- COALESCE(milestone, (-1)),
738- COALESCE(tag, ('')));
739-EXCEPTION
740- WHEN duplicate_table THEN
741- NULL;
742-END;
743-$$;
744-
745-COMMENT ON FUNCTION ensure_bugsummary_temp_journal() IS
746-'Create a temporary table bugsummary_temp_journal if it does not exist.';
747-
748-
749-CREATE OR REPLACE FUNCTION bug_summary_temp_journal_dec(d bugsummary) RETURNS VOID
750-LANGUAGE plpgsql AS
751-$$
752-BEGIN
753- -- We own the row reference, so in the absence of bugs this cannot
754- -- fail - just decrement the row.
755- UPDATE BugSummary_Temp_Journal SET count = count - 1
756- WHERE
757- ((d.product IS NULL AND product IS NULL)
758- OR product = d.product)
759- AND ((d.productseries IS NULL AND productseries IS NULL)
760- OR productseries = d.productseries)
761- AND ((d.distribution IS NULL AND distribution IS NULL)
762- OR distribution = d.distribution)
763- AND ((d.distroseries IS NULL AND distroseries IS NULL)
764- OR distroseries = d.distroseries)
765- AND ((d.sourcepackagename IS NULL AND sourcepackagename IS NULL)
766- OR sourcepackagename = d.sourcepackagename)
767- AND ((d.viewed_by IS NULL AND viewed_by IS NULL)
768- OR viewed_by = d.viewed_by)
769- AND ((d.tag IS NULL AND tag IS NULL)
770- OR tag = d.tag)
771- AND ((d.status IS NULL AND status IS NULL)
772- OR status = d.status)
773- AND ((d.milestone IS NULL AND milestone IS NULL)
774- OR milestone = d.milestone);
775- IF found THEN
776- RETURN;
777- END IF;
778- -- not there, so try to insert the key
779- INSERT INTO BugSummary_Temp_Journal(
780- count, product, productseries, distribution,
781- distroseries, sourcepackagename, viewed_by, tag,
782- status, milestone)
783- VALUES (
784- -1, d.product, d.productseries, d.distribution,
785- d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
786- d.status, d.milestone);
787- RETURN;
788-END;
789-$$;
790-
791-COMMENT ON FUNCTION bug_summary_temp_journal_dec(bugsummary) IS
792-'UPSERT into bugsummary_temp_journal decrementing one row';
793-
794-CREATE OR REPLACE FUNCTION bug_summary_temp_journal_inc(d bugsummary) RETURNS VOID
795-LANGUAGE plpgsql AS
796-$$
797-BEGIN
798- -- first try to update the row
799- UPDATE BugSummary_Temp_Journal SET count = count + 1
800- WHERE
801- ((d.product IS NULL AND product IS NULL)
802- OR product = d.product)
803- AND ((d.productseries IS NULL AND productseries IS NULL)
804- OR productseries = d.productseries)
805- AND ((d.distribution IS NULL AND distribution IS NULL)
806- OR distribution = d.distribution)
807- AND ((d.distroseries IS NULL AND distroseries IS NULL)
808- OR distroseries = d.distroseries)
809- AND ((d.sourcepackagename IS NULL AND sourcepackagename IS NULL)
810- OR sourcepackagename = d.sourcepackagename)
811- AND ((d.viewed_by IS NULL AND viewed_by IS NULL)
812- OR viewed_by = d.viewed_by)
813- AND ((d.tag IS NULL AND tag IS NULL)
814- OR tag = d.tag)
815- AND ((d.status IS NULL AND status IS NULL)
816- OR status = d.status)
817- AND ((d.milestone IS NULL AND milestone IS NULL)
818- OR milestone = d.milestone);
819- IF found THEN
820- RETURN;
821- END IF;
822- -- not there, so try to insert the key
823- INSERT INTO BugSummary_Temp_Journal(
824- count, product, productseries, distribution,
825- distroseries, sourcepackagename, viewed_by, tag,
826- status, milestone)
827- VALUES (
828- 1, d.product, d.productseries, d.distribution,
829- d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
830- d.status, d.milestone);
831- RETURN;
832-END;
833-$$;
834-
835-COMMENT ON FUNCTION bug_summary_temp_journal_inc(bugsummary) IS
836-'UPSERT into bugsummary incrementing one row';
837-
838-CREATE OR REPLACE FUNCTION bug_summary_flush_temp_journal() RETURNS VOID
839-LANGUAGE plpgsql VOLATILE AS
840-$$
841-DECLARE
842- d bugsummary%ROWTYPE;
843-BEGIN
844- -- may get called even though no summaries were made (for simplicity in the
845- -- callers)
846- PERFORM ensure_bugsummary_temp_journal();
847- FOR d IN SELECT * FROM bugsummary_temp_journal LOOP
848- IF d.count < 0 THEN
849- PERFORM bug_summary_dec(d);
850- ELSIF d.count > 0 THEN
851- PERFORM bug_summary_inc(d);
852- END IF;
853- END LOOP;
854- DELETE FROM bugsummary_temp_journal;
855-END;
856-$$;
857-
858-COMMENT ON FUNCTION bug_summary_flush_temp_journal() IS
859-'flush the temporary bugsummary journal into the bugsummary table';
860-
861-CREATE OR REPLACE FUNCTION unsummarise_bug(BUG_ROW bug) RETURNS VOID
862-LANGUAGE plpgsql VOLATILE AS
863-$$
864-DECLARE
865- d bugsummary%ROWTYPE;
866-BEGIN
867- PERFORM ensure_bugsummary_temp_journal();
868- FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
869- PERFORM bug_summary_temp_journal_dec(d);
870- END LOOP;
871-END;
872-$$;
873-
874-CREATE OR REPLACE FUNCTION summarise_bug(BUG_ROW bug) RETURNS VOID
875-LANGUAGE plpgsql VOLATILE AS
876-$$
877-DECLARE
878- d bugsummary%ROWTYPE;
879-BEGIN
880- PERFORM ensure_bugsummary_temp_journal();
881- FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
882- PERFORM bug_summary_temp_journal_inc(d);
883- END LOOP;
884-END;
885-$$;
886-
887--- fixed to summarise less often and use the journal.
888-CREATE OR REPLACE FUNCTION bugsubscription_maintain_bug_summary()
889-RETURNS TRIGGER LANGUAGE plpgsql VOLATILE
890-SECURITY DEFINER SET search_path TO public AS
891-$$
892-BEGIN
893- -- This trigger only works if we are inserting, updating or deleting
894- -- a single row per statement.
895- IF TG_OP = 'INSERT' THEN
896- IF NOT (bug_row(NEW.bug)).private THEN
897- -- Public subscriptions are not aggregated.
898- RETURN NEW;
899- END IF;
900- IF TG_WHEN = 'BEFORE' THEN
901- PERFORM unsummarise_bug(bug_row(NEW.bug));
902- ELSE
903- PERFORM summarise_bug(bug_row(NEW.bug));
904- END IF;
905- PERFORM bug_summary_flush_temp_journal();
906- RETURN NEW;
907- ELSIF TG_OP = 'DELETE' THEN
908- IF NOT (bug_row(OLD.bug)).private THEN
909- -- Public subscriptions are not aggregated.
910- RETURN OLD;
911- END IF;
912- IF TG_WHEN = 'BEFORE' THEN
913- PERFORM unsummarise_bug(bug_row(OLD.bug));
914- ELSE
915- PERFORM summarise_bug(bug_row(OLD.bug));
916- END IF;
917- PERFORM bug_summary_flush_temp_journal();
918- RETURN OLD;
919- ELSE
920- IF (OLD.person IS DISTINCT FROM NEW.person
921- OR OLD.bug IS DISTINCT FROM NEW.bug) THEN
922- IF TG_WHEN = 'BEFORE' THEN
923- IF (bug_row(OLD.bug)).private THEN
924- -- Public subscriptions are not aggregated.
925- PERFORM unsummarise_bug(bug_row(OLD.bug));
926- END IF;
927- IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN
928- -- Public subscriptions are not aggregated.
929- PERFORM unsummarise_bug(bug_row(NEW.bug));
930- END IF;
931- ELSE
932- IF (bug_row(OLD.bug)).private THEN
933- -- Public subscriptions are not aggregated.
934- PERFORM summarise_bug(bug_row(OLD.bug));
935- END IF;
936- IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN
937- -- Public subscriptions are not aggregated.
938- PERFORM summarise_bug(bug_row(NEW.bug));
939- END IF;
940- END IF;
941- END IF;
942- PERFORM bug_summary_flush_temp_journal();
943- RETURN NEW;
944- END IF;
945-END;
946-$$;
947-
948--- fixed to use the journal
949-CREATE OR REPLACE FUNCTION bugtag_maintain_bug_summary() RETURNS TRIGGER
950-LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
951-$$
952-BEGIN
953- IF TG_OP = 'INSERT' THEN
954- IF TG_WHEN = 'BEFORE' THEN
955- PERFORM unsummarise_bug(bug_row(NEW.bug));
956- ELSE
957- PERFORM summarise_bug(bug_row(NEW.bug));
958- END IF;
959- PERFORM bug_summary_flush_temp_journal();
960- RETURN NEW;
961- ELSIF TG_OP = 'DELETE' THEN
962- IF TG_WHEN = 'BEFORE' THEN
963- PERFORM unsummarise_bug(bug_row(OLD.bug));
964- ELSE
965- PERFORM summarise_bug(bug_row(OLD.bug));
966- END IF;
967- PERFORM bug_summary_flush_temp_journal();
968- RETURN OLD;
969- ELSE
970- IF TG_WHEN = 'BEFORE' THEN
971- PERFORM unsummarise_bug(bug_row(OLD.bug));
972- IF OLD.bug <> NEW.bug THEN
973- PERFORM unsummarise_bug(bug_row(NEW.bug));
974- END IF;
975- ELSE
976- PERFORM summarise_bug(bug_row(OLD.bug));
977- IF OLD.bug <> NEW.bug THEN
978- PERFORM summarise_bug(bug_row(NEW.bug));
979- END IF;
980- END IF;
981- PERFORM bug_summary_flush_temp_journal();
982- RETURN NEW;
983- END IF;
984-END;
985-$$;
986-
987--- fixed to use the journal
988-CREATE OR REPLACE FUNCTION bug_maintain_bug_summary() RETURNS TRIGGER
989-LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
990-$$
991-BEGIN
992- -- There is no INSERT logic, as a bug will not have any summary
993- -- information until BugTask rows have been attached.
994- IF TG_OP = 'UPDATE' THEN
995- IF OLD.duplicateof IS DISTINCT FROM NEW.duplicateof
996- OR OLD.private IS DISTINCT FROM NEW.private THEN
997- PERFORM unsummarise_bug(OLD);
998- PERFORM summarise_bug(NEW);
999- END IF;
1000-
1001- ELSIF TG_OP = 'DELETE' THEN
1002- PERFORM unsummarise_bug(OLD);
1003- END IF;
1004-
1005- PERFORM bug_summary_flush_temp_journal();
1006- RETURN NULL; -- Ignored - this is an AFTER trigger
1007-END;
1008-$$;
1009-
1010--- fixed to use the journal
1011-CREATE OR REPLACE FUNCTION bugtask_maintain_bug_summary() RETURNS TRIGGER
1012-LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
1013-$$
1014-BEGIN
1015- -- This trigger only works if we are inserting, updating or deleting
1016- -- a single row per statement.
1017-
1018- -- Unlike bug_maintain_bug_summary, this trigger does not have access
1019- -- to the old bug when invoked as an AFTER trigger. To work around this
1020- -- we install this trigger as both a BEFORE and an AFTER trigger.
1021- IF TG_OP = 'INSERT' THEN
1022- IF TG_WHEN = 'BEFORE' THEN
1023- PERFORM unsummarise_bug(bug_row(NEW.bug));
1024- ELSE
1025- PERFORM summarise_bug(bug_row(NEW.bug));
1026- END IF;
1027- PERFORM bug_summary_flush_temp_journal();
1028- RETURN NEW;
1029-
1030- ELSIF TG_OP = 'DELETE' THEN
1031- IF TG_WHEN = 'BEFORE' THEN
1032- PERFORM unsummarise_bug(bug_row(OLD.bug));
1033- ELSE
1034- PERFORM summarise_bug(bug_row(OLD.bug));
1035- END IF;
1036- PERFORM bug_summary_flush_temp_journal();
1037- RETURN OLD;
1038-
1039- ELSE
1040- IF (OLD.product IS DISTINCT FROM NEW.product
1041- OR OLD.productseries IS DISTINCT FROM NEW.productseries
1042- OR OLD.distribution IS DISTINCT FROM NEW.distribution
1043- OR OLD.distroseries IS DISTINCT FROM NEW.distroseries
1044- OR OLD.sourcepackagename IS DISTINCT FROM NEW.sourcepackagename
1045- OR OLD.status IS DISTINCT FROM NEW.status
1046- OR OLD.milestone IS DISTINCT FROM NEW.milestone) THEN
1047- IF TG_WHEN = 'BEFORE' THEN
1048- PERFORM unsummarise_bug(bug_row(OLD.bug));
1049- IF OLD.bug <> NEW.bug THEN
1050- PERFORM unsummarise_bug(bug_row(NEW.bug));
1051- END IF;
1052- ELSE
1053- PERFORM summarise_bug(bug_row(OLD.bug));
1054- IF OLD.bug <> NEW.bug THEN
1055- PERFORM summarise_bug(bug_row(NEW.bug));
1056- END IF;
1057- END IF;
1058- END IF;
1059- PERFORM bug_summary_flush_temp_journal();
1060- RETURN NEW;
1061- END IF;
1062-END;
1063-$$;
1064-
1065-
1066-INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 1);
1067
1068=== removed file 'database/schema/patch-2208-63-2.sql'
1069--- database/schema/patch-2208-63-2.sql 2011-06-09 11:50:58 +0000
1070+++ database/schema/patch-2208-63-2.sql 1970-01-01 00:00:00 +0000
1071@@ -1,13 +0,0 @@
1072--- Copyright 2011 Canonical Ltd. This software is licensed under the
1073--- GNU Affero General Public License version 3 (see the file LICENSE).
1074-
1075-SET client_min_messages=ERROR;
1076-
1077-CREATE INDEX bugsummary__milestone__idx
1078-ON BugSummary(milestone) WHERE milestone IS NOT NULL;
1079-
1080-
1081-CREATE INDEX bugsummary__full__idx
1082-ON BugSummary(status, product, productseries, distribution, distroseries, sourcepackagename, viewed_by, milestone, tag);
1083-
1084-INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 2);
1085
1086=== removed file 'database/schema/patch-2208-63-3.sql'
1087--- database/schema/patch-2208-63-3.sql 2011-06-09 22:27:34 +0000
1088+++ database/schema/patch-2208-63-3.sql 1970-01-01 00:00:00 +0000
1089@@ -1,55 +0,0 @@
1090--- Copyright 2011 Canonical Ltd. This software is licensed under the
1091--- GNU Affero General Public License version 3 (see the file LICENSE).
1092-
1093-SET client_min_messages=ERROR;
1094-
1095-
1096--- Create a journal for BugSummary updates.
1097--- This is a separate DB patch as the table needs to be created and
1098--- added to replication before triggers are created, and we want to
1099--- do this live. We discussed not replicating this table, but this
1100--- would break our ability to failover to a new master.
1101-
1102-CREATE TABLE BugSummaryJournal (
1103- id serial PRIMARY KEY,
1104- count INTEGER NOT NULL default 0,
1105- product INTEGER REFERENCES Product ON DELETE CASCADE,
1106- productseries INTEGER REFERENCES ProductSeries ON DELETE CASCADE,
1107- distribution INTEGER REFERENCES Distribution ON DELETE CASCADE,
1108- distroseries INTEGER REFERENCES DistroSeries ON DELETE CASCADE,
1109- sourcepackagename INTEGER REFERENCES SourcePackageName ON DELETE CASCADE,
1110- viewed_by INTEGER,
1111- tag TEXT,
1112- status INTEGER NOT NULL,
1113- milestone INTEGER REFERENCES Milestone ON DELETE CASCADE);
1114-
1115--- Fat index for fast lookups
1116-CREATE INDEX bugsummaryjournal__full__idx ON BugSummaryJournal (
1117- status, product, productseries, distribution, distroseries,
1118- sourcepackagename, viewed_by, milestone, tag);
1119-
1120--- Indexes for fast deletions.
1121-CREATE INDEX bugsummaryjournal__viewed_by__idx
1122- ON BugSummaryJournal(viewed_by) WHERE viewed_by IS NOT NULL;
1123-CREATE INDEX bugsummaryjournal__milestone__idx
1124- ON BugSummaryJournal(milestone) WHERE milestone IS NOT NULL;
1125-
1126-
1127--- Combined view so we don't have to manually collate rows from both tables.
1128--- Note that we flip the sign of the id column of BugSummaryJournal to avoid
1129--- clashes. This is enough to keep Storm happy as it never needs to update
1130--- this table, and there are no other suitable primary keys.
1131--- We don't SUM() rows here to ensure PostgreSQL has the most hope of
1132--- generating good query plans when we query this view.
1133-CREATE OR REPLACE VIEW CombinedBugSummary AS (
1134- SELECT
1135- id, count, product, productseries, distribution, distroseries,
1136- sourcepackagename, viewed_by, tag, status, milestone
1137- FROM BugSummary
1138- UNION ALL
1139- SELECT
1140- -id as id, count, product, productseries, distribution, distroseries,
1141- sourcepackagename, viewed_by, tag, status, milestone
1142- FROM BugSummaryJournal);
1143-
1144-INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 3);
1145
1146=== removed file 'database/schema/patch-2208-63-4.sql'
1147--- database/schema/patch-2208-63-4.sql 2011-06-09 20:46:15 +0000
1148+++ database/schema/patch-2208-63-4.sql 1970-01-01 00:00:00 +0000
1149@@ -1,171 +0,0 @@
1150--- Copyright 2011 Canonical Ltd. This software is licensed under the
1151--- GNU Affero General Public License version 3 (see the file LICENSE).
1152-
1153-SET client_min_messages=ERROR;
1154-
1155-CREATE OR REPLACE FUNCTION bugsummary_journal_ins(d bugsummary)
1156-RETURNS VOID
1157-LANGUAGE plpgsql AS
1158-$$
1159-BEGIN
1160- IF d.count <> 0 THEN
1161- INSERT INTO BugSummaryJournal (
1162- count, product, productseries, distribution,
1163- distroseries, sourcepackagename, viewed_by, tag,
1164- status, milestone)
1165- VALUES (
1166- d.count, d.product, d.productseries, d.distribution,
1167- d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
1168- d.status, d.milestone);
1169- END IF;
1170-END;
1171-$$;
1172-
1173-COMMENT ON FUNCTION bugsummary_journal_ins(bugsummary) IS
1174-'Add an entry into BugSummaryJournal';
1175-
1176-
1177-CREATE OR REPLACE FUNCTION bugsummary_rollup_journal() RETURNS VOID
1178-LANGUAGE plpgsql VOLATILE
1179-SECURITY DEFINER SET search_path TO public AS
1180-$$
1181-DECLARE
1182- d bugsummary%ROWTYPE;
1183- max_id integer;
1184-BEGIN
1185- -- Lock so we don't content with other invokations of this
1186- -- function. We can happily lock the BugSummary table for writes
1187- -- as this function is the only thing that updates that table.
1188- -- BugSummaryJournal remains unlocked so nothing should be blocked.
1189- LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE;
1190-
1191- SELECT MAX(id) INTO max_id FROM BugSummaryJournal;
1192-
1193- FOR d IN
1194- SELECT
1195- NULL as id,
1196- SUM(count),
1197- product,
1198- productseries,
1199- distribution,
1200- distroseries,
1201- sourcepackagename,
1202- viewed_by,
1203- tag,
1204- status,
1205- milestone
1206- FROM BugSummaryJournal
1207- WHERE id <= max_id
1208- GROUP BY
1209- product, productseries, distribution, distroseries,
1210- sourcepackagename, viewed_by, tag, status, milestone
1211- HAVING sum(count) <> 0
1212- LOOP
1213- IF d.count < 0 THEN
1214- PERFORM bug_summary_dec(d);
1215- ELSIF d.count > 0 THEN
1216- PERFORM bug_summary_inc(d);
1217- END IF;
1218- END LOOP;
1219-
1220- DELETE FROM BugSummaryJournal WHERE id <= max_id;
1221-END;
1222-$$;
1223-
1224-CREATE OR REPLACE FUNCTION bug_summary_dec(bugsummary) RETURNS VOID
1225-LANGUAGE SQL AS
1226-$$
1227- -- We own the row reference, so in the absence of bugs this cannot
1228- -- fail - just decrement the row.
1229- UPDATE BugSummary SET count = count + $1.count
1230- WHERE
1231- product IS NOT DISTINCT FROM $1.product
1232- AND productseries IS NOT DISTINCT FROM $1.productseries
1233- AND distribution IS NOT DISTINCT FROM $1.distribution
1234- AND distroseries IS NOT DISTINCT FROM $1.distroseries
1235- AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename
1236- AND viewed_by IS NOT DISTINCT FROM $1.viewed_by
1237- AND tag IS NOT DISTINCT FROM $1.tag
1238- AND status IS NOT DISTINCT FROM $1.status
1239- AND milestone IS NOT DISTINCT FROM $1.milestone;
1240- -- gc the row (perhaps should be garbo but easy enough to add here:
1241- DELETE FROM bugsummary
1242- WHERE
1243- count=0
1244- AND product IS NOT DISTINCT FROM $1.product
1245- AND productseries IS NOT DISTINCT FROM $1.productseries
1246- AND distribution IS NOT DISTINCT FROM $1.distribution
1247- AND distroseries IS NOT DISTINCT FROM $1.distroseries
1248- AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename
1249- AND viewed_by IS NOT DISTINCT FROM $1.viewed_by
1250- AND tag IS NOT DISTINCT FROM $1.tag
1251- AND status IS NOT DISTINCT FROM $1.status
1252- AND milestone IS NOT DISTINCT FROM $1.milestone;
1253- -- If its not found then someone else also dec'd and won concurrently.
1254-$$;
1255-
1256-CREATE OR REPLACE FUNCTION bug_summary_inc(d bugsummary) RETURNS VOID
1257-LANGUAGE plpgsql AS
1258-$$
1259-BEGIN
1260- -- Shameless adaption from postgresql manual
1261- LOOP
1262- -- first try to update the row
1263- UPDATE BugSummary SET count = count + d.count
1264- WHERE
1265- product IS NOT DISTINCT FROM d.product
1266- AND productseries IS NOT DISTINCT FROM d.productseries
1267- AND distribution IS NOT DISTINCT FROM d.distribution
1268- AND distroseries IS NOT DISTINCT FROM d.distroseries
1269- AND sourcepackagename IS NOT DISTINCT FROM d.sourcepackagename
1270- AND viewed_by IS NOT DISTINCT FROM d.viewed_by
1271- AND tag IS NOT DISTINCT FROM d.tag
1272- AND status IS NOT DISTINCT FROM d.status
1273- AND milestone IS NOT DISTINCT FROM d.milestone;
1274- IF found THEN
1275- RETURN;
1276- END IF;
1277- -- not there, so try to insert the key
1278- -- if someone else inserts the same key concurrently,
1279- -- we could get a unique-key failure
1280- BEGIN
1281- INSERT INTO BugSummary(
1282- count, product, productseries, distribution,
1283- distroseries, sourcepackagename, viewed_by, tag,
1284- status, milestone)
1285- VALUES (
1286- d.count, d.product, d.productseries, d.distribution,
1287- d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
1288- d.status, d.milestone);
1289- RETURN;
1290- EXCEPTION WHEN unique_violation THEN
1291- -- do nothing, and loop to try the UPDATE again
1292- END;
1293- END LOOP;
1294-END;
1295-$$;
1296-
1297-COMMENT ON FUNCTION bugsummary_rollup_journal() IS
1298-'Collate and migrate rows from BugSummaryJournal to BugSummary';
1299-
1300-CREATE OR REPLACE FUNCTION bug_summary_flush_temp_journal() RETURNS VOID
1301-LANGUAGE plpgsql VOLATILE AS
1302-$$
1303-DECLARE
1304- d bugsummary%ROWTYPE;
1305-BEGIN
1306- -- may get called even though no summaries were made (for simplicity in the
1307- -- callers)
1308- PERFORM ensure_bugsummary_temp_journal();
1309- FOR d IN SELECT * FROM bugsummary_temp_journal LOOP
1310- PERFORM bugsummary_journal_ins(d);
1311- END LOOP;
1312- TRUNCATE bugsummary_temp_journal;
1313-END;
1314-$$;
1315-
1316-COMMENT ON FUNCTION bug_summary_flush_temp_journal() IS
1317-'flush the temporary bugsummary journal into the bugsummaryjournal table';
1318-
1319-
1320-INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 4);
1321
1322=== modified file 'lib/canonical/launchpad/webapp/errorlog.py'
1323--- lib/canonical/launchpad/webapp/errorlog.py 2011-06-01 19:23:24 +0000
1324+++ lib/canonical/launchpad/webapp/errorlog.py 2011-06-14 11:48:38 +0000
1325@@ -40,6 +40,7 @@
1326 IErrorReport,
1327 IErrorReportEvent,
1328 IErrorReportRequest,
1329+ IUnloggedException,
1330 )
1331 from canonical.launchpad.webapp.opstats import OpStats
1332 from canonical.launchpad.webapp.vhosts import allvhosts
1333@@ -372,7 +373,18 @@
1334 notify(ErrorReportEvent(entry))
1335 return entry
1336
1337- def _isIgnoredException(self, strtype, request=None):
1338+ def _isIgnoredException(self, strtype, request=None, exception=None):
1339+ """Should the given exception generate an OOPS or be ignored?
1340+
1341+ Exceptions will be ignored if they
1342+ - are specially tagged as being ignorable by having the marker
1343+ interface IUnloggedException
1344+ - are of a type included in self._ignored_exceptions, or
1345+ - were requested with an off-site REFERRER header and are of a
1346+ type included in self._ignored_exceptions_for_offsite_referer
1347+ """
1348+ if IUnloggedException.providedBy(exception):
1349+ return True
1350 if strtype in self._ignored_exceptions:
1351 return True
1352 if strtype in self._ignored_exceptions_for_offsite_referer:
1353@@ -409,7 +421,7 @@
1354 tb_text = None
1355
1356 strtype = str(getattr(info[0], '__name__', info[0]))
1357- if self._isIgnoredException(strtype, request):
1358+ if self._isIgnoredException(strtype, request, info[1]):
1359 return
1360
1361 if not isinstance(info[2], basestring):
1362
1363=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
1364--- lib/canonical/launchpad/webapp/interfaces.py 2011-04-27 16:01:06 +0000
1365+++ lib/canonical/launchpad/webapp/interfaces.py 2011-06-14 11:48:38 +0000
1366@@ -178,7 +178,8 @@
1367 class ILink(ILinkData):
1368 """An object that represents a link in a menu.
1369
1370- The attributes name, url and linked may be set by the menus infrastructure.
1371+ The attributes name, url and linked may be set by the menus
1372+ infrastructure.
1373 """
1374
1375 name = Attribute("The name of this link in Python data structures.")
1376@@ -262,6 +263,7 @@
1377 (object_url_requested_for, broken_link_in_chain)
1378 )
1379
1380+
1381 # XXX kiko 2007-02-08: this needs reconsideration if we are to make it a truly
1382 # generic thing. The problem lies in the fact that half of this (user, login,
1383 # time zone, developer) is actually useful inside webapp/, and the other half
1384@@ -307,6 +309,7 @@
1385 connection blows up.
1386 '''
1387
1388+
1389 #
1390 # Request
1391 #
1392@@ -406,6 +409,7 @@
1393
1394 class CookieAuthLoggedInEvent:
1395 implements(ILoggedInEvent)
1396+
1397 def __init__(self, request, login):
1398 self.request = request
1399 self.login = login
1400@@ -413,6 +417,7 @@
1401
1402 class CookieAuthPrincipalIdentifiedEvent:
1403 implements(IPrincipalIdentifiedEvent)
1404+
1405 def __init__(self, principal, request, login):
1406 self.principal = principal
1407 self.request = request
1408@@ -421,6 +426,7 @@
1409
1410 class BasicAuthLoggedInEvent:
1411 implements(ILoggedInEvent, IPrincipalIdentifiedEvent)
1412+
1413 def __init__(self, request, login, principal):
1414 # these one from ILoggedInEvent
1415 self.login = login
1416@@ -436,6 +442,7 @@
1417
1418 class LoggedOutEvent:
1419 implements(ILoggedOutEvent)
1420+
1421 def __init__(self, request):
1422 self.request = request
1423
1424@@ -527,6 +534,7 @@
1425 you're using right now.
1426 """)
1427
1428+
1429 class AccessLevel(DBEnumeratedType):
1430 """The level of access any given principal has."""
1431 use_template(OAuthPermission, exclude='UNAUTHORIZED')
1432@@ -553,10 +561,10 @@
1433
1434 class BrowserNotificationLevel:
1435 """Matches the standard logging levels."""
1436- DEBUG = logging.DEBUG # A debugging message
1437- INFO = logging.INFO # simple confirmation of a change
1438- WARNING = logging.WARNING # action will not be successful unless you ...
1439- ERROR = logging.ERROR # the previous action did not succeed, and why
1440+ DEBUG = logging.DEBUG # debugging message
1441+ INFO = logging.INFO # simple confirmation of a change
1442+ WARNING = logging.WARNING # action will not be successful unless you ...
1443+ ERROR = logging.ERROR # the previous action did not succeed, and why
1444
1445 ALL_LEVELS = (DEBUG, INFO, WARNING, ERROR)
1446
1447@@ -646,6 +654,10 @@
1448 """
1449
1450
1451+class IUnloggedException(Interface):
1452+ """An exception that should not be logged in an OOPS report (marker)."""
1453+
1454+
1455 class IErrorReportEvent(IObjectEvent):
1456 """A new error report has been created."""
1457
1458@@ -673,6 +685,7 @@
1459 description=u"""an identifier for the exception, or None if no
1460 exception has occurred""")
1461
1462+
1463 #
1464 # Batch Navigation
1465 #
1466@@ -720,12 +733,12 @@
1467 # Database policies
1468 #
1469
1470-MAIN_STORE = 'main' # The main database.
1471+MAIN_STORE = 'main' # The main database.
1472 ALL_STORES = frozenset([MAIN_STORE])
1473
1474-DEFAULT_FLAVOR = 'default' # Default flavor for current state.
1475-MASTER_FLAVOR = 'master' # The master database.
1476-SLAVE_FLAVOR = 'slave' # A slave database.
1477+DEFAULT_FLAVOR = 'default' # Default flavor for current state.
1478+MASTER_FLAVOR = 'master' # The master database.
1479+SLAVE_FLAVOR = 'slave' # A slave database.
1480
1481
1482 class IDatabasePolicy(Interface):
1483@@ -856,7 +869,6 @@
1484
1485 request = Attribute("The request the event is about")
1486
1487-
1488 class StartRequestEvent:
1489 """An event fired once at the start of requests.
1490
1491
1492=== modified file 'lib/canonical/launchpad/webapp/tests/test_errorlog.py'
1493--- lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-06-01 19:35:22 +0000
1494+++ lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-06-14 11:48:38 +0000
1495@@ -41,7 +41,10 @@
1496 OopsLoggingHandler,
1497 ScriptRequest,
1498 )
1499-from canonical.launchpad.webapp.interfaces import NoReferrerError
1500+from canonical.launchpad.webapp.interfaces import (
1501+ IUnloggedException,
1502+ NoReferrerError,
1503+ )
1504 from canonical.testing import reset_logging
1505 from lp.app.errors import (
1506 GoneError,
1507@@ -50,6 +53,7 @@
1508 from lp.services.log.uniquefileallocator import UniqueFileAllocator
1509 from lp.services.osutils import remove_tree
1510 from lp.testing import TestCase
1511+from lp_sitecustomize import customize_get_converter
1512
1513
1514 UTC = pytz.timezone('UTC')
1515@@ -967,7 +971,7 @@
1516 self.assertIs(None, self.error_utility.getLastOopsReport())
1517
1518
1519-class Test404Oops(testtools.TestCase):
1520+class TestOopsIgnoring(testtools.TestCase):
1521
1522 def test_offsite_404_ignored(self):
1523 # A request originating from another site that generates a NotFound
1524@@ -990,6 +994,78 @@
1525 request = dict()
1526 self.assertTrue(utility._isIgnoredException('NotFound', request))
1527
1528+ def test_marked_exception_is_ignored(self):
1529+ # If an exception has been marked as ignorable, then it is ignored.
1530+ utility = ErrorReportingUtility()
1531+ exception = Exception()
1532+ directlyProvides(exception, IUnloggedException)
1533+ self.assertTrue(
1534+ utility._isIgnoredException('RuntimeError', exception=exception))
1535+
1536+ def test_unmarked_exception_generates_oops(self):
1537+ # If an exception has not been marked as ignorable, then it is not.
1538+ utility = ErrorReportingUtility()
1539+ exception = Exception()
1540+ self.assertFalse(
1541+ utility._isIgnoredException('RuntimeError', exception=exception))
1542+
1543+
1544+class TestWrappedParameterConverter(testtools.TestCase):
1545+ """Make sure URL parameter type conversions don't generate OOPS reports"""
1546+
1547+ def test_return_value_untouched(self):
1548+ # When a converter succeeds, its return value is passed through the
1549+ # wrapper untouched.
1550+
1551+ class FauxZopePublisherBrowserModule:
1552+ def get_converter(self, type_):
1553+ def the_converter(value):
1554+ return 'converted %r to %s' % (value, type_)
1555+ return the_converter
1556+
1557+ module = FauxZopePublisherBrowserModule()
1558+ customize_get_converter(module)
1559+ converter = module.get_converter('int')
1560+ self.assertEqual("converted '42' to int", converter('42'))
1561+
1562+ def test_value_errors_marked(self):
1563+ # When a ValueError is raised by the wrapped converter, the exception
1564+ # is marked with IUnloggedException so the OOPS machinery knows that a
1565+ # report should not be logged.
1566+
1567+ class FauxZopePublisherBrowserModule:
1568+ def get_converter(self, type_):
1569+ def the_converter(value):
1570+ raise ValueError
1571+ return the_converter
1572+
1573+ module = FauxZopePublisherBrowserModule()
1574+ customize_get_converter(module)
1575+ converter = module.get_converter('int')
1576+ try:
1577+ converter(42)
1578+ except ValueError, e:
1579+ self.assertTrue(IUnloggedException.providedBy(e))
1580+
1581+ def test_other_errors_not_marked(self):
1582+ # When an exception other than ValueError is raised by the wrapped
1583+ # converter, the exception is not marked with IUnloggedException an
1584+ # OOPS report will be created.
1585+
1586+ class FauxZopePublisherBrowserModule:
1587+ def get_converter(self, type_):
1588+ def the_converter(value):
1589+ raise RuntimeError
1590+ return the_converter
1591+
1592+ module = FauxZopePublisherBrowserModule()
1593+ customize_get_converter(module)
1594+ converter = module.get_converter('int')
1595+ try:
1596+ converter(42)
1597+ except RuntimeError, e:
1598+ self.assertFalse(IUnloggedException.providedBy(e))
1599+
1600
1601 def test_suite():
1602 return unittest.TestLoader().loadTestsFromName(__name__)
1603
1604=== modified file 'lib/lp_sitecustomize.py'
1605--- lib/lp_sitecustomize.py 2011-04-05 12:41:25 +0000
1606+++ lib/lp_sitecustomize.py 2011-06-14 11:48:38 +0000
1607@@ -16,12 +16,15 @@
1608 )
1609
1610 from bzrlib.branch import Branch
1611+from canonical.launchpad.webapp.interfaces import IUnloggedException
1612 from lp.services.log import loglevels
1613 from lp.services.log.logger import LaunchpadLogger
1614 from lp.services.log.mappingfilter import MappingFilter
1615 from lp.services.log.nullhandler import NullHandler
1616 from lp.services.mime import customizeMimetypes
1617+from zope.interface import alsoProvides
1618 from zope.security import checker
1619+import zope.publisher.browser
1620
1621
1622 def add_custom_loglevels():
1623@@ -136,6 +139,33 @@
1624 silence_transaction_logger()
1625
1626
1627+def customize_get_converter(zope_publisher_browser=zope.publisher.browser):
1628+ """URL parameter conversion errors shouldn't generate an OOPS report.
1629+
1630+ This injects (monkey patches) our wrapper around get_converter so improper
1631+ use of parameter type converters (like http://...?foo=bar:int) won't
1632+ generate OOPS reports.
1633+ """
1634+
1635+ original_get_converter = zope_publisher_browser.get_converter
1636+
1637+ def get_converter(*args, **kws):
1638+ """Get a type converter but turn off OOPS reporting if it fails."""
1639+ converter = original_get_converter(*args, **kws)
1640+
1641+ def wrapped_converter(v):
1642+ try:
1643+ return converter(v)
1644+ except ValueError, e:
1645+ # Mark the exception as not being OOPS-worthy.
1646+ alsoProvides(e, IUnloggedException)
1647+ raise
1648+
1649+ return wrapped_converter
1650+
1651+ zope_publisher_browser.get_converter = get_converter
1652+
1653+
1654 def main(instance_name):
1655 # This is called by our custom buildout-generated sitecustomize.py
1656 # in parts/scripts/sitecustomize.py. The instance name is sent to
1657@@ -161,3 +191,4 @@
1658 checker.BasicTypes[grouper] = checker._iteratorChecker
1659 silence_warnings()
1660 customize_logger()
1661+ customize_get_converter()