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