teach the overlay functions about data matching REs; add a template-based merge inste...
[working/Evergreen.git] / Open-ILS / src / sql / Pg / 012.schema.vandelay.sql
1 DROP SCHEMA 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 );
23
24
25
26 /* Bib stuff at the top */
27 ----------------------------------------------------
28
29 CREATE TABLE vandelay.bib_attr_definition (
30         id                      SERIAL  PRIMARY KEY,
31         code            TEXT    UNIQUE NOT NULL,
32         description     TEXT,
33         xpath           TEXT    NOT NULL,
34         remove          TEXT    NOT NULL DEFAULT '',
35         ident           BOOL    NOT NULL DEFAULT FALSE
36 );
37
38 -- Each TEXT field (other than 'name') should hold an XPath predicate for pulling the data needed
39 -- DROP TABLE vandelay.import_item_attr_definition CASCADE;
40 CREATE TABLE vandelay.import_item_attr_definition (
41     id              BIGSERIAL   PRIMARY KEY,
42     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
43     name            TEXT        NOT NULL,
44     tag             TEXT        NOT NULL,
45     keep            BOOL        NOT NULL DEFAULT FALSE,
46     owning_lib      TEXT,
47     circ_lib        TEXT,
48     call_number     TEXT,
49     copy_number     TEXT,
50     status          TEXT,
51     location        TEXT,
52     circulate       TEXT,
53     deposit         TEXT,
54     deposit_amount  TEXT,
55     ref             TEXT,
56     holdable        TEXT,
57     price           TEXT,
58     barcode         TEXT,
59     circ_modifier   TEXT,
60     circ_as_type    TEXT,
61     alert_message   TEXT,
62     opac_visible    TEXT,
63     pub_note_title  TEXT,
64     pub_note        TEXT,
65     priv_note_title TEXT,
66     priv_note       TEXT,
67         CONSTRAINT vand_import_item_attr_def_idx UNIQUE (owner,name)
68 );
69
70 CREATE TABLE vandelay.bib_queue (
71         queue_type          TEXT        NOT NULL DEFAULT 'bib' CHECK (queue_type = 'bib'),
72         item_attr_def   BIGINT REFERENCES vandelay.import_item_attr_definition (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
73         CONSTRAINT vand_bib_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
74 ) INHERITS (vandelay.queue);
75 ALTER TABLE vandelay.bib_queue ADD PRIMARY KEY (id);
76
77 CREATE TABLE vandelay.queued_bib_record (
78         queue           INT             NOT NULL REFERENCES vandelay.bib_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
79         bib_source      INT             REFERENCES config.bib_source (id) DEFERRABLE INITIALLY DEFERRED,
80         imported_as     INT             REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED
81 ) INHERITS (vandelay.queued_record);
82 ALTER TABLE vandelay.queued_bib_record ADD PRIMARY KEY (id);
83
84 CREATE TABLE vandelay.queued_bib_record_attr (
85         id                      BIGSERIAL       PRIMARY KEY,
86         record          BIGINT          NOT NULL REFERENCES vandelay.queued_bib_record (id) DEFERRABLE INITIALLY DEFERRED,
87         field           INT                     NOT NULL REFERENCES vandelay.bib_attr_definition (id) DEFERRABLE INITIALLY DEFERRED,
88         attr_value      TEXT            NOT NULL
89 );
90
91 CREATE TABLE vandelay.bib_match (
92         id                              BIGSERIAL       PRIMARY KEY,
93         field_type              TEXT            NOT NULL CHECK (field_type in ('isbn','tcn_value','id')),
94         matched_attr    INT                     REFERENCES vandelay.queued_bib_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
95         queued_record   BIGINT          REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
96         eg_record               BIGINT          REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
97 );
98
99 -- DROP TABLE vandelay.import_item CASCADE;
100 CREATE TABLE vandelay.import_item (
101     id              BIGSERIAL   PRIMARY KEY,
102     record          BIGINT      NOT NULL REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
103     definition      BIGINT      NOT NULL REFERENCES vandelay.import_item_attr_definition (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
104     owning_lib      INT,
105     circ_lib        INT,
106     call_number     TEXT,
107     copy_number     INT,
108     status          INT,
109     location        INT,
110     circulate       BOOL,
111     deposit         BOOL,
112     deposit_amount  NUMERIC(8,2),
113     ref             BOOL,
114     holdable        BOOL,
115     price           NUMERIC(8,2),
116     barcode         TEXT,
117     circ_modifier   TEXT,
118     circ_as_type    TEXT,
119     alert_message   TEXT,
120     pub_note        TEXT,
121     priv_note       TEXT,
122     opac_visible    BOOL
123 );
124  
125 CREATE TABLE vandelay.import_bib_trash_fields (
126     id              BIGSERIAL   PRIMARY KEY,
127     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
128     field           TEXT        NOT NULL,
129         CONSTRAINT vand_import_bib_trash_fields_idx UNIQUE (owner,field)
130 );
131
132 CREATE TABLE vandelay.merge_profile (
133     id              BIGSERIAL   PRIMARY KEY,
134     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
135     name            TEXT        NOT NULL,
136     add_spec        TEXT,
137     replace_spec    TEXT,
138     strip_spec      TEXT,
139     preserve_spec   TEXT,
140         CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
141         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))
142 );
143
144
145 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
146 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
147 DECLARE
148     eg_tcn          TEXT;
149     eg_tcn_source   TEXT;
150     output          vandelay.tcn_data%ROWTYPE;
151 BEGIN
152
153     -- 001/003
154     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
155     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
156
157         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
158         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
159             eg_tcn_source := 'System Local';
160         END IF;
161
162         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
163
164         IF NOT FOUND THEN
165             output.used := FALSE;
166         ELSE
167             output.used := TRUE;
168         END IF;
169
170         output.tcn := eg_tcn;
171         output.tcn_source := eg_tcn_source;
172         RETURN NEXT output;
173
174     END IF;
175
176     -- 901 ab
177     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
178     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
179
180         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
181         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
182             eg_tcn_source := 'System Local';
183         END IF;
184
185         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
186
187         IF NOT FOUND THEN
188             output.used := FALSE;
189         ELSE
190             output.used := TRUE;
191         END IF;
192
193         output.tcn := eg_tcn;
194         output.tcn_source := eg_tcn_source;
195         RETURN NEXT output;
196
197     END IF;
198
199     -- 039 ab
200     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
201     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
202
203         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
204         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
205             eg_tcn_source := 'System Local';
206         END IF;
207
208         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
209
210         IF NOT FOUND THEN
211             output.used := FALSE;
212         ELSE
213             output.used := TRUE;
214         END IF;
215
216         output.tcn := eg_tcn;
217         output.tcn_source := eg_tcn_source;
218         RETURN NEXT output;
219
220     END IF;
221
222     -- 020 a
223     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
224     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
225
226         eg_tcn_source := 'ISBN';
227
228         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
229
230         IF NOT FOUND THEN
231             output.used := FALSE;
232         ELSE
233             output.used := TRUE;
234         END IF;
235
236         output.tcn := eg_tcn;
237         output.tcn_source := eg_tcn_source;
238         RETURN NEXT output;
239
240     END IF;
241
242     -- 022 a
243     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
244     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
245
246         eg_tcn_source := 'ISSN';
247
248         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
249
250         IF NOT FOUND THEN
251             output.used := FALSE;
252         ELSE
253             output.used := TRUE;
254         END IF;
255
256         output.tcn := eg_tcn;
257         output.tcn_source := eg_tcn_source;
258         RETURN NEXT output;
259
260     END IF;
261
262     -- 010 a
263     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
264     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
265
266         eg_tcn_source := 'LCCN';
267
268         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
269
270         IF NOT FOUND THEN
271             output.used := FALSE;
272         ELSE
273             output.used := TRUE;
274         END IF;
275
276         output.tcn := eg_tcn;
277         output.tcn_source := eg_tcn_source;
278         RETURN NEXT output;
279
280     END IF;
281
282     -- 035 a
283     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
284     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
285
286         eg_tcn_source := 'System Legacy';
287
288         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
289
290         IF NOT FOUND THEN
291             output.used := FALSE;
292         ELSE
293             output.used := TRUE;
294         END IF;
295
296         output.tcn := eg_tcn;
297         output.tcn_source := eg_tcn_source;
298         RETURN NEXT output;
299
300     END IF;
301
302     RETURN;
303 END;
304 $_$ LANGUAGE PLPGSQL;
305
306 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
307
308     use MARC::Record;
309     use MARC::File::XML;
310
311     my $target_xml = shift;
312     my $source_xml = shift;
313     my $field_spec = shift;
314
315     my $target_r = MARC::Record->new_from_xml( $target_xml );
316     my $source_r = MARC::Record->new_from_xml( $source_xml );
317
318     return $target_xml unless ($target_r && $source_r);
319
320     my @field_list = split(',', $field_spec);
321
322     my %fields;
323     for my $f (@field_list) {
324         $f =~ s/^\s*//; $f =~ s/\s*$//;
325         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
326             my $field = $1;
327             $field =~ s/\s+//;
328             my $sf = $2;
329             $sf =~ s/\s+//;
330             my $match = $3;
331             $match =~ s/^\s*//; $match =~ s/\s*$//;
332             $fields{$field} = { sf => [ split('', $sf) ] };
333             if ($match) {
334                 my ($msf,$mre) = split('~', $match);
335                 if (length($msf) > 0 and length($mre) > 0) {
336                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
337                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
338                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
339                 }
340             }
341         }
342     }
343
344     for my $f ( keys %fields) {
345         if ( @{$fields{$f}} ) {
346             for my $from_field ($source_r->field( $f )) {
347                 for my $to_field ($target_r->field( $f )) {
348                     if (exists($fields{$f}{match})) {
349                         next unless (grep { $_ =~ $field{$f}{match}{re} } $to_field->subfield($field{$f}{match}{sf}));
350                     }
351                     my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}};
352                     $to_field->add_subfields( @new_sf );
353                 }
354             }
355         } else {
356             my @new_fields = map { $_->clone } $source_r->field( $f );
357             $target_r->insert_fields_ordered( @new_fields );
358         }
359     }
360
361     $target_xml = $target_r->as_xml_record;
362     $target_xml =~ s/^<\?.+?\?>$//mo;
363     $target_xml =~ s/\n//sgo;
364     $target_xml =~ s/>\s+</></sgo;
365
366     return $target_xml;
367
368 $_$ LANGUAGE PLPERLU;
369
370 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
371
372     use MARC::Record;
373     use MARC::File::XML;
374
375     my $xml = shift;
376     my $r = MARC::Record->new_from_xml( $xml );
377
378     return $xml unless ($r);
379
380     my $field_spec = shift;
381     my @field_list = split(',', $field_spec);
382
383     my %fields;
384     for my $f (@field_list) {
385         $f =~ s/^\s*//; $f =~ s/\s*$//;
386         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
387             my $field = $1;
388             $field =~ s/\s+//;
389             my $sf = $2;
390             $sf =~ s/\s+//;
391             my $match = $3;
392             $match =~ s/^\s*//; $match =~ s/\s*$//;
393             $fields{$field} = { sf => [ split('', $sf) ] };
394             if ($match) {
395                 my ($msf,$mre) = split('~', $match);
396                 if (length($msf) > 0 and length($mre) > 0) {
397                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
398                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
399                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
400                 }
401             }
402         }
403     }
404
405     for my $f ( keys %fields) {
406         for my $to_field ($r->field( $f )) {
407             if (exists($fields{$f}{match})) {
408                 next unless (grep { $_ =~ $field{$f}{match}{re} } $to_field->subfield($field{$f}{match}{sf}));
409             }
410
411             if ( @{$fields{$f}} ) {
412                 $to_field->delete_subfield(code => $fields{$f}{sf});
413             } else {
414                 $r->delete_field( $to_field );
415             }
416         }
417     }
418
419     $xml = $r->as_xml_record;
420     $xml =~ s/^<\?.+?\?>$//mo;
421     $xml =~ s/\n//sgo;
422     $xml =~ s/>\s+</></sgo;
423
424     return $xml;
425
426 $_$ LANGUAGE PLPERLU;
427
428 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
429     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
430 $_$ LANGUAGE SQL;
431
432 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 $_$
433     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
434 $_$ LANGUAGE SQL;
435
436 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
437 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
438 DECLARE
439     output              vandelay.compile_profile%ROWTYPE;
440     profile             vandelay.merge_profile%ROWTYPE;
441     profile_tmpl        TEXT;
442     profile_tmpl_owner  TEXT;
443     add_rule            TEXT := '';
444     strip_rule          TEXT := '';
445     replace_rule        TEXT := '';
446     preserve_rule       TEXT := '';
447
448 BEGIN
449
450     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
451     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
452
453     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
454         SELECT  p.* INTO profile
455           FROM  vandelay.merge_profile p
456                 JOIN actor.org_unit u ON (u.id = p.owner)
457           WHERE p.name = profile_tmpl
458                 AND u.shortname = profile_tmpl_owner;
459
460         IF profile.id IS NOT NULL THEN
461             add_rule := COALESCE(profile.add_spec,'');
462             strip_rule := COALESCE(profile.strip_spec,'');
463             replace_rule := COALESCE(profile.replace_spec,'');
464             preserve_rule := COALESCE(profile.preserve_spec,'');
465         END IF;
466     END IF;
467
468     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),''),'');
469     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),''),'');
470     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),''),'');
471     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),''),'');
472
473     output.add_rule := BTRIM(add_rule,',');
474     output.replace_rule := BTRIM(replace_rule,',');
475     output.strip_rule := BTRIM(strip_rule,',');
476     output.preserve_rule := BTRIM(preserve_rule,',');
477
478     RETURN output;
479 END;
480 $_$ LANGUAGE PLPGSQL;
481
482 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
483 DECLARE
484     merge_profile   vandelay.merge_profile%ROWTYPE;
485     dyn_profile     vandelay.compile_profile%ROWTYPE;
486     editor_string   TEXT;
487     editor_id       INT;
488     source_marc     TEXT;
489     target_marc     TEXT;
490     eg_marc         TEXT;
491     replace_rule    TEXT;
492     match_count     INT;
493 BEGIN
494
495     SELECT  b.marc INTO eg_marc
496       FROM  biblio.record_entry b
497             JOIN vandelay.bib_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
498       LIMIT 1;
499
500     IF eg_marc IS NULL OR v_marc IS NULL THEN
501         -- RAISE NOTICE 'no marc for template or bib record';
502         RETURN FALSE;
503     END IF;
504
505     dyn_profile := vandelay.compile_profile( v_marc );
506
507     IF merge_profile_id IS NOT NULL THEN
508         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
509         IF FOUND THEN
510             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
511             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
512             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
513             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
514         END IF;
515     END IF;
516
517     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
518         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
519         RETURN FALSE;
520     END IF;
521
522     IF dyn_profile.replace_rule <> '' THEN
523         source_marc = v_marc;
524         target_marc = eg_marc;
525         replace_rule = dyn_profile.replace_rule;
526     ELSE
527         source_marc = eg_marc;
528         target_marc = v_marc;
529         replace_rule = dyn_profile.preserve_rule;
530     END IF;
531
532     UPDATE  biblio.record_entry
533       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
534       WHERE id = eg_id;
535
536     IF NOT FOUND THEN
537         -- RAISE NOTICE 'update of biblio.record_entry failed';
538         RETURN FALSE;
539     END IF;
540
541     RETURN TRUE;
542
543 END;
544 $$ LANGUAGE PLPGSQL;
545
546 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
547     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
548 $$ LANGUAGE SQL;
549
550 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
551 DECLARE
552     merge_profile   vandelay.merge_profile%ROWTYPE;
553     dyn_profile     vandelay.compile_profile%ROWTYPE;
554     editor_string   TEXT;
555     editor_id       INT;
556     source_marc     TEXT;
557     target_marc     TEXT;
558     eg_marc         TEXT;
559     v_marc          TEXT;
560     replace_rule    TEXT;
561     match_count     INT;
562 BEGIN
563
564     SELECT  q.marc INTO v_marc
565       FROM  vandelay.queued_record q
566             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
567       LIMIT 1;
568
569     IF v_marc IS NULL THEN
570         -- RAISE NOTICE 'no marc for vandelay or bib record';
571         RETURN FALSE;
572     END IF;
573
574     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
575         UPDATE  vandelay.queued_bib_record
576           SET   imported_as = eg_id,
577                 import_time = NOW()
578           WHERE id = import_id;
579
580         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
581
582         IF editor_string IS NOT NULL AND editor_string <> '' THEN
583             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
584
585             IF editor_id IS NULL THEN
586                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
587             END IF;
588
589             IF editor_id IS NOT NULL THEN
590                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
591             END IF;
592         END IF;
593
594         RETURN TRUE;
595     END IF;
596
597     -- RAISE NOTICE 'update of biblio.record_entry failed';
598
599     RETURN FALSE;
600
601 END;
602 $$ LANGUAGE PLPGSQL;
603
604 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
605 DECLARE
606     eg_id           BIGINT;
607     match_count     INT;
608     match_attr      vandelay.bib_attr_definition%ROWTYPE;
609 BEGIN
610
611     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
612
613     IF FOUND THEN
614         -- RAISE NOTICE 'already imported, cannot auto-overlay'
615         RETURN FALSE;
616     END IF;
617
618     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
619
620     IF match_count <> 1 THEN
621         -- RAISE NOTICE 'not an exact match';
622         RETURN FALSE;
623     END IF;
624
625     SELECT  d.* INTO match_attr
626       FROM  vandelay.bib_attr_definition d
627             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
628             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
629       WHERE m.queued_record = import_id;
630
631     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
632         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
633         RETURN FALSE;
634     END IF;
635
636     SELECT  m.eg_record INTO eg_id
637       FROM  vandelay.bib_match m
638       WHERE m.queued_record = import_id
639       LIMIT 1;
640
641     IF eg_id IS NULL THEN
642         RETURN FALSE;
643     END IF;
644
645     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
646 END;
647 $$ LANGUAGE PLPGSQL;
648
649 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
650 DECLARE
651     queued_record   vandelay.queued_bib_record%ROWTYPE;
652 BEGIN
653
654     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
655
656         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
657             RETURN NEXT queued_record.id;
658         END IF;
659
660     END LOOP;
661
662     RETURN;
663     
664 END;
665 $$ LANGUAGE PLPGSQL;
666
667 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
668     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
669 $$ LANGUAGE SQL;
670
671 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
672 DECLARE
673
674     owning_lib      TEXT;
675     circ_lib        TEXT;
676     call_number     TEXT;
677     copy_number     TEXT;
678     status          TEXT;
679     location        TEXT;
680     circulate       TEXT;
681     deposit         TEXT;
682     deposit_amount  TEXT;
683     ref             TEXT;
684     holdable        TEXT;
685     price           TEXT;
686     barcode         TEXT;
687     circ_modifier   TEXT;
688     circ_as_type    TEXT;
689     alert_message   TEXT;
690     opac_visible    TEXT;
691     pub_note        TEXT;
692     priv_note       TEXT;
693
694     attr_def        RECORD;
695     tmp_attr_set    RECORD;
696     attr_set        vandelay.import_item%ROWTYPE;
697
698     xpath           TEXT;
699
700 BEGIN
701
702     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
703
704     IF FOUND THEN
705
706         attr_set.definition := attr_def.id; 
707     
708         -- Build the combined XPath
709     
710         owning_lib :=
711             CASE
712                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
713                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
714                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
715             END;
716     
717         circ_lib :=
718             CASE
719                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
720                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
721                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
722             END;
723     
724         call_number :=
725             CASE
726                 WHEN attr_def.call_number IS NULL THEN 'null()'
727                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
728                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
729             END;
730     
731         copy_number :=
732             CASE
733                 WHEN attr_def.copy_number IS NULL THEN 'null()'
734                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
735                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
736             END;
737     
738         status :=
739             CASE
740                 WHEN attr_def.status IS NULL THEN 'null()'
741                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
742                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
743             END;
744     
745         location :=
746             CASE
747                 WHEN attr_def.location IS NULL THEN 'null()'
748                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
749                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
750             END;
751     
752         circulate :=
753             CASE
754                 WHEN attr_def.circulate IS NULL THEN 'null()'
755                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
756                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
757             END;
758     
759         deposit :=
760             CASE
761                 WHEN attr_def.deposit IS NULL THEN 'null()'
762                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
763                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
764             END;
765     
766         deposit_amount :=
767             CASE
768                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
769                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
770                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
771             END;
772     
773         ref :=
774             CASE
775                 WHEN attr_def.ref IS NULL THEN 'null()'
776                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
777                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
778             END;
779     
780         holdable :=
781             CASE
782                 WHEN attr_def.holdable IS NULL THEN 'null()'
783                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
784                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
785             END;
786     
787         price :=
788             CASE
789                 WHEN attr_def.price IS NULL THEN 'null()'
790                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
791                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
792             END;
793     
794         barcode :=
795             CASE
796                 WHEN attr_def.barcode IS NULL THEN 'null()'
797                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
798                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
799             END;
800     
801         circ_modifier :=
802             CASE
803                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
804                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
805                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
806             END;
807     
808         circ_as_type :=
809             CASE
810                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
811                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
812                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
813             END;
814     
815         alert_message :=
816             CASE
817                 WHEN attr_def.alert_message IS NULL THEN 'null()'
818                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
819                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
820             END;
821     
822         opac_visible :=
823             CASE
824                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
825                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
826                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
827             END;
828
829         pub_note :=
830             CASE
831                 WHEN attr_def.pub_note IS NULL THEN 'null()'
832                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
833                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
834             END;
835         priv_note :=
836             CASE
837                 WHEN attr_def.priv_note IS NULL THEN 'null()'
838                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
839                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
840             END;
841     
842     
843         xpath := 
844             owning_lib      || '|' || 
845             circ_lib        || '|' || 
846             call_number     || '|' || 
847             copy_number     || '|' || 
848             status          || '|' || 
849             location        || '|' || 
850             circulate       || '|' || 
851             deposit         || '|' || 
852             deposit_amount  || '|' || 
853             ref             || '|' || 
854             holdable        || '|' || 
855             price           || '|' || 
856             barcode         || '|' || 
857             circ_modifier   || '|' || 
858             circ_as_type    || '|' || 
859             alert_message   || '|' || 
860             pub_note        || '|' || 
861             priv_note       || '|' || 
862             opac_visible;
863
864         -- RAISE NOTICE 'XPath: %', xpath;
865         
866         FOR tmp_attr_set IN
867                 SELECT  *
868                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
869                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
870                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
871                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
872         LOOP
873     
874             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
875             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
876
877             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
878             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
879     
880             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
881             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
882             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
883     
884             SELECT  id INTO attr_set.location
885               FROM  asset.copy_location
886               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
887                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
888     
889             attr_set.circulate      :=
890                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
891                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
892
893             attr_set.deposit        :=
894                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
895                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
896
897             attr_set.holdable       :=
898                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
899                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
900
901             attr_set.opac_visible   :=
902                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
903                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
904
905             attr_set.ref            :=
906                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
907                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
908     
909             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
910             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
911             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
912     
913             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
914             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
915             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
916             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
917             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
918             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
919             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
920             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
921     
922             RETURN NEXT attr_set;
923     
924         END LOOP;
925     
926     END IF;
927
928 END;
929 $$ LANGUAGE PLPGSQL;
930
931
932 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
933 DECLARE
934     value   TEXT;
935     atype   TEXT;
936     adef    RECORD;
937 BEGIN
938     FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
939
940         SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
941         IF (value IS NOT NULL AND value <> '') THEN
942             INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
943         END IF;
944
945     END LOOP;
946
947     RETURN NULL;
948 END;
949 $$ LANGUAGE PLPGSQL;
950
951 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
952 DECLARE
953     attr_def    BIGINT;
954     item_data   vandelay.import_item%ROWTYPE;
955 BEGIN
956
957     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
958
959     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
960         INSERT INTO vandelay.import_item (
961             record,
962             definition,
963             owning_lib,
964             circ_lib,
965             call_number,
966             copy_number,
967             status,
968             location,
969             circulate,
970             deposit,
971             deposit_amount,
972             ref,
973             holdable,
974             price,
975             barcode,
976             circ_modifier,
977             circ_as_type,
978             alert_message,
979             pub_note,
980             priv_note,
981             opac_visible
982         ) VALUES (
983             NEW.id,
984             item_data.definition,
985             item_data.owning_lib,
986             item_data.circ_lib,
987             item_data.call_number,
988             item_data.copy_number,
989             item_data.status,
990             item_data.location,
991             item_data.circulate,
992             item_data.deposit,
993             item_data.deposit_amount,
994             item_data.ref,
995             item_data.holdable,
996             item_data.price,
997             item_data.barcode,
998             item_data.circ_modifier,
999             item_data.circ_as_type,
1000             item_data.alert_message,
1001             item_data.pub_note,
1002             item_data.priv_note,
1003             item_data.opac_visible
1004         );
1005     END LOOP;
1006
1007     RETURN NULL;
1008 END;
1009 $func$ LANGUAGE PLPGSQL;
1010
1011 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
1012 DECLARE
1013     attr        RECORD;
1014     attr_def    RECORD;
1015     eg_rec      RECORD;
1016     id_value    TEXT;
1017     exact_id    BIGINT;
1018 BEGIN
1019
1020     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
1021
1022     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
1023
1024     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
1025         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
1026     
1027         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
1028             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
1029             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
1030             IF exact_id IS NOT NULL THEN
1031                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
1032             END IF;
1033         END IF;
1034     END IF;
1035
1036     IF exact_id IS NULL THEN
1037         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
1038     
1039                 -- All numbers? check for an id match
1040                 IF (attr.attr_value ~ $r$^\d+$$r$) THEN
1041                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
1042                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
1043                         END LOOP;
1044                 END IF;
1045     
1046                 -- Looks like an ISBN? check for an isbn match
1047                 IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
1048                 FOR eg_rec IN EXECUTE $$SELECT * FROM metabib.full_rec fr WHERE fr.value LIKE LOWER('$$ || attr.attr_value || $$%') AND fr.tag = '020' AND fr.subfield = 'a'$$ LOOP
1049                                 PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
1050                                 IF FOUND THEN
1051                                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
1052                                 END IF;
1053                         END LOOP;
1054     
1055                         -- subcheck for isbn-as-tcn
1056                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
1057                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1058                 END LOOP;
1059                 END IF;
1060     
1061                 -- check for an OCLC tcn_value match
1062                 IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
1063                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
1064                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1065                 END LOOP;
1066                 END IF;
1067     
1068                 -- check for a direct tcn_value match
1069             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
1070                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1071             END LOOP;
1072     
1073                 -- check for a direct item barcode match
1074             FOR eg_rec IN
1075                     SELECT  DISTINCT b.*
1076                       FROM  biblio.record_entry b
1077                             JOIN asset.call_number cn ON (cn.record = b.id)
1078                             JOIN asset.copy cp ON (cp.call_number = cn.id)
1079                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
1080             LOOP
1081                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
1082             END LOOP;
1083     
1084         END LOOP;
1085     END IF;
1086
1087     RETURN NULL;
1088 END;
1089 $func$ LANGUAGE PLPGSQL;
1090
1091 CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
1092 BEGIN
1093     DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
1094     DELETE FROM vandelay.import_item WHERE record = OLD.id;
1095
1096     IF TG_OP = 'UPDATE' THEN
1097         RETURN NEW;
1098     END IF;
1099     RETURN OLD;
1100 END;
1101 $$ LANGUAGE PLPGSQL;
1102
1103 CREATE TRIGGER cleanup_bib_trigger
1104     BEFORE UPDATE OR DELETE ON vandelay.queued_bib_record
1105     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_bib_marc();
1106
1107 CREATE TRIGGER ingest_bib_trigger
1108     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1109     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_marc();
1110
1111 CREATE TRIGGER ingest_item_trigger
1112     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1113     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_items();
1114
1115 CREATE TRIGGER zz_match_bibs_trigger
1116     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1117     FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
1118
1119
1120 /* Authority stuff down here */
1121 ---------------------------------------
1122 CREATE TABLE vandelay.authority_attr_definition (
1123         id                      SERIAL  PRIMARY KEY,
1124         code            TEXT    UNIQUE NOT NULL,
1125         description     TEXT,
1126         xpath           TEXT    NOT NULL,
1127         remove          TEXT    NOT NULL DEFAULT '',
1128         ident           BOOL    NOT NULL DEFAULT FALSE
1129 );
1130
1131 CREATE TABLE vandelay.authority_queue (
1132         queue_type      TEXT            NOT NULL DEFAULT 'authority' CHECK (queue_type = 'authority'),
1133         CONSTRAINT vand_authority_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
1134 ) INHERITS (vandelay.queue);
1135 ALTER TABLE vandelay.authority_queue ADD PRIMARY KEY (id);
1136
1137 CREATE TABLE vandelay.queued_authority_record (
1138         queue           INT     NOT NULL REFERENCES vandelay.authority_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1139         imported_as     INT     REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
1140 ) INHERITS (vandelay.queued_record);
1141 ALTER TABLE vandelay.queued_authority_record ADD PRIMARY KEY (id);
1142
1143 CREATE TABLE vandelay.queued_authority_record_attr (
1144         id                      BIGSERIAL       PRIMARY KEY,
1145         record          BIGINT          NOT NULL REFERENCES vandelay.queued_authority_record (id) DEFERRABLE INITIALLY DEFERRED,
1146         field           INT                     NOT NULL REFERENCES vandelay.authority_attr_definition (id) DEFERRABLE INITIALLY DEFERRED,
1147         attr_value      TEXT            NOT NULL
1148 );
1149
1150 CREATE TABLE vandelay.authority_match (
1151         id                              BIGSERIAL       PRIMARY KEY,
1152         matched_attr    INT                     REFERENCES vandelay.queued_authority_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1153         queued_record   BIGINT          REFERENCES vandelay.queued_authority_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1154         eg_record               BIGINT          REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
1155 );
1156
1157 CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
1158 DECLARE
1159     value   TEXT;
1160     atype   TEXT;
1161     adef    RECORD;
1162 BEGIN
1163     FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
1164
1165         SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
1166         IF (value IS NOT NULL AND value <> '') THEN
1167             INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
1168         END IF;
1169
1170     END LOOP;
1171
1172     RETURN NULL;
1173 END;
1174 $$ LANGUAGE PLPGSQL;
1175
1176 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
1177 BEGIN
1178     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
1179     IF TG_OP = 'UPDATE' THEN
1180         RETURN NEW;
1181     END IF;
1182     RETURN OLD;
1183 END;
1184 $$ LANGUAGE PLPGSQL;
1185
1186 CREATE TRIGGER cleanup_authority_trigger
1187     BEFORE UPDATE OR DELETE ON vandelay.queued_authority_record
1188     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_authority_marc();
1189
1190 CREATE TRIGGER ingest_authority_trigger
1191     AFTER INSERT OR UPDATE ON vandelay.queued_authority_record
1192     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_authority_marc();
1193
1194 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1195 DECLARE
1196     merge_profile   vandelay.merge_profile%ROWTYPE;
1197     dyn_profile     vandelay.compile_profile%ROWTYPE;
1198     source_marc     TEXT;
1199     target_marc     TEXT;
1200     eg_marc         TEXT;
1201     v_marc          TEXT;
1202     replace_rule    TEXT;
1203     match_count     INT;
1204 BEGIN
1205
1206     SELECT  b.marc INTO eg_marc
1207       FROM  authority.record_entry b
1208             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
1209       LIMIT 1;
1210
1211     SELECT  q.marc INTO v_marc
1212       FROM  vandelay.queued_record q
1213             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
1214       LIMIT 1;
1215
1216     IF eg_marc IS NULL OR v_marc IS NULL THEN
1217         -- RAISE NOTICE 'no marc for vandelay or authority record';
1218         RETURN FALSE;
1219     END IF;
1220
1221     dyn_profile := vandelay.compile_profile( v_marc );
1222
1223     IF merge_profile_id IS NOT NULL THEN
1224         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
1225         IF FOUND THEN
1226             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
1227             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
1228             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
1229             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
1230         END IF;
1231     END IF;
1232
1233     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
1234         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
1235         RETURN FALSE;
1236     END IF;
1237
1238     IF dyn_profile.replace_rule <> '' THEN
1239         source_marc = v_marc;
1240         target_marc = eg_marc;
1241         replace_rule = dyn_profile.replace_rule;
1242     ELSE
1243         source_marc = eg_marc;
1244         target_marc = v_marc;
1245         replace_rule = dyn_profile.preserve_rule;
1246     END IF;
1247
1248     UPDATE  authority.record_entry
1249       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
1250       WHERE id = eg_id;
1251
1252     IF FOUND THEN
1253         UPDATE  vandelay.queued_authority_record
1254           SET   imported_as = eg_id,
1255                 import_time = NOW()
1256           WHERE id = import_id;
1257         RETURN TRUE;
1258     END IF;
1259
1260     -- RAISE NOTICE 'update of authority.record_entry failed';
1261
1262     RETURN FALSE;
1263
1264 END;
1265 $$ LANGUAGE PLPGSQL;
1266
1267 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1268 DECLARE
1269     eg_id           BIGINT;
1270     match_count     INT;
1271 BEGIN
1272     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
1273
1274     IF match_count <> 1 THEN
1275         -- RAISE NOTICE 'not an exact match';
1276         RETURN FALSE;
1277     END IF;
1278
1279     SELECT  m.eg_record INTO eg_id
1280       FROM  vandelay.authority_match m
1281       WHERE m.queued_record = import_id
1282       LIMIT 1;
1283
1284     IF eg_id IS NULL THEN
1285         RETURN FALSE;
1286     END IF;
1287
1288     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
1289 END;
1290 $$ LANGUAGE PLPGSQL;
1291
1292 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
1293 DECLARE
1294     queued_record   vandelay.queued_authority_record%ROWTYPE;
1295 BEGIN
1296
1297     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
1298
1299         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
1300             RETURN NEXT queued_record.id;
1301         END IF;
1302
1303     END LOOP;
1304
1305     RETURN;
1306     
1307 END;
1308 $$ LANGUAGE PLPGSQL;
1309
1310 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
1311     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
1312 $$ LANGUAGE SQL;
1313
1314
1315 -- Vandelay (for importing and exporting records) 012.schema.vandelay.sql 
1316 --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)]');
1317 --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)]');
1318 --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]');
1319 --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]');
1320 --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$);
1321 --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$);
1322 --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]');
1323 --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);
1324 --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);
1325 --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);
1326 --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);
1327 --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]');
1328 --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$);
1329 --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]');
1330 --
1331 --INSERT INTO vandelay.import_item_attr_definition (
1332 --    owner, name, tag, owning_lib, circ_lib, location,
1333 --    call_number, circ_modifier, barcode, price, copy_number,
1334 --    circulate, ref, holdable, opac_visible, status
1335 --) VALUES (
1336 --    1,
1337 --    'Evergreen 852 export format',
1338 --    '852',
1339 --    '[@code = "b"][1]',
1340 --    '[@code = "b"][2]',
1341 --    'c',
1342 --    'j',
1343 --    'g',
1344 --    'p',
1345 --    'y',
1346 --    't',
1347 --    '[@code = "x" and text() = "circulating"]',
1348 --    '[@code = "x" and text() = "reference"]',
1349 --    '[@code = "x" and text() = "holdable"]',
1350 --    '[@code = "x" and text() = "visible"]',
1351 --    'z'
1352 --);
1353 --
1354 --INSERT INTO vandelay.import_item_attr_definition (
1355 --    owner,
1356 --    name,
1357 --    tag,
1358 --    owning_lib,
1359 --    location,
1360 --    call_number,
1361 --    circ_modifier,
1362 --    barcode,
1363 --    price,
1364 --    status
1365 --) VALUES (
1366 --    1,
1367 --    'Unicorn Import format -- 999',
1368 --    '999',
1369 --    'm',
1370 --    'l',
1371 --    'a',
1372 --    't',
1373 --    'i',
1374 --    'p',
1375 --    'k'
1376 --);
1377 --
1378 --INSERT INTO vandelay.authority_attr_definition ( code, description, xpath, ident ) VALUES ('rec_identifier','Identifier','//*[@tag="001"]', TRUE);
1379
1380 COMMIT;
1381