]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/0960.schema.decouple_co_history.sql
LP#1947173: Clean up bad cataloging pot hole
[Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / 0960.schema.decouple_co_history.sql
1
2 BEGIN;
3
4 -- TODO process to delete history items once the age threshold 
5 -- history.circ.retention_age is reached?
6
7 SELECT evergreen.upgrade_deps_block_check('0960', :eg_version); 
8
9 CREATE TABLE action.usr_circ_history (
10     id           BIGSERIAL PRIMARY KEY,
11     usr          INTEGER NOT NULL REFERENCES actor.usr(id)
12                  DEFERRABLE INITIALLY DEFERRED,
13     xact_start   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
14     target_copy  BIGINT NOT NULL,
15     due_date     TIMESTAMP WITH TIME ZONE NOT NULL,
16     checkin_time TIMESTAMP WITH TIME ZONE,
17     source_circ  BIGINT REFERENCES action.circulation(id)
18                  ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED
19 );
20
21 CREATE OR REPLACE FUNCTION action.maintain_usr_circ_history() 
22     RETURNS TRIGGER AS $FUNK$
23 DECLARE
24     cur_circ  BIGINT;
25     first_circ BIGINT;
26 BEGIN                                                                          
27
28     -- Any retention value signifies history is enabled.
29     -- This assumes that clearing these values via external 
30     -- process deletes the action.usr_circ_history rows.
31     -- TODO: replace these settings w/ a single bool setting?
32     PERFORM 1 FROM actor.usr_setting 
33         WHERE usr = NEW.usr AND value IS NOT NULL AND name IN (
34             'history.circ.retention_age', 
35             'history.circ.retention_start'
36         );
37
38     IF NOT FOUND THEN
39         RETURN NEW;
40     END IF;
41
42     IF TG_OP = 'INSERT' AND NEW.parent_circ IS NULL THEN
43         -- Starting a new circulation.  Insert the history row.
44         INSERT INTO action.usr_circ_history 
45             (usr, xact_start, target_copy, due_date, source_circ)
46         VALUES (
47             NEW.usr, 
48             NEW.xact_start, 
49             NEW.target_copy, 
50             NEW.due_date, 
51             NEW.id
52         );
53
54         RETURN NEW;
55     END IF;
56
57     -- find the first and last circs in the circ chain 
58     -- for the currently modified circ.
59     FOR cur_circ IN SELECT id FROM action.circ_chain(NEW.id) LOOP
60         IF first_circ IS NULL THEN
61             first_circ := cur_circ;
62             CONTINUE;
63         END IF;
64         -- Allow the loop to continue so that at as the loop
65         -- completes cur_circ points to the final circulation.
66     END LOOP;
67
68     IF NEW.id <> cur_circ THEN
69         -- Modifying an intermediate circ.  Ignore it.
70         RETURN NEW;
71     END IF;
72
73     -- Update the due_date/checkin_time on the history row if the current 
74     -- circ is the last circ in the chain and an update is warranted.
75
76     UPDATE action.usr_circ_history 
77         SET 
78             due_date = NEW.due_date,
79             checkin_time = NEW.checkin_time
80         WHERE 
81             source_circ = first_circ 
82             AND (
83                 due_date <> NEW.due_date OR (
84                     (checkin_time IS NULL AND NEW.checkin_time IS NOT NULL) OR
85                     (checkin_time IS NOT NULL AND NEW.checkin_time IS NULL) OR
86                     (checkin_time <> NEW.checkin_time)
87                 )
88             );
89     RETURN NEW;
90 END;                                                                           
91 $FUNK$ LANGUAGE PLPGSQL; 
92
93 CREATE TRIGGER maintain_usr_circ_history_tgr 
94     AFTER INSERT OR UPDATE ON action.circulation 
95     FOR EACH ROW EXECUTE PROCEDURE action.maintain_usr_circ_history();
96
97 UPDATE action_trigger.hook 
98     SET core_type = 'auch' 
99     WHERE key ~ '^circ.format.history.'; 
100
101 UPDATE action_trigger.event_definition SET template = 
102 $$
103 [%- USE date -%]
104 [%- SET user = target.0.usr -%]
105 To: [%- params.recipient_email || user.email %]
106 From: [%- params.sender_email || default_sender %]
107 Subject: Circulation History
108
109     [% FOR circ IN target %]
110             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
111             Barcode: [% circ.target_copy.barcode %]
112             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
113             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
114             Returned: [%
115                 date.format(
116                     helpers.format_date(circ.checkin_time), '%Y-%m-%d') 
117                     IF circ.checkin_time; 
118             %]
119     [% END %]
120 $$
121 WHERE id = 25 AND template = 
122 $$
123 [%- USE date -%]
124 [%- SET user = target.0.usr -%]
125 To: [%- params.recipient_email || user.email %]
126 From: [%- params.sender_email || default_sender %]
127 Subject: Circulation History
128
129     [% FOR circ IN target %]
130             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
131             Barcode: [% circ.target_copy.barcode %]
132             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
133             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
134             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
135     [% END %]
136 $$;
137
138 -- avoid TT undef date errors
139 UPDATE action_trigger.event_definition SET template = 
140 $$
141 [%- USE date -%]
142 <div>
143     <style> li { padding: 8px; margin 5px; }</style>
144     <div>[% date.format %]</div>
145     <br/>
146
147     [% user.family_name %], [% user.first_given_name %]
148     <ol>
149     [% FOR circ IN target %]
150         <li>
151             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
152             <div>Barcode: [% circ.target_copy.barcode %]</div>
153             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
154             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
155             <div>Returned: [%
156                 date.format(
157                     helpers.format_date(circ.checkin_time), '%Y-%m-%d') 
158                     IF circ.checkin_time; -%]
159             </div>
160         </li>
161     [% END %]
162     </ol>
163 </div>
164 $$
165 WHERE id = 26 AND template = -- only replace template if it matches stock
166 $$
167 [%- USE date -%]
168 <div>
169     <style> li { padding: 8px; margin 5px; }</style>
170     <div>[% date.format %]</div>
171     <br/>
172
173     [% user.family_name %], [% user.first_given_name %]
174     <ol>
175     [% FOR circ IN target %]
176         <li>
177             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
178             <div>Barcode: [% circ.target_copy.barcode %]</div>
179             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
180             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
181             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
182         </li>
183     [% END %]
184     </ol>
185 </div>
186 $$;
187
188 -- NOTE: ^-- stock CSV template does not include checkin_time, so 
189 -- no modifications are required.
190
191 -- Create circ history rows for existing circ history data.
192 DO $FUNK$
193 DECLARE
194     cur_usr   INTEGER;
195     cur_circ  action.circulation%ROWTYPE;
196     last_circ action.circulation%ROWTYPE;
197     counter   INTEGER DEFAULT 1;
198 BEGIN
199
200     RAISE NOTICE 
201         'Migrating circ history for % users.  This might take a while...',
202         (SELECT COUNT(DISTINCT(au.id)) FROM actor.usr au
203             JOIN actor.usr_setting aus ON (aus.usr = au.id)
204             WHERE NOT au.deleted AND 
205                 aus.name ~ '^history.circ.retention_');
206
207     FOR cur_usr IN 
208         SELECT DISTINCT(au.id)
209             FROM actor.usr au 
210             JOIN actor.usr_setting aus ON (aus.usr = au.id)
211             WHERE NOT au.deleted AND 
212                 aus.name ~ '^history.circ.retention_' LOOP
213
214         FOR cur_circ IN SELECT * FROM action.usr_visible_circs(cur_usr) LOOP
215
216             PERFORM TRUE FROM asset.copy WHERE id = cur_circ.target_copy;
217
218             -- Avoid inserting a circ history row when the circulated
219             -- item has been (forcibly) removed from the database.
220             IF NOT FOUND THEN
221                 CONTINUE;
222             END IF;
223
224             -- Find the last circ in the circ chain.
225             SELECT INTO last_circ * 
226                 FROM action.circ_chain(cur_circ.id) 
227                 ORDER BY xact_start DESC LIMIT 1;
228
229             -- Create the history row.
230             -- It's OK if last_circ = cur_circ
231             INSERT INTO action.usr_circ_history 
232                 (usr, xact_start, target_copy, 
233                     due_date, checkin_time, source_circ)
234             VALUES (
235                 cur_circ.usr, 
236                 cur_circ.xact_start, 
237                 cur_circ.target_copy, 
238                 last_circ.due_date, 
239                 last_circ.checkin_time,
240                 cur_circ.id
241             );
242
243             -- useful for alleviating administrator anxiety.
244             IF counter % 10000 = 0 THEN
245                 RAISE NOTICE 'Migrated history for % total circs', counter;
246             END IF;
247
248             counter := counter + 1;
249
250         END LOOP;
251     END LOOP;
252
253 END $FUNK$;
254
255 DROP FUNCTION IF EXISTS action.usr_visible_circs (INTEGER);
256 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies (INTEGER);
257
258 -- remove user retention age checks
259 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
260 DECLARE
261     org_keep_age    INTERVAL;
262     org_use_last    BOOL = false;
263     org_age_is_min  BOOL = false;
264     org_keep_count  INT;
265
266     keep_age        INTERVAL;
267
268     target_acp      RECORD;
269     circ_chain_head action.circulation%ROWTYPE;
270     circ_chain_tail action.circulation%ROWTYPE;
271
272     count_purged    INT;
273     num_incomplete  INT;
274
275     last_finished   TIMESTAMP WITH TIME ZONE;
276 BEGIN
277
278     count_purged := 0;
279
280     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
281
282     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
283     IF org_keep_count IS NULL THEN
284         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
285     END IF;
286
287     SELECT enabled INTO org_use_last FROM config.global_flag WHERE name = 'history.circ.retention_uses_last_finished';
288     SELECT enabled INTO org_age_is_min FROM config.global_flag WHERE name = 'history.circ.retention_age_is_min';
289
290     -- First, find copies with more than keep_count non-renewal circs
291     FOR target_acp IN
292         SELECT  target_copy,
293                 COUNT(*) AS total_real_circs
294           FROM  action.circulation
295           WHERE parent_circ IS NULL
296                 AND xact_finish IS NOT NULL
297           GROUP BY target_copy
298           HAVING COUNT(*) > org_keep_count
299     LOOP
300         -- And, for those, select circs that are finished and older than keep_age
301         FOR circ_chain_head IN
302             -- For reference, the subquery uses a window function to order the circs newest to oldest and number them
303             -- The outer query then uses that information to skip the most recent set the library wants to keep
304             -- End result is we don't care what order they come out in, as they are all potentials for deletion.
305             SELECT ac.* FROM action.circulation ac JOIN (
306               SELECT  rank() OVER (ORDER BY xact_start DESC), ac.id
307                 FROM  action.circulation ac
308                 WHERE ac.target_copy = target_acp.target_copy
309                   AND ac.parent_circ IS NULL
310                 ORDER BY ac.xact_start ) ranked USING (id)
311                 WHERE ranked.rank > org_keep_count
312         LOOP
313
314             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
315             SELECT COUNT(CASE WHEN xact_finish IS NULL THEN 1 ELSE NULL END), MAX(xact_finish) INTO num_incomplete, last_finished FROM action.circ_chain(circ_chain_head.id);
316             CONTINUE WHEN circ_chain_tail.xact_finish IS NULL OR num_incomplete > 0;
317
318             IF NOT org_use_last THEN
319                 last_finished := circ_chain_tail.xact_finish;
320             END IF;
321
322             keep_age := COALESCE( org_keep_age, '2000 years'::INTERVAL );
323
324             IF org_age_is_min THEN
325                 keep_age := GREATEST( keep_age, org_keep_age );
326             END IF;
327
328             CONTINUE WHEN AGE(NOW(), last_finished) < keep_age;
329
330             -- We've passed the purging tests, purge the circ chain starting at the end
331             -- A trigger should auto-purge the rest of the chain.
332             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
333
334             count_purged := count_purged + 1;
335
336         END LOOP;
337     END LOOP;
338
339     return count_purged;
340 END;
341 $func$ LANGUAGE PLPGSQL;
342
343 -- delete circ history rows when a user is purged.
344 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
345         src_usr  IN INTEGER,
346         specified_dest_usr IN INTEGER
347 ) RETURNS VOID AS $$
348 DECLARE
349         suffix TEXT;
350         renamable_row RECORD;
351         dest_usr INTEGER;
352 BEGIN
353
354         IF specified_dest_usr IS NULL THEN
355                 dest_usr := 1; -- Admin user on stock installs
356         ELSE
357                 dest_usr := specified_dest_usr;
358         END IF;
359
360         -- acq.*
361         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
362         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
363         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
364         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
365         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
366         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
367         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
368
369         -- Update with a rename to avoid collisions
370         FOR renamable_row in
371                 SELECT id, name
372                 FROM   acq.picklist
373                 WHERE  owner = src_usr
374         LOOP
375                 suffix := ' (' || src_usr || ')';
376                 LOOP
377                         BEGIN
378                                 UPDATE  acq.picklist
379                                 SET     owner = dest_usr, name = name || suffix
380                                 WHERE   id = renamable_row.id;
381                         EXCEPTION WHEN unique_violation THEN
382                                 suffix := suffix || ' ';
383                                 CONTINUE;
384                         END;
385                         EXIT;
386                 END LOOP;
387         END LOOP;
388
389         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
390         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
391         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
392         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
393         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
394         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
395         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
396         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
397
398         -- action.*
399         DELETE FROM action.circulation WHERE usr = src_usr;
400         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
401         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
402         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
403         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
404         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
405         DELETE FROM action.hold_request WHERE usr = src_usr;
406         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
407         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
408         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
409         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
410         DELETE FROM action.survey_response WHERE usr = src_usr;
411         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
412     DELETE FROM action.usr_circ_history WHERE usr = src_usr;
413
414         -- actor.*
415         DELETE FROM actor.card WHERE usr = src_usr;
416         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
417
418         -- The following update is intended to avoid transient violations of a foreign
419         -- key constraint, whereby actor.usr_address references itself.  It may not be
420         -- necessary, but it does no harm.
421         UPDATE actor.usr_address SET replaces = NULL
422                 WHERE usr = src_usr AND replaces IS NOT NULL;
423         DELETE FROM actor.usr_address WHERE usr = src_usr;
424         DELETE FROM actor.usr_note WHERE usr = src_usr;
425         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
426         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
427         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
428         DELETE FROM actor.usr_setting WHERE usr = src_usr;
429         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
430         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
431
432         -- asset.*
433         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
434         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
435         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
436         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
437         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
438         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
439
440         -- auditor.*
441         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
442         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
443         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
444         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
445         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
446         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
447         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
448         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
449
450         -- biblio.*
451         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
452         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
453         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
454         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
455
456         -- container.*
457         -- Update buckets with a rename to avoid collisions
458         FOR renamable_row in
459                 SELECT id, name
460                 FROM   container.biblio_record_entry_bucket
461                 WHERE  owner = src_usr
462         LOOP
463                 suffix := ' (' || src_usr || ')';
464                 LOOP
465                         BEGIN
466                                 UPDATE  container.biblio_record_entry_bucket
467                                 SET     owner = dest_usr, name = name || suffix
468                                 WHERE   id = renamable_row.id;
469                         EXCEPTION WHEN unique_violation THEN
470                                 suffix := suffix || ' ';
471                                 CONTINUE;
472                         END;
473                         EXIT;
474                 END LOOP;
475         END LOOP;
476
477         FOR renamable_row in
478                 SELECT id, name
479                 FROM   container.call_number_bucket
480                 WHERE  owner = src_usr
481         LOOP
482                 suffix := ' (' || src_usr || ')';
483                 LOOP
484                         BEGIN
485                                 UPDATE  container.call_number_bucket
486                                 SET     owner = dest_usr, name = name || suffix
487                                 WHERE   id = renamable_row.id;
488                         EXCEPTION WHEN unique_violation THEN
489                                 suffix := suffix || ' ';
490                                 CONTINUE;
491                         END;
492                         EXIT;
493                 END LOOP;
494         END LOOP;
495
496         FOR renamable_row in
497                 SELECT id, name
498                 FROM   container.copy_bucket
499                 WHERE  owner = src_usr
500         LOOP
501                 suffix := ' (' || src_usr || ')';
502                 LOOP
503                         BEGIN
504                                 UPDATE  container.copy_bucket
505                                 SET     owner = dest_usr, name = name || suffix
506                                 WHERE   id = renamable_row.id;
507                         EXCEPTION WHEN unique_violation THEN
508                                 suffix := suffix || ' ';
509                                 CONTINUE;
510                         END;
511                         EXIT;
512                 END LOOP;
513         END LOOP;
514
515         FOR renamable_row in
516                 SELECT id, name
517                 FROM   container.user_bucket
518                 WHERE  owner = src_usr
519         LOOP
520                 suffix := ' (' || src_usr || ')';
521                 LOOP
522                         BEGIN
523                                 UPDATE  container.user_bucket
524                                 SET     owner = dest_usr, name = name || suffix
525                                 WHERE   id = renamable_row.id;
526                         EXCEPTION WHEN unique_violation THEN
527                                 suffix := suffix || ' ';
528                                 CONTINUE;
529                         END;
530                         EXIT;
531                 END LOOP;
532         END LOOP;
533
534         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
535
536         -- money.*
537         DELETE FROM money.billable_xact WHERE usr = src_usr;
538         DELETE FROM money.collections_tracker WHERE usr = src_usr;
539         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
540
541         -- permission.*
542         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
543         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
544         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
545         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
546
547         -- reporter.*
548         -- Update with a rename to avoid collisions
549         BEGIN
550                 FOR renamable_row in
551                         SELECT id, name
552                         FROM   reporter.output_folder
553                         WHERE  owner = src_usr
554                 LOOP
555                         suffix := ' (' || src_usr || ')';
556                         LOOP
557                                 BEGIN
558                                         UPDATE  reporter.output_folder
559                                         SET     owner = dest_usr, name = name || suffix
560                                         WHERE   id = renamable_row.id;
561                                 EXCEPTION WHEN unique_violation THEN
562                                         suffix := suffix || ' ';
563                                         CONTINUE;
564                                 END;
565                                 EXIT;
566                         END LOOP;
567                 END LOOP;
568         EXCEPTION WHEN undefined_table THEN
569                 -- do nothing
570         END;
571
572         BEGIN
573                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
574         EXCEPTION WHEN undefined_table THEN
575                 -- do nothing
576         END;
577
578         -- Update with a rename to avoid collisions
579         BEGIN
580                 FOR renamable_row in
581                         SELECT id, name
582                         FROM   reporter.report_folder
583                         WHERE  owner = src_usr
584                 LOOP
585                         suffix := ' (' || src_usr || ')';
586                         LOOP
587                                 BEGIN
588                                         UPDATE  reporter.report_folder
589                                         SET     owner = dest_usr, name = name || suffix
590                                         WHERE   id = renamable_row.id;
591                                 EXCEPTION WHEN unique_violation THEN
592                                         suffix := suffix || ' ';
593                                         CONTINUE;
594                                 END;
595                                 EXIT;
596                         END LOOP;
597                 END LOOP;
598         EXCEPTION WHEN undefined_table THEN
599                 -- do nothing
600         END;
601
602         BEGIN
603                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
604         EXCEPTION WHEN undefined_table THEN
605                 -- do nothing
606         END;
607
608         BEGIN
609                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
610         EXCEPTION WHEN undefined_table THEN
611                 -- do nothing
612         END;
613
614         -- Update with a rename to avoid collisions
615         BEGIN
616                 FOR renamable_row in
617                         SELECT id, name
618                         FROM   reporter.template_folder
619                         WHERE  owner = src_usr
620                 LOOP
621                         suffix := ' (' || src_usr || ')';
622                         LOOP
623                                 BEGIN
624                                         UPDATE  reporter.template_folder
625                                         SET     owner = dest_usr, name = name || suffix
626                                         WHERE   id = renamable_row.id;
627                                 EXCEPTION WHEN unique_violation THEN
628                                         suffix := suffix || ' ';
629                                         CONTINUE;
630                                 END;
631                                 EXIT;
632                         END LOOP;
633                 END LOOP;
634         EXCEPTION WHEN undefined_table THEN
635         -- do nothing
636         END;
637
638         -- vandelay.*
639         -- Update with a rename to avoid collisions
640         FOR renamable_row in
641                 SELECT id, name
642                 FROM   vandelay.queue
643                 WHERE  owner = src_usr
644         LOOP
645                 suffix := ' (' || src_usr || ')';
646                 LOOP
647                         BEGIN
648                                 UPDATE  vandelay.queue
649                                 SET     owner = dest_usr, name = name || suffix
650                                 WHERE   id = renamable_row.id;
651                         EXCEPTION WHEN unique_violation THEN
652                                 suffix := suffix || ' ';
653                                 CONTINUE;
654                         END;
655                         EXIT;
656                 END LOOP;
657         END LOOP;
658
659     -- NULL-ify addresses last so other cleanup (e.g. circ anonymization)
660     -- can access the information before deletion.
661         UPDATE actor.usr SET
662                 active = FALSE,
663                 card = NULL,
664                 mailing_address = NULL,
665                 billing_address = NULL
666         WHERE id = src_usr;
667
668 END;
669 $$ LANGUAGE plpgsql;
670
671
672
673 COMMIT;
674