]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/012.schema.vandelay.sql
Foundations of Action/Triger-based telephony,
[working/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 );
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     RETURN;
929
930 END;
931 $$ LANGUAGE PLPGSQL;
932
933
934 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
935 DECLARE
936     value   TEXT;
937     atype   TEXT;
938     adef    RECORD;
939 BEGIN
940     FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
941
942         SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
943         IF (value IS NOT NULL AND value <> '') THEN
944             INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
945         END IF;
946
947     END LOOP;
948
949     RETURN NULL;
950 END;
951 $$ LANGUAGE PLPGSQL;
952
953 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
954 DECLARE
955     attr_def    BIGINT;
956     item_data   vandelay.import_item%ROWTYPE;
957 BEGIN
958
959     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
960
961     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
962         INSERT INTO vandelay.import_item (
963             record,
964             definition,
965             owning_lib,
966             circ_lib,
967             call_number,
968             copy_number,
969             status,
970             location,
971             circulate,
972             deposit,
973             deposit_amount,
974             ref,
975             holdable,
976             price,
977             barcode,
978             circ_modifier,
979             circ_as_type,
980             alert_message,
981             pub_note,
982             priv_note,
983             opac_visible
984         ) VALUES (
985             NEW.id,
986             item_data.definition,
987             item_data.owning_lib,
988             item_data.circ_lib,
989             item_data.call_number,
990             item_data.copy_number,
991             item_data.status,
992             item_data.location,
993             item_data.circulate,
994             item_data.deposit,
995             item_data.deposit_amount,
996             item_data.ref,
997             item_data.holdable,
998             item_data.price,
999             item_data.barcode,
1000             item_data.circ_modifier,
1001             item_data.circ_as_type,
1002             item_data.alert_message,
1003             item_data.pub_note,
1004             item_data.priv_note,
1005             item_data.opac_visible
1006         );
1007     END LOOP;
1008
1009     RETURN NULL;
1010 END;
1011 $func$ LANGUAGE PLPGSQL;
1012
1013 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
1014 DECLARE
1015     attr        RECORD;
1016     attr_def    RECORD;
1017     eg_rec      RECORD;
1018     id_value    TEXT;
1019     exact_id    BIGINT;
1020 BEGIN
1021
1022     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
1023
1024     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
1025
1026     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
1027         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
1028     
1029         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
1030             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
1031             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
1032             IF exact_id IS NOT NULL THEN
1033                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
1034             END IF;
1035         END IF;
1036     END IF;
1037
1038     IF exact_id IS NULL THEN
1039         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
1040     
1041                 -- All numbers? check for an id match
1042                 IF (attr.attr_value ~ $r$^\d+$$r$) THEN
1043                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
1044                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
1045                         END LOOP;
1046                 END IF;
1047     
1048                 -- Looks like an ISBN? check for an isbn match
1049                 IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
1050                 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
1051                                 PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
1052                                 IF FOUND THEN
1053                                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
1054                                 END IF;
1055                         END LOOP;
1056     
1057                         -- subcheck for isbn-as-tcn
1058                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
1059                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1060                 END LOOP;
1061                 END IF;
1062     
1063                 -- check for an OCLC tcn_value match
1064                 IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
1065                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
1066                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1067                 END LOOP;
1068                 END IF;
1069     
1070                 -- check for a direct tcn_value match
1071             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
1072                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1073             END LOOP;
1074     
1075                 -- check for a direct item barcode match
1076             FOR eg_rec IN
1077                     SELECT  DISTINCT b.*
1078                       FROM  biblio.record_entry b
1079                             JOIN asset.call_number cn ON (cn.record = b.id)
1080                             JOIN asset.copy cp ON (cp.call_number = cn.id)
1081                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
1082             LOOP
1083                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
1084             END LOOP;
1085     
1086         END LOOP;
1087     END IF;
1088
1089     RETURN NULL;
1090 END;
1091 $func$ LANGUAGE PLPGSQL;
1092
1093 CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
1094 BEGIN
1095     DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
1096     DELETE FROM vandelay.import_item WHERE record = OLD.id;
1097
1098     IF TG_OP = 'UPDATE' THEN
1099         RETURN NEW;
1100     END IF;
1101     RETURN OLD;
1102 END;
1103 $$ LANGUAGE PLPGSQL;
1104
1105 CREATE TRIGGER cleanup_bib_trigger
1106     BEFORE UPDATE OR DELETE ON vandelay.queued_bib_record
1107     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_bib_marc();
1108
1109 CREATE TRIGGER ingest_bib_trigger
1110     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1111     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_marc();
1112
1113 CREATE TRIGGER ingest_item_trigger
1114     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1115     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_items();
1116
1117 CREATE TRIGGER zz_match_bibs_trigger
1118     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1119     FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
1120
1121
1122 /* Authority stuff down here */
1123 ---------------------------------------
1124 CREATE TABLE vandelay.authority_attr_definition (
1125         id                      SERIAL  PRIMARY KEY,
1126         code            TEXT    UNIQUE NOT NULL,
1127         description     TEXT,
1128         xpath           TEXT    NOT NULL,
1129         remove          TEXT    NOT NULL DEFAULT '',
1130         ident           BOOL    NOT NULL DEFAULT FALSE
1131 );
1132
1133 CREATE TABLE vandelay.authority_queue (
1134         queue_type      TEXT            NOT NULL DEFAULT 'authority' CHECK (queue_type = 'authority'),
1135         CONSTRAINT vand_authority_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
1136 ) INHERITS (vandelay.queue);
1137 ALTER TABLE vandelay.authority_queue ADD PRIMARY KEY (id);
1138
1139 CREATE TABLE vandelay.queued_authority_record (
1140         queue           INT     NOT NULL REFERENCES vandelay.authority_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1141         imported_as     INT     REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
1142 ) INHERITS (vandelay.queued_record);
1143 ALTER TABLE vandelay.queued_authority_record ADD PRIMARY KEY (id);
1144
1145 CREATE TABLE vandelay.queued_authority_record_attr (
1146         id                      BIGSERIAL       PRIMARY KEY,
1147         record          BIGINT          NOT NULL REFERENCES vandelay.queued_authority_record (id) DEFERRABLE INITIALLY DEFERRED,
1148         field           INT                     NOT NULL REFERENCES vandelay.authority_attr_definition (id) DEFERRABLE INITIALLY DEFERRED,
1149         attr_value      TEXT            NOT NULL
1150 );
1151
1152 CREATE TABLE vandelay.authority_match (
1153         id                              BIGSERIAL       PRIMARY KEY,
1154         matched_attr    INT                     REFERENCES vandelay.queued_authority_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1155         queued_record   BIGINT          REFERENCES vandelay.queued_authority_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1156         eg_record               BIGINT          REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
1157 );
1158
1159 CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
1160 DECLARE
1161     value   TEXT;
1162     atype   TEXT;
1163     adef    RECORD;
1164 BEGIN
1165     FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
1166
1167         SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
1168         IF (value IS NOT NULL AND value <> '') THEN
1169             INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
1170         END IF;
1171
1172     END LOOP;
1173
1174     RETURN NULL;
1175 END;
1176 $$ LANGUAGE PLPGSQL;
1177
1178 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
1179 BEGIN
1180     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
1181     IF TG_OP = 'UPDATE' THEN
1182         RETURN NEW;
1183     END IF;
1184     RETURN OLD;
1185 END;
1186 $$ LANGUAGE PLPGSQL;
1187
1188 CREATE TRIGGER cleanup_authority_trigger
1189     BEFORE UPDATE OR DELETE ON vandelay.queued_authority_record
1190     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_authority_marc();
1191
1192 CREATE TRIGGER ingest_authority_trigger
1193     AFTER INSERT OR UPDATE ON vandelay.queued_authority_record
1194     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_authority_marc();
1195
1196 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1197 DECLARE
1198     merge_profile   vandelay.merge_profile%ROWTYPE;
1199     dyn_profile     vandelay.compile_profile%ROWTYPE;
1200     source_marc     TEXT;
1201     target_marc     TEXT;
1202     eg_marc         TEXT;
1203     v_marc          TEXT;
1204     replace_rule    TEXT;
1205     match_count     INT;
1206 BEGIN
1207
1208     SELECT  b.marc INTO eg_marc
1209       FROM  authority.record_entry b
1210             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
1211       LIMIT 1;
1212
1213     SELECT  q.marc INTO v_marc
1214       FROM  vandelay.queued_record q
1215             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
1216       LIMIT 1;
1217
1218     IF eg_marc IS NULL OR v_marc IS NULL THEN
1219         -- RAISE NOTICE 'no marc for vandelay or authority record';
1220         RETURN FALSE;
1221     END IF;
1222
1223     dyn_profile := vandelay.compile_profile( v_marc );
1224
1225     IF merge_profile_id IS NOT NULL THEN
1226         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
1227         IF FOUND THEN
1228             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
1229             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
1230             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
1231             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
1232         END IF;
1233     END IF;
1234
1235     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
1236         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
1237         RETURN FALSE;
1238     END IF;
1239
1240     IF dyn_profile.replace_rule <> '' THEN
1241         source_marc = v_marc;
1242         target_marc = eg_marc;
1243         replace_rule = dyn_profile.replace_rule;
1244     ELSE
1245         source_marc = eg_marc;
1246         target_marc = v_marc;
1247         replace_rule = dyn_profile.preserve_rule;
1248     END IF;
1249
1250     UPDATE  authority.record_entry
1251       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
1252       WHERE id = eg_id;
1253
1254     IF FOUND THEN
1255         UPDATE  vandelay.queued_authority_record
1256           SET   imported_as = eg_id,
1257                 import_time = NOW()
1258           WHERE id = import_id;
1259         RETURN TRUE;
1260     END IF;
1261
1262     -- RAISE NOTICE 'update of authority.record_entry failed';
1263
1264     RETURN FALSE;
1265
1266 END;
1267 $$ LANGUAGE PLPGSQL;
1268
1269 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1270 DECLARE
1271     eg_id           BIGINT;
1272     match_count     INT;
1273 BEGIN
1274     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
1275
1276     IF match_count <> 1 THEN
1277         -- RAISE NOTICE 'not an exact match';
1278         RETURN FALSE;
1279     END IF;
1280
1281     SELECT  m.eg_record INTO eg_id
1282       FROM  vandelay.authority_match m
1283       WHERE m.queued_record = import_id
1284       LIMIT 1;
1285
1286     IF eg_id IS NULL THEN
1287         RETURN FALSE;
1288     END IF;
1289
1290     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
1291 END;
1292 $$ LANGUAGE PLPGSQL;
1293
1294 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
1295 DECLARE
1296     queued_record   vandelay.queued_authority_record%ROWTYPE;
1297 BEGIN
1298
1299     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
1300
1301         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
1302             RETURN NEXT queued_record.id;
1303         END IF;
1304
1305     END LOOP;
1306
1307     RETURN;
1308     
1309 END;
1310 $$ LANGUAGE PLPGSQL;
1311
1312 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
1313     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
1314 $$ LANGUAGE SQL;
1315
1316
1317 -- Vandelay (for importing and exporting records) 012.schema.vandelay.sql 
1318 --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)]');
1319 --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)]');
1320 --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]');
1321 --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]');
1322 --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$);
1323 --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$);
1324 --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]');
1325 --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);
1326 --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);
1327 --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);
1328 --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);
1329 --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]');
1330 --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$);
1331 --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]');
1332 --
1333 --INSERT INTO vandelay.import_item_attr_definition (
1334 --    owner, name, tag, owning_lib, circ_lib, location,
1335 --    call_number, circ_modifier, barcode, price, copy_number,
1336 --    circulate, ref, holdable, opac_visible, status
1337 --) VALUES (
1338 --    1,
1339 --    'Evergreen 852 export format',
1340 --    '852',
1341 --    '[@code = "b"][1]',
1342 --    '[@code = "b"][2]',
1343 --    'c',
1344 --    'j',
1345 --    'g',
1346 --    'p',
1347 --    'y',
1348 --    't',
1349 --    '[@code = "x" and text() = "circulating"]',
1350 --    '[@code = "x" and text() = "reference"]',
1351 --    '[@code = "x" and text() = "holdable"]',
1352 --    '[@code = "x" and text() = "visible"]',
1353 --    'z'
1354 --);
1355 --
1356 --INSERT INTO vandelay.import_item_attr_definition (
1357 --    owner,
1358 --    name,
1359 --    tag,
1360 --    owning_lib,
1361 --    location,
1362 --    call_number,
1363 --    circ_modifier,
1364 --    barcode,
1365 --    price,
1366 --    status
1367 --) VALUES (
1368 --    1,
1369 --    'Unicorn Import format -- 999',
1370 --    '999',
1371 --    'm',
1372 --    'l',
1373 --    'a',
1374 --    't',
1375 --    'i',
1376 --    'p',
1377 --    'k'
1378 --);
1379 --
1380 --INSERT INTO vandelay.authority_attr_definition ( code, description, xpath, ident ) VALUES ('rec_identifier','Identifier','//*[@tag="001"]', TRUE);
1381
1382 COMMIT;
1383