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