Stamping upgrade script for purge circs fixes
[working/Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / 0798.function.purge_circulations_updates.sql
1 BEGIN;
2
3 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0798', :eg_version); -- tsbere/Dyrcona/dbwells
4
5 INSERT INTO config.global_flag (name, label)
6     VALUES (
7         'history.circ.retention_uses_last_finished',
8         oils_i18n_gettext(
9             'history.circ.retention_uses_last_finished',
10             'Historical Circulations use most recent xact_finish date instead of last circ''s.',
11             'cgf',
12             'label'
13         )
14     ),(
15         'history.circ.retention_age_is_min',
16         oils_i18n_gettext(
17             'history.circ.retention_age_is_min',
18             'Historical Circulations are kept for global retention age at a minimum, regardless of user preferences.',
19             'cgf',
20             'label'
21         )
22     );
23
24
25 -- Drop old variants
26 DROP FUNCTION IF EXISTS action.circ_chain(INTEGER);
27 DROP FUNCTION IF EXISTS action.summarize_circ_chain(INTEGER);
28
29 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id BIGINT ) RETURNS SETOF action.circulation AS $$
30 DECLARE
31     tmp_circ action.circulation%ROWTYPE;
32     circ_0 action.circulation%ROWTYPE;
33 BEGIN
34
35     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
36
37     IF tmp_circ IS NULL THEN
38         RETURN NEXT tmp_circ;
39     END IF;
40     circ_0 := tmp_circ;
41
42     -- find the front of the chain
43     WHILE TRUE LOOP
44         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
45         IF tmp_circ IS NULL THEN
46             EXIT;
47         END IF;
48         circ_0 := tmp_circ;
49     END LOOP;
50
51     -- now send the circs to the caller, oldest to newest
52     tmp_circ := circ_0;
53     WHILE TRUE LOOP
54         IF tmp_circ IS NULL THEN
55             EXIT;
56         END IF;
57         RETURN NEXT tmp_circ;
58         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
59     END LOOP;
60
61 END;
62 $$ LANGUAGE 'plpgsql';
63
64 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id BIGINT ) RETURNS action.circ_chain_summary AS $$
65
66 DECLARE
67
68     -- first circ in the chain
69     circ_0 action.circulation%ROWTYPE;
70
71     -- last circ in the chain
72     circ_n action.circulation%ROWTYPE;
73
74     -- circ chain under construction
75     chain action.circ_chain_summary;
76     tmp_circ action.circulation%ROWTYPE;
77
78 BEGIN
79     
80     chain.num_circs := 0;
81     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
82
83         IF chain.num_circs = 0 THEN
84             circ_0 := tmp_circ;
85         END IF;
86
87         chain.num_circs := chain.num_circs + 1;
88         circ_n := tmp_circ;
89     END LOOP;
90
91     chain.start_time := circ_0.xact_start;
92     chain.last_stop_fines := circ_n.stop_fines;
93     chain.last_stop_fines_time := circ_n.stop_fines_time;
94     chain.last_checkin_time := circ_n.checkin_time;
95     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
96     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
97     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
98
99     IF chain.num_circs > 1 THEN
100         chain.last_renewal_time := circ_n.xact_start;
101         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
102     END IF;
103
104     RETURN chain;
105
106 END;
107 $$ LANGUAGE 'plpgsql';
108
109 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
110 DECLARE
111     usr_keep_age    actor.usr_setting%ROWTYPE;
112     usr_keep_start  actor.usr_setting%ROWTYPE;
113     org_keep_age    INTERVAL;
114     org_use_last    BOOL = false;
115     org_age_is_min  BOOL = false;
116     org_keep_count  INT;
117
118     keep_age        INTERVAL;
119
120     target_acp      RECORD;
121     circ_chain_head action.circulation%ROWTYPE;
122     circ_chain_tail action.circulation%ROWTYPE;
123
124     count_purged    INT;
125     num_incomplete  INT;
126
127     last_finished   TIMESTAMP WITH TIME ZONE;
128 BEGIN
129
130     count_purged := 0;
131
132     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
133
134     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
135     IF org_keep_count IS NULL THEN
136         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
137     END IF;
138
139     SELECT enabled INTO org_use_last FROM config.global_flag WHERE name = 'history.circ.retention_uses_last_finished';
140     SELECT enabled INTO org_age_is_min FROM config.global_flag WHERE name = 'history.circ.retention_age_is_min';
141
142     -- First, find copies with more than keep_count non-renewal circs
143     FOR target_acp IN
144         SELECT  target_copy,
145                 COUNT(*) AS total_real_circs
146           FROM  action.circulation
147           WHERE parent_circ IS NULL
148                 AND xact_finish IS NOT NULL
149           GROUP BY target_copy
150           HAVING COUNT(*) > org_keep_count
151     LOOP
152         -- And, for those, select circs that are finished and older than keep_age
153         FOR circ_chain_head IN
154             -- For reference, the subquery uses a window function to order the circs newest to oldest and number them
155             -- The outer query then uses that information to skip the most recent set the library wants to keep
156             -- End result is we don't care what order they come out in, as they are all potentials for deletion.
157             SELECT ac.* FROM action.circulation ac JOIN (
158               SELECT  rank() OVER (ORDER BY xact_start DESC), ac.id
159                 FROM  action.circulation ac
160                 WHERE ac.target_copy = target_acp.target_copy
161                   AND ac.parent_circ IS NULL
162                 ORDER BY ac.xact_start ) ranked USING (id)
163                 WHERE ranked.rank > org_keep_count
164         LOOP
165
166             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
167             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);
168             CONTINUE WHEN circ_chain_tail.xact_finish IS NULL OR num_incomplete > 0;
169
170             IF NOT org_use_last THEN
171                 last_finished := circ_chain_tail.xact_finish;
172             END IF;
173
174             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
175             usr_keep_age.value := NULL;
176             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
177
178             usr_keep_start.value := NULL;
179             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
180
181             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
182                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
183                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
184                 ELSE
185                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
186                 END IF;
187             ELSIF usr_keep_start.value IS NOT NULL THEN
188                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
189             ELSE
190                 keep_age := COALESCE( org_keep_age, '2000 years'::INTERVAL );
191             END IF;
192
193             IF org_age_is_min THEN
194                 keep_age := GREATEST( keep_age, org_keep_age );
195             END IF;
196
197             CONTINUE WHEN AGE(NOW(), last_finished) < keep_age;
198
199             -- We've passed the purging tests, purge the circ chain starting at the end
200             -- A trigger should auto-purge the rest of the chain.
201             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
202
203             count_purged := count_purged + 1;
204
205         END LOOP;
206     END LOOP;
207
208     return count_purged;
209 END;
210 $func$ LANGUAGE PLPGSQL;
211
212 COMMIT;