]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/XXXX.vandelay-record-matching-and-quality.sql
Seed data and schema cleanup for merge
[working/Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / XXXX.vandelay-record-matching-and-quality.sql
1 -- Evergreen DB patch XXXX.vandelay-record-matching-and-quality.sql
2 --
3 -- FIXME: insert description of change, if needed
4 --
5 BEGIN;
6
7
8 -- check whether patch can be applied
9 --SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
10
11 CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT) RETURNS anyarray AS $$ SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2; $$ LANGUAGE SQL;
12
13 CREATE TABLE vandelay.match_set (
14     id      SERIAL  PRIMARY KEY,
15     name    TEXT        NOT NULL,
16     owner   INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE,
17     mtype   TEXT        NOT NULL DEFAULT 'biblio', -- 'biblio','authority','mfhd'?, others?
18     CONSTRAINT name_once_per_owner_mtype UNIQUE (name, owner, mtype)
19 );
20
21 -- Table to define match points, either FF via SVF or tag+subfield
22 CREATE TABLE vandelay.match_set_point (
23     id          SERIAL  PRIMARY KEY,
24     match_set   INT     REFERENCES vandelay.match_set (id) ON DELETE CASCADE,
25     parent      INT     REFERENCES vandelay.match_set_point (id),
26     bool_op     TEXT    CHECK (bool_op IS NULL OR (bool_op IN ('AND','OR','NOT'))),
27     svf         TEXT    REFERENCES config.record_attr_definition (name),
28     tag         TEXT,
29     subfield    TEXT,
30     negate      BOOL    DEFAULT FALSE,
31     quality     INT     NOT NULL DEFAULT 1, -- higher is better
32     CONSTRAINT vmsp_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
33     CONSTRAINT vmsp_need_a_tag_or_a_ff_or_a_bo CHECK (
34         (tag IS NOT NULL AND svf IS NULL AND bool_op IS NULL) OR
35         (tag IS NULL AND svf IS NOT NULL AND bool_op IS NULL) OR
36         (tag IS NULL AND svf IS NULL AND bool_op IS NOT NULL)
37     )
38 );
39
40 CREATE TABLE vandelay.match_set_quality (
41     id          SERIAL  PRIMARY KEY,
42     match_set   INT     NOT NULL REFERENCES vandelay.match_set (id) ON DELETE CASCADE,
43     svf         TEXT    REFERENCES config.record_attr_definition,
44     tag         TEXT,
45     subfield    TEXT,
46     value       TEXT    NOT NULL,
47     quality     INT     NOT NULL DEFAULT 1, -- higher is better
48     CONSTRAINT vmsq_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
49     CONSTRAINT vmsq_need_a_tag_or_a_ff CHECK ((tag IS NOT NULL AND svf IS NULL) OR (tag IS NULL AND svf IS NOT NULL))
50 );
51 CREATE UNIQUE INDEX vmsq_def_once_per_set ON vandelay.match_set_quality (match_set, COALESCE(tag,''), COALESCE(subfield,''), COALESCE(svf,''), value);
52
53
54 -- ALTER TABLEs...
55 ALTER TABLE vandelay.queue ADD COLUMN match_set INT REFERENCES vandelay.match_set (id) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
56 ALTER TABLE vandelay.queued_record ADD COLUMN quality INT NOT NULL DEFAULT 0;
57 ALTER TABLE vandelay.bib_attr_definition DROP COLUMN ident;
58
59 CREATE TABLE vandelay.import_error (
60     code        TEXT    PRIMARY KEY,
61     description TEXT    NOT NULL -- i18n
62 );
63
64 ALTER TABLE vandelay.queued_bib_record
65     ADD COLUMN import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
66     ADD COLUMN error_detail TEXT;
67
68 ALTER TABLE vandelay.bib_match
69     DROP COLUMN field_type,
70     DROP COLUMN matched_attr,
71     ADD COLUMN quality INT NOT NULL DEFAULT 1,
72     ADD COLUMN match_score INT NOT NULL DEFAULT 0;
73
74 ALTER TABLE vandelay.import_item
75     ADD COLUMN import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
76     ADD COLUMN error_detail TEXT,
77     ADD COLUMN imported_as BIGINT REFERENCES asset.copy (id) DEFERRABLE INITIALLY DEFERRED,
78     ADD COLUMN import_time TIMESTAMP WITH TIME ZONE;
79
80 ALTER TABLE vandelay.merge_profile ADD COLUMN lwm_ratio NUMERIC;
81
82 CREATE OR REPLACE FUNCTION vandelay.marc21_record_type( marc TEXT ) RETURNS config.marc21_rec_type_map AS $func$
83 DECLARE
84     ldr         TEXT;
85     tval        TEXT;
86     tval_rec    RECORD;
87     bval        TEXT;
88     bval_rec    RECORD;
89     retval      config.marc21_rec_type_map%ROWTYPE;
90 BEGIN
91     ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
92
93     IF ldr IS NULL OR ldr = '' THEN
94         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
95         RETURN retval;
96     END IF;
97
98     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
99     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
100
101
102     tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
103     bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
104
105     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
106
107     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
108
109
110     IF retval.code IS NULL THEN
111         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
112     END IF;
113
114     RETURN retval;
115 END;
116 $func$ LANGUAGE PLPGSQL;
117
118 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
119 DECLARE
120     rtype       TEXT;
121     ff_pos      RECORD;
122     tag_data    RECORD;
123     val         TEXT;
124 BEGIN
125     rtype := (vandelay.marc21_record_type( marc )).code;
126     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
127         IF ff_pos.tag = 'ldr' THEN
128             val := oils_xpath_string('//*[local-name()="leader"]', marc);
129             IF val IS NOT NULL THEN
130                 val := SUBSTRING( val, ff_pos.start_pos + 1, ff_pos.length );
131                 RETURN val;
132             END IF;
133         ELSE
134             FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
135                 val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
136                 RETURN val;
137             END LOOP;
138         END IF;
139         val := REPEAT( ff_pos.default_val, ff_pos.length );
140         RETURN val;
141     END LOOP;
142
143     RETURN NULL;
144 END;
145 $func$ LANGUAGE PLPGSQL;
146
147 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
148 DECLARE
149     tag_data    TEXT;
150     rtype       TEXT;
151     ff_pos      RECORD;
152     output      biblio.record_ff_map%ROWTYPE;
153 BEGIN
154     rtype := (vandelay.marc21_record_type( marc )).code;
155
156     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
157         output.ff_name  := ff_pos.fixed_field;
158         output.ff_value := NULL;
159
160         IF ff_pos.tag = 'ldr' THEN
161             output.ff_value := oils_xpath_string('//*[local-name()="leader"]', marc);
162             IF output.ff_value IS NOT NULL THEN
163                 output.ff_value := SUBSTRING( output.ff_value, ff_pos.start_pos + 1, ff_pos.length );
164                 RETURN NEXT output;
165                 output.ff_value := NULL;
166             END IF;
167         ELSE
168             FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
169                 output.ff_value := SUBSTRING( tag_data, ff_pos.start_pos + 1, ff_pos.length );
170                 IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
171                 RETURN NEXT output;
172                 output.ff_value := NULL;
173             END LOOP;
174         END IF;
175     
176     END LOOP;
177
178     RETURN;
179 END;
180 $func$ LANGUAGE PLPGSQL;
181
182 CREATE OR REPLACE FUNCTION vandelay.marc21_physical_characteristics( marc TEXT) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
183 DECLARE
184     rowid   INT := 0;
185     _007    TEXT;
186     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
187     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
188     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
189     retval  biblio.marc21_physical_characteristics%ROWTYPE;
190 BEGIN
191
192     _007 := oils_xpath_string( '//*[@tag="007"]', marc );
193
194     IF _007 IS NOT NULL AND _007 <> '' THEN
195         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
196
197         IF ptype.ptype_key IS NOT NULL THEN
198             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
199                 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 );
200
201                 IF pval.id IS NOT NULL THEN
202                     rowid := rowid + 1;
203                     retval.id := rowid;
204                     retval.ptype := ptype.ptype_key;
205                     retval.subfield := psf.id;
206                     retval.value := pval.id;
207                     RETURN NEXT retval;
208                 END IF;
209
210             END LOOP;
211         END IF;
212     END IF;
213
214     RETURN;
215 END;
216 $func$ LANGUAGE PLPGSQL;
217
218 CREATE TYPE vandelay.flat_marc AS ( tag CHAR(3), ind1 TEXT, ind2 TEXT, subfield TEXT, value TEXT );
219 CREATE OR REPLACE FUNCTION vandelay.flay_marc ( TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
220
221 use MARC::Record;
222 use MARC::File::XML (BinaryEncoding => 'UTF-8');
223 use MARC::Charset;
224 use strict;
225
226 MARC::Charset->assume_unicode(1);
227
228 my $xml = shift;
229 my $r = MARC::Record->new_from_xml( $xml );
230
231 return_next( { tag => 'LDR', value => $r->leader } );
232
233 for my $f ( $r->fields ) {
234     if ($f->is_control_field) {
235         return_next({ tag => $f->tag, value => $f->data });
236     } else {
237         for my $s ($f->subfields) {
238             return_next({
239                 tag      => $f->tag,
240                 ind1     => $f->indicator(1),
241                 ind2     => $f->indicator(2),
242                 subfield => $s->[0],
243                 value    => $s->[1]
244             });
245
246             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
247                 my $trim = $f->indicator(2) || 0;
248                 return_next({
249                     tag      => 'tnf',
250                     ind1     => $f->indicator(1),
251                     ind2     => $f->indicator(2),
252                     subfield => 'a',
253                     value    => substr( $s->[1], $trim )
254                 });
255             }
256         }
257     }
258 }
259
260 return undef;
261
262 $func$ LANGUAGE PLPERLU;
263
264 CREATE OR REPLACE FUNCTION vandelay.flatten_marc ( marc TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
265 DECLARE
266     output  vandelay.flat_marc%ROWTYPE;
267     field   RECORD;
268 BEGIN
269     FOR field IN SELECT * FROM vandelay.flay_marc( marc ) LOOP
270         output.ind1 := field.ind1;
271         output.ind2 := field.ind2;
272         output.tag := field.tag;
273         output.subfield := field.subfield;
274         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
275             output.value := naco_normalize(field.value, field.subfield);
276         ELSE
277             output.value := field.value;
278         END IF;
279
280         CONTINUE WHEN output.value IS NULL;
281
282         RETURN NEXT output;
283     END LOOP;
284 END;
285 $func$ LANGUAGE PLPGSQL;
286
287 CREATE OR REPLACE FUNCTION vandelay.extract_rec_attrs ( xml TEXT, attr_defs TEXT[]) RETURNS hstore AS $_$
288 DECLARE
289     transformed_xml TEXT;
290     prev_xfrm       TEXT;
291     normalizer      RECORD;
292     xfrm            config.xml_transform%ROWTYPE;
293     attr_value      TEXT;
294     new_attrs       HSTORE := ''::HSTORE;
295     attr_def        config.record_attr_definition%ROWTYPE;
296 BEGIN
297
298     FOR attr_def IN SELECT * FROM config.record_attr_definition WHERE name IN (SELECT * FROM UNNEST(attr_defs)) ORDER BY format LOOP
299
300         IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
301             SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(x.value), COALESCE(attr_def.joiner,' ')) INTO attr_value
302               FROM  vandelay.flatten_marc(xml) AS x
303               WHERE x.tag LIKE attr_def.tag
304                     AND CASE
305                         WHEN attr_def.sf_list IS NOT NULL
306                             THEN POSITION(x.subfield IN attr_def.sf_list) > 0
307                         ELSE TRUE
308                         END
309               GROUP BY x.tag
310               ORDER BY x.tag
311               LIMIT 1;
312
313         ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
314             attr_value := vandelay.marc21_extract_fixed_field(xml, attr_def.fixed_field);
315
316         ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
317
318             SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
319
320             -- See if we can skip the XSLT ... it's expensive
321             IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
322                 -- Can't skip the transform
323                 IF xfrm.xslt <> '---' THEN
324                     transformed_xml := oils_xslt_process(xml,xfrm.xslt);
325                 ELSE
326                     transformed_xml := xml;
327                 END IF;
328
329                 prev_xfrm := xfrm.name;
330             END IF;
331
332             IF xfrm.name IS NULL THEN
333                 -- just grab the marcxml (empty) transform
334                 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
335                 prev_xfrm := xfrm.name;
336             END IF;
337
338             attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
339
340         ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
341             SELECT  m.value::TEXT INTO attr_value
342               FROM  vandelay.marc21_physical_characteristics(xml) v
343                     JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
344               WHERE v.subfield = attr_def.phys_char_sf
345               LIMIT 1; -- Just in case ...
346
347         END IF;
348
349         -- apply index normalizers to attr_value
350         FOR normalizer IN
351             SELECT  n.func AS func,
352                     n.param_count AS param_count,
353                     m.params AS params
354               FROM  config.index_normalizer n
355                     JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
356               WHERE attr = attr_def.name
357               ORDER BY m.pos LOOP
358                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
359                     quote_literal( attr_value ) ||
360                     CASE
361                         WHEN normalizer.param_count > 0
362                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
363                             ELSE ''
364                         END ||
365                     ')' INTO attr_value;
366
367         END LOOP;
368
369         -- Add the new value to the hstore
370         new_attrs := new_attrs || hstore( attr_def.name, attr_value );
371
372     END LOOP;
373
374     RETURN new_attrs;
375 END;
376 $_$ LANGUAGE PLPGSQL;
377
378 CREATE OR REPLACE FUNCTION vandelay.extract_rec_attrs ( xml TEXT ) RETURNS hstore AS $_$
379     SELECT vandelay.extract_rec_attrs( $1, (SELECT ARRAY_ACCUM(name) FROM config.record_attr_definition));
380 $_$ LANGUAGE SQL;
381
382 -- Everything between this comment and the beginning of the definition of
383 -- vandelay.match_bib_record() is strictly in service of that function.
384 CREATE TYPE vandelay.match_set_test_result AS (record BIGINT, quality INTEGER);
385
386 CREATE OR REPLACE FUNCTION vandelay.match_set_test_marcxml(
387     match_set_id INTEGER, record_xml TEXT
388 ) RETURNS SETOF vandelay.match_set_test_result AS $$
389 DECLARE
390     tags_rstore HSTORE;
391     svf_rstore  HSTORE;
392     coal        TEXT;
393     joins       TEXT;
394     query_      TEXT;
395     wq          TEXT;
396     qvalue      INTEGER;
397     rec         RECORD;
398 BEGIN
399     tags_rstore := vandelay.flatten_marc_hstore(record_xml);
400     svf_rstore := vandelay.extract_rec_attrs(record_xml);
401
402     CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
403     CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
404
405     -- generate the where clause and return that directly (into wq), and as
406     -- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
407     wq := vandelay.get_expr_from_match_set(match_set_id);
408
409     query_ := 'SELECT bre.id AS record, ';
410
411     -- qrows table is for the quality bits we add to the SELECT clause
412     SELECT ARRAY_TO_STRING(
413         ARRAY_ACCUM('COALESCE(n' || q::TEXT || '.quality, 0)'), ' + '
414     ) INTO coal FROM _vandelay_tmp_qrows;
415
416     -- our query string so far is the SELECT clause and the inital FROM.
417     -- no JOINs yet nor the WHERE clause
418     query_ := query_ || coal || ' AS quality ' || E'\n' ||
419         'FROM biblio.record_entry bre ';
420
421     -- jrows table is for the joins we must make (and the real text conditions)
422     SELECT ARRAY_TO_STRING(ARRAY_ACCUM(j), E'\n') INTO joins
423         FROM _vandelay_tmp_jrows;
424
425     -- add those joins and the where clause to our query.
426     query_ := query_ || joins || E'\n' || 'WHERE ' || wq || ' AND not bre.deleted';
427
428     -- this will return rows of record,quality
429     FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
430         RETURN NEXT rec;
431     END LOOP;
432
433     DROP TABLE _vandelay_tmp_qrows;
434     DROP TABLE _vandelay_tmp_jrows;
435     RETURN;
436 END;
437
438 $$ LANGUAGE PLPGSQL;
439
440 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
441     record_xml TEXT
442 ) RETURNS HSTORE AS $$
443 BEGIN
444     RETURN (SELECT
445         HSTORE(
446             ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
447             ARRAY_ACCUM(value)
448         )
449         FROM (
450             SELECT tag, subfield, ARRAY_ACCUM(value)::TEXT AS value
451                 FROM vandelay.flatten_marc(record_xml)
452                 GROUP BY tag, subfield ORDER BY tag, subfield
453         ) subquery
454     );
455 END;
456 $$ LANGUAGE PLPGSQL;
457
458 CREATE OR REPLACE FUNCTION vandelay.get_expr_from_match_set(
459     match_set_id INTEGER
460 ) RETURNS TEXT AS $$
461 DECLARE
462     root    vandelay.match_set_point;
463 BEGIN
464     SELECT * INTO root FROM vandelay.match_set_point
465         WHERE parent IS NULL AND match_set = match_set_id;
466
467     RETURN vandelay.get_expr_from_match_set_point(root);
468 END;
469 $$  LANGUAGE PLPGSQL;
470
471 CREATE OR REPLACE FUNCTION vandelay.get_expr_from_match_set_point(
472     node vandelay.match_set_point
473 ) RETURNS TEXT AS $$
474 DECLARE
475     q           TEXT;
476     i           INTEGER;
477     this_op     TEXT;
478     children    INTEGER[];
479     child       vandelay.match_set_point;
480 BEGIN
481     SELECT ARRAY_ACCUM(id) INTO children FROM vandelay.match_set_point
482         WHERE parent = node.id;
483
484     IF ARRAY_LENGTH(children, 1) > 0 THEN
485         this_op := vandelay._get_expr_render_one(node);
486         q := '(';
487         i := 1;
488         WHILE children[i] IS NOT NULL LOOP
489             SELECT * INTO child FROM vandelay.match_set_point
490                 WHERE id = children[i];
491             IF i > 1 THEN
492                 q := q || ' ' || this_op || ' ';
493             END IF;
494             i := i + 1;
495             q := q || vandelay.get_expr_from_match_set_point(child);
496         END LOOP;
497         q := q || ')';
498         RETURN q;
499     ELSIF node.bool_op IS NULL THEN
500         PERFORM vandelay._get_expr_push_qrow(node);
501         PERFORM vandelay._get_expr_push_jrow(node);
502         RETURN vandelay._get_expr_render_one(node);
503     ELSE
504         RETURN '';
505     END IF;
506 END;
507 $$  LANGUAGE PLPGSQL;
508
509 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_qrow(
510     node vandelay.match_set_point
511 ) RETURNS VOID AS $$
512 DECLARE
513 BEGIN
514     INSERT INTO _vandelay_tmp_qrows (q) VALUES (node.id);
515 END;
516 $$ LANGUAGE PLPGSQL;
517
518 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
519     node vandelay.match_set_point
520 ) RETURNS VOID AS $$
521 DECLARE
522     jrow        TEXT;
523     my_alias    TEXT;
524     op          TEXT;
525     tagkey      TEXT;
526 BEGIN
527     IF node.negate THEN
528         op := '<>';
529     ELSE
530         op := '=';
531     END IF;
532
533     IF node.tag IS NOT NULL THEN
534         tagkey := node.tag;
535         IF node.subfield IS NOT NULL THEN
536             tagkey := tagkey || node.subfield;
537         END IF;
538     END IF;
539
540     my_alias := 'n' || node.id::TEXT;
541
542     jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
543         ' AS quality FROM metabib.';
544     IF node.tag IS NOT NULL THEN
545         jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
546             my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
547             node.tag || '''';
548         IF node.subfield IS NOT NULL THEN
549             jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
550                 node.subfield || '''';
551         END IF;
552         jrow := jrow || ' AND (' || my_alias || '.value ' || op ||
553             ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
554     ELSE    -- svf
555         jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
556             my_alias || '.id = bre.id AND (' ||
557             my_alias || '.attrs->''' || node.svf ||
558             ''' ' || op || ' $2->''' || node.svf || '''))';
559     END IF;
560     INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
561 END;
562 $$ LANGUAGE PLPGSQL;
563
564 CREATE OR REPLACE FUNCTION vandelay._get_expr_render_one(
565     node vandelay.match_set_point
566 ) RETURNS TEXT AS $$
567 DECLARE
568     s           TEXT;
569 BEGIN
570     IF node.bool_op IS NOT NULL THEN
571         RETURN node.bool_op;
572     ELSE
573         RETURN '(n' || node.id::TEXT || '.id IS NOT NULL)';
574     END IF;
575 END;
576 $$ LANGUAGE PLPGSQL;
577
578 CREATE OR REPLACE FUNCTION vandelay.match_bib_record() RETURNS TRIGGER AS $func$
579 DECLARE
580     incoming_existing_id    TEXT;
581     test_result             vandelay.match_set_test_result%ROWTYPE;
582     tmp_rec                 BIGINT;
583     match_set               INT;
584 BEGIN
585     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
586         RETURN NEW;
587     END IF;
588
589     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
590
591     SELECT q.match_set INTO match_set FROM vandelay.bib_queue q WHERE q.id = NEW.queue;
592
593     IF match_set IS NOT NULL THEN
594         NEW.quality := vandelay.measure_record_quality( NEW.marc, match_set );
595     END IF;
596
597     -- Perfect matches on 901$c exit early with a match with high quality.
598     incoming_existing_id :=
599         oils_xpath_string('//*[@tag="901"]/*[@code="c"][1]', NEW.marc);
600
601     IF incoming_existing_id IS NOT NULL AND incoming_existing_id != '' THEN
602         SELECT id INTO tmp_rec FROM biblio.record_entry WHERE id = incoming_existing_id::bigint;
603         IF tmp_rec IS NOT NULL THEN
604             INSERT INTO vandelay.bib_match (queued_record, eg_record, match_score, quality) 
605                 SELECT
606                     NEW.id, 
607                     b.id,
608                     9999,
609                     -- note: no match_set means quality==0
610                     vandelay.measure_record_quality( b.marc, match_set )
611                 FROM biblio.record_entry b
612                 WHERE id = incoming_existing_id::bigint;
613         END IF;
614     END IF;
615
616     IF match_set IS NULL THEN
617         RETURN NEW;
618     END IF;
619
620     FOR test_result IN SELECT * FROM
621         vandelay.match_set_test_marcxml(match_set, NEW.marc) LOOP
622
623         INSERT INTO vandelay.bib_match ( queued_record, eg_record, match_score, quality )
624             SELECT  
625                 NEW.id,
626                 test_result.record,
627                 test_result.quality,
628                 vandelay.measure_record_quality( b.marc, match_set )
629                 FROM  biblio.record_entry b
630                 WHERE id = test_result.record;
631
632     END LOOP;
633
634     RETURN NEW;
635 END;
636 $func$ LANGUAGE PLPGSQL;
637
638 CREATE OR REPLACE FUNCTION vandelay.measure_record_quality ( xml TEXT, match_set_id INT ) RETURNS INT AS $_$
639 DECLARE
640     out_q   INT := 0;
641     rvalue  TEXT;
642     test    vandelay.match_set_quality%ROWTYPE;
643 BEGIN
644
645     FOR test IN SELECT * FROM vandelay.match_set_quality WHERE match_set = match_set_id LOOP
646         IF test.tag IS NOT NULL THEN
647             FOR rvalue IN SELECT value FROM vandelay.flatten_marc( xml ) WHERE tag = test.tag AND subfield = test.subfield LOOP
648                 IF test.value = rvalue THEN
649                     out_q := out_q + test.quality;
650                 END IF;
651             END LOOP;
652         ELSE
653             IF test.value = vandelay.extract_rec_attrs(xml, ARRAY[test.svf]) -> test.svf THEN
654                 out_q := out_q + test.quality;
655             END IF;
656         END IF;
657     END LOOP;
658
659     RETURN out_q;
660 END;
661 $_$ LANGUAGE PLPGSQL;
662
663
664 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
665 DECLARE
666     merge_profile   vandelay.merge_profile%ROWTYPE;
667     dyn_profile     vandelay.compile_profile%ROWTYPE;
668     editor_string   TEXT;
669     editor_id       INT;
670     source_marc     TEXT;
671     target_marc     TEXT;
672     eg_marc         TEXT;
673     v_marc          TEXT;
674     replace_rule    TEXT;
675 BEGIN
676
677     SELECT  q.marc INTO v_marc
678       FROM  vandelay.queued_record q
679             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
680       LIMIT 1;
681
682     IF v_marc IS NULL THEN
683         -- RAISE NOTICE 'no marc for vandelay or bib record';
684         RETURN FALSE;
685     END IF;
686
687     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
688         UPDATE  vandelay.queued_bib_record
689           SET   imported_as = eg_id,
690                 import_time = NOW()
691           WHERE id = import_id;
692
693         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
694
695         IF editor_string IS NOT NULL AND editor_string <> '' THEN
696             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
697
698             IF editor_id IS NULL THEN
699                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
700             END IF;
701
702             IF editor_id IS NOT NULL THEN
703                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
704             END IF;
705         END IF;
706
707         RETURN TRUE;
708     END IF;
709
710     -- RAISE NOTICE 'update of biblio.record_entry failed';
711
712     RETURN FALSE;
713
714 END;
715 $$ LANGUAGE PLPGSQL;
716
717
718 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record_with_best ( import_id BIGINT, merge_profile_id INT, lwm_ratio_value_p NUMERIC ) RETURNS BOOL AS $$
719 DECLARE
720     eg_id           BIGINT;
721     lwm_ratio_value NUMERIC;
722 BEGIN
723
724     lwm_ratio_value := COALESCE(lwm_ratio_value_p, 0.0);
725
726     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
727
728     IF FOUND THEN
729         -- RAISE NOTICE 'already imported, cannot auto-overlay'
730         RETURN FALSE;
731     END IF;
732
733     SELECT  m.eg_record INTO eg_id
734       FROM  vandelay.bib_match m
735             JOIN vandelay.queued_bib_record qr ON (m.queued_record = qr.id)
736             JOIN vandelay.bib_queue q ON (qr.queue = q.id)
737             JOIN biblio.record_entry r ON (r.id = m.eg_record)
738       WHERE m.queued_record = import_id
739             AND qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC >= lwm_ratio_value
740       ORDER BY  m.match_score DESC, -- required match score
741                 qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC DESC, -- quality tie breaker
742                 m.id -- when in doubt, use the first match
743       LIMIT 1;
744
745     IF eg_id IS NULL THEN
746         -- RAISE NOTICE 'incoming record is not of high enough quality';
747         RETURN FALSE;
748     END IF;
749
750     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
751 END;
752 $$ LANGUAGE PLPGSQL;
753
754 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record_with_best ( import_id BIGINT, merge_profile_id INT, lwm_ratio_value_p NUMERIC ) RETURNS BOOL AS $$
755 DECLARE
756     eg_id           BIGINT;
757     lwm_ratio_value NUMERIC;
758 BEGIN
759
760     lwm_ratio_value := COALESCE(lwm_ratio_value_p, 0.0);
761
762     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
763
764     IF FOUND THEN
765         -- RAISE NOTICE 'already imported, cannot auto-overlay'
766         RETURN FALSE;
767     END IF;
768
769     SELECT  m.eg_record INTO eg_id
770       FROM  vandelay.bib_match m
771             JOIN vandelay.queued_bib_record qr ON (m.queued_record = qr.id)
772             JOIN vandelay.bib_queue q ON (qr.queue = q.id)
773             JOIN biblio.record_entry r ON (r.id = m.eg_record)
774       WHERE m.queued_record = import_id
775             AND qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC >= lwm_ratio_value
776       ORDER BY  m.match_score DESC, -- required match score
777                 qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC DESC, -- quality tie breaker
778                 m.id -- when in doubt, use the first match
779       LIMIT 1;
780
781     IF eg_id IS NULL THEN
782         -- RAISE NOTICE 'incoming record is not of high enough quality';
783         RETURN FALSE;
784     END IF;
785
786     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
787 END;
788 $$ LANGUAGE PLPGSQL;
789
790
791 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue_with_best ( queue_id BIGINT, merge_profile_id INT, lwm_ratio_value NUMERIC ) RETURNS SETOF BIGINT AS $$
792 DECLARE
793     queued_record   vandelay.queued_bib_record%ROWTYPE;
794 BEGIN
795
796     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
797
798         IF vandelay.auto_overlay_bib_record_with_best( queued_record.id, merge_profile_id, lwm_ratio_value ) THEN
799             RETURN NEXT queued_record.id;
800         END IF;
801
802     END LOOP;
803
804     RETURN;
805     
806 END;
807 $$ LANGUAGE PLPGSQL;
808
809 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue_with_best ( import_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
810     SELECT vandelay.auto_overlay_bib_queue_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;
811 $$ LANGUAGE SQL;
812
813 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
814 DECLARE
815     value   TEXT;
816     atype   TEXT;
817     adef    RECORD;
818 BEGIN
819     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
820         RETURN NEW;
821     END IF;
822
823     FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
824
825         SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
826         IF (value IS NOT NULL AND value <> '') THEN
827             INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
828         END IF;
829
830     END LOOP;
831
832     RETURN NULL;
833 END;
834 $$ LANGUAGE PLPGSQL;
835
836 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
837 DECLARE
838     attr_def    BIGINT;
839     item_data   vandelay.import_item%ROWTYPE;
840 BEGIN
841
842     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
843         RETURN NEW;
844     END IF;
845
846     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
847
848     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
849         INSERT INTO vandelay.import_item (
850             record,
851             definition,
852             owning_lib,
853             circ_lib,
854             call_number,
855             copy_number,
856             status,
857             location,
858             circulate,
859             deposit,
860             deposit_amount,
861             ref,
862             holdable,
863             price,
864             barcode,
865             circ_modifier,
866             circ_as_type,
867             alert_message,
868             pub_note,
869             priv_note,
870             opac_visible
871         ) VALUES (
872             NEW.id,
873             item_data.definition,
874             item_data.owning_lib,
875             item_data.circ_lib,
876             item_data.call_number,
877             item_data.copy_number,
878             item_data.status,
879             item_data.location,
880             item_data.circulate,
881             item_data.deposit,
882             item_data.deposit_amount,
883             item_data.ref,
884             item_data.holdable,
885             item_data.price,
886             item_data.barcode,
887             item_data.circ_modifier,
888             item_data.circ_as_type,
889             item_data.alert_message,
890             item_data.pub_note,
891             item_data.priv_note,
892             item_data.opac_visible
893         );
894     END LOOP;
895
896     RETURN NULL;
897 END;
898 $func$ LANGUAGE PLPGSQL;
899
900 CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
901 BEGIN
902     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
903         RETURN NEW;
904     END IF;
905
906     DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
907     DELETE FROM vandelay.import_item WHERE record = OLD.id;
908
909     IF TG_OP = 'UPDATE' THEN
910         RETURN NEW;
911     END IF;
912     RETURN OLD;
913 END;
914 $$ LANGUAGE PLPGSQL;
915
916 -- ALTER TABLEs...
917
918 DROP TRIGGER zz_match_bibs_trigger ON vandelay.queued_bib_record;
919 CREATE TRIGGER zz_match_bibs_trigger
920     BEFORE INSERT OR UPDATE ON vandelay.queued_bib_record
921     FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
922
923 CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
924 DECLARE
925     value   TEXT;
926     atype   TEXT;
927     adef    RECORD;
928 BEGIN
929     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
930         RETURN NEW;
931     END IF;
932
933     FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
934
935         SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
936         IF (value IS NOT NULL AND value <> '') THEN
937             INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
938         END IF;
939
940     END LOOP;
941
942     RETURN NULL;
943 END;
944 $$ LANGUAGE PLPGSQL;
945
946 ALTER TABLE vandelay.authority_attr_definition DROP COLUMN ident;
947 ALTER TABLE vandelay.queued_authority_record
948     ADD COLUMN import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
949     ADD COLUMN error_detail TEXT;
950
951 ALTER TABLE vandelay.authority_match DROP COLUMN matched_attr;
952
953 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
954 BEGIN
955     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
956         RETURN NEW;
957     END IF;
958
959     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
960     IF TG_OP = 'UPDATE' THEN
961         RETURN NEW;
962     END IF;
963     RETURN OLD;
964 END;
965 $$ LANGUAGE PLPGSQL;
966
967 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
968 DECLARE
969         auth    authority.record_entry%ROWTYPE;
970         output  authority.full_rec%ROWTYPE;
971         field   RECORD;
972 BEGIN
973         SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
974
975         FOR field IN SELECT * FROM vandelay.flatten_marc( auth.marc ) LOOP
976                 output.record := rid;
977                 output.ind1 := field.ind1;
978                 output.ind2 := field.ind2;
979                 output.tag := field.tag;
980                 output.subfield := field.subfield;
981                 output.value := field.value;
982
983                 RETURN NEXT output;
984         END LOOP;
985 END;
986 $func$ LANGUAGE PLPGSQL;
987
988 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
989 DECLARE
990         bib     biblio.record_entry%ROWTYPE;
991         output  metabib.full_rec%ROWTYPE;
992         field   RECORD;
993 BEGIN
994         SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
995
996         FOR field IN SELECT * FROM vandelay.flatten_marc( bib.marc ) LOOP
997                 output.record := rid;
998                 output.ind1 := field.ind1;
999                 output.ind2 := field.ind2;
1000                 output.tag := field.tag;
1001                 output.subfield := field.subfield;
1002                 output.value := field.value;
1003
1004                 RETURN NEXT output;
1005         END LOOP;
1006 END;
1007 $func$ LANGUAGE PLPGSQL;
1008
1009 -----------------------------------------------
1010 -- Seed data for import errors
1011 -----------------------------------------------
1012
1013 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'general.unknown', oils_i18n_gettext('general.unknown', 'Import or Overlay failed', 'vie', 'description') );
1014 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.item.duplicate.barcode', oils_i18n_gettext('import.item.duplicate.barcode', 'Import failed due to barcode collision', 'vie', 'description') );
1015 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.item.invalid.circ_modifier', oils_i18n_gettext('import.item.invalid.circ_modifier', 'Import failed due to invalid circulation modifier', 'vie', 'description') );
1016 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.item.invalid.location', oils_i18n_gettext('import.item.invalid.location', 'Import failed due to invalid copy location', 'vie', 'description') );
1017 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.duplicate.sysid', oils_i18n_gettext('import.duplicate.sysid', 'Import failed due to system id collision', 'vie', 'description') );
1018 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.duplicate.tcn', oils_i18n_gettext('import.duplicate.sysid', 'Import failed due to system id collision', 'vie', 'description') );
1019 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.missing.sysid', oils_i18n_gettext('overlay.missing.sysid', 'Overlay failed due to missing system id', 'vie', 'description') );
1020 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.auth.duplicate.acn', oils_i18n_gettext('import.auth.duplicate.acn', 'Import failed due to Accession Number collision', 'vie', 'description') );
1021 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.xml.malformed', oils_i18n_gettext('import.xml.malformed', 'Malformed record cause Import failure', 'vie', 'description') );
1022 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.xml.malformed', oils_i18n_gettext('overlay.xml.malformed', 'Malformed record cause Overlay failure', 'vie', 'description') );
1023 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.record.quality', oils_i18n_gettext('overlay.record.quality', 'New record had insufficient quality', 'vie', 'description') );
1024
1025
1026 ----------------------------------------------------------------
1027 -- Seed data for queued record/item exports
1028 ----------------------------------------------------------------
1029
1030 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
1031         'vandelay.queued_bib_record.print',
1032         'vqbr', 
1033         oils_i18n_gettext(
1034             'vandelay.queued_bib_record.print',
1035             'Print output has been requested for records in an Importer Bib Queue.',
1036             'ath',
1037             'description'
1038         ), 
1039         FALSE
1040     )
1041     ,(
1042         'vandelay.queued_bib_record.csv',
1043         'vqbr', 
1044         oils_i18n_gettext(
1045             'vandelay.queued_bib_record.csv',
1046             'CSV output has been requested for records in an Importer Bib Queue.',
1047             'ath',
1048             'description'
1049         ), 
1050         FALSE
1051     )
1052     ,(
1053         'vandelay.queued_bib_record.email',
1054         'vqbr', 
1055         oils_i18n_gettext(
1056             'vandelay.queued_bib_record.email',
1057             'An email has been requested for records in an Importer Bib Queue.',
1058             'ath',
1059             'description'
1060         ), 
1061         FALSE
1062     )
1063     ,(
1064         'vandelay.queued_auth_record.print',
1065         'vqar', 
1066         oils_i18n_gettext(
1067             'vandelay.queued_auth_record.print',
1068             'Print output has been requested for records in an Importer Authority Queue.',
1069             'ath',
1070             'description'
1071         ), 
1072         FALSE
1073     )
1074     ,(
1075         'vandelay.queued_auth_record.csv',
1076         'vqar', 
1077         oils_i18n_gettext(
1078             'vandelay.queued_auth_record.csv',
1079             'CSV output has been requested for records in an Importer Authority Queue.',
1080             'ath',
1081             'description'
1082         ), 
1083         FALSE
1084     )
1085     ,(
1086         'vandelay.queued_auth_record.email',
1087         'vqar', 
1088         oils_i18n_gettext(
1089             'vandelay.queued_auth_record.email',
1090             'An email has been requested for records in an Importer Authority Queue.',
1091             'ath',
1092             'description'
1093         ), 
1094         FALSE
1095     )
1096     ,(
1097         'vandelay.import_items.print',
1098         'vii', 
1099         oils_i18n_gettext(
1100             'vandelay.import_items.print',
1101             'Print output has been requested for Import Items from records in an Importer Bib Queue.',
1102             'ath',
1103             'description'
1104         ), 
1105         FALSE
1106     )
1107     ,(
1108         'vandelay.import_items.csv',
1109         'vii', 
1110         oils_i18n_gettext(
1111             'vandelay.import_items.csv',
1112             'CSV output has been requested for Import Items from records in an Importer Bib Queue.',
1113             'ath',
1114             'description'
1115         ), 
1116         FALSE
1117     )
1118     ,(
1119         'vandelay.import_items.email',
1120         'vii', 
1121         oils_i18n_gettext(
1122             'vandelay.import_items.email',
1123             'An email has been requested for Import Items from records in an Importer Bib Queue.',
1124             'ath',
1125             'description'
1126         ), 
1127         FALSE
1128     )
1129 ;
1130
1131 INSERT INTO action_trigger.event_definition (
1132         id,
1133         active,
1134         owner,
1135         name,
1136         hook,
1137         validator,
1138         reactor,
1139         group_field,
1140         granularity,
1141         template
1142     ) VALUES (
1143         39,
1144         TRUE,
1145         1,
1146         'Print Output for Queued Bib Records',
1147         'vandelay.queued_bib_record.print',
1148         'NOOP_True',
1149         'ProcessTemplate',
1150         'queue.owner',
1151         'print-on-demand',
1152 $$
1153 [%- USE date -%]
1154 <pre>
1155 Queue ID: [% target.0.queue.id %]
1156 Queue Name: [% target.0.queue.name %]
1157 Queue Type: [% target.0.queue.queue_type %]
1158 Complete? [% target.0.queue.complete %]
1159
1160     [% FOR vqbr IN target %]
1161 =-=-=
1162  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
1163  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
1164  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
1165  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
1166  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
1167  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
1168  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
1169  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
1170  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
1171  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
1172  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
1173  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
1174  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
1175  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
1176  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
1177
1178     [% END %]
1179 </pre>
1180 $$
1181     )
1182 ;
1183
1184 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
1185     39, 'attributes')
1186     ,( 39, 'queue')
1187 ;
1188
1189 INSERT INTO action_trigger.event_definition (
1190         id,
1191         active,
1192         owner,
1193         name,
1194         hook,
1195         validator,
1196         reactor,
1197         group_field,
1198         granularity,
1199         template
1200     ) VALUES (
1201         40,
1202         TRUE,
1203         1,
1204         'CSV Output for Queued Bib Records',
1205         'vandelay.queued_bib_record.csv',
1206         'NOOP_True',
1207         'ProcessTemplate',
1208         'queue.owner',
1209         'print-on-demand',
1210 $$
1211 [%- USE date -%]
1212 "Title of work","Author of work","Language of work","Pagination","ISBN","ISSN","Price","Accession Number","TCN Value","TCN Source","Internal ID","Publisher","Publication Date","Edition","Item Barcode"
1213 [% FOR vqbr IN target %]"[% helpers.get_queued_bib_attr('title',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('author',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('language',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pagination',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('isbn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('issn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('price',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('publisher',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('edition',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) | replace('"', '""') %]"
1214 [% END %]
1215 $$
1216     )
1217 ;
1218
1219 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
1220     40, 'attributes')
1221     ,( 40, 'queue')
1222 ;
1223
1224 INSERT INTO action_trigger.event_definition (
1225         id,
1226         active,
1227         owner,
1228         name,
1229         hook,
1230         validator,
1231         reactor,
1232         group_field,
1233         granularity,
1234         template
1235     ) VALUES (
1236         41,
1237         TRUE,
1238         1,
1239         'Email Output for Queued Bib Records',
1240         'vandelay.queued_bib_record.email',
1241         'NOOP_True',
1242         'SendEmail',
1243         'queue.owner',
1244         NULL,
1245 $$
1246 [%- USE date -%]
1247 [%- SET user = target.0.queue.owner -%]
1248 To: [%- params.recipient_email || user.email || 'root@localhost' %]
1249 From: [%- params.sender_email || default_sender %]
1250 Subject: Bibs from Import Queue
1251
1252 Queue ID: [% target.0.queue.id %]
1253 Queue Name: [% target.0.queue.name %]
1254 Queue Type: [% target.0.queue.queue_type %]
1255 Complete? [% target.0.queue.complete %]
1256
1257     [% FOR vqbr IN target %]
1258 =-=-=
1259  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
1260  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
1261  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
1262  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
1263  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
1264  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
1265  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
1266  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
1267  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
1268  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
1269  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
1270  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
1271  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
1272  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
1273  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
1274
1275     [% END %]
1276
1277 $$
1278     )
1279 ;
1280
1281 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
1282     41, 'attributes')
1283     ,( 41, 'queue')
1284     ,( 41, 'queue.owner')
1285 ;
1286
1287 INSERT INTO action_trigger.event_definition (
1288         id,
1289         active,
1290         owner,
1291         name,
1292         hook,
1293         validator,
1294         reactor,
1295         group_field,
1296         granularity,
1297         template
1298     ) VALUES (
1299         42,
1300         TRUE,
1301         1,
1302         'Print Output for Queued Authority Records',
1303         'vandelay.queued_auth_record.print',
1304         'NOOP_True',
1305         'ProcessTemplate',
1306         'queue.owner',
1307         'print-on-demand',
1308 $$
1309 [%- USE date -%]
1310 <pre>
1311 Queue ID: [% target.0.queue.id %]
1312 Queue Name: [% target.0.queue.name %]
1313 Queue Type: [% target.0.queue.queue_type %]
1314 Complete? [% target.0.queue.complete %]
1315
1316     [% FOR vqar IN target %]
1317 =-=-=
1318  Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
1319
1320     [% END %]
1321 </pre>
1322 $$
1323     )
1324 ;
1325
1326 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
1327     42, 'attributes')
1328     ,( 42, 'queue')
1329 ;
1330
1331 INSERT INTO action_trigger.event_definition (
1332         id,
1333         active,
1334         owner,
1335         name,
1336         hook,
1337         validator,
1338         reactor,
1339         group_field,
1340         granularity,
1341         template
1342     ) VALUES (
1343         43,
1344         TRUE,
1345         1,
1346         'CSV Output for Queued Authority Records',
1347         'vandelay.queued_auth_record.csv',
1348         'NOOP_True',
1349         'ProcessTemplate',
1350         'queue.owner',
1351         'print-on-demand',
1352 $$
1353 [%- USE date -%]
1354 "Record Identifier"
1355 [% FOR vqar IN target %]"[% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) | replace('"', '""') %]"
1356 [% END %]
1357 $$
1358     )
1359 ;
1360
1361 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
1362     43, 'attributes')
1363     ,( 43, 'queue')
1364 ;
1365
1366 INSERT INTO action_trigger.event_definition (
1367         id,
1368         active,
1369         owner,
1370         name,
1371         hook,
1372         validator,
1373         reactor,
1374         group_field,
1375         granularity,
1376         template
1377     ) VALUES (
1378         44,
1379         TRUE,
1380         1,
1381         'Email Output for Queued Authority Records',
1382         'vandelay.queued_auth_record.email',
1383         'NOOP_True',
1384         'SendEmail',
1385         'queue.owner',
1386         NULL,
1387 $$
1388 [%- USE date -%]
1389 [%- SET user = target.0.queue.owner -%]
1390 To: [%- params.recipient_email || user.email || 'root@localhost' %]
1391 From: [%- params.sender_email || default_sender %]
1392 Subject: Authorities from Import Queue
1393
1394 Queue ID: [% target.0.queue.id %]
1395 Queue Name: [% target.0.queue.name %]
1396 Queue Type: [% target.0.queue.queue_type %]
1397 Complete? [% target.0.queue.complete %]
1398
1399     [% FOR vqar IN target %]
1400 =-=-=
1401  Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
1402
1403     [% END %]
1404
1405 $$
1406     )
1407 ;
1408
1409 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
1410     44, 'attributes')
1411     ,( 44, 'queue')
1412     ,( 44, 'queue.owner')
1413 ;
1414
1415 INSERT INTO action_trigger.event_definition (
1416         id,
1417         active,
1418         owner,
1419         name,
1420         hook,
1421         validator,
1422         reactor,
1423         group_field,
1424         granularity,
1425         template
1426     ) VALUES (
1427         45,
1428         TRUE,
1429         1,
1430         'Print Output for Import Items from Queued Bib Records',
1431         'vandelay.import_items.print',
1432         'NOOP_True',
1433         'ProcessTemplate',
1434         'record.queue.owner',
1435         'print-on-demand',
1436 $$
1437 [%- USE date -%]
1438 <pre>
1439 Queue ID: [% target.0.record.queue.id %]
1440 Queue Name: [% target.0.record.queue.name %]
1441 Queue Type: [% target.0.record.queue.queue_type %]
1442 Complete? [% target.0.record.queue.complete %]
1443
1444     [% FOR vii IN target %]
1445 =-=-=
1446  Import Item ID         | [% vii.id %]
1447  Title of work          | [% helpers.get_queued_bib_attr('title',vii.record.attributes) %]
1448  ISBN                   | [% helpers.get_queued_bib_attr('isbn',vii.record.attributes) %]
1449  Attribute Definition   | [% vii.definition %]
1450  Import Error           | [% vii.import_error %]
1451  Import Error Detail    | [% vii.error_detail %]
1452  Owning Library         | [% vii.owning_lib %]
1453  Circulating Library    | [% vii.circ_lib %]
1454  Call Number            | [% vii.call_number %]
1455  Copy Number            | [% vii.copy_number %]
1456  Status                 | [% vii.status.name %]
1457  Shelving Location      | [% vii.location.name %]
1458  Circulate              | [% vii.circulate %]
1459  Deposit                | [% vii.deposit %]
1460  Deposit Amount         | [% vii.deposit_amount %]
1461  Reference              | [% vii.ref %]
1462  Holdable               | [% vii.holdable %]
1463  Price                  | [% vii.price %]
1464  Barcode                | [% vii.barcode %]
1465  Circulation Modifier   | [% vii.circ_modifier %]
1466  Circulate As MARC Type | [% vii.circ_as_type %]
1467  Alert Message          | [% vii.alert_message %]
1468  Public Note            | [% vii.pub_note %]
1469  Private Note           | [% vii.priv_note %]
1470  OPAC Visible           | [% vii.opac_visible %]
1471
1472     [% END %]
1473 </pre>
1474 $$
1475     )
1476 ;
1477
1478 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
1479     45, 'record')
1480     ,( 45, 'record.attributes')
1481     ,( 45, 'record.queue')
1482     ,( 45, 'record.queue.owner')
1483 ;
1484
1485 INSERT INTO action_trigger.event_definition (
1486         id,
1487         active,
1488         owner,
1489         name,
1490         hook,
1491         validator,
1492         reactor,
1493         group_field,
1494         granularity,
1495         template
1496     ) VALUES (
1497         46,
1498         TRUE,
1499         1,
1500         'CSV Output for Import Items from Queued Bib Records',
1501         'vandelay.import_items.csv',
1502         'NOOP_True',
1503         'ProcessTemplate',
1504         'record.queue.owner',
1505         'print-on-demand',
1506 $$
1507 [%- USE date -%]
1508 "Import Item ID","Title of work","ISBN","Attribute Definition","Import Error","Import Error Detail","Owning Library","Circulating Library","Call Number","Copy Number","Status","Shelving Location","Circulate","Deposit","Deposit Amount","Reference","Holdable","Price","Barcode","Circulation Modifier","Circulate As MARC Type","Alert Message","Public Note","Private Note","OPAC Visible"
1509 [% FOR vii IN target %]"[% vii.id | replace('"', '""') %]","[% helpers.get_queued_bib_attr('title',vii.record.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('isbn',vii.record.attributes) | replace('"', '""') %]","[% vii.definition | replace('"', '""') %]","[% vii.import_error | replace('"', '""') %]","[% vii.error_detail | replace('"', '""') %]","[% vii.owning_lib | replace('"', '""') %]","[% vii.circ_lib | replace('"', '""') %]","[% vii.call_number | replace('"', '""') %]","[% vii.copy_number | replace('"', '""') %]","[% vii.status.name | replace('"', '""') %]","[% vii.location.name | replace('"', '""') %]","[% vii.circulate | replace('"', '""') %]","[% vii.deposit | replace('"', '""') %]","[% vii.deposit_amount | replace('"', '""') %]","[% vii.ref | replace('"', '""') %]","[% vii.holdable | replace('"', '""') %]","[% vii.price | replace('"', '""') %]","[% vii.barcode | replace('"', '""') %]","[% vii.circ_modifier | replace('"', '""') %]","[% vii.circ_as_type | replace('"', '""') %]","[% vii.alert_message | replace('"', '""') %]","[% vii.pub_note | replace('"', '""') %]","[% vii.priv_note | replace('"', '""') %]","[% vii.opac_visible | replace('"', '""') %]"
1510 [% END %]
1511 $$
1512     )
1513 ;
1514
1515 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
1516     46, 'record')
1517     ,( 46, 'record.attributes')
1518     ,( 46, 'record.queue')
1519     ,( 46, 'record.queue.owner')
1520 ;
1521
1522 INSERT INTO action_trigger.event_definition (
1523         id,
1524         active,
1525         owner,
1526         name,
1527         hook,
1528         validator,
1529         reactor,
1530         group_field,
1531         granularity,
1532         template
1533     ) VALUES (
1534         47,
1535         TRUE,
1536         1,
1537         'Email Output for Import Items from Queued Bib Records',
1538         'vandelay.import_items.email',
1539         'NOOP_True',
1540         'SendEmail',
1541         'record.queue.owner',
1542         NULL,
1543 $$
1544 [%- USE date -%]
1545 [%- SET user = target.0.record.queue.owner -%]
1546 To: [%- params.recipient_email || user.email || 'root@localhost' %]
1547 From: [%- params.sender_email || default_sender %]
1548 Subject: Import Items from Import Queue
1549
1550 Queue ID: [% target.0.record.queue.id %]
1551 Queue Name: [% target.0.record.queue.name %]
1552 Queue Type: [% target.0.record.queue.queue_type %]
1553 Complete? [% target.0.record.queue.complete %]
1554
1555     [% FOR vii IN target %]
1556 =-=-=
1557  Import Item ID         | [% vii.id %]
1558  Title of work          | [% helpers.get_queued_bib_attr('title',vii.record.attributes) %]
1559  ISBN                   | [% helpers.get_queued_bib_attr('isbn',vii.record.attributes) %]
1560  Attribute Definition   | [% vii.definition %]
1561  Import Error           | [% vii.import_error %]
1562  Import Error Detail    | [% vii.error_detail %]
1563  Owning Library         | [% vii.owning_lib %]
1564  Circulating Library    | [% vii.circ_lib %]
1565  Call Number            | [% vii.call_number %]
1566  Copy Number            | [% vii.copy_number %]
1567  Status                 | [% vii.status.name %]
1568  Shelving Location      | [% vii.location.name %]
1569  Circulate              | [% vii.circulate %]
1570  Deposit                | [% vii.deposit %]
1571  Deposit Amount         | [% vii.deposit_amount %]
1572  Reference              | [% vii.ref %]
1573  Holdable               | [% vii.holdable %]
1574  Price                  | [% vii.price %]
1575  Barcode                | [% vii.barcode %]
1576  Circulation Modifier   | [% vii.circ_modifier %]
1577  Circulate As MARC Type | [% vii.circ_as_type %]
1578  Alert Message          | [% vii.alert_message %]
1579  Public Note            | [% vii.pub_note %]
1580  Private Note           | [% vii.priv_note %]
1581  OPAC Visible           | [% vii.opac_visible %]
1582
1583     [% END %]
1584 $$
1585     )
1586 ;
1587
1588 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
1589     47, 'record')
1590     ,( 47, 'record.attributes')
1591     ,( 47, 'record.queue')
1592     ,( 47, 'record.queue.owner')
1593 ;
1594
1595
1596 COMMIT;