]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/012.schema.vandelay.sql
add a table to manage vandelay merge profiles
[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 NULL OR (add_spec IS NULL AND replace_spec IS NULL AND strip_spec IS NULL))
142 );
143
144 CREATE OR REPLACE FUNCTION vandelay.add_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
145
146     use MARC::Record;
147     use MARC::File::XML;
148
149     my $incumbent_xml = shift;
150     my $incoming_xml = shift;
151     my $field_spec = shift;
152     $field_spec =~ s/\s+//g;
153
154     my $incumbent_r = MARC::Record->new_from_xml( $incumbent_xml );
155     my $incoming_r = MARC::Record->new_from_xml( $incoming_xml );
156
157     return $incumbent_xml unless ($incumbent_r && $incoming_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 ($incoming_r->field( $f )) {
171                 for my $to_field ($incumbent_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 } $incoming_r->field( $f );
178             $incumbent_r->insert_fields_ordered( @new_fields );
179         }
180     }
181
182     $incumbent_xml = $incumbent_r->as_xml_record;
183     $incumbent_xml =~ s/^<\?.+?\?>$//mo;
184     $incumbent_xml =~ s/\n//sgo;
185     $incumbent_xml =~ s/>\s+</></sgo;
186
187     return $incumbent_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+//g;
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 ( incumbent_xml TEXT, incoming_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.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
235     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
236 $_$ LANGUAGE SQL;
237
238 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
239 DECLARE
240
241     owning_lib      TEXT;
242     circ_lib        TEXT;
243     call_number     TEXT;
244     copy_number     TEXT;
245     status          TEXT;
246     location        TEXT;
247     circulate       TEXT;
248     deposit         TEXT;
249     deposit_amount  TEXT;
250     ref             TEXT;
251     holdable        TEXT;
252     price           TEXT;
253     barcode         TEXT;
254     circ_modifier   TEXT;
255     circ_as_type    TEXT;
256     alert_message   TEXT;
257     opac_visible    TEXT;
258     pub_note        TEXT;
259     priv_note       TEXT;
260
261     attr_def        RECORD;
262     tmp_attr_set    RECORD;
263     attr_set        vandelay.import_item%ROWTYPE;
264
265     xpath           TEXT;
266
267 BEGIN
268
269     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
270
271     IF FOUND THEN
272
273         attr_set.definition := attr_def.id; 
274     
275         -- Build the combined XPath
276     
277         owning_lib :=
278             CASE
279                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
280                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
281                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
282             END;
283     
284         circ_lib :=
285             CASE
286                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
287                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
288                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
289             END;
290     
291         call_number :=
292             CASE
293                 WHEN attr_def.call_number IS NULL THEN 'null()'
294                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
295                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
296             END;
297     
298         copy_number :=
299             CASE
300                 WHEN attr_def.copy_number IS NULL THEN 'null()'
301                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
302                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
303             END;
304     
305         status :=
306             CASE
307                 WHEN attr_def.status IS NULL THEN 'null()'
308                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
309                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
310             END;
311     
312         location :=
313             CASE
314                 WHEN attr_def.location IS NULL THEN 'null()'
315                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
316                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
317             END;
318     
319         circulate :=
320             CASE
321                 WHEN attr_def.circulate IS NULL THEN 'null()'
322                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
323                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
324             END;
325     
326         deposit :=
327             CASE
328                 WHEN attr_def.deposit IS NULL THEN 'null()'
329                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
330                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
331             END;
332     
333         deposit_amount :=
334             CASE
335                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
336                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
337                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
338             END;
339     
340         ref :=
341             CASE
342                 WHEN attr_def.ref IS NULL THEN 'null()'
343                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
344                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
345             END;
346     
347         holdable :=
348             CASE
349                 WHEN attr_def.holdable IS NULL THEN 'null()'
350                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
351                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
352             END;
353     
354         price :=
355             CASE
356                 WHEN attr_def.price IS NULL THEN 'null()'
357                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
358                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
359             END;
360     
361         barcode :=
362             CASE
363                 WHEN attr_def.barcode IS NULL THEN 'null()'
364                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
365                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
366             END;
367     
368         circ_modifier :=
369             CASE
370                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
371                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
372                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
373             END;
374     
375         circ_as_type :=
376             CASE
377                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
378                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
379                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
380             END;
381     
382         alert_message :=
383             CASE
384                 WHEN attr_def.alert_message IS NULL THEN 'null()'
385                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
386                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
387             END;
388     
389         opac_visible :=
390             CASE
391                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
392                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
393                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
394             END;
395
396         pub_note :=
397             CASE
398                 WHEN attr_def.pub_note IS NULL THEN 'null()'
399                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
400                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
401             END;
402         priv_note :=
403             CASE
404                 WHEN attr_def.priv_note IS NULL THEN 'null()'
405                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
406                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
407             END;
408     
409     
410         xpath := 
411             owning_lib      || '|' || 
412             circ_lib        || '|' || 
413             call_number     || '|' || 
414             copy_number     || '|' || 
415             status          || '|' || 
416             location        || '|' || 
417             circulate       || '|' || 
418             deposit         || '|' || 
419             deposit_amount  || '|' || 
420             ref             || '|' || 
421             holdable        || '|' || 
422             price           || '|' || 
423             barcode         || '|' || 
424             circ_modifier   || '|' || 
425             circ_as_type    || '|' || 
426             alert_message   || '|' || 
427             pub_note        || '|' || 
428             priv_note       || '|' || 
429             opac_visible;
430
431         -- RAISE NOTICE 'XPath: %', xpath;
432         
433         FOR tmp_attr_set IN
434                 SELECT  *
435                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
436                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
437                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
438                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
439         LOOP
440     
441             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
442             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
443
444             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
445             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
446     
447             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
448             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
449             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
450     
451             SELECT  id INTO attr_set.location
452               FROM  asset.copy_location
453               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
454                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
455     
456             attr_set.circulate      :=
457                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
458                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
459
460             attr_set.deposit        :=
461                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
462                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
463
464             attr_set.holdable       :=
465                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
466                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
467
468             attr_set.opac_visible   :=
469                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
470                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
471
472             attr_set.ref            :=
473                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
474                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
475     
476             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
477             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
478             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
479     
480             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
481             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
482             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
483             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
484             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
485             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
486             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
487             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
488     
489             RETURN NEXT attr_set;
490     
491         END LOOP;
492     
493     END IF;
494
495 END;
496 $$ LANGUAGE PLPGSQL;
497
498
499 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
500 DECLARE
501     value   TEXT;
502     atype   TEXT;
503     adef    RECORD;
504 BEGIN
505     FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
506
507         SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
508         IF (value IS NOT NULL AND value <> '') THEN
509             INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
510         END IF;
511
512     END LOOP;
513
514     RETURN NULL;
515 END;
516 $$ LANGUAGE PLPGSQL;
517
518 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
519 DECLARE
520     attr_def    BIGINT;
521     item_data   vandelay.import_item%ROWTYPE;
522 BEGIN
523
524     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
525
526     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
527         INSERT INTO vandelay.import_item (
528             record,
529             definition,
530             owning_lib,
531             circ_lib,
532             call_number,
533             copy_number,
534             status,
535             location,
536             circulate,
537             deposit,
538             deposit_amount,
539             ref,
540             holdable,
541             price,
542             barcode,
543             circ_modifier,
544             circ_as_type,
545             alert_message,
546             pub_note,
547             priv_note,
548             opac_visible
549         ) VALUES (
550             NEW.id,
551             item_data.definition,
552             item_data.owning_lib,
553             item_data.circ_lib,
554             item_data.call_number,
555             item_data.copy_number,
556             item_data.status,
557             item_data.location,
558             item_data.circulate,
559             item_data.deposit,
560             item_data.deposit_amount,
561             item_data.ref,
562             item_data.holdable,
563             item_data.price,
564             item_data.barcode,
565             item_data.circ_modifier,
566             item_data.circ_as_type,
567             item_data.alert_message,
568             item_data.pub_note,
569             item_data.priv_note,
570             item_data.opac_visible
571         );
572     END LOOP;
573
574     RETURN NULL;
575 END;
576 $func$ LANGUAGE PLPGSQL;
577
578 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
579 DECLARE
580     attr    RECORD;
581     eg_rec  RECORD;
582 BEGIN
583     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
584
585                 -- All numbers? check for an id match
586                 IF (attr.attr_value ~ $r$^\d+$$r$) THEN
587                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
588                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
589                         END LOOP;
590                 END IF;
591
592                 -- Looks like an ISBN? check for an isbn match
593                 IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
594                 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
595                                 PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
596                                 IF FOUND THEN
597                                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
598                                 END IF;
599                         END LOOP;
600
601                         -- subcheck for isbn-as-tcn
602                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
603                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
604                 END LOOP;
605                 END IF;
606
607                 -- check for an OCLC tcn_value match
608                 IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
609                     FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
610                             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
611                 END LOOP;
612                 END IF;
613
614                 -- check for a direct tcn_value match
615         FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
616             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
617         END LOOP;
618
619                 -- check for a direct item barcode match
620         FOR eg_rec IN
621                 SELECT  DISTINCT b.*
622                   FROM  biblio.record_entry b
623                         JOIN asset.call_number cn ON (cn.record = b.id)
624                         JOIN asset.copy cp ON (cp.call_number = cn.id)
625                   WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
626         LOOP
627             INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
628         END LOOP;
629
630     END LOOP;
631
632     RETURN NULL;
633 END;
634 $func$ LANGUAGE PLPGSQL;
635
636 CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
637 BEGIN
638     DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
639     DELETE FROM vandelay.import_item WHERE record = OLD.id;
640
641     IF TG_OP = 'UPDATE' THEN
642         RETURN NEW;
643     END IF;
644     RETURN OLD;
645 END;
646 $$ LANGUAGE PLPGSQL;
647
648 CREATE TRIGGER cleanup_bib_trigger
649     BEFORE UPDATE OR DELETE ON vandelay.queued_bib_record
650     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_bib_marc();
651
652 CREATE TRIGGER ingest_bib_trigger
653     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
654     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_marc();
655
656 CREATE TRIGGER ingest_item_trigger
657     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
658     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_items();
659
660 CREATE TRIGGER zz_match_bibs_trigger
661     AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
662     FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
663
664
665 /* Authority stuff down here */
666 ---------------------------------------
667 CREATE TABLE vandelay.authority_attr_definition (
668         id                      SERIAL  PRIMARY KEY,
669         code            TEXT    UNIQUE NOT NULL,
670         description     TEXT,
671         xpath           TEXT    NOT NULL,
672         remove          TEXT    NOT NULL DEFAULT '',
673         ident           BOOL    NOT NULL DEFAULT FALSE
674 );
675
676 CREATE TABLE vandelay.authority_queue (
677         queue_type      TEXT            NOT NULL DEFAULT 'authority' CHECK (queue_type = 'authority'),
678         CONSTRAINT vand_authority_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
679 ) INHERITS (vandelay.queue);
680 ALTER TABLE vandelay.authority_queue ADD PRIMARY KEY (id);
681
682 CREATE TABLE vandelay.queued_authority_record (
683         queue           INT     NOT NULL REFERENCES vandelay.authority_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
684         imported_as     INT     REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
685 ) INHERITS (vandelay.queued_record);
686 ALTER TABLE vandelay.queued_authority_record ADD PRIMARY KEY (id);
687
688 CREATE TABLE vandelay.queued_authority_record_attr (
689         id                      BIGSERIAL       PRIMARY KEY,
690         record          BIGINT          NOT NULL REFERENCES vandelay.queued_authority_record (id) DEFERRABLE INITIALLY DEFERRED,
691         field           INT                     NOT NULL REFERENCES vandelay.authority_attr_definition (id) DEFERRABLE INITIALLY DEFERRED,
692         attr_value      TEXT            NOT NULL
693 );
694
695 CREATE TABLE vandelay.authority_match (
696         id                              BIGSERIAL       PRIMARY KEY,
697         matched_attr    INT                     REFERENCES vandelay.queued_authority_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
698         queued_record   BIGINT          REFERENCES vandelay.queued_authority_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
699         eg_record               BIGINT          REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
700 );
701
702 CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
703 DECLARE
704     value   TEXT;
705     atype   TEXT;
706     adef    RECORD;
707 BEGIN
708     FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
709
710         SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
711         IF (value IS NOT NULL AND value <> '') THEN
712             INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
713         END IF;
714
715     END LOOP;
716
717     RETURN NULL;
718 END;
719 $$ LANGUAGE PLPGSQL;
720
721 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
722 BEGIN
723     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
724     IF TG_OP = 'UPDATE' THEN
725         RETURN NEW;
726     END IF;
727     RETURN OLD;
728 END;
729 $$ LANGUAGE PLPGSQL;
730
731 CREATE TRIGGER cleanup_authority_trigger
732     BEFORE UPDATE OR DELETE ON vandelay.queued_authority_record
733     FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_authority_marc();
734
735 CREATE TRIGGER ingest_authority_trigger
736     AFTER INSERT OR UPDATE ON vandelay.queued_authority_record
737     FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_authority_marc();
738
739 -- Vandelay (for importing and exporting records) 012.schema.vandelay.sql 
740 --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)]');
741 --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)]');
742 --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]');
743 --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]');
744 --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$);
745 --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$);
746 --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]');
747 --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);
748 --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);
749 --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);
750 --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);
751 --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]');
752 --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$);
753 --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]');
754 --
755 --INSERT INTO vandelay.import_item_attr_definition (
756 --    owner, name, tag, owning_lib, circ_lib, location,
757 --    call_number, circ_modifier, barcode, price, copy_number,
758 --    circulate, ref, holdable, opac_visible, status
759 --) VALUES (
760 --    1,
761 --    'Evergreen 852 export format',
762 --    '852',
763 --    '[@code = "b"][1]',
764 --    '[@code = "b"][2]',
765 --    'c',
766 --    'j',
767 --    'g',
768 --    'p',
769 --    'y',
770 --    't',
771 --    '[@code = "x" and text() = "circulating"]',
772 --    '[@code = "x" and text() = "reference"]',
773 --    '[@code = "x" and text() = "holdable"]',
774 --    '[@code = "x" and text() = "visible"]',
775 --    'z'
776 --);
777 --
778 --INSERT INTO vandelay.import_item_attr_definition (
779 --    owner,
780 --    name,
781 --    tag,
782 --    owning_lib,
783 --    location,
784 --    call_number,
785 --    circ_modifier,
786 --    barcode,
787 --    price,
788 --    status
789 --) VALUES (
790 --    1,
791 --    'Unicorn Import format -- 999',
792 --    '999',
793 --    'm',
794 --    'l',
795 --    'a',
796 --    't',
797 --    'i',
798 --    'p',
799 --    'k'
800 --);
801 --
802 --INSERT INTO vandelay.authority_attr_definition ( code, description, xpath, ident ) VALUES ('rec_identifier','Identifier','//*[@tag="001"]', TRUE);
803
804 COMMIT;
805