]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/012.schema.vandelay.sql
moving functions around; tables for configuring match points and quality metrics
[Evergreen.git] / Open-ILS / src / sql / Pg / 012.schema.vandelay.sql
1 DROP SCHEMA IF EXISTS vandelay CASCADE;
2
3 BEGIN;
4
5 CREATE SCHEMA vandelay;
6
7 CREATE TABLE vandelay.queue (
8         id                              BIGSERIAL       PRIMARY KEY,
9         owner                   INT                     NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
10         name                    TEXT            NOT NULL,
11         complete                BOOL            NOT NULL DEFAULT FALSE,
12         queue_type              TEXT            NOT NULL DEFAULT 'bib' CHECK (queue_type IN ('bib','authority')),
13         CONSTRAINT vand_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
14 );
15
16 CREATE TABLE vandelay.queued_record (
17     id                  BIGSERIAL                   PRIMARY KEY,
18     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
19     import_time TIMESTAMP WITH TIME ZONE,
20         purpose         TEXT                                            NOT NULL DEFAULT 'import' CHECK (purpose IN ('import','overlay')),
21     marc                TEXT                        NOT NULL,
22     quality     INT                         NOT NULL DEFAULT 0
23 );
24
25
26
27 /* Bib stuff at the top */
28 ----------------------------------------------------
29
30 CREATE TABLE vandelay.bib_attr_definition (
31         id                      SERIAL  PRIMARY KEY,
32         code            TEXT    UNIQUE NOT NULL,
33         description     TEXT,
34         xpath           TEXT    NOT NULL,
35         remove          TEXT    NOT NULL DEFAULT '',
36         ident           BOOL    NOT NULL DEFAULT FALSE
37 );
38
39 -- Each TEXT field (other than 'name') should hold an XPath predicate for pulling the data needed
40 -- DROP TABLE vandelay.import_item_attr_definition CASCADE;
41 CREATE TABLE vandelay.import_item_attr_definition (
42     id              BIGSERIAL   PRIMARY KEY,
43     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
44     name            TEXT        NOT NULL,
45     tag             TEXT        NOT NULL,
46     keep            BOOL        NOT NULL DEFAULT FALSE,
47     owning_lib      TEXT,
48     circ_lib        TEXT,
49     call_number     TEXT,
50     copy_number     TEXT,
51     status          TEXT,
52     location        TEXT,
53     circulate       TEXT,
54     deposit         TEXT,
55     deposit_amount  TEXT,
56     ref             TEXT,
57     holdable        TEXT,
58     price           TEXT,
59     barcode         TEXT,
60     circ_modifier   TEXT,
61     circ_as_type    TEXT,
62     alert_message   TEXT,
63     opac_visible    TEXT,
64     pub_note_title  TEXT,
65     pub_note        TEXT,
66     priv_note_title TEXT,
67     priv_note       TEXT,
68         CONSTRAINT vand_import_item_attr_def_idx UNIQUE (owner,name)
69 );
70
71 CREATE TABLE vandelay.bib_queue (
72         queue_type          TEXT        NOT NULL DEFAULT 'bib' CHECK (queue_type = 'bib'),
73         item_attr_def   BIGINT REFERENCES vandelay.import_item_attr_definition (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
74         CONSTRAINT vand_bib_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
75 ) INHERITS (vandelay.queue);
76 ALTER TABLE vandelay.bib_queue ADD PRIMARY KEY (id);
77
78 CREATE TABLE vandelay.queued_bib_record (
79         queue           INT             NOT NULL REFERENCES vandelay.bib_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
80         bib_source      INT             REFERENCES config.bib_source (id) DEFERRABLE INITIALLY DEFERRED,
81         imported_as     BIGINT  REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED
82 ) INHERITS (vandelay.queued_record);
83 ALTER TABLE vandelay.queued_bib_record ADD PRIMARY KEY (id);
84 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
85
86 CREATE TABLE vandelay.queued_bib_record_attr (
87         id                      BIGSERIAL       PRIMARY KEY,
88         record          BIGINT          NOT NULL REFERENCES vandelay.queued_bib_record (id) DEFERRABLE INITIALLY DEFERRED,
89         field           INT                     NOT NULL REFERENCES vandelay.bib_attr_definition (id) DEFERRABLE INITIALLY DEFERRED,
90         attr_value      TEXT            NOT NULL
91 );
92 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
93
94 CREATE TABLE vandelay.bib_match (
95         id                              BIGSERIAL       PRIMARY KEY,
96         field_type              TEXT            NOT NULL CHECK (field_type in ('isbn','tcn_value','id')),
97         matched_attr    INT                     REFERENCES vandelay.queued_bib_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
98         queued_record   BIGINT          REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
99         eg_record               BIGINT          REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
100     quality         INT         NOT NULL DEFAULT 0
101 );
102
103 -- DROP TABLE vandelay.import_item CASCADE;
104 CREATE TABLE vandelay.import_item (
105     id              BIGSERIAL   PRIMARY KEY,
106     record          BIGINT      NOT NULL REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
107     definition      BIGINT      NOT NULL REFERENCES vandelay.import_item_attr_definition (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
108     owning_lib      INT,
109     circ_lib        INT,
110     call_number     TEXT,
111     copy_number     INT,
112     status          INT,
113     location        INT,
114     circulate       BOOL,
115     deposit         BOOL,
116     deposit_amount  NUMERIC(8,2),
117     ref             BOOL,
118     holdable        BOOL,
119     price           NUMERIC(8,2),
120     barcode         TEXT,
121     circ_modifier   TEXT,
122     circ_as_type    TEXT,
123     alert_message   TEXT,
124     pub_note        TEXT,
125     priv_note       TEXT,
126     opac_visible    BOOL
127 );
128  
129 CREATE TABLE vandelay.import_bib_trash_fields (
130     id              BIGSERIAL   PRIMARY KEY,
131     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
132     field           TEXT        NOT NULL,
133         CONSTRAINT vand_import_bib_trash_fields_idx UNIQUE (owner,field)
134 );
135
136 CREATE TABLE vandelay.merge_profile (
137     id              BIGSERIAL   PRIMARY KEY,
138     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
139     name            TEXT        NOT NULL,
140     add_spec        TEXT,
141     replace_spec    TEXT,
142     strip_spec      TEXT,
143     preserve_spec   TEXT,
144         CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
145         CONSTRAINT add_replace_strip_or_preserve CHECK ((preserve_spec IS NOT NULL OR replace_spec IS NOT NULL) OR (preserve_spec IS NULL AND replace_spec IS NULL))
146 );
147
148 CREATE TABLE vandelay.match_set (
149     id      SERIAL  PRIMARY KEY,
150     name    TEXT        NOT NULL,
151     owner   INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE,
152     mtype   TEXT        NOT NULL DEFAULT 'biblio', -- 'biblio','authority','mfhd'?, others?
153     CONSTRAINT name_once_per_owner_mtype UNIQUE (name, owner, mtype)
154 );
155
156 -- Table to define match points, either FF via SVF or tag+subfield
157 CREATE TABLE vandelay.match_set_point (
158     id          SERIAL  PRIMARY KEY,
159     match_set   INT     NOT NULL REFERENCES vandelay.match_set (id),
160     fixed_field TEXT, -- should exist in config.marc21_ff_pos_map.fixed_field
161     tag         TEXT,
162     subfield    TEXT,
163     required    BOOL    NOT NULL DEFAULT TRUE,
164     quality     INT     NOT NULL DEFAULT 1, -- higher is better
165     CONSTRAINT vmsp_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
166     CONSTRAINT vmsp_need_a_tag_or_a_ff CHECK (tag IS NOT NULL AND fixed_field IS NULL) OR (tag IS NULL AND fixed_field IS NOT NULL)),
167     CONSTRAINT vmsp_def_once_per_set UNIQUE (match_set, COALESCE(tag,''), COALESCE(subfield,''), COALESCE(fixed_field,''))
168 );
169
170 CREATE TABLE vandelay.match_set_quality (
171     id          SERIAL  PRIMARY KEY,
172     match_set   INT     NOT NULL REFERENCES vandelay.match_set (id),
173     fixed_field TEXT, -- should exist in config.marc21_ff_pos_map.fixed_field
174     tag         TEXT,
175     subfield    TEXT,
176     value       TEXT    NOT NULL,
177     quality     INT     NOT NULL DEFAULT 1, -- higher is better
178     CONSTRAINT vmsq_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
179     CONSTRAINT vmsq_need_a_tag_or_a_ff CHECK (tag IS NOT NULL AND fixed_field IS NULL) OR (tag IS NULL AND fixed_field IS NOT NULL)),
180     CONSTRAINT vmsq_def_once_per_set UNIQUE (match_set, COALESCE(tag,''), COALESCE(subfield,''), COALESCE(fixed_field,''))
181 );
182
183 CREATE OR REPLACE FUNCTION vandelay.marc21_record_type( marc TEXT ) RETURNS config.marc21_rec_type_map AS $func$
184 DECLARE
185     ldr         TEXT;
186     tval        TEXT;
187     tval_rec    RECORD;
188     bval        TEXT;
189     bval_rec    RECORD;
190     retval      config.marc21_rec_type_map%ROWTYPE;
191 BEGIN
192     ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
193
194     IF ldr IS NULL OR ldr = '' THEN
195         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
196         RETURN retval;
197     END IF;
198
199     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
200     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
201
202
203     tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
204     bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
205
206     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
207
208     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
209
210
211     IF retval.code IS NULL THEN
212         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
213     END IF;
214
215     RETURN retval;
216 END;
217 $func$ LANGUAGE PLPGSQL;
218
219 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
220 DECLARE
221     rtype       TEXT;
222     ff_pos      RECORD;
223     tag_data    RECORD;
224     val         TEXT;
225 BEGIN
226     rtype := (vandelay.marc21_record_type( marc )).code;
227     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
228         FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
229             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
230             RETURN val;
231         END LOOP;
232         val := REPEAT( ff_pos.default_val, ff_pos.length );
233         RETURN val;
234     END LOOP;
235
236     RETURN NULL;
237 END;
238 $func$ LANGUAGE PLPGSQL;
239
240 CREATE TYPE biblio.record_ff_map AS (record BIGINT, ff_name TEXT, ff_value TEXT);
241 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
242 DECLARE
243     tag_data    TEXT;
244     rtype       TEXT;
245     ff_pos      RECORD;
246     output      biblio.record_ff_map%ROWTYPE;
247 BEGIN
248     rtype := (vandelay.marc21_record_type( marc )).code;
249
250     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
251         output.ff_name  := ff_pos.fixed_field;
252         output.ff_value := NULL;
253
254         FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(tag) || '"]/text()', marc ) ) x(value) LOOP
255             output.ff_value := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
256             IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
257             RETURN NEXT output;
258             output.ff_value := NULL;
259         END LOOP;
260
261     END LOOP;
262
263     RETURN;
264 END;
265 $func$ LANGUAGE PLPGSQL;
266
267 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
268 CREATE OR REPLACE FUNCTION vandelay.marc21_physical_characteristics( marc TEXT) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
269 DECLARE
270     rowid   INT := 0;
271     _007    TEXT;
272     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
273     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
274     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
275     retval  biblio.marc21_physical_characteristics%ROWTYPE;
276 BEGIN
277
278     _007 := oils_xpath_string( '//*[@tag="007"]', marc );
279
280     IF _007 IS NOT NULL AND _007 <> '' THEN
281         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
282
283         IF ptype.ptype_key IS NOT NULL THEN
284             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
285                 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 );
286
287                 IF pval.id IS NOT NULL THEN
288                     rowid := rowid + 1;
289                     retval.id := rowid;
290                     retval.ptype := ptype.ptype_key;
291                     retval.subfield := psf.id;
292                     retval.value := pval.id;
293                     RETURN NEXT retval;
294                 END IF;
295
296             END LOOP;
297         END IF;
298     END IF;
299
300     RETURN;
301 END;
302 $func$ LANGUAGE PLPGSQL;
303
304 CREAT TYPE vandelay.flat_marc AS ( tag CHAR(3), ind1 TEXT, ind2 TEXT, subfield TEXT, value TEXT );
305 CREATE OR REPLACE FUNCTION vandelay.flay_marc ( TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
306
307 use MARC::Record;
308 use MARC::File::XML (BinaryEncoding => 'UTF-8');
309
310 my $xml = shift;
311 my $r = MARC::Record->new_from_xml( $xml );
312
313 return_next( { tag => 'LDR', value => $r->leader } );
314
315 for my $f ( $r->fields ) {
316     if ($f->is_control_field) {
317         return_next({ tag => $f->tag, value => $f->data });
318     } else {
319         for my $s ($f->subfields) {
320             return_next({
321                 tag      => $f->tag,
322                 ind1     => $f->indicator(1),
323                 ind2     => $f->indicator(2),
324                 subfield => $s->[0],
325                 value    => $s->[1]
326             });
327
328             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
329                 my $trim = $f->indicator(2) || 0;
330                 return_next({
331                     tag      => 'tnf',
332                     ind1     => $f->indicator(1),
333                     ind2     => $f->indicator(2),
334                     subfield => 'a',
335                     value    => substr( $s->[1], $trim )
336                 });
337             }
338         }
339     }
340 }
341
342 return undef;
343
344 $func$ LANGUAGE PLPERLU;
345
346 CREATE OR REPLACE FUNCTION vandelay.flatten_marc ( marc TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
347 DECLARE
348     output  vandelay.flat_marc%ROWTYPE;
349     field   RECORD;
350 BEGIN
351     FOR field IN SELECT * FROM vandelay.flay_marc( marc ) LOOP
352         output.ind1 := field.ind1;
353         output.ind2 := field.ind2;
354         output.tag := field.tag;
355         output.subfield := field.subfield;
356         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
357             output.value := naco_normalize(field.value, field.subfield);
358         ELSE
359             output.value := field.value;
360         END IF;
361
362         CONTINUE WHEN output.value IS NULL;
363
364         RETURN NEXT output;
365     END LOOP;
366 END;
367 $func$ LANGUAGE PLPGSQL;
368
369 CREATE OR REPLACE FUNCTION vandelay.incoming_record_quality ( xml TEXT, match_set_id INT ) RETURNS INT AS $_$
370 DECLARE
371     out_q   INT := 0;
372     rvalue  TEXT;
373     test    vandelay.match_set_quality%ROWTYPE;
374 BEGIN
375
376     FOR test IN SELECT * FROM vandelay.match_set_quality WHERE match_set = match_set_id LOOP
377         IF test.tag IS NOT NULL THEN
378             FOR rvalue IN SELECT value FROM vandelay.flatten_marc( xml ) WHERE tag = test.tag AND subfield = test.subfield LOOP
379                 IF test.value = rvalue THEN
380                     out_q := out_q + test.quality;
381                 END IF;
382             END LOOP;
383         ELSIF test.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
384             IF test.value = vandelay.marc21_extract_fixed_field(xml, test.fixed_field) THEN
385                 out_q := out_q + test.quality;
386             END IF;
387         END IF;
388     END LOOP;
389
390     RETURN out_q;
391 END;
392 $_$ LANGUAGE PLPGSQL;
393
394 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
395 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
396 DECLARE
397     eg_tcn          TEXT;
398     eg_tcn_source   TEXT;
399     output          vandelay.tcn_data%ROWTYPE;
400 BEGIN
401
402     -- 001/003
403     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
404     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
405
406         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
407         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
408             eg_tcn_source := 'System Local';
409         END IF;
410
411         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
412
413         IF NOT FOUND THEN
414             output.used := FALSE;
415         ELSE
416             output.used := TRUE;
417         END IF;
418
419         output.tcn := eg_tcn;
420         output.tcn_source := eg_tcn_source;
421         RETURN NEXT output;
422
423     END IF;
424
425     -- 901 ab
426     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
427     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
428
429         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
430         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
431             eg_tcn_source := 'System Local';
432         END IF;
433
434         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
435
436         IF NOT FOUND THEN
437             output.used := FALSE;
438         ELSE
439             output.used := TRUE;
440         END IF;
441
442         output.tcn := eg_tcn;
443         output.tcn_source := eg_tcn_source;
444         RETURN NEXT output;
445
446     END IF;
447
448     -- 039 ab
449     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
450     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
451
452         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
453         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
454             eg_tcn_source := 'System Local';
455         END IF;
456
457         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
458
459         IF NOT FOUND THEN
460             output.used := FALSE;
461         ELSE
462             output.used := TRUE;
463         END IF;
464
465         output.tcn := eg_tcn;
466         output.tcn_source := eg_tcn_source;
467         RETURN NEXT output;
468
469     END IF;
470
471     -- 020 a
472     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
473     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
474
475         eg_tcn_source := 'ISBN';
476
477         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
478
479         IF NOT FOUND THEN
480             output.used := FALSE;
481         ELSE
482             output.used := TRUE;
483         END IF;
484
485         output.tcn := eg_tcn;
486         output.tcn_source := eg_tcn_source;
487         RETURN NEXT output;
488
489     END IF;
490
491     -- 022 a
492     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
493     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
494
495         eg_tcn_source := 'ISSN';
496
497         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
498
499         IF NOT FOUND THEN
500             output.used := FALSE;
501         ELSE
502             output.used := TRUE;
503         END IF;
504
505         output.tcn := eg_tcn;
506         output.tcn_source := eg_tcn_source;
507         RETURN NEXT output;
508
509     END IF;
510
511     -- 010 a
512     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
513     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
514
515         eg_tcn_source := 'LCCN';
516
517         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
518
519         IF NOT FOUND THEN
520             output.used := FALSE;
521         ELSE
522             output.used := TRUE;
523         END IF;
524
525         output.tcn := eg_tcn;
526         output.tcn_source := eg_tcn_source;
527         RETURN NEXT output;
528
529     END IF;
530
531     -- 035 a
532     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
533     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
534
535         eg_tcn_source := 'System Legacy';
536
537         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
538
539         IF NOT FOUND THEN
540             output.used := FALSE;
541         ELSE
542             output.used := TRUE;
543         END IF;
544
545         output.tcn := eg_tcn;
546         output.tcn_source := eg_tcn_source;
547         RETURN NEXT output;
548
549     END IF;
550
551     RETURN;
552 END;
553 $_$ LANGUAGE PLPGSQL;
554
555 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
556
557     use MARC::Record;
558     use MARC::File::XML (BinaryEncoding => 'UTF-8');
559     use MARC::Charset;
560     use strict;
561
562     MARC::Charset->assume_unicode(1);
563
564     my $target_xml = shift;
565     my $source_xml = shift;
566     my $field_spec = shift;
567     my $force_add = shift || 0;
568
569     my $target_r = MARC::Record->new_from_xml( $target_xml );
570     my $source_r = MARC::Record->new_from_xml( $source_xml );
571
572     return $target_xml unless ($target_r && $source_r);
573
574     my @field_list = split(',', $field_spec);
575
576     my %fields;
577     for my $f (@field_list) {
578         $f =~ s/^\s*//; $f =~ s/\s*$//;
579         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
580             my $field = $1;
581             $field =~ s/\s+//;
582             my $sf = $2;
583             $sf =~ s/\s+//;
584             my $match = $3;
585             $match =~ s/^\s*//; $match =~ s/\s*$//;
586             $fields{$field} = { sf => [ split('', $sf) ] };
587             if ($match) {
588                 my ($msf,$mre) = split('~', $match);
589                 if (length($msf) > 0 and length($mre) > 0) {
590                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
591                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
592                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
593                 }
594             }
595         }
596     }
597
598     for my $f ( keys %fields) {
599         if ( @{$fields{$f}{sf}} ) {
600             for my $from_field ($source_r->field( $f )) {
601                 my @tos = $target_r->field( $f );
602                 if (!@tos) {
603                     next if (exists($fields{$f}{match}) and !$force_add);
604                     my @new_fields = map { $_->clone } $source_r->field( $f );
605                     $target_r->insert_fields_ordered( @new_fields );
606                 } else {
607                     for my $to_field (@tos) {
608                         if (exists($fields{$f}{match})) {
609                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
610                         }
611                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
612                         $to_field->add_subfields( @new_sf );
613                     }
614                 }
615             }
616         } else {
617             my @new_fields = map { $_->clone } $source_r->field( $f );
618             $target_r->insert_fields_ordered( @new_fields );
619         }
620     }
621
622     $target_xml = $target_r->as_xml_record;
623     $target_xml =~ s/^<\?.+?\?>$//mo;
624     $target_xml =~ s/\n//sgo;
625     $target_xml =~ s/>\s+</></sgo;
626
627     return $target_xml;
628
629 $_$ LANGUAGE PLPERLU;
630
631 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
632     SELECT vandelay.add_field( $1, $2, $3, 0 );
633 $_$ LANGUAGE SQL;
634
635 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
636
637     use MARC::Record;
638     use MARC::File::XML (BinaryEncoding => 'UTF-8');
639     use MARC::Charset;
640     use strict;
641
642     MARC::Charset->assume_unicode(1);
643
644     my $xml = shift;
645     my $r = MARC::Record->new_from_xml( $xml );
646
647     return $xml unless ($r);
648
649     my $field_spec = shift;
650     my @field_list = split(',', $field_spec);
651
652     my %fields;
653     for my $f (@field_list) {
654         $f =~ s/^\s*//; $f =~ s/\s*$//;
655         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
656             my $field = $1;
657             $field =~ s/\s+//;
658             my $sf = $2;
659             $sf =~ s/\s+//;
660             my $match = $3;
661             $match =~ s/^\s*//; $match =~ s/\s*$//;
662             $fields{$field} = { sf => [ split('', $sf) ] };
663             if ($match) {
664                 my ($msf,$mre) = split('~', $match);
665                 if (length($msf) > 0 and length($mre) > 0) {
666                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
667                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
668                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
669                 }
670             }
671         }
672     }
673
674     for my $f ( keys %fields) {
675         for my $to_field ($r->field( $f )) {
676             if (exists($fields{$f}{match})) {
677                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
678             }
679
680             if ( @{$fields{$f}{sf}} ) {
681                 $to_field->delete_subfield(code => $fields{$f}{sf});
682             } else {
683                 $r->delete_field( $to_field );
684             }
685         }
686     }
687
688     $xml = $r->as_xml_record;
689     $xml =~ s/^<\?.+?\?>$//mo;
690     $xml =~ s/\n//sgo;
691     $xml =~ s/>\s+</></sgo;
692
693     return $xml;
694
695 $_$ LANGUAGE PLPERLU;
696
697 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
698 DECLARE
699     xml_output TEXT;
700     parsed_target TEXT;
701     curr_field TEXT;
702 BEGIN
703
704     parsed_target := vandelay.strip_field( target_xml, ''); -- this dance normalizes the format of the xml for the IF below
705
706     FOR curr_field IN SELECT UNNEST( STRING_TO_ARRAY(field, ',') ) LOOP -- naive split, but it's the same we use in the perl
707
708         xml_output := vandelay.strip_field( parsed_target, curr_field);
709
710         IF xml_output <> parsed_target  AND curr_field ~ E'~' THEN
711             -- we removed something, and there was a regexp restriction in the curr_field definition, so proceed
712             xml_output := vandelay.add_field( xml_output, source_xml, curr_field, 1 );
713         ELSIF curr_field !~ E'~' THEN
714             -- No regexp restriction, add the curr_field
715             xml_output := vandelay.add_field( xml_output, source_xml, curr_field, 0 );
716         END IF;
717
718         parsed_target := xml_output; -- in prep for any following loop iterations
719
720     END LOOP;
721
722     RETURN xml_output;
723 END;
724 $_$ LANGUAGE PLPGSQL;
725
726 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_xml TEXT, source_xml TEXT, add_rule TEXT, replace_preserve_rule TEXT, strip_rule TEXT ) RETURNS TEXT AS $_$
727     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
728 $_$ LANGUAGE SQL;
729
730 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
731 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
732 DECLARE
733     output              vandelay.compile_profile%ROWTYPE;
734     profile             vandelay.merge_profile%ROWTYPE;
735     profile_tmpl        TEXT;
736     profile_tmpl_owner  TEXT;
737     add_rule            TEXT := '';
738     strip_rule          TEXT := '';
739     replace_rule        TEXT := '';
740     preserve_rule       TEXT := '';
741
742 BEGIN
743
744     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
745     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
746
747     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
748         SELECT  p.* INTO profile
749           FROM  vandelay.merge_profile p
750                 JOIN actor.org_unit u ON (u.id = p.owner)
751           WHERE p.name = profile_tmpl
752                 AND u.shortname = profile_tmpl_owner;
753
754         IF profile.id IS NOT NULL THEN
755             add_rule := COALESCE(profile.add_spec,'');
756             strip_rule := COALESCE(profile.strip_spec,'');
757             replace_rule := COALESCE(profile.replace_spec,'');
758             preserve_rule := COALESCE(profile.preserve_spec,'');
759         END IF;
760     END IF;
761
762     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
763     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
764     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
765     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
766
767     output.add_rule := BTRIM(add_rule,',');
768     output.replace_rule := BTRIM(replace_rule,',');
769     output.strip_rule := BTRIM(strip_rule,',');
770     output.preserve_rule := BTRIM(preserve_rule,',');
771
772     RETURN output;
773 END;
774 $_$ LANGUAGE PLPGSQL;
775
776 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
777 DECLARE
778     merge_profile   vandelay.merge_profile%ROWTYPE;
779     dyn_profile     vandelay.compile_profile%ROWTYPE;
780     editor_string   TEXT;
781     editor_id       INT;
782     source_marc     TEXT;
783     target_marc     TEXT;
784     eg_marc         TEXT;
785     replace_rule    TEXT;
786     match_count     INT;
787 BEGIN
788
789     SELECT  b.marc INTO eg_marc
790       FROM  biblio.record_entry b
791       WHERE b.id = eg_id
792       LIMIT 1;
793
794     IF eg_marc IS NULL OR v_marc IS NULL THEN
795         -- RAISE NOTICE 'no marc for template or bib record';
796         RETURN FALSE;
797     END IF;
798
799     dyn_profile := vandelay.compile_profile( v_marc );
800
801     IF merge_profile_id IS NOT NULL THEN
802         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
803         IF FOUND THEN
804             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
805             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
806             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
807             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
808         END IF;
809     END IF;
810
811     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
812         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
813         RETURN FALSE;
814     END IF;
815
816     IF dyn_profile.replace_rule <> '' THEN
817         source_marc = v_marc;
818         target_marc = eg_marc;
819         replace_rule = dyn_profile.replace_rule;
820     ELSE
821         source_marc = eg_marc;
822         target_marc = v_marc;
823         replace_rule = dyn_profile.preserve_rule;
824     END IF;
825
826     UPDATE  biblio.record_entry
827       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
828       WHERE id = eg_id;
829
830     IF NOT FOUND THEN
831         -- RAISE NOTICE 'update of biblio.record_entry failed';
832         RETURN FALSE;
833     END IF;
834
835     RETURN TRUE;
836
837 END;
838 $$ LANGUAGE PLPGSQL;
839
840 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
841 DECLARE
842     dyn_profile     vandelay.compile_profile%ROWTYPE;
843     replace_rule    TEXT;
844     tmp_marc        TEXT;
845     trgt_marc        TEXT;
846     tmpl_marc        TEXT;
847     match_count     INT;
848 BEGIN
849
850     IF target_marc IS NULL OR template_marc IS NULL THEN
851         -- RAISE NOTICE 'no marc for target or template record';
852         RETURN NULL;
853     END IF;
854
855     dyn_profile := vandelay.compile_profile( template_marc );
856
857     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
858         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
859         RETURN NULL;
860     END IF;
861
862     IF dyn_profile.replace_rule <> '' THEN
863         trgt_marc = target_marc;
864         tmpl_marc = template_marc;
865         replace_rule = dyn_profile.replace_rule;
866     ELSE
867         tmp_marc = target_marc;
868         trgt_marc = template_marc;
869         tmpl_marc = tmp_marc;
870         replace_rule = dyn_profile.preserve_rule;
871     END IF;
872
873     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
874
875 END;
876 $$ LANGUAGE PLPGSQL;
877
878 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
879     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
880 $$ LANGUAGE SQL;
881
882 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
883 DECLARE
884     merge_profile   vandelay.merge_profile%ROWTYPE;
885     dyn_profile     vandelay.compile_profile%ROWTYPE;
886     editor_string   TEXT;
887     editor_id       INT;
888     source_marc     TEXT;
889     target_marc     TEXT;
890     eg_marc         TEXT;
891     v_marc          TEXT;
892     replace_rule    TEXT;
893     match_count     INT;
894 BEGIN
895
896     SELECT  q.marc INTO v_marc
897       FROM  vandelay.queued_record q
898             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
899       LIMIT 1;
900
901     IF v_marc IS NULL THEN
902         -- RAISE NOTICE 'no marc for vandelay or bib record';
903         RETURN FALSE;
904     END IF;
905
906     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
907         UPDATE  vandelay.queued_bib_record
908           SET   imported_as = eg_id,
909                 import_time = NOW()
910           WHERE id = import_id;
911
912         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
913
914         IF editor_string IS NOT NULL AND editor_string <> '' THEN
915             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
916
917             IF editor_id IS NULL THEN
918                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
919             END IF;
920
921             IF editor_id IS NOT NULL THEN
922                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
923             END IF;
924         END IF;
925
926         RETURN TRUE;
927     END IF;
928
929     -- RAISE NOTICE 'update of biblio.record_entry failed';
930
931     RETURN FALSE;
932
933 END;
934 $$ LANGUAGE PLPGSQL;
935
936 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
937 DECLARE
938     eg_id           BIGINT;
939     match_count     INT;
940     match_attr      vandelay.bib_attr_definition%ROWTYPE;
941 BEGIN
942
943     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
944
945     IF FOUND THEN
946         -- RAISE NOTICE 'already imported, cannot auto-overlay'
947         RETURN FALSE;
948     END IF;
949
950     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
951
952     IF match_count <> 1 THEN
953         -- RAISE NOTICE 'not an exact match';
954         RETURN FALSE;
955     END IF;
956
957     SELECT  d.* INTO match_attr
958       FROM  vandelay.bib_attr_definition d
959             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
960             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
961       WHERE m.queued_record = import_id;
962
963     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
964         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
965         RETURN FALSE;
966     END IF;
967
968     SELECT  m.eg_record INTO eg_id
969       FROM  vandelay.bib_match m
970       WHERE m.queued_record = import_id
971       LIMIT 1;
972
973     IF eg_id IS NULL THEN
974         RETURN FALSE;
975     END IF;
976
977     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
978 END;
979 $$ LANGUAGE PLPGSQL;
980
981 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
982 DECLARE
983     queued_record   vandelay.queued_bib_record%ROWTYPE;
984 BEGIN
985
986     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
987
988         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
989             RETURN NEXT queued_record.id;
990         END IF;
991
992     END LOOP;
993
994     RETURN;
995     
996 END;
997 $$ LANGUAGE PLPGSQL;
998
999 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
1000     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
1001 $$ LANGUAGE SQL;
1002
1003 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
1004 DECLARE
1005
1006     owning_lib      TEXT;
1007     circ_lib        TEXT;
1008     call_number     TEXT;
1009     copy_number     TEXT;
1010     status          TEXT;
1011     location        TEXT;
1012     circulate       TEXT;
1013     deposit         TEXT;
1014     deposit_amount  TEXT;
1015     ref             TEXT;
1016     holdable        TEXT;
1017     price           TEXT;
1018     barcode         TEXT;
1019     circ_modifier   TEXT;
1020     circ_as_type    TEXT;
1021     alert_message   TEXT;
1022     opac_visible    TEXT;
1023     pub_note        TEXT;
1024     priv_note       TEXT;
1025
1026     attr_def        RECORD;
1027     tmp_attr_set    RECORD;
1028     attr_set        vandelay.import_item%ROWTYPE;
1029
1030     xpath           TEXT;
1031
1032 BEGIN
1033
1034     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
1035
1036     IF FOUND THEN
1037
1038         attr_set.definition := attr_def.id; 
1039     
1040         -- Build the combined XPath
1041     
1042         owning_lib :=
1043             CASE
1044                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
1045                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
1046                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
1047             END;
1048     
1049         circ_lib :=
1050             CASE
1051                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
1052                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
1053                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
1054             END;
1055     
1056         call_number :=
1057             CASE
1058                 WHEN attr_def.call_number IS NULL THEN 'null()'
1059                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
1060                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
1061             END;
1062     
1063         copy_number :=
1064             CASE
1065                 WHEN attr_def.copy_number IS NULL THEN 'null()'
1066                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
1067                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
1068             END;
1069     
1070         status :=
1071             CASE
1072                 WHEN attr_def.status IS NULL THEN 'null()'
1073                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
1074                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
1075             END;
1076     
1077         location :=
1078             CASE
1079                 WHEN attr_def.location IS NULL THEN 'null()'
1080                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
1081                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
1082             END;
1083     
1084         circulate :=
1085             CASE
1086                 WHEN attr_def.circulate IS NULL THEN 'null()'
1087                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
1088                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
1089             END;
1090     
1091         deposit :=
1092             CASE
1093                 WHEN attr_def.deposit IS NULL THEN 'null()'
1094                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
1095                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
1096             END;
1097     
1098         deposit_amount :=
1099             CASE
1100                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
1101                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
1102                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
1103             END;
1104     
1105         ref :=
1106             CASE
1107                 WHEN attr_def.ref IS NULL THEN 'null()'
1108                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
1109                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
1110             END;
1111     
1112         holdable :=
1113             CASE
1114                 WHEN attr_def.holdable IS NULL THEN 'null()'
1115                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
1116                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
1117             END;
1118     
1119         price :=
1120             CASE
1121                 WHEN attr_def.price IS NULL THEN 'null()'
1122                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
1123                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
1124             END;
1125     
1126         barcode :=
1127             CASE
1128                 WHEN attr_def.barcode IS NULL THEN 'null()'
1129                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
1130                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
1131             END;
1132     
1133         circ_modifier :=
1134             CASE
1135                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
1136                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
1137                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
1138             END;
1139     
1140         circ_as_type :=
1141             CASE
1142                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
1143                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
1144                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
1145             END;
1146     
1147         alert_message :=
1148             CASE
1149                 WHEN attr_def.alert_message IS NULL THEN 'null()'
1150                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
1151                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
1152             END;
1153     
1154         opac_visible :=
1155             CASE
1156                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
1157                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
1158                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
1159             END;
1160
1161         pub_note :=
1162             CASE
1163                 WHEN attr_def.pub_note IS NULL THEN 'null()'
1164                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
1165                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
1166             END;
1167         priv_note :=
1168             CASE
1169                 WHEN attr_def.priv_note IS NULL THEN 'null()'
1170                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
1171                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
1172             END;
1173     
1174     
1175         xpath := 
1176             owning_lib      || '|' || 
1177             circ_lib        || '|' || 
1178             call_number     || '|' || 
1179             copy_number     || '|' || 
1180             status          || '|' || 
1181             location        || '|' || 
1182             circulate       || '|' || 
1183             deposit         || '|' || 
1184             deposit_amount  || '|' || 
1185             ref             || '|' || 
1186             holdable        || '|' || 
1187             price           || '|' || 
1188             barcode         || '|' || 
1189             circ_modifier   || '|' || 
1190             circ_as_type    || '|' || 
1191             alert_message   || '|' || 
1192             pub_note        || '|' || 
1193             priv_note       || '|' || 
1194             opac_visible;
1195
1196         -- RAISE NOTICE 'XPath: %', xpath;
1197         
1198         FOR tmp_attr_set IN
1199                 SELECT  *
1200                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
1201                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
1202                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
1203                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
1204         LOOP
1205     
1206             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
1207             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
1208
1209             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
1210             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
1211     
1212             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
1213             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
1214             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
1215     
1216             SELECT  id INTO attr_set.location
1217               FROM  asset.copy_location
1218               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
1219                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
1220     
1221             attr_set.circulate      :=
1222                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
1223                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
1224
1225             attr_set.deposit        :=
1226                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
1227                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
1228
1229             attr_set.holdable       :=
1230                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
1231                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
1232
1233             attr_set.opac_visible   :=
1234                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
1235                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
1236
1237             attr_set.ref            :=
1238                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
1239                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
1240     
1241             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
1242             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
1243             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
1244     
1245             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
1246             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
1247             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
1248             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
1249             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
1250             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
1251             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
1252             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
1253     
1254             RETURN NEXT attr_set;
1255     
1256         END LOOP;
1257     
1258     END IF;
1259
1260     RETURN;
1261
1262 END;
1263 $$ LANGUAGE PLPGSQL;
1264
1265
1266 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
1267 DECLARE
1268     value   TEXT;
1269     atype   TEXT;
1270     adef    RECORD;
1271 BEGIN
1272     FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
1273
1274         SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
1275         IF (value IS NOT NULL AND value <> '') THEN
1276             INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
1277         END IF;
1278
1279     END LOOP;
1280
1281     RETURN NULL;
1282 END;
1283 $$ LANGUAGE PLPGSQL;
1284
1285 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
1286 DECLARE
1287     attr_def    BIGINT;
1288     item_data   vandelay.import_item%ROWTYPE;
1289 BEGIN
1290
1291     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
1292
1293     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
1294         INSERT INTO vandelay.import_item (
1295             record,
1296             definition,
1297             owning_lib,
1298             circ_lib,
1299             call_number,
1300             copy_number,
1301             status,
1302             location,
1303             circulate,
1304             deposit,
1305             deposit_amount,
1306             ref,
1307             holdable,
1308             price,
1309             barcode,
1310             circ_modifier,
1311             circ_as_type,
1312             alert_message,
1313             pub_note,
1314             priv_note,
1315             opac_visible
1316         ) VALUES (
1317             NEW.id,
1318             item_data.definition,
1319             item_data.owning_lib,
1320             item_data.circ_lib,
1321             item_data.call_number,
1322             item_data.copy_number,
1323             item_data.status,
1324             item_data.location,
1325             item_data.circulate,
1326             item_data.deposit,
1327             item_data.deposit_amount,
1328             item_data.ref,
1329             item_data.holdable,
1330             item_data.price,
1331             item_data.barcode,
1332             item_data.circ_modifier,
1333             item_data.circ_as_type,
1334             item_data.alert_message,
1335             item_data.pub_note,
1336             item_data.priv_note,
1337             item_data.opac_visible
1338         );
1339     END LOOP;
1340
1341     RETURN NULL;
1342 END;
1343 $func$ LANGUAGE PLPGSQL;
1344
1345 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
1346 DECLARE
1347     attr        RECORD;
1348     attr_def    RECORD;
1349     eg_rec      RECORD;
1350     id_value    TEXT;
1351     exact_id    BIGINT;
1352 BEGIN
1353
1354     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
1355
1356     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
1357
1358     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
1359         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
1360     
1361         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
1362             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
1363             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
1364             IF exact_id IS NOT NULL THEN
1365                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
1366             END IF;
1367         END IF;
1368     END IF;
1369
1370     IF exact_id IS NULL THEN
1371         FOR attr IN SELECT a.* FROM vandelay.queued_bib_record_attr a JOIN vandelay.bib_attr_definition d ON (d.id = a.field) WHERE record = NEW.id AND d.ident IS TRUE LOOP
1372     
1373                 -- All numbers? check for an id match
1374                 IF (attr.attr_value ~ $r$^\d+$$r$) THEN
1375                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
1376                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
1377                         END LOOP;
1378                 END IF;
1379     
1380                 -- Looks like an ISBN? check for an isbn match
1381                 IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
1382                 FOR eg_rec IN EXECUTE $$SELECT * FROM metabib.full_rec fr WHERE fr.value LIKE evergreen.lowercase('$$ || attr.attr_value || $$%') AND fr.tag = '020' AND fr.subfield = 'a'$$ LOOP
1383                                 PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
1384                                 IF FOUND THEN
1385                                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
1386                                 END IF;
1387                         END LOOP;
1388     
1389                         -- subcheck for isbn-as-tcn
1390                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
1391                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1392                 END LOOP;
1393                 END IF;
1394     
1395                 -- check for an OCLC tcn_value match
1396                 IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
1397                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
1398                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1399                 END LOOP;
1400                 END IF;
1401     
1402                 -- check for a direct tcn_value match
1403             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
1404                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1405             END LOOP;
1406     
1407                 -- check for a direct item barcode match
1408             FOR eg_rec IN
1409                     SELECT  DISTINCT b.*
1410                       FROM  biblio.record_entry b
1411                             JOIN asset.call_number cn ON (cn.record = b.id)
1412                             JOIN asset.copy cp ON (cp.call_number = cn.id)
1413                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
1414             LOOP
1415                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
1416             END LOOP;
1417     
1418         END LOOP;
1419     END IF;
1420
1421     RETURN NULL;
1422 END;
1423 $func$ LANGUAGE PLPGSQL;
1424
1425 CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
1426 BEGIN
1427     DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
1428     DELETE FROM vandelay.import_item WHERE record = OLD.id;
1429
1430     IF TG_OP = 'UPDATE' THEN
1431         RETURN NEW;
1432     END IF;
1433     RETURN OLD;
1434 END;
1435 $$ LANGUAGE PLPGSQL;
1436
1437 CREATE TRIGGER cleanup_bib_trigger
1438     BEFORE UPDATE OR DELETE ON vandelay.queued_bib_record
1439     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_bib_marc();
1440
1441 CREATE TRIGGER ingest_bib_trigger
1442     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1443     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_marc();
1444
1445 CREATE TRIGGER ingest_item_trigger
1446     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1447     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_items();
1448
1449 CREATE TRIGGER zz_match_bibs_trigger
1450     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1451     FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
1452
1453
1454 /* Authority stuff down here */
1455 ---------------------------------------
1456 CREATE TABLE vandelay.authority_attr_definition (
1457         id                      SERIAL  PRIMARY KEY,
1458         code            TEXT    UNIQUE NOT NULL,
1459         description     TEXT,
1460         xpath           TEXT    NOT NULL,
1461         remove          TEXT    NOT NULL DEFAULT '',
1462         ident           BOOL    NOT NULL DEFAULT FALSE
1463 );
1464
1465 CREATE TABLE vandelay.authority_queue (
1466         queue_type      TEXT            NOT NULL DEFAULT 'authority' CHECK (queue_type = 'authority'),
1467         CONSTRAINT vand_authority_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
1468 ) INHERITS (vandelay.queue);
1469 ALTER TABLE vandelay.authority_queue ADD PRIMARY KEY (id);
1470
1471 CREATE TABLE vandelay.queued_authority_record (
1472         queue           INT     NOT NULL REFERENCES vandelay.authority_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1473         imported_as     INT     REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
1474 ) INHERITS (vandelay.queued_record);
1475 ALTER TABLE vandelay.queued_authority_record ADD PRIMARY KEY (id);
1476 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
1477
1478 CREATE TABLE vandelay.queued_authority_record_attr (
1479         id                      BIGSERIAL       PRIMARY KEY,
1480         record          BIGINT          NOT NULL REFERENCES vandelay.queued_authority_record (id) DEFERRABLE INITIALLY DEFERRED,
1481         field           INT                     NOT NULL REFERENCES vandelay.authority_attr_definition (id) DEFERRABLE INITIALLY DEFERRED,
1482         attr_value      TEXT            NOT NULL
1483 );
1484 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
1485
1486 CREATE TABLE vandelay.authority_match (
1487         id                              BIGSERIAL       PRIMARY KEY,
1488         matched_attr    INT                     REFERENCES vandelay.queued_authority_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1489         queued_record   BIGINT          REFERENCES vandelay.queued_authority_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1490         eg_record               BIGINT          REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
1491 );
1492
1493 CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
1494 DECLARE
1495     value   TEXT;
1496     atype   TEXT;
1497     adef    RECORD;
1498 BEGIN
1499     FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
1500
1501         SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
1502         IF (value IS NOT NULL AND value <> '') THEN
1503             INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
1504         END IF;
1505
1506     END LOOP;
1507
1508     RETURN NULL;
1509 END;
1510 $$ LANGUAGE PLPGSQL;
1511
1512 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
1513 BEGIN
1514     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
1515     IF TG_OP = 'UPDATE' THEN
1516         RETURN NEW;
1517     END IF;
1518     RETURN OLD;
1519 END;
1520 $$ LANGUAGE PLPGSQL;
1521
1522 CREATE TRIGGER cleanup_authority_trigger
1523     BEFORE UPDATE OR DELETE ON vandelay.queued_authority_record
1524     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_authority_marc();
1525
1526 CREATE TRIGGER ingest_authority_trigger
1527     AFTER INSERT OR UPDATE ON vandelay.queued_authority_record
1528     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_authority_marc();
1529
1530 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1531 DECLARE
1532     merge_profile   vandelay.merge_profile%ROWTYPE;
1533     dyn_profile     vandelay.compile_profile%ROWTYPE;
1534     source_marc     TEXT;
1535     target_marc     TEXT;
1536     eg_marc         TEXT;
1537     v_marc          TEXT;
1538     replace_rule    TEXT;
1539     match_count     INT;
1540 BEGIN
1541
1542     SELECT  b.marc INTO eg_marc
1543       FROM  authority.record_entry b
1544             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
1545       LIMIT 1;
1546
1547     SELECT  q.marc INTO v_marc
1548       FROM  vandelay.queued_record q
1549             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
1550       LIMIT 1;
1551
1552     IF eg_marc IS NULL OR v_marc IS NULL THEN
1553         -- RAISE NOTICE 'no marc for vandelay or authority record';
1554         RETURN FALSE;
1555     END IF;
1556
1557     dyn_profile := vandelay.compile_profile( v_marc );
1558
1559     IF merge_profile_id IS NOT NULL THEN
1560         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
1561         IF FOUND THEN
1562             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
1563             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
1564             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
1565             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
1566         END IF;
1567     END IF;
1568
1569     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
1570         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
1571         RETURN FALSE;
1572     END IF;
1573
1574     IF dyn_profile.replace_rule <> '' THEN
1575         source_marc = v_marc;
1576         target_marc = eg_marc;
1577         replace_rule = dyn_profile.replace_rule;
1578     ELSE
1579         source_marc = eg_marc;
1580         target_marc = v_marc;
1581         replace_rule = dyn_profile.preserve_rule;
1582     END IF;
1583
1584     UPDATE  authority.record_entry
1585       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
1586       WHERE id = eg_id;
1587
1588     IF FOUND THEN
1589         UPDATE  vandelay.queued_authority_record
1590           SET   imported_as = eg_id,
1591                 import_time = NOW()
1592           WHERE id = import_id;
1593         RETURN TRUE;
1594     END IF;
1595
1596     -- RAISE NOTICE 'update of authority.record_entry failed';
1597
1598     RETURN FALSE;
1599
1600 END;
1601 $$ LANGUAGE PLPGSQL;
1602
1603 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1604 DECLARE
1605     eg_id           BIGINT;
1606     match_count     INT;
1607 BEGIN
1608     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
1609
1610     IF match_count <> 1 THEN
1611         -- RAISE NOTICE 'not an exact match';
1612         RETURN FALSE;
1613     END IF;
1614
1615     SELECT  m.eg_record INTO eg_id
1616       FROM  vandelay.authority_match m
1617       WHERE m.queued_record = import_id
1618       LIMIT 1;
1619
1620     IF eg_id IS NULL THEN
1621         RETURN FALSE;
1622     END IF;
1623
1624     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
1625 END;
1626 $$ LANGUAGE PLPGSQL;
1627
1628 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
1629 DECLARE
1630     queued_record   vandelay.queued_authority_record%ROWTYPE;
1631 BEGIN
1632
1633     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
1634
1635         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
1636             RETURN NEXT queued_record.id;
1637         END IF;
1638
1639     END LOOP;
1640
1641     RETURN;
1642     
1643 END;
1644 $$ LANGUAGE PLPGSQL;
1645
1646 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
1647     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
1648 $$ LANGUAGE SQL;
1649
1650
1651 -- Vandelay (for importing and exporting records) 012.schema.vandelay.sql 
1652 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (1, 'title', oils_i18n_gettext(1, 'vqbrad', 'Title of work', 'description'),'//*[@tag="245"]/*[contains("abcmnopr",@code)]');
1653 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (2, 'author', oils_i18n_gettext(1, 'vqbrad', 'Author of work', 'description'),'//*[@tag="100" or @tag="110" or @tag="113"]/*[contains("ad",@code)]');
1654 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (3, 'language', oils_i18n_gettext(3, 'vqbrad', 'Language of work', 'description'),'//*[@tag="240"]/*[@code="l"][1]');
1655 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (4, 'pagination', oils_i18n_gettext(4, 'vqbrad', 'Pagination', 'description'),'//*[@tag="300"]/*[@code="a"][1]');
1656 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident, remove ) VALUES (5, 'isbn',oils_i18n_gettext(5, 'vqbrad', 'ISBN', 'description'),'//*[@tag="020"]/*[@code="a"]', TRUE, $r$(?:-|\s.+$)$r$);
1657 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident, remove ) VALUES (6, 'issn',oils_i18n_gettext(6, 'vqbrad', 'ISSN', 'description'),'//*[@tag="022"]/*[@code="a"]', TRUE, $r$(?:-|\s.+$)$r$);
1658 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (7, 'price',oils_i18n_gettext(7, 'vqbrad', 'Price', 'description'),'//*[@tag="020" or @tag="022"]/*[@code="c"][1]');
1659 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (8, 'rec_identifier',oils_i18n_gettext(8, 'vqbrad', 'Accession Number', 'description'),'//*[@tag="001"]', TRUE);
1660 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (9, 'eg_tcn',oils_i18n_gettext(9, 'vqbrad', 'TCN Value', 'description'),'//*[@tag="901"]/*[@code="a"]', TRUE);
1661 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (10, 'eg_tcn_source',oils_i18n_gettext(10, 'vqbrad', 'TCN Source', 'description'),'//*[@tag="901"]/*[@code="b"]', TRUE);
1662 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (11, 'eg_identifier',oils_i18n_gettext(11, 'vqbrad', 'Internal ID', 'description'),'//*[@tag="901"]/*[@code="c"]', TRUE);
1663 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (12, 'publisher',oils_i18n_gettext(12, 'vqbrad', 'Publisher', 'description'),'//*[@tag="260"]/*[@code="b"][1]');
1664 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, remove ) VALUES (13, 'pubdate',oils_i18n_gettext(13, 'vqbrad', 'Publication Date', 'description'),'//*[@tag="260"]/*[@code="c"][1]',$r$\D$r$);
1665 --INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (14, 'edition',oils_i18n_gettext(14, 'vqbrad', 'Edition', 'description'),'//*[@tag="250"]/*[@code="a"][1]');
1666 --
1667 --INSERT INTO vandelay.import_item_attr_definition (
1668 --    owner, name, tag, owning_lib, circ_lib, location,
1669 --    call_number, circ_modifier, barcode, price, copy_number,
1670 --    circulate, ref, holdable, opac_visible, status
1671 --) VALUES (
1672 --    1,
1673 --    'Evergreen 852 export format',
1674 --    '852',
1675 --    '[@code = "b"][1]',
1676 --    '[@code = "b"][2]',
1677 --    'c',
1678 --    'j',
1679 --    'g',
1680 --    'p',
1681 --    'y',
1682 --    't',
1683 --    '[@code = "x" and text() = "circulating"]',
1684 --    '[@code = "x" and text() = "reference"]',
1685 --    '[@code = "x" and text() = "holdable"]',
1686 --    '[@code = "x" and text() = "visible"]',
1687 --    'z'
1688 --);
1689 --
1690 --INSERT INTO vandelay.import_item_attr_definition (
1691 --    owner,
1692 --    name,
1693 --    tag,
1694 --    owning_lib,
1695 --    location,
1696 --    call_number,
1697 --    circ_modifier,
1698 --    barcode,
1699 --    price,
1700 --    status
1701 --) VALUES (
1702 --    1,
1703 --    'Unicorn Import format -- 999',
1704 --    '999',
1705 --    'm',
1706 --    'l',
1707 --    'a',
1708 --    't',
1709 --    'i',
1710 --    'p',
1711 --    'k'
1712 --);
1713 --
1714 --INSERT INTO vandelay.authority_attr_definition ( code, description, xpath, ident ) VALUES ('rec_identifier','Identifier','//*[@tag="001"]', TRUE);
1715
1716 COMMIT;
1717