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