1 INSERT INTO config.global_flag (name, label)
3 'history.circ.retention_uses_last_finished',
5 'history.circ.retention_uses_last_finished',
6 'Historical Circulations use most recent xact_finish date instead of last circ''s.',
11 'history.circ.retention_age_is_min',
13 'history.circ.retention_age_is_min',
14 'Historical Circulations are kept for global retention age at a minimum, regardless of user preferences.',
22 DROP FUNCTION IF EXISTS action.circ_chain(INTEGER);
23 DROP FUNCTION IF EXISTS action.summarize_circ_chain(INTEGER);
25 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id BIGINT ) RETURNS SETOF action.circulation AS $$
27 tmp_circ action.circulation%ROWTYPE;
28 circ_0 action.circulation%ROWTYPE;
31 SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
33 IF tmp_circ IS NULL THEN
38 -- find the front of the chain
40 SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
41 IF tmp_circ IS NULL THEN
47 -- now send the circs to the caller, oldest to newest
50 IF tmp_circ IS NULL THEN
54 SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
58 $$ LANGUAGE 'plpgsql';
60 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id BIGINT ) RETURNS action.circ_chain_summary AS $$
64 -- first circ in the chain
65 circ_0 action.circulation%ROWTYPE;
67 -- last circ in the chain
68 circ_n action.circulation%ROWTYPE;
70 -- circ chain under construction
71 chain action.circ_chain_summary;
72 tmp_circ action.circulation%ROWTYPE;
77 FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
79 IF chain.num_circs = 0 THEN
83 chain.num_circs := chain.num_circs + 1;
87 chain.start_time := circ_0.xact_start;
88 chain.last_stop_fines := circ_n.stop_fines;
89 chain.last_stop_fines_time := circ_n.stop_fines_time;
90 chain.last_checkin_time := circ_n.checkin_time;
91 chain.last_checkin_scan_time := circ_n.checkin_scan_time;
92 SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
93 SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
95 IF chain.num_circs > 1 THEN
96 chain.last_renewal_time := circ_n.xact_start;
97 SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
103 $$ LANGUAGE 'plpgsql';
105 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
107 usr_keep_age actor.usr_setting%ROWTYPE;
108 usr_keep_start actor.usr_setting%ROWTYPE;
109 org_keep_age INTERVAL;
110 org_use_last BOOL = false;
111 org_age_is_min BOOL = false;
117 circ_chain_head action.circulation%ROWTYPE;
118 circ_chain_tail action.circulation%ROWTYPE;
123 last_finished TIMESTAMP WITH TIME ZONE;
128 SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
130 SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
131 IF org_keep_count IS NULL THEN
132 RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
135 SELECT enabled INTO org_use_last FROM config.global_flag WHERE name = 'history.circ.retention_uses_last_finished';
136 SELECT enabled INTO org_age_is_min FROM config.global_flag WHERE name = 'history.circ.retention_age_is_min';
138 -- First, find copies with more than keep_count non-renewal circs
141 COUNT(*) AS total_real_circs
142 FROM action.circulation
143 WHERE parent_circ IS NULL
144 AND xact_finish IS NOT NULL
146 HAVING COUNT(*) > org_keep_count
148 -- And, for those, select circs that are finished and older than keep_age
149 FOR circ_chain_head IN
150 -- For reference, the subquery uses a window function to order the circs newest to oldest and number them
151 -- The outer query then uses that information to skip the most recent set the library wants to keep
152 -- End result is we don't care what order they come out in, as they are all potentials for deletion.
153 SELECT ac.* FROM action.circulation ac JOIN (
154 SELECT rank() OVER (ORDER BY xact_start DESC), ac.id
155 FROM action.circulation ac
156 WHERE ac.target_copy = target_acp.target_copy
157 AND ac.parent_circ IS NULL
158 ORDER BY ac.xact_start ) ranked USING (id)
159 WHERE ranked.rank > org_keep_count
162 SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
163 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);
164 CONTINUE WHEN circ_chain_tail.xact_finish IS NULL OR num_incomplete > 0;
166 IF NOT org_use_last THEN
167 last_finished := circ_chain_tail.xact_finish;
170 -- Now get the user settings, if any, to block purging if the user wants to keep more circs
171 usr_keep_age.value := NULL;
172 SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
174 usr_keep_start.value := NULL;
175 SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
177 IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
178 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
179 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
181 keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
183 ELSIF usr_keep_start.value IS NOT NULL THEN
184 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
186 keep_age := COALESCE( org_keep_age, '2000 years'::INTERVAL );
189 IF org_age_is_min THEN
190 keep_age := GREATEST( keep_age, org_keep_age );
193 CONTINUE WHEN AGE(NOW(), last_finished) < keep_age;
195 -- We've passed the purging tests, purge the circ chain starting at the end
196 -- A trigger should auto-purge the rest of the chain.
197 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
199 count_purged := count_purged + 1;
206 $func$ LANGUAGE PLPGSQL;