]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/012.schema.vandelay.sql
b9f7bca962fa3666799f3b5114a2c89b79311592
[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}{sf}} ) {
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.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
547 DECLARE
548     dyn_profile     vandelay.compile_profile%ROWTYPE;
549     replace_rule    TEXT;
550     tmp_marc        TEXT;
551     trgt_marc        TEXT;
552     tmpl_marc        TEXT;
553     match_count     INT;
554 BEGIN
555
556     IF target_marc IS NULL OR template_marc IS NULL THEN
557         -- RAISE NOTICE 'no marc for target or template record';
558         RETURN NULL;
559     END IF;
560
561     dyn_profile := vandelay.compile_profile( template_marc );
562
563     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
564         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
565         RETURN NULL;
566     END IF;
567
568     IF dyn_profile.replace_rule <> '' THEN
569         trgt_marc = target_marc;
570         tmpl_marc = template_marc;
571         replace_rule = dyn_profile.replace_rule;
572     ELSE
573         tmp_marc = target_marc;
574         trgt_marc = template_marc;
575         tmpl_marc = tmp_marc;
576         replace_rule = dyn_profile.preserve_rule;
577     END IF;
578
579     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
580
581 END;
582 $$ LANGUAGE PLPGSQL;
583
584 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
585     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
586 $$ LANGUAGE SQL;
587
588 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
589 DECLARE
590     merge_profile   vandelay.merge_profile%ROWTYPE;
591     dyn_profile     vandelay.compile_profile%ROWTYPE;
592     editor_string   TEXT;
593     editor_id       INT;
594     source_marc     TEXT;
595     target_marc     TEXT;
596     eg_marc         TEXT;
597     v_marc          TEXT;
598     replace_rule    TEXT;
599     match_count     INT;
600 BEGIN
601
602     SELECT  q.marc INTO v_marc
603       FROM  vandelay.queued_record q
604             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
605       LIMIT 1;
606
607     IF v_marc IS NULL THEN
608         -- RAISE NOTICE 'no marc for vandelay or bib record';
609         RETURN FALSE;
610     END IF;
611
612     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
613         UPDATE  vandelay.queued_bib_record
614           SET   imported_as = eg_id,
615                 import_time = NOW()
616           WHERE id = import_id;
617
618         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
619
620         IF editor_string IS NOT NULL AND editor_string <> '' THEN
621             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
622
623             IF editor_id IS NULL THEN
624                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
625             END IF;
626
627             IF editor_id IS NOT NULL THEN
628                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
629             END IF;
630         END IF;
631
632         RETURN TRUE;
633     END IF;
634
635     -- RAISE NOTICE 'update of biblio.record_entry failed';
636
637     RETURN FALSE;
638
639 END;
640 $$ LANGUAGE PLPGSQL;
641
642 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
643 DECLARE
644     eg_id           BIGINT;
645     match_count     INT;
646     match_attr      vandelay.bib_attr_definition%ROWTYPE;
647 BEGIN
648
649     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
650
651     IF FOUND THEN
652         -- RAISE NOTICE 'already imported, cannot auto-overlay'
653         RETURN FALSE;
654     END IF;
655
656     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
657
658     IF match_count <> 1 THEN
659         -- RAISE NOTICE 'not an exact match';
660         RETURN FALSE;
661     END IF;
662
663     SELECT  d.* INTO match_attr
664       FROM  vandelay.bib_attr_definition d
665             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
666             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
667       WHERE m.queued_record = import_id;
668
669     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
670         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
671         RETURN FALSE;
672     END IF;
673
674     SELECT  m.eg_record INTO eg_id
675       FROM  vandelay.bib_match m
676       WHERE m.queued_record = import_id
677       LIMIT 1;
678
679     IF eg_id IS NULL THEN
680         RETURN FALSE;
681     END IF;
682
683     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
684 END;
685 $$ LANGUAGE PLPGSQL;
686
687 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
688 DECLARE
689     queued_record   vandelay.queued_bib_record%ROWTYPE;
690 BEGIN
691
692     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
693
694         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
695             RETURN NEXT queued_record.id;
696         END IF;
697
698     END LOOP;
699
700     RETURN;
701     
702 END;
703 $$ LANGUAGE PLPGSQL;
704
705 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
706     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
707 $$ LANGUAGE SQL;
708
709 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
710 DECLARE
711
712     owning_lib      TEXT;
713     circ_lib        TEXT;
714     call_number     TEXT;
715     copy_number     TEXT;
716     status          TEXT;
717     location        TEXT;
718     circulate       TEXT;
719     deposit         TEXT;
720     deposit_amount  TEXT;
721     ref             TEXT;
722     holdable        TEXT;
723     price           TEXT;
724     barcode         TEXT;
725     circ_modifier   TEXT;
726     circ_as_type    TEXT;
727     alert_message   TEXT;
728     opac_visible    TEXT;
729     pub_note        TEXT;
730     priv_note       TEXT;
731
732     attr_def        RECORD;
733     tmp_attr_set    RECORD;
734     attr_set        vandelay.import_item%ROWTYPE;
735
736     xpath           TEXT;
737
738 BEGIN
739
740     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
741
742     IF FOUND THEN
743
744         attr_set.definition := attr_def.id; 
745     
746         -- Build the combined XPath
747     
748         owning_lib :=
749             CASE
750                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
751                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
752                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
753             END;
754     
755         circ_lib :=
756             CASE
757                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
758                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
759                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
760             END;
761     
762         call_number :=
763             CASE
764                 WHEN attr_def.call_number IS NULL THEN 'null()'
765                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
766                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
767             END;
768     
769         copy_number :=
770             CASE
771                 WHEN attr_def.copy_number IS NULL THEN 'null()'
772                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
773                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
774             END;
775     
776         status :=
777             CASE
778                 WHEN attr_def.status IS NULL THEN 'null()'
779                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
780                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
781             END;
782     
783         location :=
784             CASE
785                 WHEN attr_def.location IS NULL THEN 'null()'
786                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
787                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
788             END;
789     
790         circulate :=
791             CASE
792                 WHEN attr_def.circulate IS NULL THEN 'null()'
793                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
794                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
795             END;
796     
797         deposit :=
798             CASE
799                 WHEN attr_def.deposit IS NULL THEN 'null()'
800                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
801                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
802             END;
803     
804         deposit_amount :=
805             CASE
806                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
807                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
808                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
809             END;
810     
811         ref :=
812             CASE
813                 WHEN attr_def.ref IS NULL THEN 'null()'
814                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
815                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
816             END;
817     
818         holdable :=
819             CASE
820                 WHEN attr_def.holdable IS NULL THEN 'null()'
821                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
822                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
823             END;
824     
825         price :=
826             CASE
827                 WHEN attr_def.price IS NULL THEN 'null()'
828                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
829                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
830             END;
831     
832         barcode :=
833             CASE
834                 WHEN attr_def.barcode IS NULL THEN 'null()'
835                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
836                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
837             END;
838     
839         circ_modifier :=
840             CASE
841                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
842                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
843                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
844             END;
845     
846         circ_as_type :=
847             CASE
848                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
849                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
850                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
851             END;
852     
853         alert_message :=
854             CASE
855                 WHEN attr_def.alert_message IS NULL THEN 'null()'
856                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
857                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
858             END;
859     
860         opac_visible :=
861             CASE
862                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
863                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
864                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
865             END;
866
867         pub_note :=
868             CASE
869                 WHEN attr_def.pub_note IS NULL THEN 'null()'
870                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
871                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
872             END;
873         priv_note :=
874             CASE
875                 WHEN attr_def.priv_note IS NULL THEN 'null()'
876                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
877                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
878             END;
879     
880     
881         xpath := 
882             owning_lib      || '|' || 
883             circ_lib        || '|' || 
884             call_number     || '|' || 
885             copy_number     || '|' || 
886             status          || '|' || 
887             location        || '|' || 
888             circulate       || '|' || 
889             deposit         || '|' || 
890             deposit_amount  || '|' || 
891             ref             || '|' || 
892             holdable        || '|' || 
893             price           || '|' || 
894             barcode         || '|' || 
895             circ_modifier   || '|' || 
896             circ_as_type    || '|' || 
897             alert_message   || '|' || 
898             pub_note        || '|' || 
899             priv_note       || '|' || 
900             opac_visible;
901
902         -- RAISE NOTICE 'XPath: %', xpath;
903         
904         FOR tmp_attr_set IN
905                 SELECT  *
906                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
907                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
908                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
909                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
910         LOOP
911     
912             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
913             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
914
915             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
916             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
917     
918             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
919             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
920             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
921     
922             SELECT  id INTO attr_set.location
923               FROM  asset.copy_location
924               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
925                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
926     
927             attr_set.circulate      :=
928                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
929                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
930
931             attr_set.deposit        :=
932                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
933                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
934
935             attr_set.holdable       :=
936                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
937                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
938
939             attr_set.opac_visible   :=
940                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
941                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
942
943             attr_set.ref            :=
944                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
945                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
946     
947             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
948             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
949             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
950     
951             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
952             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
953             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
954             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
955             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
956             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
957             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
958             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
959     
960             RETURN NEXT attr_set;
961     
962         END LOOP;
963     
964     END IF;
965
966     RETURN;
967
968 END;
969 $$ LANGUAGE PLPGSQL;
970
971
972 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
973 DECLARE
974     value   TEXT;
975     atype   TEXT;
976     adef    RECORD;
977 BEGIN
978     FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
979
980         SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
981         IF (value IS NOT NULL AND value <> '') THEN
982             INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
983         END IF;
984
985     END LOOP;
986
987     RETURN NULL;
988 END;
989 $$ LANGUAGE PLPGSQL;
990
991 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
992 DECLARE
993     attr_def    BIGINT;
994     item_data   vandelay.import_item%ROWTYPE;
995 BEGIN
996
997     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
998
999     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
1000         INSERT INTO vandelay.import_item (
1001             record,
1002             definition,
1003             owning_lib,
1004             circ_lib,
1005             call_number,
1006             copy_number,
1007             status,
1008             location,
1009             circulate,
1010             deposit,
1011             deposit_amount,
1012             ref,
1013             holdable,
1014             price,
1015             barcode,
1016             circ_modifier,
1017             circ_as_type,
1018             alert_message,
1019             pub_note,
1020             priv_note,
1021             opac_visible
1022         ) VALUES (
1023             NEW.id,
1024             item_data.definition,
1025             item_data.owning_lib,
1026             item_data.circ_lib,
1027             item_data.call_number,
1028             item_data.copy_number,
1029             item_data.status,
1030             item_data.location,
1031             item_data.circulate,
1032             item_data.deposit,
1033             item_data.deposit_amount,
1034             item_data.ref,
1035             item_data.holdable,
1036             item_data.price,
1037             item_data.barcode,
1038             item_data.circ_modifier,
1039             item_data.circ_as_type,
1040             item_data.alert_message,
1041             item_data.pub_note,
1042             item_data.priv_note,
1043             item_data.opac_visible
1044         );
1045     END LOOP;
1046
1047     RETURN NULL;
1048 END;
1049 $func$ LANGUAGE PLPGSQL;
1050
1051 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
1052 DECLARE
1053     attr        RECORD;
1054     attr_def    RECORD;
1055     eg_rec      RECORD;
1056     id_value    TEXT;
1057     exact_id    BIGINT;
1058 BEGIN
1059
1060     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
1061
1062     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
1063
1064     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
1065         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
1066     
1067         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
1068             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
1069             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
1070             IF exact_id IS NOT NULL THEN
1071                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
1072             END IF;
1073         END IF;
1074     END IF;
1075
1076     IF exact_id IS NULL THEN
1077         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
1078     
1079                 -- All numbers? check for an id match
1080                 IF (attr.attr_value ~ $r$^\d+$$r$) THEN
1081                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
1082                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
1083                         END LOOP;
1084                 END IF;
1085     
1086                 -- Looks like an ISBN? check for an isbn match
1087                 IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
1088                 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
1089                                 PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
1090                                 IF FOUND THEN
1091                                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
1092                                 END IF;
1093                         END LOOP;
1094     
1095                         -- subcheck for isbn-as-tcn
1096                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
1097                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1098                 END LOOP;
1099                 END IF;
1100     
1101                 -- check for an OCLC tcn_value match
1102                 IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
1103                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
1104                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1105                 END LOOP;
1106                 END IF;
1107     
1108                 -- check for a direct tcn_value match
1109             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
1110                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
1111             END LOOP;
1112     
1113                 -- check for a direct item barcode match
1114             FOR eg_rec IN
1115                     SELECT  DISTINCT b.*
1116                       FROM  biblio.record_entry b
1117                             JOIN asset.call_number cn ON (cn.record = b.id)
1118                             JOIN asset.copy cp ON (cp.call_number = cn.id)
1119                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
1120             LOOP
1121                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
1122             END LOOP;
1123     
1124         END LOOP;
1125     END IF;
1126
1127     RETURN NULL;
1128 END;
1129 $func$ LANGUAGE PLPGSQL;
1130
1131 CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
1132 BEGIN
1133     DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
1134     DELETE FROM vandelay.import_item WHERE record = OLD.id;
1135
1136     IF TG_OP = 'UPDATE' THEN
1137         RETURN NEW;
1138     END IF;
1139     RETURN OLD;
1140 END;
1141 $$ LANGUAGE PLPGSQL;
1142
1143 CREATE TRIGGER cleanup_bib_trigger
1144     BEFORE UPDATE OR DELETE ON vandelay.queued_bib_record
1145     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_bib_marc();
1146
1147 CREATE TRIGGER ingest_bib_trigger
1148     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1149     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_marc();
1150
1151 CREATE TRIGGER ingest_item_trigger
1152     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1153     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_items();
1154
1155 CREATE TRIGGER zz_match_bibs_trigger
1156     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
1157     FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
1158
1159
1160 /* Authority stuff down here */
1161 ---------------------------------------
1162 CREATE TABLE vandelay.authority_attr_definition (
1163         id                      SERIAL  PRIMARY KEY,
1164         code            TEXT    UNIQUE NOT NULL,
1165         description     TEXT,
1166         xpath           TEXT    NOT NULL,
1167         remove          TEXT    NOT NULL DEFAULT '',
1168         ident           BOOL    NOT NULL DEFAULT FALSE
1169 );
1170
1171 CREATE TABLE vandelay.authority_queue (
1172         queue_type      TEXT            NOT NULL DEFAULT 'authority' CHECK (queue_type = 'authority'),
1173         CONSTRAINT vand_authority_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
1174 ) INHERITS (vandelay.queue);
1175 ALTER TABLE vandelay.authority_queue ADD PRIMARY KEY (id);
1176
1177 CREATE TABLE vandelay.queued_authority_record (
1178         queue           INT     NOT NULL REFERENCES vandelay.authority_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1179         imported_as     INT     REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
1180 ) INHERITS (vandelay.queued_record);
1181 ALTER TABLE vandelay.queued_authority_record ADD PRIMARY KEY (id);
1182
1183 CREATE TABLE vandelay.queued_authority_record_attr (
1184         id                      BIGSERIAL       PRIMARY KEY,
1185         record          BIGINT          NOT NULL REFERENCES vandelay.queued_authority_record (id) DEFERRABLE INITIALLY DEFERRED,
1186         field           INT                     NOT NULL REFERENCES vandelay.authority_attr_definition (id) DEFERRABLE INITIALLY DEFERRED,
1187         attr_value      TEXT            NOT NULL
1188 );
1189
1190 CREATE TABLE vandelay.authority_match (
1191         id                              BIGSERIAL       PRIMARY KEY,
1192         matched_attr    INT                     REFERENCES vandelay.queued_authority_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1193         queued_record   BIGINT          REFERENCES vandelay.queued_authority_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1194         eg_record               BIGINT          REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
1195 );
1196
1197 CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
1198 DECLARE
1199     value   TEXT;
1200     atype   TEXT;
1201     adef    RECORD;
1202 BEGIN
1203     FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
1204
1205         SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
1206         IF (value IS NOT NULL AND value <> '') THEN
1207             INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
1208         END IF;
1209
1210     END LOOP;
1211
1212     RETURN NULL;
1213 END;
1214 $$ LANGUAGE PLPGSQL;
1215
1216 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
1217 BEGIN
1218     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
1219     IF TG_OP = 'UPDATE' THEN
1220         RETURN NEW;
1221     END IF;
1222     RETURN OLD;
1223 END;
1224 $$ LANGUAGE PLPGSQL;
1225
1226 CREATE TRIGGER cleanup_authority_trigger
1227     BEFORE UPDATE OR DELETE ON vandelay.queued_authority_record
1228     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_authority_marc();
1229
1230 CREATE TRIGGER ingest_authority_trigger
1231     AFTER INSERT OR UPDATE ON vandelay.queued_authority_record
1232     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_authority_marc();
1233
1234 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1235 DECLARE
1236     merge_profile   vandelay.merge_profile%ROWTYPE;
1237     dyn_profile     vandelay.compile_profile%ROWTYPE;
1238     source_marc     TEXT;
1239     target_marc     TEXT;
1240     eg_marc         TEXT;
1241     v_marc          TEXT;
1242     replace_rule    TEXT;
1243     match_count     INT;
1244 BEGIN
1245
1246     SELECT  b.marc INTO eg_marc
1247       FROM  authority.record_entry b
1248             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
1249       LIMIT 1;
1250
1251     SELECT  q.marc INTO v_marc
1252       FROM  vandelay.queued_record q
1253             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
1254       LIMIT 1;
1255
1256     IF eg_marc IS NULL OR v_marc IS NULL THEN
1257         -- RAISE NOTICE 'no marc for vandelay or authority record';
1258         RETURN FALSE;
1259     END IF;
1260
1261     dyn_profile := vandelay.compile_profile( v_marc );
1262
1263     IF merge_profile_id IS NOT NULL THEN
1264         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
1265         IF FOUND THEN
1266             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
1267             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
1268             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
1269             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
1270         END IF;
1271     END IF;
1272
1273     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
1274         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
1275         RETURN FALSE;
1276     END IF;
1277
1278     IF dyn_profile.replace_rule <> '' THEN
1279         source_marc = v_marc;
1280         target_marc = eg_marc;
1281         replace_rule = dyn_profile.replace_rule;
1282     ELSE
1283         source_marc = eg_marc;
1284         target_marc = v_marc;
1285         replace_rule = dyn_profile.preserve_rule;
1286     END IF;
1287
1288     UPDATE  authority.record_entry
1289       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
1290       WHERE id = eg_id;
1291
1292     IF FOUND THEN
1293         UPDATE  vandelay.queued_authority_record
1294           SET   imported_as = eg_id,
1295                 import_time = NOW()
1296           WHERE id = import_id;
1297         RETURN TRUE;
1298     END IF;
1299
1300     -- RAISE NOTICE 'update of authority.record_entry failed';
1301
1302     RETURN FALSE;
1303
1304 END;
1305 $$ LANGUAGE PLPGSQL;
1306
1307 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1308 DECLARE
1309     eg_id           BIGINT;
1310     match_count     INT;
1311 BEGIN
1312     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
1313
1314     IF match_count <> 1 THEN
1315         -- RAISE NOTICE 'not an exact match';
1316         RETURN FALSE;
1317     END IF;
1318
1319     SELECT  m.eg_record INTO eg_id
1320       FROM  vandelay.authority_match m
1321       WHERE m.queued_record = import_id
1322       LIMIT 1;
1323
1324     IF eg_id IS NULL THEN
1325         RETURN FALSE;
1326     END IF;
1327
1328     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
1329 END;
1330 $$ LANGUAGE PLPGSQL;
1331
1332 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
1333 DECLARE
1334     queued_record   vandelay.queued_authority_record%ROWTYPE;
1335 BEGIN
1336
1337     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
1338
1339         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
1340             RETURN NEXT queued_record.id;
1341         END IF;
1342
1343     END LOOP;
1344
1345     RETURN;
1346     
1347 END;
1348 $$ LANGUAGE PLPGSQL;
1349
1350 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
1351     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
1352 $$ LANGUAGE SQL;
1353
1354
1355 -- Vandelay (for importing and exporting records) 012.schema.vandelay.sql 
1356 --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)]');
1357 --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)]');
1358 --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]');
1359 --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]');
1360 --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$);
1361 --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$);
1362 --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]');
1363 --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);
1364 --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);
1365 --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);
1366 --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);
1367 --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]');
1368 --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$);
1369 --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]');
1370 --
1371 --INSERT INTO vandelay.import_item_attr_definition (
1372 --    owner, name, tag, owning_lib, circ_lib, location,
1373 --    call_number, circ_modifier, barcode, price, copy_number,
1374 --    circulate, ref, holdable, opac_visible, status
1375 --) VALUES (
1376 --    1,
1377 --    'Evergreen 852 export format',
1378 --    '852',
1379 --    '[@code = "b"][1]',
1380 --    '[@code = "b"][2]',
1381 --    'c',
1382 --    'j',
1383 --    'g',
1384 --    'p',
1385 --    'y',
1386 --    't',
1387 --    '[@code = "x" and text() = "circulating"]',
1388 --    '[@code = "x" and text() = "reference"]',
1389 --    '[@code = "x" and text() = "holdable"]',
1390 --    '[@code = "x" and text() = "visible"]',
1391 --    'z'
1392 --);
1393 --
1394 --INSERT INTO vandelay.import_item_attr_definition (
1395 --    owner,
1396 --    name,
1397 --    tag,
1398 --    owning_lib,
1399 --    location,
1400 --    call_number,
1401 --    circ_modifier,
1402 --    barcode,
1403 --    price,
1404 --    status
1405 --) VALUES (
1406 --    1,
1407 --    'Unicorn Import format -- 999',
1408 --    '999',
1409 --    'm',
1410 --    'l',
1411 --    'a',
1412 --    't',
1413 --    'i',
1414 --    'p',
1415 --    'k'
1416 --);
1417 --
1418 --INSERT INTO vandelay.authority_attr_definition ( code, description, xpath, ident ) VALUES ('rec_identifier','Identifier','//*[@tag="001"]', TRUE);
1419
1420 COMMIT;
1421