]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/0872.functions.metarecord-deleted-constituents.sql
LP#1758426: Disable triggers before recalculating bib visibility in 1085
[working/Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / 0872.functions.metarecord-deleted-constituents.sql
1 BEGIN;
2
3 SELECT evergreen.upgrade_deps_block_check('0872', :eg_version);
4
5 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT, bib_is_deleted BOOL DEFAULT FALSE, retain_deleted BOOL DEFAULT FALSE ) RETURNS BIGINT AS $func$
6 DECLARE
7     new_mapping     BOOL := TRUE;
8     source_count    INT;
9     old_mr          BIGINT;
10     tmp_mr          metabib.metarecord%ROWTYPE;
11     deleted_mrs     BIGINT[];
12 BEGIN
13
14     -- We need to make sure we're not a deleted master record of an MR
15     IF bib_is_deleted THEN
16         FOR old_mr IN SELECT id FROM metabib.metarecord WHERE master_record = bib_id LOOP
17
18             IF NOT retain_deleted THEN -- Go away for any MR that we're master of, unless retained
19                 DELETE FROM metabib.metarecord_source_map WHERE source = bib_id;
20             END IF;
21
22             -- Now, are there any more sources on this MR?
23             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = old_mr;
24
25             IF source_count = 0 AND NOT retain_deleted THEN -- No other records
26                 deleted_mrs := ARRAY_APPEND(deleted_mrs, old_mr); -- Just in case...
27                 DELETE FROM metabib.metarecord WHERE id = old_mr;
28
29             ELSE -- indeed there are. Update it with a null cache and recalcualated master record
30                 UPDATE  metabib.metarecord
31                   SET   mods = NULL,
32                         master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC LIMIT 1)
33                   WHERE id = old_mr;
34             END IF;
35         END LOOP;
36
37     ELSE -- insert or update
38
39         FOR tmp_mr IN SELECT m.* FROM metabib.metarecord m JOIN metabib.metarecord_source_map s ON (s.metarecord = m.id) WHERE s.source = bib_id LOOP
40
41             -- Find the first fingerprint-matching
42             IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN
43                 old_mr := tmp_mr.id;
44                 new_mapping := FALSE;
45
46             ELSE -- Our fingerprint changed ... maybe remove the old MR
47                 DELETE FROM metabib.metarecord_source_map WHERE metarecord = old_mr AND source = bib_id; -- remove the old source mapping
48                 SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
49                 IF source_count = 0 THEN -- No other records
50                     deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
51                     DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
52                 END IF;
53             END IF;
54
55         END LOOP;
56
57         -- we found no suitable, preexisting MR based on old source maps
58         IF old_mr IS NULL THEN
59             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
60
61             IF old_mr IS NULL THEN -- nope, create one and grab its id
62                 INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
63                 SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
64
65             ELSE -- indeed there is. update it with a null cache and recalcualated master record
66                 UPDATE  metabib.metarecord
67                   SET   mods = NULL,
68                         master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC LIMIT 1)
69                   WHERE id = old_mr;
70             END IF;
71
72         ELSE -- there was one we already attached to, update its mods cache and master_record
73             UPDATE  metabib.metarecord
74               SET   mods = NULL,
75                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC LIMIT 1)
76               WHERE id = old_mr;
77         END IF;
78
79         IF new_mapping THEN
80             INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
81         END IF;
82
83     END IF;
84
85     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
86         UPDATE action.hold_request SET target = old_mr WHERE target IN ( SELECT unnest(deleted_mrs) ) AND hold_type = 'M'; -- if we had to delete any MRs above, make sure their holds are moved
87     END IF;
88
89     RETURN old_mr;
90
91 END;
92 $func$ LANGUAGE PLPGSQL;
93
94 DROP FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT );
95
96 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
97 DECLARE
98     tmp_bool BOOL;
99 BEGIN
100
101     IF NEW.deleted THEN -- If this bib is deleted
102
103         PERFORM * FROM config.internal_flag WHERE
104             name = 'ingest.metarecord_mapping.preserve_on_delete' AND enabled;
105
106         tmp_bool := FOUND; -- Just in case this is changed by some other statement
107
108         PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint, TRUE, tmp_bool );
109
110         IF NOT tmp_bool THEN
111             -- One needs to keep these around to support searches
112             -- with the #deleted modifier, so one should turn on the named
113             -- internal flag for that functionality.
114             DELETE FROM metabib.record_attr_vector_list WHERE source = NEW.id;
115         END IF;
116
117         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
118         DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
119         DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs
120         RETURN NEW; -- and we're done
121     END IF;
122
123     IF TG_OP = 'UPDATE' THEN -- re-ingest?
124         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
125
126         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
127             RETURN NEW;
128         END IF;
129     END IF;
130
131     -- Record authority linking
132     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
133     IF NOT FOUND THEN
134         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
135     END IF;
136
137     -- Flatten and insert the mfr data
138     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
139     IF NOT FOUND THEN
140         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
141
142         -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
143         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
144         IF NOT FOUND THEN
145             PERFORM metabib.reingest_record_attributes(NEW.id, NULL, NEW.marc, TG_OP = 'INSERT' OR OLD.deleted);
146         END IF;
147     END IF;
148
149     -- Gather and insert the field entry data
150     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
151
152     -- Located URI magic
153     IF TG_OP = 'INSERT' THEN
154         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
155         IF NOT FOUND THEN
156             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
157         END IF;
158     ELSE
159         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
160         IF NOT FOUND THEN
161             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
162         END IF;
163     END IF;
164
165     -- (re)map metarecord-bib linking
166     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
167         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
168         IF NOT FOUND THEN
169             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
170         END IF;
171     ELSE -- we're doing an update, and we're not deleted, remap
172         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
173         IF NOT FOUND THEN
174             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
175         END IF;
176     END IF;
177
178     RETURN NEW;
179 END;
180 $func$ LANGUAGE PLPGSQL;
181
182 CREATE OR REPLACE FUNCTION unapi.mmr (
183     obj_id BIGINT,
184     format TEXT,
185     ename TEXT,
186     includes TEXT[],
187     org TEXT,
188     depth INT DEFAULT NULL,
189     slimit HSTORE DEFAULT NULL,
190     soffset HSTORE DEFAULT NULL,
191     include_xmlns BOOL DEFAULT TRUE,
192     pref_lib INT DEFAULT NULL
193 )
194 RETURNS XML AS $F$
195 DECLARE
196     mmrec   metabib.metarecord%ROWTYPE;
197     leadrec biblio.record_entry%ROWTYPE;
198     subrec biblio.record_entry%ROWTYPE;
199     layout  unapi.bre_output_layout%ROWTYPE;
200     xfrm    config.xml_transform%ROWTYPE;
201     ouid    INT;
202     xml_buf TEXT; -- growing XML document
203     tmp_xml TEXT; -- single-use XML string
204     xml_frag TEXT; -- single-use XML fragment
205     top_el  TEXT;
206     output  XML;
207     hxml    XML;
208     axml    XML;
209     subxml  XML; -- subordinate records elements
210     sub_xpath TEXT; 
211     parts   TEXT[]; 
212 BEGIN
213
214     -- xpath for extracting bre.marc values from subordinate records 
215     -- so they may be appended to the MARC of the master record prior
216     -- to XSLT processing.
217     -- subjects, isbn, issn, upc -- anything else?
218     sub_xpath := 
219       '//*[starts-with(@tag, "6") or @tag="020" or @tag="022" or @tag="024"]';
220
221     IF org = '-' OR org IS NULL THEN
222         SELECT shortname INTO org FROM evergreen.org_top();
223     END IF;
224
225     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
226
227     IF ouid IS NULL THEN
228         RETURN NULL::XML;
229     END IF;
230
231     SELECT INTO mmrec * FROM metabib.metarecord WHERE id = obj_id;
232     IF NOT FOUND THEN
233         RETURN NULL::XML;
234     END IF;
235
236     -- TODO: aggregate holdings from constituent records
237     IF format = 'holdings_xml' THEN -- the special case
238         output := unapi.mmr_holdings_xml(
239             obj_id, ouid, org, depth,
240             evergreen.array_remove_item_by_value(includes,'holdings_xml'),
241             slimit, soffset, include_xmlns, pref_lib);
242         RETURN output;
243     END IF;
244
245     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
246
247     IF layout.name IS NULL THEN
248         RETURN NULL::XML;
249     END IF;
250
251     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
252
253     SELECT INTO leadrec * FROM biblio.record_entry WHERE id = mmrec.master_record;
254
255     -- Grab distinct MVF for all records if requested
256     IF ('mra' = ANY (includes)) THEN 
257         axml := unapi.mmr_mra(obj_id,NULL,NULL,NULL,org,depth,NULL,NULL,TRUE,pref_lib);
258     ELSE
259         axml := NULL::XML;
260     END IF;
261
262     xml_buf = leadrec.marc;
263
264     hxml := NULL::XML;
265     IF ('holdings_xml' = ANY (includes)) THEN
266         hxml := unapi.mmr_holdings_xml(
267                     obj_id, ouid, org, depth,
268                     evergreen.array_remove_item_by_value(includes,'holdings_xml'),
269                     slimit, soffset, include_xmlns, pref_lib);
270     END IF;
271
272     subxml := NULL::XML;
273     parts := '{}'::TEXT[];
274     FOR subrec IN SELECT bre.* FROM biblio.record_entry bre
275          JOIN metabib.metarecord_source_map mmsm ON (mmsm.source = bre.id)
276          JOIN metabib.metarecord mmr ON (mmr.id = mmsm.metarecord)
277          WHERE mmr.id = obj_id AND NOT bre.deleted
278          ORDER BY CASE WHEN bre.id = mmr.master_record THEN 0 ELSE bre.id END
279          LIMIT COALESCE((slimit->'bre')::INT, 5) LOOP
280
281         IF subrec.id = leadrec.id THEN CONTINUE; END IF;
282         -- Append choice data from the the non-lead records to the 
283         -- the lead record document
284
285         parts := parts || xpath(sub_xpath, subrec.marc::XML)::TEXT[];
286     END LOOP;
287
288     SELECT ARRAY_TO_STRING( ARRAY_AGG( DISTINCT p ), '' )::XML INTO subxml FROM UNNEST(parts) p;
289
290     -- append data from the subordinate records to the 
291     -- main record document before applying the XSLT
292
293     IF subxml IS NOT NULL THEN 
294         xml_buf := REGEXP_REPLACE(xml_buf, 
295             '</record>(.*?)$', subxml || '</record>' || E'\\1');
296     END IF;
297
298     IF format = 'marcxml' THEN
299          -- If we're not using the prefixed namespace in 
300          -- this record, then remove all declarations of it
301         IF xml_buf !~ E'<marc:' THEN
302            xml_buf := REGEXP_REPLACE(xml_buf, 
303             ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
304         END IF; 
305     ELSE
306         xml_buf := oils_xslt_process(xml_buf, xfrm.xslt)::XML;
307     END IF;
308
309     -- update top_el to reflect the change in xml_buf, which may
310     -- now be a different type of document (e.g. record -> mods)
311     top_el := REGEXP_REPLACE(xml_buf, E'^.*?<((?:\\S+:)?' || 
312         layout.holdings_element || ').*$', E'\\1');
313
314     IF axml IS NOT NULL THEN 
315         xml_buf := REGEXP_REPLACE(xml_buf, 
316             '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
317     END IF;
318
319     IF hxml IS NOT NULL THEN
320         xml_buf := REGEXP_REPLACE(xml_buf, 
321             '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
322     END IF;
323
324     IF ('mmr.unapi' = ANY (includes)) THEN 
325         output := REGEXP_REPLACE(
326             xml_buf,
327             '</' || top_el || '>(.*?)',
328             XMLELEMENT(
329                 name abbr,
330                 XMLATTRIBUTES(
331                     'http://www.w3.org/1999/xhtml' AS xmlns,
332                     'unapi-id' AS class,
333                     'tag:open-ils.org:U2@mmr/' || obj_id || '/' || org AS title
334                 )
335             )::TEXT || '</' || top_el || E'>\\1'
336         );
337     ELSE
338         output := xml_buf;
339     END IF;
340
341     -- remove ignorable whitesace
342     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
343     RETURN output;
344 END;
345 $F$ LANGUAGE PLPGSQL STABLE;
346
347 -- Forcibly remap deleted master records, retaining the linkage if so configured.
348 SELECT  count(metabib.remap_metarecord_for_bib( bre.id, bre.fingerprint, TRUE, COALESCE(flag.enabled,FALSE)))
349   FROM  metabib.metarecord metar
350         JOIN biblio.record_entry bre ON bre.id = metar.master_record,
351         config.internal_flag flag
352   WHERE bre.deleted = TRUE AND flag.name = 'ingest.metarecord_mapping.preserve_on_delete';
353
354 COMMIT;
355