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
=== removed file 'database/schema/patch-2208-63-0.sql'
--- database/schema/patch-2208-63-0.sql 2011-06-05 07:13:43 +0000
+++ database/schema/patch-2208-63-0.sql 1970-01-01 00:00:00 +0000
@@ -1,597 +0,0 @@
1-- Copyright 2011 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6CREATE TABLE BugSummary(
7 -- Slony needs a primary key and there are no natural candidates.
8 id serial PRIMARY KEY,
9 count INTEGER NOT NULL default 0,
10 product INTEGER REFERENCES Product ON DELETE CASCADE,
11 productseries INTEGER REFERENCES ProductSeries ON DELETE CASCADE,
12 distribution INTEGER REFERENCES Distribution ON DELETE CASCADE,
13 distroseries INTEGER REFERENCES DistroSeries ON DELETE CASCADE,
14 sourcepackagename INTEGER REFERENCES SourcePackageName ON DELETE CASCADE,
15 viewed_by INTEGER, -- No REFERENCES because it is trigger maintained.
16 tag TEXT,
17 status INTEGER NOT NULL,
18 milestone INTEGER REFERENCES Milestone ON DELETE CASCADE,
19 CONSTRAINT bugtask_assignment_checks CHECK (
20 CASE
21 WHEN product IS NOT NULL THEN
22 productseries IS NULL
23 AND distribution IS NULL
24 AND distroseries IS NULL
25 AND sourcepackagename IS NULL
26 WHEN productseries IS NOT NULL THEN
27 distribution IS NULL
28 AND distroseries IS NULL
29 AND sourcepackagename IS NULL
30 WHEN distribution IS NOT NULL THEN
31 distroseries IS NULL
32 WHEN distroseries IS NOT NULL THEN
33 TRUE
34 ELSE
35 FALSE
36 END)
37);
38
39---- Bulk load into the table - after this it is maintained by trigger. Timed
40-- at 2-3 minutes on staging.
41-- basic theory: each bug *task* has some unary dimensions (like status) and
42-- some N-ary dimensions (like contexts [sourcepackage+distro, distro only], or
43-- subscriptions, or tags). For N-ary dimensions we record the bug against all
44-- positions in that dimension.
45-- Some tasks aggregate into the same dimension - e.g. two different source
46-- packages tasks in Ubuntu. At the time of writing we only want to count those
47-- once ( because we have had user confusion when two tasks of the same bug are
48-- both counted toward portal aggregates). So we add bug.id distinct.
49-- We don't map INCOMPLETE to INCOMPLETE_WITH_RESPONSE - instead we'll let that
50-- migration happen separately.
51-- So the rules the code below should be implementing are:
52-- once for each task in a different target
53-- once for each subscription (private bugs) (left join subscribers conditionally on privacy)
54-- once for each sourcepackage name + one with sourcepackagename=NULL (two queries unioned)
55-- once for each tag + one with tag=NULL (two queries unioned)
56-- bugs with duplicateof non null are excluded because we exclude them from all our aggregates.
57INSERT INTO bugsummary (
58 count, product, productseries, distribution, distroseries,
59 sourcepackagename, viewed_by, tag, status, milestone)
60WITH
61 -- kill dupes
62 relevant_bug AS (SELECT * FROM bug where duplicateof is NULL),
63
64 -- (bug.id, tag) for all bug-tag pairs plus (bug.id, NULL) for all bugs
65 bug_tags AS (
66 SELECT relevant_bug.id, NULL::text AS tag FROM relevant_bug
67 UNION
68 SELECT relevant_bug.id, tag
69 FROM relevant_bug INNER JOIN bugtag ON relevant_bug.id=bugtag.bug),
70 -- (bug.id, NULL) for all public bugs + (bug.id, viewer) for all
71 -- (subscribers+assignee) on private bugs
72 bug_viewers AS (
73 SELECT relevant_bug.id, NULL::integer AS person
74 FROM relevant_bug WHERE NOT relevant_bug.private
75 UNION
76 SELECT relevant_bug.id, assignee AS person
77 FROM relevant_bug
78 INNER JOIN bugtask ON relevant_bug.id=bugtask.bug
79 WHERE relevant_bug.private and bugtask.assignee IS NOT NULL
80 UNION
81 SELECT relevant_bug.id, bugsubscription.person
82 FROM relevant_bug INNER JOIN bugsubscription
83 ON bugsubscription.bug=relevant_bug.id WHERE relevant_bug.private),
84
85 -- (bugtask.(bug, product, productseries, distribution, distroseries,
86 -- sourcepackagename, status, milestone) for all bugs + the same with
87 -- sourcepackage squashed to NULL)
88 tasks AS (
89 SELECT
90 bug, product, productseries, distribution, distroseries,
91 sourcepackagename, status, milestone
92 FROM bugtask
93 UNION
94 SELECT DISTINCT ON (
95 bug, product, productseries, distribution, distroseries,
96 sourcepackagename, milestone)
97 bug, product, productseries, distribution, distroseries,
98 NULL::integer as sourcepackagename,
99 status, milestone
100 FROM bugtask where sourcepackagename IS NOT NULL)
101
102 -- Now combine
103 SELECT
104 count(*), product, productseries, distribution, distroseries,
105 sourcepackagename, person, tag, status, milestone
106 FROM relevant_bug
107 INNER JOIN bug_tags ON relevant_bug.id=bug_tags.id
108 INNER JOIN bug_viewers ON relevant_bug.id=bug_viewers.id
109 INNER JOIN tasks on tasks.bug=relevant_bug.id
110 GROUP BY
111 product, productseries, distribution, distroseries,
112 sourcepackagename, person, tag, status, milestone;
113
114-- Need indices for FK CASCADE DELETE to find any FK easily
115CREATE INDEX bugsummary__distribution__idx ON BugSummary (distribution)
116 WHERE distribution IS NOT NULL;
117
118CREATE INDEX bugsummary__distroseries__idx ON BugSummary (distroseries)
119 WHERE distroseries IS NOT NULL;
120
121CREATE INDEX bugsummary__viewed_by__idx ON BugSummary (viewed_by)
122 WHERE viewed_by IS NOT NULL;
123
124CREATE INDEX bugsummary__product__idx ON BugSummary (product)
125 WHERE product IS NOT NULL;
126
127CREATE INDEX bugsummary__productseries__idx ON BugSummary (productseries)
128 WHERE productseries IS NOT NULL;
129
130-- can only have one fact row per set of dimensions
131CREATE UNIQUE INDEX bugsummary__dimensions__unique ON bugsummary (
132 status,
133 COALESCE(product, (-1)),
134 COALESCE(productseries, (-1)),
135 COALESCE(distribution, (-1)),
136 COALESCE(distroseries, (-1)),
137 COALESCE(sourcepackagename, (-1)),
138 COALESCE(viewed_by, (-1)),
139 COALESCE(milestone, (-1)),
140 COALESCE(tag, ('')));
141
142-- While querying is tolerably fast with the base dimension indices,
143-- we want snappy:
144-- Distribution bug counts
145CREATE INDEX bugsummary__distribution_count__idx
146ON BugSummary (distribution)
147WHERE sourcepackagename IS NULL AND tag IS NULL;
148
149-- Distribution wide tag counts
150CREATE INDEX bugsummary__distribution_tag_count__idx
151ON BugSummary (distribution)
152WHERE sourcepackagename IS NULL AND tag IS NOT NULL;
153
154-- Everything (counts)
155CREATE INDEX bugsummary__status_count__idx
156ON BugSummary (status)
157WHERE sourcepackagename IS NULL AND tag IS NULL;
158
159-- Everything (tags)
160CREATE INDEX bugsummary__tag_count__idx
161ON BugSummary (status)
162WHERE sourcepackagename IS NULL AND tag IS NOT NULL;
163
164
165--
166-- Functions exist here for pathalogical reasons.
167--
168-- They can't go in trusted.sql at the moment, because trusted.sql is
169-- run against an empty database. If these functions where in there,
170-- it would fail because they use BugSummary table as a useful
171-- composite type.
172-- I suspect we will need to leave these function definitions in here,
173-- and move them to trusted.sql after the baseline SQL script contains
174-- the BugSummary table definition.
175
176-- We also considered switching from one 'trusted.sql' to two files -
177-- pre_patch.sql and post_patch.sql. But that doesn't gain us much
178-- as the functions need to be declared before the triggers can be
179-- created. It would work, but we would still need stub 'forward
180-- declarations' of the functions in here, with the functions recreated
181-- with the real implementation in post_patch.sql.
182
183CREATE OR REPLACE FUNCTION bug_summary_inc(d bugsummary) RETURNS VOID
184LANGUAGE plpgsql AS
185$$
186BEGIN
187 -- Shameless adaption from postgresql manual
188 LOOP
189 -- first try to update the row
190 UPDATE BugSummary SET count = count + 1
191 WHERE
192 product IS NOT DISTINCT FROM d.product
193 AND productseries IS NOT DISTINCT FROM d.productseries
194 AND distribution IS NOT DISTINCT FROM d.distribution
195 AND distroseries IS NOT DISTINCT FROM d.distroseries
196 AND sourcepackagename IS NOT DISTINCT FROM d.sourcepackagename
197 AND viewed_by IS NOT DISTINCT FROM d.viewed_by
198 AND tag IS NOT DISTINCT FROM d.tag
199 AND status IS NOT DISTINCT FROM d.status
200 AND milestone IS NOT DISTINCT FROM d.milestone;
201 IF found THEN
202 RETURN;
203 END IF;
204 -- not there, so try to insert the key
205 -- if someone else inserts the same key concurrently,
206 -- we could get a unique-key failure
207 BEGIN
208 INSERT INTO BugSummary(
209 count, product, productseries, distribution,
210 distroseries, sourcepackagename, viewed_by, tag,
211 status, milestone)
212 VALUES (
213 1, d.product, d.productseries, d.distribution,
214 d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
215 d.status, d.milestone);
216 RETURN;
217 EXCEPTION WHEN unique_violation THEN
218 -- do nothing, and loop to try the UPDATE again
219 END;
220 END LOOP;
221END;
222$$;
223
224COMMENT ON FUNCTION bug_summary_inc(bugsummary) IS
225'UPSERT into bugsummary incrementing one row';
226
227CREATE OR REPLACE FUNCTION bug_summary_dec(bugsummary) RETURNS VOID
228LANGUAGE SQL AS
229$$
230 -- We own the row reference, so in the absence of bugs this cannot
231 -- fail - just decrement the row.
232 UPDATE BugSummary SET count = count - 1
233 WHERE
234 product IS NOT DISTINCT FROM $1.product
235 AND productseries IS NOT DISTINCT FROM $1.productseries
236 AND distribution IS NOT DISTINCT FROM $1.distribution
237 AND distroseries IS NOT DISTINCT FROM $1.distroseries
238 AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename
239 AND viewed_by IS NOT DISTINCT FROM $1.viewed_by
240 AND tag IS NOT DISTINCT FROM $1.tag
241 AND status IS NOT DISTINCT FROM $1.status
242 AND milestone IS NOT DISTINCT FROM $1.milestone;
243 -- gc the row (perhaps should be garbo but easy enough to add here:
244 DELETE FROM bugsummary
245 WHERE
246 count=0
247 AND product IS NOT DISTINCT FROM $1.product
248 AND productseries IS NOT DISTINCT FROM $1.productseries
249 AND distribution IS NOT DISTINCT FROM $1.distribution
250 AND distroseries IS NOT DISTINCT FROM $1.distroseries
251 AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename
252 AND viewed_by IS NOT DISTINCT FROM $1.viewed_by
253 AND tag IS NOT DISTINCT FROM $1.tag
254 AND status IS NOT DISTINCT FROM $1.status
255 AND milestone IS NOT DISTINCT FROM $1.milestone;
256 -- If its not found then someone else also dec'd and won concurrently.
257$$;
258
259COMMENT ON FUNCTION bug_summary_inc(bugsummary) IS
260'UPSERT into bugsummary incrementing one row';
261
262
263CREATE OR REPLACE FUNCTION bug_row(bug_id integer)
264RETURNS bug LANGUAGE SQL STABLE AS
265$$
266 SELECT * FROM Bug WHERE id=$1;
267$$;
268COMMENT ON FUNCTION bug_row(integer) IS
269'Helper for manually testing functions requiring a bug row as input. eg. SELECT * FROM bugsummary_tags(bug_row(1))';
270
271
272CREATE OR REPLACE FUNCTION bugsummary_viewers(BUG_ROW bug)
273RETURNS SETOF bugsubscription LANGUAGE SQL STABLE AS
274$$
275 SELECT *
276 FROM BugSubscription
277 WHERE
278 bugsubscription.bug=$1.id
279 AND $1.private IS TRUE;
280$$;
281
282COMMENT ON FUNCTION bugsummary_viewers(bug) IS
283'Return (bug, viewer) for all viewers if private, nothing otherwise';
284
285
286CREATE OR REPLACE FUNCTION bugsummary_tags(BUG_ROW bug)
287RETURNS SETOF bugtag LANGUAGE SQL STABLE AS
288$$
289 SELECT * FROM BugTag WHERE BugTag.bug = $1.id
290 UNION ALL
291 SELECT NULL::integer, $1.id, NULL::text;
292$$;
293
294COMMENT ON FUNCTION bugsummary_tags(bug) IS
295'Return (bug, tag) for all tags + (bug, NULL::text)';
296
297
298CREATE OR REPLACE FUNCTION bugsummary_tasks(BUG_ROW bug)
299RETURNS SETOF bugtask LANGUAGE plpgsql STABLE AS
300$$
301DECLARE
302 bt bugtask%ROWTYPE;
303 r record;
304BEGIN
305 bt.bug = BUG_ROW.id;
306
307 -- One row only for each target permutation - need to ignore other fields
308 -- like date last modified to deal with conjoined masters and multiple
309 -- sourcepackage tasks in a distro.
310 FOR r IN
311 SELECT
312 product, productseries, distribution, distroseries,
313 sourcepackagename, status, milestone
314 FROM BugTask WHERE bug=BUG_ROW.id
315 UNION
316 SELECT
317 product, productseries, distribution, distroseries,
318 NULL, status, milestone
319 FROM BugTask WHERE bug=BUG_ROW.id AND sourcepackagename IS NOT NULL
320 LOOP
321 bt.product = r.product;
322 bt.productseries = r.productseries;
323 bt.distribution = r.distribution;
324 bt.distroseries = r.distroseries;
325 bt.sourcepackagename = r.sourcepackagename;
326 bt.status = r.status;
327 bt.milestone = r.milestone;
328 RETURN NEXT bt;
329 END LOOP;
330END;
331$$;
332
333COMMENT ON FUNCTION bugsummary_tasks(bug) IS
334'Return all tasks for the bug + all sourcepackagename tasks again with the sourcepackagename squashed';
335
336
337CREATE OR REPLACE FUNCTION bugsummary_locations(BUG_ROW bug)
338RETURNS SETOF bugsummary LANGUAGE plpgsql AS
339$$
340BEGIN
341 IF BUG_ROW.duplicateof IS NOT NULL THEN
342 RETURN;
343 END IF;
344 RETURN QUERY
345 SELECT
346 CAST(NULL AS integer) AS id,
347 CAST(1 AS integer) AS count,
348 product, productseries, distribution, distroseries,
349 sourcepackagename, person AS viewed_by, tag, status, milestone
350 FROM bugsummary_tasks(BUG_ROW) AS tasks
351 JOIN bugsummary_tags(BUG_ROW) AS bug_tags ON TRUE
352 LEFT OUTER JOIN bugsummary_viewers(BUG_ROW) AS bug_viewers ON TRUE;
353END;
354$$;
355
356COMMENT ON FUNCTION bugsummary_locations(bug) IS
357'Calculate what BugSummary rows should exist for a given Bug.';
358
359
360CREATE OR REPLACE FUNCTION summarise_bug(BUG_ROW bug) RETURNS VOID
361LANGUAGE plpgsql VOLATILE AS
362$$
363DECLARE
364 d bugsummary%ROWTYPE;
365BEGIN
366 -- Grab a suitable lock before we start calculating bug summary data
367 -- to avoid race conditions. This lock allows SELECT but blocks writes.
368 LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE;
369 FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
370 PERFORM bug_summary_inc(d);
371 END LOOP;
372END;
373$$;
374
375COMMENT ON FUNCTION summarise_bug(bug) IS
376'AFTER summarise a bug row into bugsummary.';
377
378
379CREATE OR REPLACE FUNCTION unsummarise_bug(BUG_ROW bug) RETURNS VOID
380LANGUAGE plpgsql VOLATILE AS
381$$
382DECLARE
383 d bugsummary%ROWTYPE;
384BEGIN
385 -- Grab a suitable lock before we start calculating bug summary data
386 -- to avoid race conditions. This lock allows SELECT but blocks writes.
387 LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE;
388 FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
389 PERFORM bug_summary_dec(d);
390 END LOOP;
391END;
392$$;
393
394COMMENT ON FUNCTION unsummarise_bug(bug) IS
395'AFTER unsummarise a bug row from bugsummary.';
396
397
398CREATE OR REPLACE FUNCTION bug_maintain_bug_summary() RETURNS TRIGGER
399LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
400$$
401BEGIN
402 -- There is no INSERT logic, as a bug will not have any summary
403 -- information until BugTask rows have been attached.
404 IF TG_OP = 'UPDATE' THEN
405 IF OLD.duplicateof IS DISTINCT FROM NEW.duplicateof
406 OR OLD.private IS DISTINCT FROM NEW.private THEN
407 PERFORM unsummarise_bug(OLD);
408 PERFORM summarise_bug(NEW);
409 END IF;
410
411 ELSIF TG_OP = 'DELETE' THEN
412 PERFORM unsummarise_bug(OLD);
413 END IF;
414
415 RETURN NULL; -- Ignored - this is an AFTER trigger
416END;
417$$;
418
419COMMENT ON FUNCTION bug_maintain_bug_summary() IS
420'AFTER trigger on bug maintaining the bugs summaries in bugsummary.';
421
422
423CREATE OR REPLACE FUNCTION bugtask_maintain_bug_summary() RETURNS TRIGGER
424LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
425$$
426BEGIN
427 -- This trigger only works if we are inserting, updating or deleting
428 -- a single row per statement.
429
430 -- Unlike bug_maintain_bug_summary, this trigger does not have access
431 -- to the old bug when invoked as an AFTER trigger. To work around this
432 -- we install this trigger as both a BEFORE and an AFTER trigger.
433 IF TG_OP = 'INSERT' THEN
434 IF TG_WHEN = 'BEFORE' THEN
435 PERFORM unsummarise_bug(bug_row(NEW.bug));
436 ELSE
437 PERFORM summarise_bug(bug_row(NEW.bug));
438 END IF;
439 RETURN NEW;
440
441 ELSIF TG_OP = 'DELETE' THEN
442 IF TG_WHEN = 'BEFORE' THEN
443 PERFORM unsummarise_bug(bug_row(OLD.bug));
444 ELSE
445 PERFORM summarise_bug(bug_row(OLD.bug));
446 END IF;
447 RETURN OLD;
448
449 ELSE
450 IF (OLD.product IS DISTINCT FROM NEW.product
451 OR OLD.productseries IS DISTINCT FROM NEW.productseries
452 OR OLD.distribution IS DISTINCT FROM NEW.distribution
453 OR OLD.distroseries IS DISTINCT FROM NEW.distroseries
454 OR OLD.sourcepackagename IS DISTINCT FROM NEW.sourcepackagename
455 OR OLD.status IS DISTINCT FROM NEW.status
456 OR OLD.milestone IS DISTINCT FROM NEW.milestone) THEN
457 IF TG_WHEN = 'BEFORE' THEN
458 PERFORM unsummarise_bug(bug_row(OLD.bug));
459 IF OLD.bug <> NEW.bug THEN
460 PERFORM unsummarise_bug(bug_row(NEW.bug));
461 END IF;
462 ELSE
463 PERFORM summarise_bug(bug_row(OLD.bug));
464 IF OLD.bug <> NEW.bug THEN
465 PERFORM summarise_bug(bug_row(NEW.bug));
466 END IF;
467 END IF;
468 END IF;
469 RETURN NEW;
470 END IF;
471END;
472$$;
473
474COMMENT ON FUNCTION bugtask_maintain_bug_summary() IS
475'Both BEFORE & AFTER trigger on bugtask maintaining the bugs summaries in bugsummary.';
476
477
478CREATE OR REPLACE FUNCTION bugsubscription_maintain_bug_summary()
479RETURNS TRIGGER LANGUAGE plpgsql VOLATILE
480SECURITY DEFINER SET search_path TO public AS
481$$
482BEGIN
483 -- This trigger only works if we are inserting, updating or deleting
484 -- a single row per statement.
485 IF TG_OP = 'INSERT' THEN
486 IF TG_WHEN = 'BEFORE' THEN
487 PERFORM unsummarise_bug(bug_row(NEW.bug));
488 ELSE
489 PERFORM summarise_bug(bug_row(NEW.bug));
490 END IF;
491 RETURN NEW;
492 ELSIF TG_OP = 'DELETE' THEN
493 IF TG_WHEN = 'BEFORE' THEN
494 PERFORM unsummarise_bug(bug_row(OLD.bug));
495 ELSE
496 PERFORM summarise_bug(bug_row(OLD.bug));
497 END IF;
498 RETURN OLD;
499 ELSE
500 IF (OLD.person IS DISTINCT FROM NEW.person
501 OR OLD.bug IS DISTINCT FROM NEW.bug) THEN
502 IF TG_WHEN = 'BEFORE' THEN
503 PERFORM unsummarise_bug(bug_row(OLD.bug));
504 IF OLD.bug <> NEW.bug THEN
505 PERFORM unsummarise_bug(bug_row(NEW.bug));
506 END IF;
507 ELSE
508 PERFORM summarise_bug(bug_row(OLD.bug));
509 IF OLD.bug <> NEW.bug THEN
510 PERFORM summarise_bug(bug_row(NEW.bug));
511 END IF;
512 END IF;
513 END IF;
514 RETURN NEW;
515 END IF;
516END;
517$$;
518
519COMMENT ON FUNCTION bugsubscription_maintain_bug_summary() IS
520'AFTER trigger on bugsubscription maintaining the bugs summaries in bugsummary.';
521
522
523CREATE OR REPLACE FUNCTION bugtag_maintain_bug_summary() RETURNS TRIGGER
524LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
525$$
526BEGIN
527 IF TG_OP = 'INSERT' THEN
528 IF TG_WHEN = 'BEFORE' THEN
529 PERFORM unsummarise_bug(bug_row(NEW.bug));
530 ELSE
531 PERFORM summarise_bug(bug_row(NEW.bug));
532 END IF;
533 RETURN NEW;
534 ELSIF TG_OP = 'DELETE' THEN
535 IF TG_WHEN = 'BEFORE' THEN
536 PERFORM unsummarise_bug(bug_row(OLD.bug));
537 ELSE
538 PERFORM summarise_bug(bug_row(OLD.bug));
539 END IF;
540 RETURN OLD;
541 ELSE
542 IF TG_WHEN = 'BEFORE' THEN
543 PERFORM unsummarise_bug(bug_row(OLD.bug));
544 IF OLD.bug <> NEW.bug THEN
545 PERFORM unsummarise_bug(bug_row(NEW.bug));
546 END IF;
547 ELSE
548 PERFORM summarise_bug(bug_row(OLD.bug));
549 IF OLD.bug <> NEW.bug THEN
550 PERFORM summarise_bug(bug_row(NEW.bug));
551 END IF;
552 END IF;
553 RETURN NEW;
554 END IF;
555END;
556$$;
557
558COMMENT ON FUNCTION bugtag_maintain_bug_summary() IS
559'AFTER trigger on bugtag maintaining the bugs summaries in bugsummary.';
560
561
562-- we need to maintain the summaries when things change. Each variable the
563-- population script above uses needs to be accounted for.
564
565-- bug: duplicateof, private (not INSERT because a task is needed to be included in summaries.
566CREATE TRIGGER bug_maintain_bug_summary_trigger
567AFTER UPDATE OR DELETE ON bug
568FOR EACH ROW EXECUTE PROCEDURE bug_maintain_bug_summary();
569
570-- bugtask: target, status, milestone
571CREATE TRIGGER bugtask_maintain_bug_summary_before_trigger
572BEFORE INSERT OR UPDATE OR DELETE ON bugtask
573FOR EACH ROW EXECUTE PROCEDURE bugtask_maintain_bug_summary();
574
575CREATE TRIGGER bugtask_maintain_bug_summary_after_trigger
576AFTER INSERT OR UPDATE OR DELETE ON bugtask
577FOR EACH ROW EXECUTE PROCEDURE bugtask_maintain_bug_summary();
578
579-- bugsubscription: existence
580CREATE TRIGGER bugsubscription_maintain_bug_summary_before_trigger
581BEFORE INSERT OR UPDATE OR DELETE ON bugsubscription
582FOR EACH ROW EXECUTE PROCEDURE bugsubscription_maintain_bug_summary();
583
584CREATE TRIGGER bugsubscription_maintain_bug_summary_after_trigger
585AFTER INSERT OR UPDATE OR DELETE ON bugsubscription
586FOR EACH ROW EXECUTE PROCEDURE bugsubscription_maintain_bug_summary();
587
588-- bugtag: existence
589CREATE TRIGGER bugtag_maintain_bug_summary_before_trigger
590BEFORE INSERT OR UPDATE OR DELETE ON bugtag
591FOR EACH ROW EXECUTE PROCEDURE bugtag_maintain_bug_summary();
592
593CREATE TRIGGER bugtag_maintain_bug_summary_after_trigger
594AFTER INSERT OR UPDATE OR DELETE ON bugtag
595FOR EACH ROW EXECUTE PROCEDURE bugtag_maintain_bug_summary();
596
597INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 0);
5980
=== removed file 'database/schema/patch-2208-63-1.sql'
--- database/schema/patch-2208-63-1.sql 2011-06-09 12:55:22 +0000
+++ database/schema/patch-2208-63-1.sql 1970-01-01 00:00:00 +0000
@@ -1,460 +0,0 @@
1-- Copyright 2011 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6CREATE OR REPLACE FUNCTION bug_summary_inc(d bugsummary) RETURNS VOID
7LANGUAGE plpgsql AS
8$$
9BEGIN
10 -- Shameless adaption from postgresql manual
11 LOOP
12 -- first try to update the row
13 UPDATE BugSummary SET count = count + 1
14 WHERE
15 ((d.product IS NULL AND product IS NULL)
16 OR product = d.product)
17 AND ((d.productseries IS NULL AND productseries IS NULL)
18 OR productseries = d.productseries)
19 AND ((d.distribution IS NULL AND distribution IS NULL)
20 OR distribution = d.distribution)
21 AND ((d.distroseries IS NULL AND distroseries IS NULL)
22 OR distroseries = d.distroseries)
23 AND ((d.sourcepackagename IS NULL AND sourcepackagename IS NULL)
24 OR sourcepackagename = d.sourcepackagename)
25 AND ((d.viewed_by IS NULL AND viewed_by IS NULL)
26 OR viewed_by = d.viewed_by)
27 AND ((d.tag IS NULL AND tag IS NULL)
28 OR tag = d.tag)
29 AND ((d.status IS NULL AND status IS NULL)
30 OR status = d.status)
31 AND ((d.milestone IS NULL AND milestone IS NULL)
32 OR milestone = d.milestone);
33 IF found THEN
34 RETURN;
35 END IF;
36 -- not there, so try to insert the key
37 -- if someone else inserts the same key concurrently,
38 -- we could get a unique-key failure
39 BEGIN
40 INSERT INTO BugSummary(
41 count, product, productseries, distribution,
42 distroseries, sourcepackagename, viewed_by, tag,
43 status, milestone)
44 VALUES (
45 1, d.product, d.productseries, d.distribution,
46 d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
47 d.status, d.milestone);
48 RETURN;
49 EXCEPTION WHEN unique_violation THEN
50 -- do nothing, and loop to try the UPDATE again
51 END;
52 END LOOP;
53END;
54$$;
55
56COMMENT ON FUNCTION bug_summary_inc(bugsummary) IS
57'UPSERT into bugsummary incrementing one row';
58
59CREATE OR REPLACE FUNCTION bug_summary_dec(bugsummary) RETURNS VOID
60LANGUAGE SQL AS
61$$
62 -- We own the row reference, so in the absence of bugs this cannot
63 -- fail - just decrement the row.
64 UPDATE BugSummary SET count = count - 1
65 WHERE
66 (($1.product IS NULL AND product IS NULL)
67 OR product = $1.product)
68 AND (($1.productseries IS NULL AND productseries IS NULL)
69 OR productseries = $1.productseries)
70 AND (($1.distribution IS NULL AND distribution IS NULL)
71 OR distribution = $1.distribution)
72 AND (($1.distroseries IS NULL AND distroseries IS NULL)
73 OR distroseries = $1.distroseries)
74 AND (($1.sourcepackagename IS NULL AND sourcepackagename IS NULL)
75 OR sourcepackagename = $1.sourcepackagename)
76 AND (($1.viewed_by IS NULL AND viewed_by IS NULL)
77 OR viewed_by = $1.viewed_by)
78 AND (($1.tag IS NULL AND tag IS NULL)
79 OR tag = $1.tag)
80 AND (($1.status IS NULL AND status IS NULL)
81 OR status = $1.status)
82 AND (($1.milestone IS NULL AND milestone IS NULL)
83 OR milestone = $1.milestone);
84 -- gc the row (perhaps should be garbo but easy enough to add here:
85 DELETE FROM bugsummary
86 WHERE
87 count=0
88 AND (($1.product IS NULL AND product IS NULL)
89 OR product = $1.product)
90 AND (($1.productseries IS NULL AND productseries IS NULL)
91 OR productseries = $1.productseries)
92 AND (($1.distribution IS NULL AND distribution IS NULL)
93 OR distribution = $1.distribution)
94 AND (($1.distroseries IS NULL AND distroseries IS NULL)
95 OR distroseries = $1.distroseries)
96 AND (($1.sourcepackagename IS NULL AND sourcepackagename IS NULL)
97 OR sourcepackagename = $1.sourcepackagename)
98 AND (($1.viewed_by IS NULL AND viewed_by IS NULL)
99 OR viewed_by = $1.viewed_by)
100 AND (($1.tag IS NULL AND tag IS NULL)
101 OR tag = $1.tag)
102 AND (($1.status IS NULL AND status IS NULL)
103 OR status = $1.status)
104 AND (($1.milestone IS NULL AND milestone IS NULL)
105 OR milestone = $1.milestone);
106 -- If its not found then someone else also dec'd and won concurrently.
107$$;
108
109
110-- bad comment fixup
111COMMENT ON FUNCTION bug_summary_dec(bugsummary) IS
112'UPSERT into bugsummary incrementing one row';
113
114CREATE OR REPLACE FUNCTION ensure_bugsummary_temp_journal() RETURNS VOID
115LANGUAGE plpgsql VOLATILE AS
116$$
117DECLARE
118BEGIN
119 CREATE TEMPORARY TABLE bugsummary_temp_journal (
120 LIKE bugsummary ) ON COMMIT DROP;
121 ALTER TABLE bugsummary_temp_journal ALTER COLUMN id DROP NOT NULL;
122 -- For safety use a unique index.
123 CREATE UNIQUE INDEX bugsummary__temp_journal__dimensions__unique ON bugsummary_temp_journal (
124 status,
125 COALESCE(product, (-1)),
126 COALESCE(productseries, (-1)),
127 COALESCE(distribution, (-1)),
128 COALESCE(distroseries, (-1)),
129 COALESCE(sourcepackagename, (-1)),
130 COALESCE(viewed_by, (-1)),
131 COALESCE(milestone, (-1)),
132 COALESCE(tag, ('')));
133EXCEPTION
134 WHEN duplicate_table THEN
135 NULL;
136END;
137$$;
138
139COMMENT ON FUNCTION ensure_bugsummary_temp_journal() IS
140'Create a temporary table bugsummary_temp_journal if it does not exist.';
141
142
143CREATE OR REPLACE FUNCTION bug_summary_temp_journal_dec(d bugsummary) RETURNS VOID
144LANGUAGE plpgsql AS
145$$
146BEGIN
147 -- We own the row reference, so in the absence of bugs this cannot
148 -- fail - just decrement the row.
149 UPDATE BugSummary_Temp_Journal SET count = count - 1
150 WHERE
151 ((d.product IS NULL AND product IS NULL)
152 OR product = d.product)
153 AND ((d.productseries IS NULL AND productseries IS NULL)
154 OR productseries = d.productseries)
155 AND ((d.distribution IS NULL AND distribution IS NULL)
156 OR distribution = d.distribution)
157 AND ((d.distroseries IS NULL AND distroseries IS NULL)
158 OR distroseries = d.distroseries)
159 AND ((d.sourcepackagename IS NULL AND sourcepackagename IS NULL)
160 OR sourcepackagename = d.sourcepackagename)
161 AND ((d.viewed_by IS NULL AND viewed_by IS NULL)
162 OR viewed_by = d.viewed_by)
163 AND ((d.tag IS NULL AND tag IS NULL)
164 OR tag = d.tag)
165 AND ((d.status IS NULL AND status IS NULL)
166 OR status = d.status)
167 AND ((d.milestone IS NULL AND milestone IS NULL)
168 OR milestone = d.milestone);
169 IF found THEN
170 RETURN;
171 END IF;
172 -- not there, so try to insert the key
173 INSERT INTO BugSummary_Temp_Journal(
174 count, product, productseries, distribution,
175 distroseries, sourcepackagename, viewed_by, tag,
176 status, milestone)
177 VALUES (
178 -1, d.product, d.productseries, d.distribution,
179 d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
180 d.status, d.milestone);
181 RETURN;
182END;
183$$;
184
185COMMENT ON FUNCTION bug_summary_temp_journal_dec(bugsummary) IS
186'UPSERT into bugsummary_temp_journal decrementing one row';
187
188CREATE OR REPLACE FUNCTION bug_summary_temp_journal_inc(d bugsummary) RETURNS VOID
189LANGUAGE plpgsql AS
190$$
191BEGIN
192 -- first try to update the row
193 UPDATE BugSummary_Temp_Journal SET count = count + 1
194 WHERE
195 ((d.product IS NULL AND product IS NULL)
196 OR product = d.product)
197 AND ((d.productseries IS NULL AND productseries IS NULL)
198 OR productseries = d.productseries)
199 AND ((d.distribution IS NULL AND distribution IS NULL)
200 OR distribution = d.distribution)
201 AND ((d.distroseries IS NULL AND distroseries IS NULL)
202 OR distroseries = d.distroseries)
203 AND ((d.sourcepackagename IS NULL AND sourcepackagename IS NULL)
204 OR sourcepackagename = d.sourcepackagename)
205 AND ((d.viewed_by IS NULL AND viewed_by IS NULL)
206 OR viewed_by = d.viewed_by)
207 AND ((d.tag IS NULL AND tag IS NULL)
208 OR tag = d.tag)
209 AND ((d.status IS NULL AND status IS NULL)
210 OR status = d.status)
211 AND ((d.milestone IS NULL AND milestone IS NULL)
212 OR milestone = d.milestone);
213 IF found THEN
214 RETURN;
215 END IF;
216 -- not there, so try to insert the key
217 INSERT INTO BugSummary_Temp_Journal(
218 count, product, productseries, distribution,
219 distroseries, sourcepackagename, viewed_by, tag,
220 status, milestone)
221 VALUES (
222 1, d.product, d.productseries, d.distribution,
223 d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
224 d.status, d.milestone);
225 RETURN;
226END;
227$$;
228
229COMMENT ON FUNCTION bug_summary_temp_journal_inc(bugsummary) IS
230'UPSERT into bugsummary incrementing one row';
231
232CREATE OR REPLACE FUNCTION bug_summary_flush_temp_journal() RETURNS VOID
233LANGUAGE plpgsql VOLATILE AS
234$$
235DECLARE
236 d bugsummary%ROWTYPE;
237BEGIN
238 -- may get called even though no summaries were made (for simplicity in the
239 -- callers)
240 PERFORM ensure_bugsummary_temp_journal();
241 FOR d IN SELECT * FROM bugsummary_temp_journal LOOP
242 IF d.count < 0 THEN
243 PERFORM bug_summary_dec(d);
244 ELSIF d.count > 0 THEN
245 PERFORM bug_summary_inc(d);
246 END IF;
247 END LOOP;
248 DELETE FROM bugsummary_temp_journal;
249END;
250$$;
251
252COMMENT ON FUNCTION bug_summary_flush_temp_journal() IS
253'flush the temporary bugsummary journal into the bugsummary table';
254
255CREATE OR REPLACE FUNCTION unsummarise_bug(BUG_ROW bug) RETURNS VOID
256LANGUAGE plpgsql VOLATILE AS
257$$
258DECLARE
259 d bugsummary%ROWTYPE;
260BEGIN
261 PERFORM ensure_bugsummary_temp_journal();
262 FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
263 PERFORM bug_summary_temp_journal_dec(d);
264 END LOOP;
265END;
266$$;
267
268CREATE OR REPLACE FUNCTION summarise_bug(BUG_ROW bug) RETURNS VOID
269LANGUAGE plpgsql VOLATILE AS
270$$
271DECLARE
272 d bugsummary%ROWTYPE;
273BEGIN
274 PERFORM ensure_bugsummary_temp_journal();
275 FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
276 PERFORM bug_summary_temp_journal_inc(d);
277 END LOOP;
278END;
279$$;
280
281-- fixed to summarise less often and use the journal.
282CREATE OR REPLACE FUNCTION bugsubscription_maintain_bug_summary()
283RETURNS TRIGGER LANGUAGE plpgsql VOLATILE
284SECURITY DEFINER SET search_path TO public AS
285$$
286BEGIN
287 -- This trigger only works if we are inserting, updating or deleting
288 -- a single row per statement.
289 IF TG_OP = 'INSERT' THEN
290 IF NOT (bug_row(NEW.bug)).private THEN
291 -- Public subscriptions are not aggregated.
292 RETURN NEW;
293 END IF;
294 IF TG_WHEN = 'BEFORE' THEN
295 PERFORM unsummarise_bug(bug_row(NEW.bug));
296 ELSE
297 PERFORM summarise_bug(bug_row(NEW.bug));
298 END IF;
299 PERFORM bug_summary_flush_temp_journal();
300 RETURN NEW;
301 ELSIF TG_OP = 'DELETE' THEN
302 IF NOT (bug_row(OLD.bug)).private THEN
303 -- Public subscriptions are not aggregated.
304 RETURN OLD;
305 END IF;
306 IF TG_WHEN = 'BEFORE' THEN
307 PERFORM unsummarise_bug(bug_row(OLD.bug));
308 ELSE
309 PERFORM summarise_bug(bug_row(OLD.bug));
310 END IF;
311 PERFORM bug_summary_flush_temp_journal();
312 RETURN OLD;
313 ELSE
314 IF (OLD.person IS DISTINCT FROM NEW.person
315 OR OLD.bug IS DISTINCT FROM NEW.bug) THEN
316 IF TG_WHEN = 'BEFORE' THEN
317 IF (bug_row(OLD.bug)).private THEN
318 -- Public subscriptions are not aggregated.
319 PERFORM unsummarise_bug(bug_row(OLD.bug));
320 END IF;
321 IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN
322 -- Public subscriptions are not aggregated.
323 PERFORM unsummarise_bug(bug_row(NEW.bug));
324 END IF;
325 ELSE
326 IF (bug_row(OLD.bug)).private THEN
327 -- Public subscriptions are not aggregated.
328 PERFORM summarise_bug(bug_row(OLD.bug));
329 END IF;
330 IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN
331 -- Public subscriptions are not aggregated.
332 PERFORM summarise_bug(bug_row(NEW.bug));
333 END IF;
334 END IF;
335 END IF;
336 PERFORM bug_summary_flush_temp_journal();
337 RETURN NEW;
338 END IF;
339END;
340$$;
341
342-- fixed to use the journal
343CREATE OR REPLACE FUNCTION bugtag_maintain_bug_summary() RETURNS TRIGGER
344LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
345$$
346BEGIN
347 IF TG_OP = 'INSERT' THEN
348 IF TG_WHEN = 'BEFORE' THEN
349 PERFORM unsummarise_bug(bug_row(NEW.bug));
350 ELSE
351 PERFORM summarise_bug(bug_row(NEW.bug));
352 END IF;
353 PERFORM bug_summary_flush_temp_journal();
354 RETURN NEW;
355 ELSIF TG_OP = 'DELETE' THEN
356 IF TG_WHEN = 'BEFORE' THEN
357 PERFORM unsummarise_bug(bug_row(OLD.bug));
358 ELSE
359 PERFORM summarise_bug(bug_row(OLD.bug));
360 END IF;
361 PERFORM bug_summary_flush_temp_journal();
362 RETURN OLD;
363 ELSE
364 IF TG_WHEN = 'BEFORE' THEN
365 PERFORM unsummarise_bug(bug_row(OLD.bug));
366 IF OLD.bug <> NEW.bug THEN
367 PERFORM unsummarise_bug(bug_row(NEW.bug));
368 END IF;
369 ELSE
370 PERFORM summarise_bug(bug_row(OLD.bug));
371 IF OLD.bug <> NEW.bug THEN
372 PERFORM summarise_bug(bug_row(NEW.bug));
373 END IF;
374 END IF;
375 PERFORM bug_summary_flush_temp_journal();
376 RETURN NEW;
377 END IF;
378END;
379$$;
380
381-- fixed to use the journal
382CREATE OR REPLACE FUNCTION bug_maintain_bug_summary() RETURNS TRIGGER
383LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
384$$
385BEGIN
386 -- There is no INSERT logic, as a bug will not have any summary
387 -- information until BugTask rows have been attached.
388 IF TG_OP = 'UPDATE' THEN
389 IF OLD.duplicateof IS DISTINCT FROM NEW.duplicateof
390 OR OLD.private IS DISTINCT FROM NEW.private THEN
391 PERFORM unsummarise_bug(OLD);
392 PERFORM summarise_bug(NEW);
393 END IF;
394
395 ELSIF TG_OP = 'DELETE' THEN
396 PERFORM unsummarise_bug(OLD);
397 END IF;
398
399 PERFORM bug_summary_flush_temp_journal();
400 RETURN NULL; -- Ignored - this is an AFTER trigger
401END;
402$$;
403
404-- fixed to use the journal
405CREATE OR REPLACE FUNCTION bugtask_maintain_bug_summary() RETURNS TRIGGER
406LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS
407$$
408BEGIN
409 -- This trigger only works if we are inserting, updating or deleting
410 -- a single row per statement.
411
412 -- Unlike bug_maintain_bug_summary, this trigger does not have access
413 -- to the old bug when invoked as an AFTER trigger. To work around this
414 -- we install this trigger as both a BEFORE and an AFTER trigger.
415 IF TG_OP = 'INSERT' THEN
416 IF TG_WHEN = 'BEFORE' THEN
417 PERFORM unsummarise_bug(bug_row(NEW.bug));
418 ELSE
419 PERFORM summarise_bug(bug_row(NEW.bug));
420 END IF;
421 PERFORM bug_summary_flush_temp_journal();
422 RETURN NEW;
423
424 ELSIF TG_OP = 'DELETE' THEN
425 IF TG_WHEN = 'BEFORE' THEN
426 PERFORM unsummarise_bug(bug_row(OLD.bug));
427 ELSE
428 PERFORM summarise_bug(bug_row(OLD.bug));
429 END IF;
430 PERFORM bug_summary_flush_temp_journal();
431 RETURN OLD;
432
433 ELSE
434 IF (OLD.product IS DISTINCT FROM NEW.product
435 OR OLD.productseries IS DISTINCT FROM NEW.productseries
436 OR OLD.distribution IS DISTINCT FROM NEW.distribution
437 OR OLD.distroseries IS DISTINCT FROM NEW.distroseries
438 OR OLD.sourcepackagename IS DISTINCT FROM NEW.sourcepackagename
439 OR OLD.status IS DISTINCT FROM NEW.status
440 OR OLD.milestone IS DISTINCT FROM NEW.milestone) THEN
441 IF TG_WHEN = 'BEFORE' THEN
442 PERFORM unsummarise_bug(bug_row(OLD.bug));
443 IF OLD.bug <> NEW.bug THEN
444 PERFORM unsummarise_bug(bug_row(NEW.bug));
445 END IF;
446 ELSE
447 PERFORM summarise_bug(bug_row(OLD.bug));
448 IF OLD.bug <> NEW.bug THEN
449 PERFORM summarise_bug(bug_row(NEW.bug));
450 END IF;
451 END IF;
452 END IF;
453 PERFORM bug_summary_flush_temp_journal();
454 RETURN NEW;
455 END IF;
456END;
457$$;
458
459
460INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 1);
4610
=== removed file 'database/schema/patch-2208-63-2.sql'
--- database/schema/patch-2208-63-2.sql 2011-06-09 11:50:58 +0000
+++ database/schema/patch-2208-63-2.sql 1970-01-01 00:00:00 +0000
@@ -1,13 +0,0 @@
1-- Copyright 2011 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6CREATE INDEX bugsummary__milestone__idx
7ON BugSummary(milestone) WHERE milestone IS NOT NULL;
8
9
10CREATE INDEX bugsummary__full__idx
11ON BugSummary(status, product, productseries, distribution, distroseries, sourcepackagename, viewed_by, milestone, tag);
12
13INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 2);
140
=== removed file 'database/schema/patch-2208-63-3.sql'
--- database/schema/patch-2208-63-3.sql 2011-06-09 22:27:34 +0000
+++ database/schema/patch-2208-63-3.sql 1970-01-01 00:00:00 +0000
@@ -1,55 +0,0 @@
1-- Copyright 2011 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6
7-- Create a journal for BugSummary updates.
8-- This is a separate DB patch as the table needs to be created and
9-- added to replication before triggers are created, and we want to
10-- do this live. We discussed not replicating this table, but this
11-- would break our ability to failover to a new master.
12
13CREATE TABLE BugSummaryJournal (
14 id serial PRIMARY KEY,
15 count INTEGER NOT NULL default 0,
16 product INTEGER REFERENCES Product ON DELETE CASCADE,
17 productseries INTEGER REFERENCES ProductSeries ON DELETE CASCADE,
18 distribution INTEGER REFERENCES Distribution ON DELETE CASCADE,
19 distroseries INTEGER REFERENCES DistroSeries ON DELETE CASCADE,
20 sourcepackagename INTEGER REFERENCES SourcePackageName ON DELETE CASCADE,
21 viewed_by INTEGER,
22 tag TEXT,
23 status INTEGER NOT NULL,
24 milestone INTEGER REFERENCES Milestone ON DELETE CASCADE);
25
26-- Fat index for fast lookups
27CREATE INDEX bugsummaryjournal__full__idx ON BugSummaryJournal (
28 status, product, productseries, distribution, distroseries,
29 sourcepackagename, viewed_by, milestone, tag);
30
31-- Indexes for fast deletions.
32CREATE INDEX bugsummaryjournal__viewed_by__idx
33 ON BugSummaryJournal(viewed_by) WHERE viewed_by IS NOT NULL;
34CREATE INDEX bugsummaryjournal__milestone__idx
35 ON BugSummaryJournal(milestone) WHERE milestone IS NOT NULL;
36
37
38-- Combined view so we don't have to manually collate rows from both tables.
39-- Note that we flip the sign of the id column of BugSummaryJournal to avoid
40-- clashes. This is enough to keep Storm happy as it never needs to update
41-- this table, and there are no other suitable primary keys.
42-- We don't SUM() rows here to ensure PostgreSQL has the most hope of
43-- generating good query plans when we query this view.
44CREATE OR REPLACE VIEW CombinedBugSummary AS (
45 SELECT
46 id, count, product, productseries, distribution, distroseries,
47 sourcepackagename, viewed_by, tag, status, milestone
48 FROM BugSummary
49 UNION ALL
50 SELECT
51 -id as id, count, product, productseries, distribution, distroseries,
52 sourcepackagename, viewed_by, tag, status, milestone
53 FROM BugSummaryJournal);
54
55INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 3);
560
=== removed file 'database/schema/patch-2208-63-4.sql'
--- database/schema/patch-2208-63-4.sql 2011-06-09 20:46:15 +0000
+++ database/schema/patch-2208-63-4.sql 1970-01-01 00:00:00 +0000
@@ -1,171 +0,0 @@
1-- Copyright 2011 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6CREATE OR REPLACE FUNCTION bugsummary_journal_ins(d bugsummary)
7RETURNS VOID
8LANGUAGE plpgsql AS
9$$
10BEGIN
11 IF d.count <> 0 THEN
12 INSERT INTO BugSummaryJournal (
13 count, product, productseries, distribution,
14 distroseries, sourcepackagename, viewed_by, tag,
15 status, milestone)
16 VALUES (
17 d.count, d.product, d.productseries, d.distribution,
18 d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
19 d.status, d.milestone);
20 END IF;
21END;
22$$;
23
24COMMENT ON FUNCTION bugsummary_journal_ins(bugsummary) IS
25'Add an entry into BugSummaryJournal';
26
27
28CREATE OR REPLACE FUNCTION bugsummary_rollup_journal() RETURNS VOID
29LANGUAGE plpgsql VOLATILE
30SECURITY DEFINER SET search_path TO public AS
31$$
32DECLARE
33 d bugsummary%ROWTYPE;
34 max_id integer;
35BEGIN
36 -- Lock so we don't content with other invokations of this
37 -- function. We can happily lock the BugSummary table for writes
38 -- as this function is the only thing that updates that table.
39 -- BugSummaryJournal remains unlocked so nothing should be blocked.
40 LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE;
41
42 SELECT MAX(id) INTO max_id FROM BugSummaryJournal;
43
44 FOR d IN
45 SELECT
46 NULL as id,
47 SUM(count),
48 product,
49 productseries,
50 distribution,
51 distroseries,
52 sourcepackagename,
53 viewed_by,
54 tag,
55 status,
56 milestone
57 FROM BugSummaryJournal
58 WHERE id <= max_id
59 GROUP BY
60 product, productseries, distribution, distroseries,
61 sourcepackagename, viewed_by, tag, status, milestone
62 HAVING sum(count) <> 0
63 LOOP
64 IF d.count < 0 THEN
65 PERFORM bug_summary_dec(d);
66 ELSIF d.count > 0 THEN
67 PERFORM bug_summary_inc(d);
68 END IF;
69 END LOOP;
70
71 DELETE FROM BugSummaryJournal WHERE id <= max_id;
72END;
73$$;
74
75CREATE OR REPLACE FUNCTION bug_summary_dec(bugsummary) RETURNS VOID
76LANGUAGE SQL AS
77$$
78 -- We own the row reference, so in the absence of bugs this cannot
79 -- fail - just decrement the row.
80 UPDATE BugSummary SET count = count + $1.count
81 WHERE
82 product IS NOT DISTINCT FROM $1.product
83 AND productseries IS NOT DISTINCT FROM $1.productseries
84 AND distribution IS NOT DISTINCT FROM $1.distribution
85 AND distroseries IS NOT DISTINCT FROM $1.distroseries
86 AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename
87 AND viewed_by IS NOT DISTINCT FROM $1.viewed_by
88 AND tag IS NOT DISTINCT FROM $1.tag
89 AND status IS NOT DISTINCT FROM $1.status
90 AND milestone IS NOT DISTINCT FROM $1.milestone;
91 -- gc the row (perhaps should be garbo but easy enough to add here:
92 DELETE FROM bugsummary
93 WHERE
94 count=0
95 AND product IS NOT DISTINCT FROM $1.product
96 AND productseries IS NOT DISTINCT FROM $1.productseries
97 AND distribution IS NOT DISTINCT FROM $1.distribution
98 AND distroseries IS NOT DISTINCT FROM $1.distroseries
99 AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename
100 AND viewed_by IS NOT DISTINCT FROM $1.viewed_by
101 AND tag IS NOT DISTINCT FROM $1.tag
102 AND status IS NOT DISTINCT FROM $1.status
103 AND milestone IS NOT DISTINCT FROM $1.milestone;
104 -- If its not found then someone else also dec'd and won concurrently.
105$$;
106
107CREATE OR REPLACE FUNCTION bug_summary_inc(d bugsummary) RETURNS VOID
108LANGUAGE plpgsql AS
109$$
110BEGIN
111 -- Shameless adaption from postgresql manual
112 LOOP
113 -- first try to update the row
114 UPDATE BugSummary SET count = count + d.count
115 WHERE
116 product IS NOT DISTINCT FROM d.product
117 AND productseries IS NOT DISTINCT FROM d.productseries
118 AND distribution IS NOT DISTINCT FROM d.distribution
119 AND distroseries IS NOT DISTINCT FROM d.distroseries
120 AND sourcepackagename IS NOT DISTINCT FROM d.sourcepackagename
121 AND viewed_by IS NOT DISTINCT FROM d.viewed_by
122 AND tag IS NOT DISTINCT FROM d.tag
123 AND status IS NOT DISTINCT FROM d.status
124 AND milestone IS NOT DISTINCT FROM d.milestone;
125 IF found THEN
126 RETURN;
127 END IF;
128 -- not there, so try to insert the key
129 -- if someone else inserts the same key concurrently,
130 -- we could get a unique-key failure
131 BEGIN
132 INSERT INTO BugSummary(
133 count, product, productseries, distribution,
134 distroseries, sourcepackagename, viewed_by, tag,
135 status, milestone)
136 VALUES (
137 d.count, d.product, d.productseries, d.distribution,
138 d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
139 d.status, d.milestone);
140 RETURN;
141 EXCEPTION WHEN unique_violation THEN
142 -- do nothing, and loop to try the UPDATE again
143 END;
144 END LOOP;
145END;
146$$;
147
148COMMENT ON FUNCTION bugsummary_rollup_journal() IS
149'Collate and migrate rows from BugSummaryJournal to BugSummary';
150
151CREATE OR REPLACE FUNCTION bug_summary_flush_temp_journal() RETURNS VOID
152LANGUAGE plpgsql VOLATILE AS
153$$
154DECLARE
155 d bugsummary%ROWTYPE;
156BEGIN
157 -- may get called even though no summaries were made (for simplicity in the
158 -- callers)
159 PERFORM ensure_bugsummary_temp_journal();
160 FOR d IN SELECT * FROM bugsummary_temp_journal LOOP
161 PERFORM bugsummary_journal_ins(d);
162 END LOOP;
163 TRUNCATE bugsummary_temp_journal;
164END;
165$$;
166
167COMMENT ON FUNCTION bug_summary_flush_temp_journal() IS
168'flush the temporary bugsummary journal into the bugsummaryjournal table';
169
170
171INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 4);
1720
=== modified file 'lib/canonical/launchpad/webapp/errorlog.py'
--- lib/canonical/launchpad/webapp/errorlog.py 2011-06-01 19:23:24 +0000
+++ lib/canonical/launchpad/webapp/errorlog.py 2011-06-14 11:48:38 +0000
@@ -40,6 +40,7 @@
40 IErrorReport,40 IErrorReport,
41 IErrorReportEvent,41 IErrorReportEvent,
42 IErrorReportRequest,42 IErrorReportRequest,
43 IUnloggedException,
43 )44 )
44from canonical.launchpad.webapp.opstats import OpStats45from canonical.launchpad.webapp.opstats import OpStats
45from canonical.launchpad.webapp.vhosts import allvhosts46from canonical.launchpad.webapp.vhosts import allvhosts
@@ -372,7 +373,18 @@
372 notify(ErrorReportEvent(entry))373 notify(ErrorReportEvent(entry))
373 return entry374 return entry
374375
375 def _isIgnoredException(self, strtype, request=None):376 def _isIgnoredException(self, strtype, request=None, exception=None):
377 """Should the given exception generate an OOPS or be ignored?
378
379 Exceptions will be ignored if they
380 - are specially tagged as being ignorable by having the marker
381 interface IUnloggedException
382 - are of a type included in self._ignored_exceptions, or
383 - were requested with an off-site REFERRER header and are of a
384 type included in self._ignored_exceptions_for_offsite_referer
385 """
386 if IUnloggedException.providedBy(exception):
387 return True
376 if strtype in self._ignored_exceptions:388 if strtype in self._ignored_exceptions:
377 return True389 return True
378 if strtype in self._ignored_exceptions_for_offsite_referer:390 if strtype in self._ignored_exceptions_for_offsite_referer:
@@ -409,7 +421,7 @@
409 tb_text = None421 tb_text = None
410422
411 strtype = str(getattr(info[0], '__name__', info[0]))423 strtype = str(getattr(info[0], '__name__', info[0]))
412 if self._isIgnoredException(strtype, request):424 if self._isIgnoredException(strtype, request, info[1]):
413 return425 return
414426
415 if not isinstance(info[2], basestring):427 if not isinstance(info[2], basestring):
416428
=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
--- lib/canonical/launchpad/webapp/interfaces.py 2011-04-27 16:01:06 +0000
+++ lib/canonical/launchpad/webapp/interfaces.py 2011-06-14 11:48:38 +0000
@@ -178,7 +178,8 @@
178class ILink(ILinkData):178class ILink(ILinkData):
179 """An object that represents a link in a menu.179 """An object that represents a link in a menu.
180180
181 The attributes name, url and linked may be set by the menus infrastructure.181 The attributes name, url and linked may be set by the menus
182 infrastructure.
182 """183 """
183184
184 name = Attribute("The name of this link in Python data structures.")185 name = Attribute("The name of this link in Python data structures.")
@@ -262,6 +263,7 @@
262 (object_url_requested_for, broken_link_in_chain)263 (object_url_requested_for, broken_link_in_chain)
263 )264 )
264265
266
265# XXX kiko 2007-02-08: this needs reconsideration if we are to make it a truly267# XXX kiko 2007-02-08: this needs reconsideration if we are to make it a truly
266# generic thing. The problem lies in the fact that half of this (user, login,268# generic thing. The problem lies in the fact that half of this (user, login,
267# time zone, developer) is actually useful inside webapp/, and the other half269# time zone, developer) is actually useful inside webapp/, and the other half
@@ -307,6 +309,7 @@
307 connection blows up.309 connection blows up.
308 '''310 '''
309311
312
310#313#
311# Request314# Request
312#315#
@@ -406,6 +409,7 @@
406409
407class CookieAuthLoggedInEvent:410class CookieAuthLoggedInEvent:
408 implements(ILoggedInEvent)411 implements(ILoggedInEvent)
412
409 def __init__(self, request, login):413 def __init__(self, request, login):
410 self.request = request414 self.request = request
411 self.login = login415 self.login = login
@@ -413,6 +417,7 @@
413417
414class CookieAuthPrincipalIdentifiedEvent:418class CookieAuthPrincipalIdentifiedEvent:
415 implements(IPrincipalIdentifiedEvent)419 implements(IPrincipalIdentifiedEvent)
420
416 def __init__(self, principal, request, login):421 def __init__(self, principal, request, login):
417 self.principal = principal422 self.principal = principal
418 self.request = request423 self.request = request
@@ -421,6 +426,7 @@
421426
422class BasicAuthLoggedInEvent:427class BasicAuthLoggedInEvent:
423 implements(ILoggedInEvent, IPrincipalIdentifiedEvent)428 implements(ILoggedInEvent, IPrincipalIdentifiedEvent)
429
424 def __init__(self, request, login, principal):430 def __init__(self, request, login, principal):
425 # these one from ILoggedInEvent431 # these one from ILoggedInEvent
426 self.login = login432 self.login = login
@@ -436,6 +442,7 @@
436442
437class LoggedOutEvent:443class LoggedOutEvent:
438 implements(ILoggedOutEvent)444 implements(ILoggedOutEvent)
445
439 def __init__(self, request):446 def __init__(self, request):
440 self.request = request447 self.request = request
441448
@@ -527,6 +534,7 @@
527 you're using right now.534 you're using right now.
528 """)535 """)
529536
537
530class AccessLevel(DBEnumeratedType):538class AccessLevel(DBEnumeratedType):
531 """The level of access any given principal has."""539 """The level of access any given principal has."""
532 use_template(OAuthPermission, exclude='UNAUTHORIZED')540 use_template(OAuthPermission, exclude='UNAUTHORIZED')
@@ -553,10 +561,10 @@
553561
554class BrowserNotificationLevel:562class BrowserNotificationLevel:
555 """Matches the standard logging levels."""563 """Matches the standard logging levels."""
556 DEBUG = logging.DEBUG # A debugging message564 DEBUG = logging.DEBUG # debugging message
557 INFO = logging.INFO # simple confirmation of a change565 INFO = logging.INFO # simple confirmation of a change
558 WARNING = logging.WARNING # action will not be successful unless you ...566 WARNING = logging.WARNING # action will not be successful unless you ...
559 ERROR = logging.ERROR # the previous action did not succeed, and why567 ERROR = logging.ERROR # the previous action did not succeed, and why
560568
561 ALL_LEVELS = (DEBUG, INFO, WARNING, ERROR)569 ALL_LEVELS = (DEBUG, INFO, WARNING, ERROR)
562570
@@ -646,6 +654,10 @@
646 """654 """
647655
648656
657class IUnloggedException(Interface):
658 """An exception that should not be logged in an OOPS report (marker)."""
659
660
649class IErrorReportEvent(IObjectEvent):661class IErrorReportEvent(IObjectEvent):
650 """A new error report has been created."""662 """A new error report has been created."""
651663
@@ -673,6 +685,7 @@
673 description=u"""an identifier for the exception, or None if no685 description=u"""an identifier for the exception, or None if no
674 exception has occurred""")686 exception has occurred""")
675687
688
676#689#
677# Batch Navigation690# Batch Navigation
678#691#
@@ -720,12 +733,12 @@
720# Database policies733# Database policies
721#734#
722735
723MAIN_STORE = 'main' # The main database.736MAIN_STORE = 'main' # The main database.
724ALL_STORES = frozenset([MAIN_STORE])737ALL_STORES = frozenset([MAIN_STORE])
725738
726DEFAULT_FLAVOR = 'default' # Default flavor for current state.739DEFAULT_FLAVOR = 'default' # Default flavor for current state.
727MASTER_FLAVOR = 'master' # The master database.740MASTER_FLAVOR = 'master' # The master database.
728SLAVE_FLAVOR = 'slave' # A slave database.741SLAVE_FLAVOR = 'slave' # A slave database.
729742
730743
731class IDatabasePolicy(Interface):744class IDatabasePolicy(Interface):
@@ -856,7 +869,6 @@
856869
857 request = Attribute("The request the event is about")870 request = Attribute("The request the event is about")
858871
859
860 class StartRequestEvent:872 class StartRequestEvent:
861 """An event fired once at the start of requests.873 """An event fired once at the start of requests.
862874
863875
=== modified file 'lib/canonical/launchpad/webapp/tests/test_errorlog.py'
--- lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-06-01 19:35:22 +0000
+++ lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-06-14 11:48:38 +0000
@@ -41,7 +41,10 @@
41 OopsLoggingHandler,41 OopsLoggingHandler,
42 ScriptRequest,42 ScriptRequest,
43 )43 )
44from canonical.launchpad.webapp.interfaces import NoReferrerError44from canonical.launchpad.webapp.interfaces import (
45 IUnloggedException,
46 NoReferrerError,
47 )
45from canonical.testing import reset_logging48from canonical.testing import reset_logging
46from lp.app.errors import (49from lp.app.errors import (
47 GoneError,50 GoneError,
@@ -50,6 +53,7 @@
50from lp.services.log.uniquefileallocator import UniqueFileAllocator53from lp.services.log.uniquefileallocator import UniqueFileAllocator
51from lp.services.osutils import remove_tree54from lp.services.osutils import remove_tree
52from lp.testing import TestCase55from lp.testing import TestCase
56from lp_sitecustomize import customize_get_converter
5357
5458
55UTC = pytz.timezone('UTC')59UTC = pytz.timezone('UTC')
@@ -967,7 +971,7 @@
967 self.assertIs(None, self.error_utility.getLastOopsReport())971 self.assertIs(None, self.error_utility.getLastOopsReport())
968972
969973
970class Test404Oops(testtools.TestCase):974class TestOopsIgnoring(testtools.TestCase):
971975
972 def test_offsite_404_ignored(self):976 def test_offsite_404_ignored(self):
973 # A request originating from another site that generates a NotFound977 # A request originating from another site that generates a NotFound
@@ -990,6 +994,78 @@
990 request = dict()994 request = dict()
991 self.assertTrue(utility._isIgnoredException('NotFound', request))995 self.assertTrue(utility._isIgnoredException('NotFound', request))
992996
997 def test_marked_exception_is_ignored(self):
998 # If an exception has been marked as ignorable, then it is ignored.
999 utility = ErrorReportingUtility()
1000 exception = Exception()
1001 directlyProvides(exception, IUnloggedException)
1002 self.assertTrue(
1003 utility._isIgnoredException('RuntimeError', exception=exception))
1004
1005 def test_unmarked_exception_generates_oops(self):
1006 # If an exception has not been marked as ignorable, then it is not.
1007 utility = ErrorReportingUtility()
1008 exception = Exception()
1009 self.assertFalse(
1010 utility._isIgnoredException('RuntimeError', exception=exception))
1011
1012
1013class TestWrappedParameterConverter(testtools.TestCase):
1014 """Make sure URL parameter type conversions don't generate OOPS reports"""
1015
1016 def test_return_value_untouched(self):
1017 # When a converter succeeds, its return value is passed through the
1018 # wrapper untouched.
1019
1020 class FauxZopePublisherBrowserModule:
1021 def get_converter(self, type_):
1022 def the_converter(value):
1023 return 'converted %r to %s' % (value, type_)
1024 return the_converter
1025
1026 module = FauxZopePublisherBrowserModule()
1027 customize_get_converter(module)
1028 converter = module.get_converter('int')
1029 self.assertEqual("converted '42' to int", converter('42'))
1030
1031 def test_value_errors_marked(self):
1032 # When a ValueError is raised by the wrapped converter, the exception
1033 # is marked with IUnloggedException so the OOPS machinery knows that a
1034 # report should not be logged.
1035
1036 class FauxZopePublisherBrowserModule:
1037 def get_converter(self, type_):
1038 def the_converter(value):
1039 raise ValueError
1040 return the_converter
1041
1042 module = FauxZopePublisherBrowserModule()
1043 customize_get_converter(module)
1044 converter = module.get_converter('int')
1045 try:
1046 converter(42)
1047 except ValueError, e:
1048 self.assertTrue(IUnloggedException.providedBy(e))
1049
1050 def test_other_errors_not_marked(self):
1051 # When an exception other than ValueError is raised by the wrapped
1052 # converter, the exception is not marked with IUnloggedException an
1053 # OOPS report will be created.
1054
1055 class FauxZopePublisherBrowserModule:
1056 def get_converter(self, type_):
1057 def the_converter(value):
1058 raise RuntimeError
1059 return the_converter
1060
1061 module = FauxZopePublisherBrowserModule()
1062 customize_get_converter(module)
1063 converter = module.get_converter('int')
1064 try:
1065 converter(42)
1066 except RuntimeError, e:
1067 self.assertFalse(IUnloggedException.providedBy(e))
1068
9931069
994def test_suite():1070def test_suite():
995 return unittest.TestLoader().loadTestsFromName(__name__)1071 return unittest.TestLoader().loadTestsFromName(__name__)
9961072
=== modified file 'lib/lp_sitecustomize.py'
--- lib/lp_sitecustomize.py 2011-04-05 12:41:25 +0000
+++ lib/lp_sitecustomize.py 2011-06-14 11:48:38 +0000
@@ -16,12 +16,15 @@
16 )16 )
1717
18from bzrlib.branch import Branch18from bzrlib.branch import Branch
19from canonical.launchpad.webapp.interfaces import IUnloggedException
19from lp.services.log import loglevels20from lp.services.log import loglevels
20from lp.services.log.logger import LaunchpadLogger21from lp.services.log.logger import LaunchpadLogger
21from lp.services.log.mappingfilter import MappingFilter22from lp.services.log.mappingfilter import MappingFilter
22from lp.services.log.nullhandler import NullHandler23from lp.services.log.nullhandler import NullHandler
23from lp.services.mime import customizeMimetypes24from lp.services.mime import customizeMimetypes
25from zope.interface import alsoProvides
24from zope.security import checker26from zope.security import checker
27import zope.publisher.browser
2528
2629
27def add_custom_loglevels():30def add_custom_loglevels():
@@ -136,6 +139,33 @@
136 silence_transaction_logger()139 silence_transaction_logger()
137140
138141
142def customize_get_converter(zope_publisher_browser=zope.publisher.browser):
143 """URL parameter conversion errors shouldn't generate an OOPS report.
144
145 This injects (monkey patches) our wrapper around get_converter so improper
146 use of parameter type converters (like http://...?foo=bar:int) won't
147 generate OOPS reports.
148 """
149
150 original_get_converter = zope_publisher_browser.get_converter
151
152 def get_converter(*args, **kws):
153 """Get a type converter but turn off OOPS reporting if it fails."""
154 converter = original_get_converter(*args, **kws)
155
156 def wrapped_converter(v):
157 try:
158 return converter(v)
159 except ValueError, e:
160 # Mark the exception as not being OOPS-worthy.
161 alsoProvides(e, IUnloggedException)
162 raise
163
164 return wrapped_converter
165
166 zope_publisher_browser.get_converter = get_converter
167
168
139def main(instance_name):169def main(instance_name):
140 # This is called by our custom buildout-generated sitecustomize.py170 # This is called by our custom buildout-generated sitecustomize.py
141 # in parts/scripts/sitecustomize.py. The instance name is sent to171 # in parts/scripts/sitecustomize.py. The instance name is sent to
@@ -161,3 +191,4 @@
161 checker.BasicTypes[grouper] = checker._iteratorChecker191 checker.BasicTypes[grouper] = checker._iteratorChecker
162 silence_warnings()192 silence_warnings()
163 customize_logger()193 customize_logger()
194 customize_get_converter()