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