]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/0495.schema.svf-phase-one.sql
Break up expensive queries, match index to quals
[working/Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / 0495.schema.svf-phase-one.sql
1 BEGIN;
2
3 INSERT INTO config.upgrade_log (version) VALUES ('0495'); -- miker
4
5 CREATE TABLE config.record_attr_definition (
6     name        TEXT    PRIMARY KEY,
7     label       TEXT    NOT NULL, -- I18N
8     description TEXT,
9     filter      BOOL    NOT NULL DEFAULT TRUE,  -- becomes QP filter if true
10     sorter      BOOL    NOT NULL DEFAULT FALSE, -- becomes QP sort() axis if true
11
12 -- For pre-extracted fields. Takes the first occurance, uses naive subfield ordering
13     tag         TEXT, -- LIKE format
14     sf_list     TEXT, -- pile-o-values, like 'abcd' for a and b and c and d
15
16 -- This is used for both tag/sf and xpath entries
17     joiner      TEXT,
18
19 -- For xpath-extracted attrs
20     xpath       TEXT,
21     format      TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
22     start_pos   INT,
23     string_len  INT,
24
25 -- For fixed fields
26     fixed_field TEXT, -- should exist in config.marc21_ff_pos_map.fixed_field
27
28 -- For phys-char fields
29     phys_char_sf    INT REFERENCES config.marc21_physical_characteristic_subfield_map (id)
30 );
31
32 CREATE TABLE config.record_attr_index_norm_map (
33     id      SERIAL  PRIMARY KEY,
34     attr    TEXT    NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
35     norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
36     params  TEXT,
37     pos     INT     NOT NULL DEFAULT 0
38 );
39
40 CREATE TABLE config.coded_value_map (
41     id          SERIAL  PRIMARY KEY,
42     ctype       TEXT    NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
43     code        TEXT    NOT NULL,
44     value       TEXT    NOT NULL,
45     description TEXT
46 );
47
48 -- record attributes
49 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('alph','Alph','Alph');
50 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('audience','Audn','Audn');
51 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('bib_level','BLvl','BLvl');
52 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('biog','Biog','Biog');
53 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('conf','Conf','Conf');
54 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('control_type','Ctrl','Ctrl');
55 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('ctry','Ctry','Ctry');
56 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('date1','Date1','Date1');
57 INSERT INTO config.record_attr_definition (name,label,fixed_field,sorter,filter) values ('pubdate','Pub Date','Date1',TRUE,FALSE);
58 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('date2','Date2','Date2');
59 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('cat_form','Desc','Desc');
60 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('pub_status','DtSt','DtSt');
61 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('enc_level','ELvl','ELvl');
62 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('fest','Fest','Fest');
63 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('item_form','Form','Form');
64 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('gpub','GPub','GPub');
65 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('ills','Ills','Ills');
66 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('indx','Indx','Indx');
67 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('item_lang','Lang','Lang');
68 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('lit_form','LitF','LitF');
69 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('mrec','MRec','MRec');
70 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('ff_sl','S/L','S/L');
71 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('type_mat','TMat','TMat');
72 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('item_type','Type','Type');
73 INSERT INTO config.record_attr_definition (name,label,phys_char_sf) values ('vr_format','Videorecording format',72);
74 INSERT INTO config.record_attr_definition (name,label,sorter,filter,tag) values ('titlesort','Title',TRUE,FALSE,'tnf');
75 INSERT INTO config.record_attr_definition (name,label,sorter,filter,tag) values ('authorsort','Author',TRUE,FALSE,'1%');
76
77 INSERT INTO config.coded_value_map (ctype,code,value,description)
78     SELECT 'item_lang' AS ctype, code, value, NULL FROM config.language_map
79         UNION
80     SELECT 'bib_level' AS ctype, code, value, NULL FROM config.bib_level_map
81         UNION
82     SELECT 'item_form' AS ctype, code, value, NULL FROM config.item_form_map
83         UNION
84     SELECT 'item_type' AS ctype, code, value, NULL FROM config.item_type_map
85         UNION
86     SELECT 'lit_form' AS ctype, code, value, description FROM config.lit_form_map
87         UNION
88     SELECT 'audience' AS ctype, code, value, description FROM config.audience_map
89         UNION
90     SELECT 'vr_format' AS ctype, code, value, NULL FROM config.videorecording_format_map;
91
92 ALTER TABLE config.i18n_locale DROP CONSTRAINT i18n_locale_marc_code_fkey;
93
94 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT circ_matrix_matchpoint_marc_form_fkey;
95 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT circ_matrix_matchpoint_marc_type_fkey;
96 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT circ_matrix_matchpoint_marc_vr_format_fkey;
97
98 ALTER TABLE config.hold_matrix_matchpoint DROP CONSTRAINT hold_matrix_matchpoint_marc_form_fkey;
99 ALTER TABLE config.hold_matrix_matchpoint DROP CONSTRAINT hold_matrix_matchpoint_marc_type_fkey;
100 ALTER TABLE config.hold_matrix_matchpoint DROP CONSTRAINT hold_matrix_matchpoint_marc_vr_format_fkey;
101
102 DROP TABLE config.language_map;
103 DROP TABLE config.bib_level_map;
104 DROP TABLE config.item_form_map;
105 DROP TABLE config.item_type_map;
106 DROP TABLE config.lit_form_map;
107 DROP TABLE config.audience_map;
108 DROP TABLE config.videorecording_format_map;
109
110 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'clm.value' AND ccvm.ctype = 'item_lang' AND identity_value = ccvm.code;
111 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cblvl.value' AND ccvm.ctype = 'bib_level' AND identity_value = ccvm.code;
112 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cifm.value' AND ccvm.ctype = 'item_form' AND identity_value = ccvm.code;
113 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'citm.value' AND ccvm.ctype = 'item_type' AND identity_value = ccvm.code;
114 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'clfm.value' AND ccvm.ctype = 'lit_form' AND identity_value = ccvm.code;
115 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cam.value' AND ccvm.ctype = 'audience' AND identity_value = ccvm.code;
116 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cvrfm.value' AND ccvm.ctype = 'vr_format' AND identity_value = ccvm.code;
117
118 UPDATE config.i18n_core SET fq_field = 'ccvm.description', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'clfm.description' AND ccvm.ctype = 'lit_form' AND identity_value = ccvm.code;
119 UPDATE config.i18n_core SET fq_field = 'ccvm.description', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cam.description' AND ccvm.ctype = 'audience' AND identity_value = ccvm.code;
120
121 CREATE VIEW config.language_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_lang';
122 CREATE VIEW config.bib_level_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'bib_level';
123 CREATE VIEW config.item_form_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_form';
124 CREATE VIEW config.item_type_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_type';
125 CREATE VIEW config.lit_form_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'lit_form';
126 CREATE VIEW config.audience_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'audience';
127 CREATE VIEW config.videorecording_format_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'vr_format';
128
129 CREATE TABLE metabib.record_attr (
130        id              BIGINT  PRIMARY KEY REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
131        attrs   HSTORE  NOT NULL DEFAULT ''::HSTORE
132 );
133 CREATE INDEX metabib_svf_attrs_idx ON metabib.record_attr USING GIST (attrs);
134 CREATE INDEX metabib_svf_date1_idx ON metabib.record_attr ( (attrs->'date1') );
135 CREATE INDEX metabib_svf_dates_idx ON metabib.record_attr ( (attrs->'date1'), (attrs->'date2') );
136
137 INSERT INTO metabib.record_attr (id,attrs)
138     SELECT mrd.record, hstore(mrd) - '{id,record}'::TEXT[] FROM metabib.rec_descriptor mrd;
139
140 -- Back-compat view ... we're moving to an HSTORE world
141 CREATE TYPE metabib.rec_desc_type AS (
142     item_type       TEXT,
143     item_form       TEXT,
144     bib_level       TEXT,
145     control_type    TEXT,
146     char_encoding   TEXT,
147     enc_level       TEXT,
148     audience        TEXT,
149     lit_form        TEXT,
150     type_mat        TEXT,
151     cat_form        TEXT,
152     pub_status      TEXT,
153     item_lang       TEXT,
154     vr_format       TEXT,
155     date1           TEXT,
156     date2           TEXT
157 );
158
159 DROP TABLE metabib.rec_descriptor CASCADE;
160
161 CREATE VIEW metabib.rec_descriptor AS
162     SELECT  id,
163             id AS record,
164             (populate_record(NULL::metabib.rec_desc_type, attrs)).*
165       FROM  metabib.record_attr;
166
167 CREATE OR REPLACE FUNCTION vandelay.marc21_record_type( marc TEXT ) RETURNS config.marc21_rec_type_map AS $func$
168 DECLARE
169     ldr         TEXT;
170     tval        TEXT;
171     tval_rec    RECORD;
172     bval        TEXT;
173     bval_rec    RECORD;
174     retval      config.marc21_rec_type_map%ROWTYPE;
175 BEGIN
176     ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
177
178     IF ldr IS NULL OR ldr = '' THEN
179         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
180         RETURN retval;
181     END IF;
182
183     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
184     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
185
186
187     tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
188     bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
189
190     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
191
192     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
193
194
195     IF retval.code IS NULL THEN
196         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
197     END IF;
198
199     RETURN retval;
200 END;
201 $func$ LANGUAGE PLPGSQL;
202
203 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
204     SELECT * FROM vandelay.marc21_record_type( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
205 $func$ LANGUAGE SQL;
206
207 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
208 DECLARE
209     rtype       TEXT;
210     ff_pos      RECORD;
211     tag_data    RECORD;
212     val         TEXT;
213 BEGIN
214     rtype := (vandelay.marc21_record_type( marc )).code;
215     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
216         FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
217             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
218             RETURN val;
219         END LOOP;
220         val := REPEAT( ff_pos.default_val, ff_pos.length );
221         RETURN val;
222     END LOOP;
223
224     RETURN NULL;
225 END;
226 $func$ LANGUAGE PLPGSQL;
227
228 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
229     SELECT * FROM vandelay.marc21_extract_fixed_field( (SELECT marc FROM biblio.record_entry WHERE id = $1), $2 );
230 $func$ LANGUAGE SQL;
231
232 CREATE TYPE biblio.record_ff_map AS (record BIGINT, ff_name TEXT, ff_value TEXT);
233 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
234 DECLARE
235     tag_data    TEXT;
236     rtype       TEXT;
237     ff_pos      RECORD;
238     output      biblio.record_ff_map%ROWTYPE;
239 BEGIN
240     rtype := (vandelay.marc21_record_type( marc )).code;
241
242     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
243         output.ff_name  := ff_pos.fixed_field;
244         output.ff_value := NULL;
245
246         FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(tag) || '"]/text()', marc ) ) x(value) LOOP
247             output.ff_value := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
248             IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
249             RETURN NEXT output;
250             output.ff_value := NULL;
251         END LOOP;
252
253     END LOOP;
254
255     RETURN;
256 END;
257 $func$ LANGUAGE PLPGSQL;
258
259 CREATE OR REPLACE FUNCTION biblio.marc21_extract_all_fixed_fields( rid BIGINT ) RETURNS SETOF biblio.record_ff_map AS $func$
260     SELECT $1 AS record, ff_name, ff_value FROM vandelay.marc21_extract_all_fixed_fields( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
261 $func$ LANGUAGE SQL;
262
263 CREATE OR REPLACE FUNCTION vandelay.marc21_physical_characteristics( marc TEXT) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
264 DECLARE
265     rowid   INT := 0;
266     _007    TEXT;
267     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
268     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
269     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
270     retval  biblio.marc21_physical_characteristics%ROWTYPE;
271 BEGIN
272
273     _007 := oils_xpath_string( '//*[@tag="007"]', marc );
274
275     IF _007 IS NOT NULL AND _007 <> '' THEN
276         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
277
278         IF ptype.ptype_key IS NOT NULL THEN
279             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
280                 SELECT * INTO pval FROM config.marc21_physical_characteristic_value_map WHERE ptype_subfield = psf.id AND value = SUBSTRING( _007, psf.start_pos + 1, psf.length );
281
282                 IF pval.id IS NOT NULL THEN
283                     rowid := rowid + 1;
284                     retval.id := rowid;
285                     retval.ptype := ptype.ptype_key;
286                     retval.subfield := psf.id;
287                     retval.value := pval.id;
288                     RETURN NEXT retval;
289                 END IF;
290
291             END LOOP;
292         END IF;
293     END IF;
294
295     RETURN;
296 END;
297 $func$ LANGUAGE PLPGSQL;
298
299 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
300     SELECT id, $1 AS record, ptype, subfield, value FROM vandelay.marc21_physical_characteristics( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
301 $func$ LANGUAGE SQL;
302
303 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
304 DECLARE
305     transformed_xml TEXT;
306     prev_xfrm       TEXT;
307     normalizer      RECORD;
308     xfrm            config.xml_transform%ROWTYPE;
309     attr_value      TEXT;
310     new_attrs       HSTORE := ''::HSTORE;
311     attr_def        config.record_attr_definition%ROWTYPE;
312 BEGIN
313
314     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
315         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
316         DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
317         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
318         RETURN NEW; -- and we're done
319     END IF;
320
321     IF TG_OP = 'UPDATE' THEN -- re-ingest?
322         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
323
324         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
325             RETURN NEW;
326         END IF;
327     END IF;
328
329     -- Record authority linking
330     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
331     IF NOT FOUND THEN
332         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
333     END IF;
334
335     -- Flatten and insert the mfr data
336     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
337     IF NOT FOUND THEN
338         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
339
340         -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
341         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
342         IF NOT FOUND THEN
343             FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
344
345                 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
346                     SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
347                       FROM  (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
348                       WHERE record = NEW.id
349                             AND tag LIKE attr_def.tag
350                             AND CASE
351                                 WHEN attr_def.sf_list IS NOT NULL
352                                     THEN POSITION(subfield IN attr_def.sf_list) > 0
353                                 ELSE TRUE
354                                 END
355                       GROUP BY tag
356                       ORDER BY tag
357                       LIMIT 1;
358
359                 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
360                     attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
361
362                 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
363
364                     SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
365
366                     -- See if we can skip the XSLT ... it's expensive
367                     IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
368                         -- Can't skip the transform
369                         IF xfrm.xslt <> '---' THEN
370                             transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
371                         ELSE
372                             transformed_xml := NEW.marc;
373                         END IF;
374
375                         prev_xfrm := xfrm.name;
376                     END IF;
377
378                     IF xfrm.name IS NULL THEN
379                         -- just grab the marcxml (empty) transform
380                         SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
381                         prev_xfrm := xfrm.name;
382                     END IF;
383
384                     attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
385
386                 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
387                     SELECT  value::TEXT INTO attr_value
388                       FROM  biblio.marc21_physical_characteristics(NEW.id)
389                       WHERE subfield = attr_def.phys_char_sf
390                       LIMIT 1; -- Just in case ...
391
392                 END IF;
393
394                 -- apply index normalizers to attr_value
395                 FOR normalizer IN
396                     SELECT  n.func AS func,
397                             n.param_count AS param_count,
398                             m.params AS params
399                       FROM  config.index_normalizer n
400                             JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
401                       WHERE attr = attr_def.name
402                       ORDER BY m.pos LOOP
403                         EXECUTE 'SELECT ' || normalizer.func || '(' ||
404                             quote_literal( attr_value ) ||
405                             CASE
406                                 WHEN normalizer.param_count > 0
407                                     THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
408                                     ELSE ''
409                                 END ||
410                             ')' INTO attr_value;
411
412                 END LOOP;
413
414                 -- Add the new value to the hstore
415                 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
416
417             END LOOP;
418
419             IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
420                 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
421             ELSE
422                 UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id;
423             END IF;
424
425         END IF;
426     END IF;
427
428     -- Gather and insert the field entry data
429     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
430
431     -- Located URI magic
432     IF TG_OP = 'INSERT' THEN
433         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
434         IF NOT FOUND THEN
435             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
436         END IF;
437     ELSE
438         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
439         IF NOT FOUND THEN
440             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
441         END IF;
442     END IF;
443
444     -- (re)map metarecord-bib linking
445     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
446         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
447         IF NOT FOUND THEN
448             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
449         END IF;
450     ELSE -- we're doing an update, and we're not deleted, remap
451         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
452         IF NOT FOUND THEN
453             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
454         END IF;
455     END IF;
456
457     RETURN NEW;
458 END;
459 $func$ LANGUAGE PLPGSQL;
460
461 DROP FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT );
462
463 CREATE OR REPLACE FUNCTION public.approximate_date( TEXT, TEXT ) RETURNS TEXT AS $func$
464         SELECT REGEXP_REPLACE( $1, E'\\D', $2, 'g' );
465 $func$ LANGUAGE SQL STRICT IMMUTABLE;
466
467 CREATE OR REPLACE FUNCTION public.approximate_low_date( TEXT ) RETURNS TEXT AS $func$
468         SELECT approximate_date( $1, '0');
469 $func$ LANGUAGE SQL STRICT IMMUTABLE;
470
471 CREATE OR REPLACE FUNCTION public.approximate_high_date( TEXT ) RETURNS TEXT AS $func$
472         SELECT approximate_date( $1, '9');
473 $func$ LANGUAGE SQL STRICT IMMUTABLE;
474
475 CREATE OR REPLACE FUNCTION public.integer_or_null( TEXT ) RETURNS TEXT AS $func$
476         SELECT CASE WHEN $1 ~ E'^\\d+$' THEN $1 ELSE NULL END
477 $func$ LANGUAGE SQL STRICT IMMUTABLE;
478
479 CREATE OR REPLACE FUNCTION public.content_or_null( TEXT ) RETURNS TEXT AS $func$
480         SELECT CASE WHEN $1 ~ E'^\\s*$' THEN NULL ELSE $1 END
481 $func$ LANGUAGE SQL STRICT IMMUTABLE;
482
483 CREATE OR REPLACE FUNCTION public.force_to_isbn13( TEXT ) RETURNS TEXT AS $func$
484     use Business::ISBN;
485     use strict;
486     use warnings;
487
488     # Find the first ISBN, force it to ISBN13 and return it
489
490     my $input = shift;
491
492     foreach my $word (split(/\s/, $input)) {
493         my $isbn = Business::ISBN->new($word);
494
495         # First check the checksum; if it is not valid, fix it and add the original
496         # bad-checksum ISBN to the output
497         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
498             $isbn->fix_checksum();
499         }
500
501         # If we now have a valid ISBN, force it to ISBN13 and return it
502         return $isbn->as_isbn13->isbn if ($isbn && $isbn->is_valid());
503     }
504     return undef;
505 $func$ LANGUAGE PLPERLU;
506
507 COMMENT ON FUNCTION public.force_to_isbn13(TEXT) IS $$
508 /*
509  * Copyright (C) 2011 Equinox Software
510  * Mike Rylander <mrylander@gmail.com>
511  *
512  * Inspired by translate_isbn1013
513  *
514  * The force_to_isbn13 function takes an input ISBN and returns the ISBN13
515  * version without hypens and with a repaired checksum if the checksum was bad
516  */
517 $$;
518
519 COMMIT;