]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/012.schema.vandelay.sql
adding 901c cross-check to auto-overlay logic to prevent isbn, etc auto-overlay
[working/Evergreen.git] / Open-ILS / src / sql / Pg / 012.schema.vandelay.sql
1 DROP SCHEMA vandelay CASCADE;
2
3 BEGIN;
4
5 CREATE SCHEMA vandelay;
6
7 CREATE TABLE vandelay.queue (
8         id                              BIGSERIAL       PRIMARY KEY,
9         owner                   INT                     NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
10         name                    TEXT            NOT NULL,
11         complete                BOOL            NOT NULL DEFAULT FALSE,
12         queue_type              TEXT            NOT NULL DEFAULT 'bib' CHECK (queue_type IN ('bib','authority')),
13         CONSTRAINT vand_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
14 );
15
16 CREATE TABLE vandelay.queued_record (
17     id                  BIGSERIAL                   PRIMARY KEY,
18     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
19     import_time TIMESTAMP WITH TIME ZONE,
20         purpose         TEXT                                            NOT NULL DEFAULT 'import' CHECK (purpose IN ('import','overlay')),
21     marc                TEXT                        NOT NULL
22 );
23
24
25
26 /* Bib stuff at the top */
27 ----------------------------------------------------
28
29 CREATE TABLE vandelay.bib_attr_definition (
30         id                      SERIAL  PRIMARY KEY,
31         code            TEXT    UNIQUE NOT NULL,
32         description     TEXT,
33         xpath           TEXT    NOT NULL,
34         remove          TEXT    NOT NULL DEFAULT '',
35         ident           BOOL    NOT NULL DEFAULT FALSE
36 );
37
38 -- Each TEXT field (other than 'name') should hold an XPath predicate for pulling the data needed
39 -- DROP TABLE vandelay.import_item_attr_definition CASCADE;
40 CREATE TABLE vandelay.import_item_attr_definition (
41     id              BIGSERIAL   PRIMARY KEY,
42     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
43     name            TEXT        NOT NULL,
44     tag             TEXT        NOT NULL,
45     keep            BOOL        NOT NULL DEFAULT FALSE,
46     owning_lib      TEXT,
47     circ_lib        TEXT,
48     call_number     TEXT,
49     copy_number     TEXT,
50     status          TEXT,
51     location        TEXT,
52     circulate       TEXT,
53     deposit         TEXT,
54     deposit_amount  TEXT,
55     ref             TEXT,
56     holdable        TEXT,
57     price           TEXT,
58     barcode         TEXT,
59     circ_modifier   TEXT,
60     circ_as_type    TEXT,
61     alert_message   TEXT,
62     opac_visible    TEXT,
63     pub_note_title  TEXT,
64     pub_note        TEXT,
65     priv_note_title TEXT,
66     priv_note       TEXT,
67         CONSTRAINT vand_import_item_attr_def_idx UNIQUE (owner,name)
68 );
69
70 CREATE TABLE vandelay.bib_queue (
71         queue_type          TEXT        NOT NULL DEFAULT 'bib' CHECK (queue_type = 'bib'),
72         item_attr_def   BIGINT REFERENCES vandelay.import_item_attr_definition (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
73         CONSTRAINT vand_bib_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
74 ) INHERITS (vandelay.queue);
75 ALTER TABLE vandelay.bib_queue ADD PRIMARY KEY (id);
76
77 CREATE TABLE vandelay.queued_bib_record (
78         queue           INT             NOT NULL REFERENCES vandelay.bib_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
79         bib_source      INT             REFERENCES config.bib_source (id) DEFERRABLE INITIALLY DEFERRED,
80         imported_as     INT             REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED
81 ) INHERITS (vandelay.queued_record);
82 ALTER TABLE vandelay.queued_bib_record ADD PRIMARY KEY (id);
83
84 CREATE TABLE vandelay.queued_bib_record_attr (
85         id                      BIGSERIAL       PRIMARY KEY,
86         record          BIGINT          NOT NULL REFERENCES vandelay.queued_bib_record (id) DEFERRABLE INITIALLY DEFERRED,
87         field           INT                     NOT NULL REFERENCES vandelay.bib_attr_definition (id) DEFERRABLE INITIALLY DEFERRED,
88         attr_value      TEXT            NOT NULL
89 );
90
91 CREATE TABLE vandelay.bib_match (
92         id                              BIGSERIAL       PRIMARY KEY,
93         field_type              TEXT            NOT NULL CHECK (field_type in ('isbn','tcn_value','id')),
94         matched_attr    INT                     REFERENCES vandelay.queued_bib_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
95         queued_record   BIGINT          REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
96         eg_record               BIGINT          REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
97 );
98
99 -- DROP TABLE vandelay.import_item CASCADE;
100 CREATE TABLE vandelay.import_item (
101     id              BIGSERIAL   PRIMARY KEY,
102     record          BIGINT      NOT NULL REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
103     definition      BIGINT      NOT NULL REFERENCES vandelay.import_item_attr_definition (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
104     owning_lib      INT,
105     circ_lib        INT,
106     call_number     TEXT,
107     copy_number     INT,
108     status          INT,
109     location        INT,
110     circulate       BOOL,
111     deposit         BOOL,
112     deposit_amount  NUMERIC(8,2),
113     ref             BOOL,
114     holdable        BOOL,
115     price           NUMERIC(8,2),
116     barcode         TEXT,
117     circ_modifier   TEXT,
118     circ_as_type    TEXT,
119     alert_message   TEXT,
120     pub_note        TEXT,
121     priv_note       TEXT,
122     opac_visible    BOOL
123 );
124  
125 CREATE TABLE vandelay.import_bib_trash_fields (
126     id              BIGSERIAL   PRIMARY KEY,
127     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
128     field           TEXT        NOT NULL,
129         CONSTRAINT vand_import_bib_trash_fields_idx UNIQUE (owner,field)
130 );
131
132 CREATE TABLE vandelay.merge_profile (
133     id              BIGSERIAL   PRIMARY KEY,
134     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
135     name            TEXT        NOT NULL,
136     add_spec        TEXT,
137     replace_spec    TEXT,
138     strip_spec      TEXT,
139     preserve_spec   TEXT,
140         CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
141         CONSTRAINT add_replace_strip_or_preserve CHECK ((preserve_spec IS NOT NULL OR replace_spec IS NOT NULL) OR (preserve_spec IS NULL AND replace_spec IS NULL))
142 );
143
144 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
145
146     use MARC::Record;
147     use MARC::File::XML;
148
149     my $target_xml = shift;
150     my $source_xml = shift;
151     my $field_spec = shift;
152     $field_spec =~ s/\s+//sg;
153
154     my $target_r = MARC::Record->new_from_xml( $target_xml );
155     my $source_r = MARC::Record->new_from_xml( $source_xml );
156
157     return $target_xml unless ($target_r && $source_r);
158
159     my @field_list = split(',', $field_spec);
160
161     my %fields;
162     for my $f (@field_list) {
163         if ($f =~ /^(.{3})(.*)$/) {
164             $fields{$1} = [ split('', $2) ];
165         }
166     }
167
168     for my $f ( keys %fields) {
169         if ( @{$fields{$f}} ) {
170             for my $from_field ($source_r->field( $f )) {
171                 for my $to_field ($target_r->field( $f )) {
172                     my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}};
173                     $to_field->add_subfields( @new_sf );
174                 }
175             }
176         } else {
177             my @new_fields = map { $_->clone } $source_r->field( $f );
178             $target_r->insert_fields_ordered( @new_fields );
179         }
180     }
181
182     $target_xml = $target_r->as_xml_record;
183     $target_xml =~ s/^<\?.+?\?>$//mo;
184     $target_xml =~ s/\n//sgo;
185     $target_xml =~ s/>\s+</></sgo;
186
187     return $target_xml;
188
189 $_$ LANGUAGE PLPERLU;
190
191 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
192
193     use MARC::Record;
194     use MARC::File::XML;
195
196     my $xml = shift;
197     my $r = MARC::Record->new_from_xml( $xml );
198
199     return $xml unless ($r);
200
201     my $field_spec = shift;
202     $field_spec =~ s/\s+//sg;
203
204     my @field_list = split(',', $field_spec);
205
206     my %fields;
207     for my $f (@field_list) {
208         if ($f =~ /^(.{3})(.*)$/) {
209             $fields{$1} = [ split('', $2) ];
210         }
211     }
212
213     for my $f ( keys %fields) {
214         if ( @{$fields{$f}} ) {
215             $_->delete_subfield(code => $fields{$f}) for ($r->field( $f ));
216         } else {
217             $r->delete_field( $_ ) for ( $r->field( $f ) );
218         }
219     }
220
221     $xml = $r->as_xml_record;
222     $xml =~ s/^<\?.+?\?>$//mo;
223     $xml =~ s/\n//sgo;
224     $xml =~ s/>\s+</></sgo;
225
226     return $xml;
227
228 $_$ LANGUAGE PLPERLU;
229
230 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
231     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
232 $_$ LANGUAGE SQL;
233
234 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 $_$
235     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
236 $_$ LANGUAGE SQL;
237
238 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
239 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
240 DECLARE
241     output              vandelay.compile_profile%ROWTYPE;
242     profile             vandelay.merge_profile%ROWTYPE;
243     profile_tmpl        TEXT;
244     profile_tmpl_owner  TEXT;
245     add_rule            TEXT := '';
246     strip_rule          TEXT := '';
247     replace_rule        TEXT := '';
248     preserve_rule       TEXT := '';
249
250 BEGIN
251
252     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
253     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
254
255     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
256         SELECT  p.* INTO profile
257           FROM  vandelay.merge_profile p
258                 JOIN actor.org_unit u ON (u.id = p.owner)
259           WHERE p.name = profile_tmpl
260                 AND u.shortname = profile_tmpl_owner;
261
262         IF profile.id IS NOT NULL THEN
263             add_rule := COALESCE(profile.add_spec,'');
264             strip_rule := COALESCE(profile.strip_spec,'');
265             replace_rule := COALESCE(profile.replace_spec,'');
266             preserve_rule := COALESCE(profile.preserve_spec,'');
267         END IF;
268     END IF;
269
270     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),''),'');
271     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),''),'');
272     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),''),'');
273     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),''),'');
274
275     output.add_rule := BTRIM(add_rule,',');
276     output.replace_rule := BTRIM(replace_rule,',');
277     output.strip_rule := BTRIM(strip_rule,',');
278     output.preserve_rule := BTRIM(preserve_rule,',');
279
280     RETURN output;
281 END;
282 $_$ LANGUAGE PLPGSQL;
283
284 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
285 DECLARE
286     merge_profile   vandelay.merge_profile%ROWTYPE;
287     dyn_profile     vandelay.compile_profile%ROWTYPE;
288     source_marc     TEXT;
289     target_marc     TEXT;
290     eg_marc         TEXT;
291     v_marc          TEXT;
292     replace_rule    TEXT;
293     match_count     INT;
294 BEGIN
295
296     SELECT  b.marc INTO eg_marc
297       FROM  biblio.record_entry b
298             JOIN vandelay.bib_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
299       LIMIT 1;
300
301     SELECT  q.marc INTO v_marc
302       FROM  vandelay.queued_record q
303             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
304       LIMIT 1;
305
306     IF eg_marc IS NULL OR v_marc IS NULL THEN
307         -- RAISE NOTICE 'no marc for vandelay or bib record';
308         RETURN FALSE;
309     END IF;
310
311     dyn_profile := vandelay.compile_profile( v_marc );
312
313     IF merge_profile_id IS NOT NULL THEN
314         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
315         IF FOUND THEN
316             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
317             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
318             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
319             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
320         END IF;
321     END IF;
322
323     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
324         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
325         RETURN FALSE;
326     END IF;
327
328     IF dyn_profile.replace_rule <> '' THEN
329         source_marc = v_marc;
330         target_marc = eg_marc;
331         replace_rule = dyn_profile.replace_rule;
332     ELSE
333         source_marc = eg_marc;
334         target_marc = v_marc;
335         replace_rule = dyn_profile.preserve_rule;
336     END IF;
337
338     UPDATE  biblio.record_entry
339       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
340       WHERE id = eg_id;
341
342     IF FOUND THEN
343         UPDATE  vandelay.queued_bib_record
344           SET   imported_as = eg_id,
345                 import_time = NOW()
346           WHERE id = import_id;
347         RETURN TRUE;
348     END IF;
349
350     -- RAISE NOTICE 'update of biblio.record_entry failed';
351
352     RETURN FALSE;
353
354 END;
355 $$ LANGUAGE PLPGSQL;
356
357 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
358 DECLARE
359     eg_id           BIGINT;
360     match_count     INT;
361     match_attr      vandelay.bib_attr_definition%ROWTYPE;
362 BEGIN
363     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
364
365     IF match_count <> 1 THEN
366         -- RAISE NOTICE 'not an exact match';
367         RETURN FALSE;
368     END IF;
369
370     SELECT  d.* INTO match_attr
371       FROM  vandelay.bib_attr_definition d
372             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
373             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
374       WHERE m.queued_record = import_id;
375
376     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
377         -- RAISE NOTICE 'not a 901c match';
378         RETURN FALSE;
379     END IF;
380
381     SELECT  m.eg_record INTO eg_id
382       FROM  vandelay.bib_match m
383       WHERE m.queued_record = import_id
384       LIMIT 1;
385
386     IF eg_id IS NULL THEN
387         RETURN FALSE;
388     END IF;
389
390     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
391 END;
392 $$ LANGUAGE PLPGSQL;
393
394 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
395 DECLARE
396     queued_record   vandelay.queued_bib_record%ROWTYPE;
397     success         BOOL;
398 BEGIN
399
400     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
401         success := vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id );
402
403         IF success THEN
404             RETURN NEXT queued_record.id;
405         END IF;
406
407     END LOOP;
408
409     RETURN;
410     
411 END;
412 $$ LANGUAGE PLPGSQL;
413
414 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
415     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
416 $$ LANGUAGE SQL;
417
418 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
419 DECLARE
420
421     owning_lib      TEXT;
422     circ_lib        TEXT;
423     call_number     TEXT;
424     copy_number     TEXT;
425     status          TEXT;
426     location        TEXT;
427     circulate       TEXT;
428     deposit         TEXT;
429     deposit_amount  TEXT;
430     ref             TEXT;
431     holdable        TEXT;
432     price           TEXT;
433     barcode         TEXT;
434     circ_modifier   TEXT;
435     circ_as_type    TEXT;
436     alert_message   TEXT;
437     opac_visible    TEXT;
438     pub_note        TEXT;
439     priv_note       TEXT;
440
441     attr_def        RECORD;
442     tmp_attr_set    RECORD;
443     attr_set        vandelay.import_item%ROWTYPE;
444
445     xpath           TEXT;
446
447 BEGIN
448
449     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
450
451     IF FOUND THEN
452
453         attr_set.definition := attr_def.id; 
454     
455         -- Build the combined XPath
456     
457         owning_lib :=
458             CASE
459                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
460                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
461                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
462             END;
463     
464         circ_lib :=
465             CASE
466                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
467                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
468                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
469             END;
470     
471         call_number :=
472             CASE
473                 WHEN attr_def.call_number IS NULL THEN 'null()'
474                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
475                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
476             END;
477     
478         copy_number :=
479             CASE
480                 WHEN attr_def.copy_number IS NULL THEN 'null()'
481                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
482                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
483             END;
484     
485         status :=
486             CASE
487                 WHEN attr_def.status IS NULL THEN 'null()'
488                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
489                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
490             END;
491     
492         location :=
493             CASE
494                 WHEN attr_def.location IS NULL THEN 'null()'
495                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
496                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
497             END;
498     
499         circulate :=
500             CASE
501                 WHEN attr_def.circulate IS NULL THEN 'null()'
502                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
503                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
504             END;
505     
506         deposit :=
507             CASE
508                 WHEN attr_def.deposit IS NULL THEN 'null()'
509                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
510                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
511             END;
512     
513         deposit_amount :=
514             CASE
515                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
516                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
517                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
518             END;
519     
520         ref :=
521             CASE
522                 WHEN attr_def.ref IS NULL THEN 'null()'
523                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
524                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
525             END;
526     
527         holdable :=
528             CASE
529                 WHEN attr_def.holdable IS NULL THEN 'null()'
530                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
531                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
532             END;
533     
534         price :=
535             CASE
536                 WHEN attr_def.price IS NULL THEN 'null()'
537                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
538                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
539             END;
540     
541         barcode :=
542             CASE
543                 WHEN attr_def.barcode IS NULL THEN 'null()'
544                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
545                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
546             END;
547     
548         circ_modifier :=
549             CASE
550                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
551                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
552                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
553             END;
554     
555         circ_as_type :=
556             CASE
557                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
558                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
559                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
560             END;
561     
562         alert_message :=
563             CASE
564                 WHEN attr_def.alert_message IS NULL THEN 'null()'
565                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
566                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
567             END;
568     
569         opac_visible :=
570             CASE
571                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
572                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
573                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
574             END;
575
576         pub_note :=
577             CASE
578                 WHEN attr_def.pub_note IS NULL THEN 'null()'
579                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
580                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
581             END;
582         priv_note :=
583             CASE
584                 WHEN attr_def.priv_note IS NULL THEN 'null()'
585                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
586                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
587             END;
588     
589     
590         xpath := 
591             owning_lib      || '|' || 
592             circ_lib        || '|' || 
593             call_number     || '|' || 
594             copy_number     || '|' || 
595             status          || '|' || 
596             location        || '|' || 
597             circulate       || '|' || 
598             deposit         || '|' || 
599             deposit_amount  || '|' || 
600             ref             || '|' || 
601             holdable        || '|' || 
602             price           || '|' || 
603             barcode         || '|' || 
604             circ_modifier   || '|' || 
605             circ_as_type    || '|' || 
606             alert_message   || '|' || 
607             pub_note        || '|' || 
608             priv_note       || '|' || 
609             opac_visible;
610
611         -- RAISE NOTICE 'XPath: %', xpath;
612         
613         FOR tmp_attr_set IN
614                 SELECT  *
615                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
616                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
617                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
618                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
619         LOOP
620     
621             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
622             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
623
624             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
625             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
626     
627             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
628             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
629             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
630     
631             SELECT  id INTO attr_set.location
632               FROM  asset.copy_location
633               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
634                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
635     
636             attr_set.circulate      :=
637                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
638                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
639
640             attr_set.deposit        :=
641                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
642                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
643
644             attr_set.holdable       :=
645                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
646                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
647
648             attr_set.opac_visible   :=
649                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
650                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
651
652             attr_set.ref            :=
653                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
654                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
655     
656             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
657             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
658             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
659     
660             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
661             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
662             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
663             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
664             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
665             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
666             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
667             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
668     
669             RETURN NEXT attr_set;
670     
671         END LOOP;
672     
673     END IF;
674
675 END;
676 $$ LANGUAGE PLPGSQL;
677
678
679 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
680 DECLARE
681     value   TEXT;
682     atype   TEXT;
683     adef    RECORD;
684 BEGIN
685     FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
686
687         SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
688         IF (value IS NOT NULL AND value <> '') THEN
689             INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
690         END IF;
691
692     END LOOP;
693
694     RETURN NULL;
695 END;
696 $$ LANGUAGE PLPGSQL;
697
698 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
699 DECLARE
700     attr_def    BIGINT;
701     item_data   vandelay.import_item%ROWTYPE;
702 BEGIN
703
704     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
705
706     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
707         INSERT INTO vandelay.import_item (
708             record,
709             definition,
710             owning_lib,
711             circ_lib,
712             call_number,
713             copy_number,
714             status,
715             location,
716             circulate,
717             deposit,
718             deposit_amount,
719             ref,
720             holdable,
721             price,
722             barcode,
723             circ_modifier,
724             circ_as_type,
725             alert_message,
726             pub_note,
727             priv_note,
728             opac_visible
729         ) VALUES (
730             NEW.id,
731             item_data.definition,
732             item_data.owning_lib,
733             item_data.circ_lib,
734             item_data.call_number,
735             item_data.copy_number,
736             item_data.status,
737             item_data.location,
738             item_data.circulate,
739             item_data.deposit,
740             item_data.deposit_amount,
741             item_data.ref,
742             item_data.holdable,
743             item_data.price,
744             item_data.barcode,
745             item_data.circ_modifier,
746             item_data.circ_as_type,
747             item_data.alert_message,
748             item_data.pub_note,
749             item_data.priv_note,
750             item_data.opac_visible
751         );
752     END LOOP;
753
754     RETURN NULL;
755 END;
756 $func$ LANGUAGE PLPGSQL;
757
758 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
759 DECLARE
760     attr        RECORD;
761     eg_rec      RECORD;
762     id_value    TEXT;
763     exact_id    BIGINT;
764 BEGIN
765
766     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
767
768     SELECT * INTO attr FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
769
770     IF attr IS NOT NULL AND attr.id IS NOT NULL THEN
771         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr.xpath, attr.remove);
772     
773         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
774             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
775             IF exact_id IS NOT NULL THEN
776                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
777             END IF;
778         END IF;
779     END IF;
780
781     IF exact_id IS NULL THEN
782         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
783     
784                 -- All numbers? check for an id match
785                 IF (attr.attr_value ~ $r$^\d+$$r$) THEN
786                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
787                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
788                         END LOOP;
789                 END IF;
790     
791                 -- Looks like an ISBN? check for an isbn match
792                 IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
793                 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
794                                 PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
795                                 IF FOUND THEN
796                                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
797                                 END IF;
798                         END LOOP;
799     
800                         -- subcheck for isbn-as-tcn
801                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
802                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
803                 END LOOP;
804                 END IF;
805     
806                 -- check for an OCLC tcn_value match
807                 IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
808                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
809                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
810                 END LOOP;
811                 END IF;
812     
813                 -- check for a direct tcn_value match
814             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
815                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
816             END LOOP;
817     
818                 -- check for a direct item barcode match
819             FOR eg_rec IN
820                     SELECT  DISTINCT b.*
821                       FROM  biblio.record_entry b
822                             JOIN asset.call_number cn ON (cn.record = b.id)
823                             JOIN asset.copy cp ON (cp.call_number = cn.id)
824                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
825             LOOP
826                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
827             END LOOP;
828     
829         END LOOP;
830     END IF;
831
832     RETURN NULL;
833 END;
834 $func$ LANGUAGE PLPGSQL;
835
836 CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
837 BEGIN
838     DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
839     DELETE FROM vandelay.import_item WHERE record = OLD.id;
840
841     IF TG_OP = 'UPDATE' THEN
842         RETURN NEW;
843     END IF;
844     RETURN OLD;
845 END;
846 $$ LANGUAGE PLPGSQL;
847
848 CREATE TRIGGER cleanup_bib_trigger
849     BEFORE UPDATE OR DELETE ON vandelay.queued_bib_record
850     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_bib_marc();
851
852 CREATE TRIGGER ingest_bib_trigger
853     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
854     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_marc();
855
856 CREATE TRIGGER ingest_item_trigger
857     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
858     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_items();
859
860 CREATE TRIGGER zz_match_bibs_trigger
861     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
862     FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
863
864
865 /* Authority stuff down here */
866 ---------------------------------------
867 CREATE TABLE vandelay.authority_attr_definition (
868         id                      SERIAL  PRIMARY KEY,
869         code            TEXT    UNIQUE NOT NULL,
870         description     TEXT,
871         xpath           TEXT    NOT NULL,
872         remove          TEXT    NOT NULL DEFAULT '',
873         ident           BOOL    NOT NULL DEFAULT FALSE
874 );
875
876 CREATE TABLE vandelay.authority_queue (
877         queue_type      TEXT            NOT NULL DEFAULT 'authority' CHECK (queue_type = 'authority'),
878         CONSTRAINT vand_authority_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
879 ) INHERITS (vandelay.queue);
880 ALTER TABLE vandelay.authority_queue ADD PRIMARY KEY (id);
881
882 CREATE TABLE vandelay.queued_authority_record (
883         queue           INT     NOT NULL REFERENCES vandelay.authority_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
884         imported_as     INT     REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
885 ) INHERITS (vandelay.queued_record);
886 ALTER TABLE vandelay.queued_authority_record ADD PRIMARY KEY (id);
887
888 CREATE TABLE vandelay.queued_authority_record_attr (
889         id                      BIGSERIAL       PRIMARY KEY,
890         record          BIGINT          NOT NULL REFERENCES vandelay.queued_authority_record (id) DEFERRABLE INITIALLY DEFERRED,
891         field           INT                     NOT NULL REFERENCES vandelay.authority_attr_definition (id) DEFERRABLE INITIALLY DEFERRED,
892         attr_value      TEXT            NOT NULL
893 );
894
895 CREATE TABLE vandelay.authority_match (
896         id                              BIGSERIAL       PRIMARY KEY,
897         matched_attr    INT                     REFERENCES vandelay.queued_authority_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
898         queued_record   BIGINT          REFERENCES vandelay.queued_authority_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
899         eg_record               BIGINT          REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
900 );
901
902 CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
903 DECLARE
904     value   TEXT;
905     atype   TEXT;
906     adef    RECORD;
907 BEGIN
908     FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
909
910         SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
911         IF (value IS NOT NULL AND value <> '') THEN
912             INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
913         END IF;
914
915     END LOOP;
916
917     RETURN NULL;
918 END;
919 $$ LANGUAGE PLPGSQL;
920
921 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
922 BEGIN
923     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
924     IF TG_OP = 'UPDATE' THEN
925         RETURN NEW;
926     END IF;
927     RETURN OLD;
928 END;
929 $$ LANGUAGE PLPGSQL;
930
931 CREATE TRIGGER cleanup_authority_trigger
932     BEFORE UPDATE OR DELETE ON vandelay.queued_authority_record
933     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_authority_marc();
934
935 CREATE TRIGGER ingest_authority_trigger
936     AFTER INSERT OR UPDATE ON vandelay.queued_authority_record
937     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_authority_marc();
938
939 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
940 DECLARE
941     merge_profile   vandelay.merge_profile%ROWTYPE;
942     dyn_profile     vandelay.compile_profile%ROWTYPE;
943     source_marc     TEXT;
944     target_marc     TEXT;
945     eg_marc         TEXT;
946     v_marc          TEXT;
947     replace_rule    TEXT;
948     match_count     INT;
949 BEGIN
950
951     SELECT  b.marc INTO eg_marc
952       FROM  authority.record_entry b
953             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
954       LIMIT 1;
955
956     SELECT  q.marc INTO v_marc
957       FROM  vandelay.queued_record q
958             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
959       LIMIT 1;
960
961     IF eg_marc IS NULL OR v_marc IS NULL THEN
962         -- RAISE NOTICE 'no marc for vandelay or authority record';
963         RETURN FALSE;
964     END IF;
965
966     dyn_profile := vandelay.compile_profile( v_marc );
967
968     IF merge_profile_id IS NOT NULL THEN
969         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
970         IF FOUND THEN
971             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
972             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
973             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
974             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
975         END IF;
976     END IF;
977
978     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
979         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
980         RETURN FALSE;
981     END IF;
982
983     IF dyn_profile.replace_rule <> '' THEN
984         source_marc = v_marc;
985         target_marc = eg_marc;
986         replace_rule = dyn_profile.replace_rule;
987     ELSE
988         source_marc = eg_marc;
989         target_marc = v_marc;
990         replace_rule = dyn_profile.preserve_rule;
991     END IF;
992
993     UPDATE  authority.record_entry
994       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
995       WHERE id = eg_id;
996
997     IF FOUND THEN
998         UPDATE  vandelay.queued_authority_record
999           SET   imported_as = eg_id,
1000                 import_time = NOW()
1001           WHERE id = import_id;
1002         RETURN TRUE;
1003     END IF;
1004
1005     -- RAISE NOTICE 'update of authority.record_entry failed';
1006
1007     RETURN FALSE;
1008
1009 END;
1010 $$ LANGUAGE PLPGSQL;
1011
1012 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1013 DECLARE
1014     eg_id           BIGINT;
1015     match_count     INT;
1016 BEGIN
1017     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
1018
1019     IF match_count <> 1 THEN
1020         -- RAISE NOTICE 'not an exact match';
1021         RETURN FALSE;
1022     END IF;
1023
1024     SELECT  m.eg_record INTO eg_id
1025       FROM  vandelay.authority_match m
1026       WHERE m.queued_record = import_id
1027       LIMIT 1;
1028
1029     IF eg_id IS NULL THEN
1030         RETURN FALSE;
1031     END IF;
1032
1033     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
1034 END;
1035 $$ LANGUAGE PLPGSQL;
1036
1037 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
1038 DECLARE
1039     queued_record   vandelay.queued_authority_record%ROWTYPE;
1040     success         BOOL;
1041 BEGIN
1042
1043     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
1044         success := vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id );
1045
1046         IF success THEN
1047             RETURN NEXT queued_record.id;
1048         END IF;
1049
1050     END LOOP;
1051
1052     RETURN;
1053     
1054 END;
1055 $$ LANGUAGE PLPGSQL;
1056
1057 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
1058     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
1059 $$ LANGUAGE SQL;
1060
1061
1062 -- Vandelay (for importing and exporting records) 012.schema.vandelay.sql 
1063 --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)]');
1064 --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)]');
1065 --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]');
1066 --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]');
1067 --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$);
1068 --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$);
1069 --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]');
1070 --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);
1071 --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);
1072 --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);
1073 --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);
1074 --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]');
1075 --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$);
1076 --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]');
1077 --
1078 --INSERT INTO vandelay.import_item_attr_definition (
1079 --    owner, name, tag, owning_lib, circ_lib, location,
1080 --    call_number, circ_modifier, barcode, price, copy_number,
1081 --    circulate, ref, holdable, opac_visible, status
1082 --) VALUES (
1083 --    1,
1084 --    'Evergreen 852 export format',
1085 --    '852',
1086 --    '[@code = "b"][1]',
1087 --    '[@code = "b"][2]',
1088 --    'c',
1089 --    'j',
1090 --    'g',
1091 --    'p',
1092 --    'y',
1093 --    't',
1094 --    '[@code = "x" and text() = "circulating"]',
1095 --    '[@code = "x" and text() = "reference"]',
1096 --    '[@code = "x" and text() = "holdable"]',
1097 --    '[@code = "x" and text() = "visible"]',
1098 --    'z'
1099 --);
1100 --
1101 --INSERT INTO vandelay.import_item_attr_definition (
1102 --    owner,
1103 --    name,
1104 --    tag,
1105 --    owning_lib,
1106 --    location,
1107 --    call_number,
1108 --    circ_modifier,
1109 --    barcode,
1110 --    price,
1111 --    status
1112 --) VALUES (
1113 --    1,
1114 --    'Unicorn Import format -- 999',
1115 --    '999',
1116 --    'm',
1117 --    'l',
1118 --    'a',
1119 --    't',
1120 --    'i',
1121 --    'p',
1122 --    'k'
1123 --);
1124 --
1125 --INSERT INTO vandelay.authority_attr_definition ( code, description, xpath, ident ) VALUES ('rec_identifier','Identifier','//*[@tag="001"]', TRUE);
1126
1127 COMMIT;
1128