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