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