LP#1750894 Workstation & Cascade settings
[working/Evergreen.git] / Open-ILS / src / sql / Pg / 002.schema.config.sql
1 /*
2  * Copyright (C) 2004-2008  Georgia Public Library Service
3  * Copyright (C) 2008-2011  Equinox Software, Inc.
4  * Mike Rylander <miker@esilibrary.com>
5  * Copyright (C) 2010 Merrimack Valley Library Consortium
6  * Jason Stephenson <jstephenson@mvlc.org>
7  * Copyright (C) 2010 Laurentian University
8  * Dan Scott <dscott@laurentian.ca>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  */
21
22
23
24 DROP SCHEMA IF EXISTS stats CASCADE;
25 DROP SCHEMA IF EXISTS config CASCADE;
26
27 BEGIN;
28 CREATE SCHEMA stats;
29
30 CREATE SCHEMA config;
31 COMMENT ON SCHEMA config IS $$
32 The config schema holds static configuration data for the
33 Evergreen installation.
34 $$;
35
36 CREATE TABLE config.internal_flag (
37     name    TEXT    PRIMARY KEY,
38     value   TEXT,
39     enabled BOOL    NOT NULL DEFAULT FALSE
40 );
41 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
42 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
43 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
44 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
45 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
46 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
47 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
48 INSERT INTO config.internal_flag (name) VALUES ('ingest.skip_browse_indexing');
49 INSERT INTO config.internal_flag (name) VALUES ('ingest.skip_search_indexing');
50 INSERT INTO config.internal_flag (name) VALUES ('ingest.skip_facet_indexing');
51 INSERT INTO config.internal_flag (name) VALUES ('ingest.skip_display_indexing');
52 INSERT INTO config.internal_flag (name) VALUES ('serial.rematerialize_on_same_holding_code');
53 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.preserve_on_delete');
54
55
56 CREATE TABLE config.global_flag (
57     label   TEXT    NOT NULL
58 ) INHERITS (config.internal_flag);
59 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
60
61 CREATE TABLE config.upgrade_log (
62     version         TEXT    PRIMARY KEY,
63     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
64     applied_to      TEXT
65 );
66
67 CREATE TABLE config.db_patch_dependencies (
68   db_patch      TEXT PRIMARY KEY,
69   supersedes    TEXT[],
70   deprecates    TEXT[]
71 );
72
73 CREATE OR REPLACE FUNCTION evergreen.array_overlap_check (/* field */) RETURNS TRIGGER AS $$
74 DECLARE
75     fld     TEXT;
76     cnt     INT;
77 BEGIN
78     fld := TG_ARGV[0];
79     EXECUTE 'SELECT COUNT(*) FROM '|| TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME ||' WHERE '|| fld ||' && ($1).'|| fld INTO cnt USING NEW;
80     IF cnt > 0 THEN
81         RAISE EXCEPTION 'Cannot insert duplicate array into field % of table %', fld, TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME;
82     END IF;
83     RETURN NEW;
84 END;
85 $$ LANGUAGE PLPGSQL;
86
87 CREATE TRIGGER no_overlapping_sups
88     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
89     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('supersedes');
90
91 CREATE TRIGGER no_overlapping_deps
92     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
93     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
94
95 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1115', :eg_version); -- miker/berick
96
97 CREATE TABLE config.bib_source (
98         id              SERIAL  PRIMARY KEY,
99         quality         INT     CHECK ( quality BETWEEN 0 AND 100 ),
100         source          TEXT    NOT NULL UNIQUE,
101         transcendant    BOOL    NOT NULL DEFAULT FALSE,
102         can_have_copies BOOL    NOT NULL DEFAULT TRUE
103 );
104 COMMENT ON TABLE config.bib_source IS $$
105 This is table is used to set up the relative "quality" of each
106 MARC source, such as OCLC.  Also identifies "transcendant" sources,
107 i.e., sources of bib records that should display in the OPAC
108 even if no copies or located URIs are attached. Also indicates if
109 the source is allowed to have actual copies on its bibs. Volumes
110 for targeted URIs are unaffected by this setting.
111 $$;
112
113 CREATE TABLE config.standing (
114         id              SERIAL  PRIMARY KEY,
115         value           TEXT    NOT NULL UNIQUE
116 );
117 COMMENT ON TABLE config.standing IS $$
118 Patron Standings
119
120 This table contains the values that can be applied to a patron
121 by a staff member.  These values should not be changed, other
122 than for translation, as the ID column is currently a "magic
123 number" in the source. :(
124 $$;
125
126 CREATE TABLE config.standing_penalty (
127         id                      SERIAL  PRIMARY KEY,
128         name            TEXT    NOT NULL UNIQUE,
129         label           TEXT    NOT NULL,
130         block_list      TEXT,
131         staff_alert     BOOL    NOT NULL DEFAULT FALSE,
132         org_depth       INTEGER,
133         ignore_proximity INTEGER
134 );
135
136 CREATE TABLE config.xml_transform (
137         name            TEXT    PRIMARY KEY,
138         namespace_uri   TEXT    NOT NULL,
139         prefix          TEXT    NOT NULL,
140         xslt            TEXT    NOT NULL
141 );
142
143 CREATE TABLE config.biblio_fingerprint (
144         id                      SERIAL  PRIMARY KEY,
145         name            TEXT    NOT NULL, 
146         xpath           TEXT    NOT NULL,
147     first_word  BOOL    NOT NULL DEFAULT FALSE,
148         format          TEXT    NOT NULL DEFAULT 'marcxml'
149 );
150
151 INSERT INTO config.biblio_fingerprint (name, xpath, format)
152     VALUES (
153         'Title',
154         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
155             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
156             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
157             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
158             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
159         'marcxml'
160     );
161
162 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
163     VALUES (
164         'Author',
165         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
166             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
167             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
168             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
169             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
170         'marcxml',
171         TRUE
172     );
173
174 INSERT INTO config.biblio_fingerprint (name, xpath, format)
175     VALUES (
176         'PartName',
177         '//mods32:mods/mods32:titleInfo/mods32:partName',
178         'mods32'
179     );
180
181 INSERT INTO config.biblio_fingerprint (name, xpath, format)
182     VALUES (
183         'PartNumber',
184         '//mods32:mods/mods32:titleInfo/mods32:partNumber',
185         'mods32'
186     );
187
188 CREATE TABLE config.metabib_class (
189     name     TEXT    PRIMARY KEY,
190     label    TEXT    NOT NULL UNIQUE,
191     buoyant  BOOL    DEFAULT FALSE NOT NULL,
192     restrict BOOL    DEFAULT FALSE NOT NULL,
193     combined BOOL    DEFAULT FALSE NOT NULL,
194     a_weight NUMERIC  DEFAULT 1.0 NOT NULL,
195     b_weight NUMERIC  DEFAULT 0.4 NOT NULL,
196     c_weight NUMERIC  DEFAULT 0.2 NOT NULL,
197     d_weight NUMERIC  DEFAULT 0.1 NOT NULL
198 );
199
200 CREATE TABLE config.metabib_field (
201         id              SERIAL  PRIMARY KEY,
202         field_class     TEXT    NOT NULL REFERENCES config.metabib_class (name),
203         name            TEXT    NOT NULL,
204         label           TEXT    NOT NULL,
205         xpath           TEXT,
206         weight          INT     NOT NULL DEFAULT 1,
207         format          TEXT    NOT NULL REFERENCES config.xml_transform (name) DEFAULT 'mods33',
208         search_field    BOOL    NOT NULL DEFAULT TRUE,
209         facet_field     BOOL    NOT NULL DEFAULT FALSE,
210         browse_field    BOOL    NOT NULL DEFAULT TRUE,
211         browse_xpath   TEXT,
212         browse_sort_xpath TEXT,
213         facet_xpath     TEXT,
214         display_xpath   TEXT,
215         authority_xpath TEXT,
216         joiner      TEXT,
217         restrict        BOOL    DEFAULT FALSE NOT NULL,
218     display_field BOOL NOT NULL DEFAULT TRUE
219 );
220 COMMENT ON TABLE config.metabib_field IS $$
221 XPath used for record indexing ingest
222
223 This table contains the XPath used to chop up MODS into its
224 indexable parts.  Each XPath entry is named and assigned to
225 a "class" of either title, subject, author, keyword, series
226 or identifier.
227 $$;
228
229 CREATE TABLE config.metabib_field_virtual_map (
230     id      SERIAL  PRIMARY KEY,
231     real    INT NOT NULL REFERENCES config.metabib_field (id),
232     virtual INT NOT NULL REFERENCES config.metabib_field (id),
233     weight  INT NOT NULL DEFAULT 1
234 );
235 COMMENT ON TABLE config.metabib_field_virtual_map IS $$
236 Maps between real (physically extracted) index definitions
237 and virtual (target sync, no required extraction of its own)
238 index definitions.
239
240 The virtual side may not extract any data of its own, but
241 will collect data from all of the real fields.  This reduces
242 extraction (ingest) overhead by eliminating duplcated extraction,
243 and allows for searching across novel combinations of fields, such
244 as names used as either subjects or authors.  By preserving this
245 mapping rather than defining duplicate extractions, information
246 about the originating, "real" index definitions can be used
247 in interesting ways, such as highlighting in search results.
248 $$;
249
250 CREATE UNIQUE INDEX config_metabib_field_class_name_idx ON config.metabib_field (field_class, name);
251
252 CREATE TABLE config.display_field_map (
253     name    TEXT   PRIMARY KEY,
254     field   INTEGER REFERENCES config.metabib_field (id),
255     multi   BOOLEAN DEFAULT FALSE
256 );
257
258 CREATE TABLE config.ts_config_list (
259         id                      TEXT PRIMARY KEY,
260         name            TEXT NOT NULL
261 );
262 COMMENT ON TABLE config.ts_config_list IS $$
263 Full Text Configs
264
265 A list of full text configs with names and descriptions.
266 $$;
267
268 CREATE TABLE config.metabib_class_ts_map (
269         id                              SERIAL PRIMARY KEY,
270         field_class             TEXT NOT NULL REFERENCES config.metabib_class (name),
271         ts_config               TEXT NOT NULL REFERENCES config.ts_config_list (id),
272         active                  BOOL NOT NULL DEFAULT TRUE,
273         index_weight    CHAR(1) NOT NULL DEFAULT 'C' CHECK (index_weight IN ('A','B','C','D')),
274         index_lang              TEXT NULL,
275         search_lang             TEXT NULL,
276         always                  BOOL NOT NULL DEFAULT true
277 );
278 COMMENT ON TABLE config.metabib_class_ts_map IS $$
279 Text Search Configs for metabib class indexing
280
281 This table contains text search config definitions for
282 storing index_vector values.
283 $$;
284
285 CREATE TABLE config.metabib_field_ts_map (
286         id                              SERIAL PRIMARY KEY,
287         metabib_field   INT NOT NULL REFERENCES config.metabib_field (id) ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
288         ts_config               TEXT NOT NULL REFERENCES config.ts_config_list (id),
289         active                  BOOL NOT NULL DEFAULT TRUE,
290         index_weight    CHAR(1) NOT NULL DEFAULT 'C' CHECK (index_weight IN ('A','B','C','D')),
291         index_lang              TEXT NULL,
292         search_lang             TEXT NULL
293 );
294 COMMENT ON TABLE config.metabib_field_ts_map IS $$
295 Text Search Configs for metabib field indexing
296
297 This table contains text search config definitions for
298 storing index_vector values.
299 $$;
300
301 CREATE TABLE config.metabib_search_alias (
302     alias       TEXT    PRIMARY KEY,
303     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
304     field       INT     REFERENCES config.metabib_field (id) ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED
305 );
306
307 CREATE TABLE config.non_cataloged_type (
308         id              SERIAL          PRIMARY KEY,
309         owning_lib      INT             NOT NULL, -- REFERENCES actor.org_unit (id),
310         name            TEXT            NOT NULL,
311         circ_duration   INTERVAL        NOT NULL DEFAULT '14 days'::INTERVAL,
312         in_house        BOOL            NOT NULL DEFAULT FALSE,
313         CONSTRAINT noncat_once_per_lib UNIQUE (owning_lib,name)
314 );
315 COMMENT ON TABLE config.non_cataloged_type IS $$
316 Types of valid non-cataloged items.
317 $$;
318
319 CREATE TABLE config.identification_type (
320         id              SERIAL  PRIMARY KEY,
321         name            TEXT    NOT NULL UNIQUE
322 );
323 COMMENT ON TABLE config.identification_type IS $$
324 Types of valid patron identification.
325
326 Each patron must display at least one valid form of identification
327 in order to get a library card.  This table lists those forms.
328 $$;
329
330 CREATE TABLE config.rule_circ_duration (
331         id              SERIAL          PRIMARY KEY,
332         name            TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
333         extended        INTERVAL        NOT NULL,
334         normal          INTERVAL        NOT NULL,
335         shrt            INTERVAL        NOT NULL,
336         max_renewals    INT             NOT NULL
337 );
338 COMMENT ON TABLE config.rule_circ_duration IS $$
339 Circulation Duration rules
340
341 Each circulation is given a duration based on one of these rules.
342 $$;
343
344 CREATE TABLE config.hard_due_date (
345     id                  SERIAL      PRIMARY KEY,
346     name                TEXT        NOT NULL UNIQUE,
347     ceiling_date        TIMESTAMPTZ NOT NULL,
348     forceto             BOOL        NOT NULL,
349     owner               INT         NOT NULL   -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
350 );
351
352 CREATE TABLE config.hard_due_date_values (
353     id                  SERIAL      PRIMARY KEY,
354     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
355                                     DEFERRABLE INITIALLY DEFERRED,
356     ceiling_date        TIMESTAMPTZ NOT NULL,
357     active_date         TIMESTAMPTZ NOT NULL
358 );
359
360 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
361 DECLARE
362     temp_value  config.hard_due_date_values%ROWTYPE;
363     updated     INT := 0;
364 BEGIN
365     FOR temp_value IN
366       SELECT  DISTINCT ON (hard_due_date) *
367         FROM  config.hard_due_date_values
368         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
369         ORDER BY hard_due_date, active_date DESC -- Latest (nearest to us) active time
370    LOOP
371         UPDATE  config.hard_due_date
372           SET   ceiling_date = temp_value.ceiling_date
373           WHERE id = temp_value.hard_due_date
374                 AND ceiling_date <> temp_value.ceiling_date -- Time is equal if we've already updated the chdd
375                 AND temp_value.ceiling_date >= NOW(); -- Don't update ceiling dates to the past
376
377         IF FOUND THEN
378             updated := updated + 1;
379         END IF;
380     END LOOP;
381
382     RETURN updated;
383 END;
384 $func$ LANGUAGE plpgsql;
385
386 CREATE TABLE config.rule_max_fine (
387     id          SERIAL          PRIMARY KEY,
388     name        TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
389     amount      NUMERIC(6,2)    NOT NULL,
390     is_percent  BOOL            NOT NULL DEFAULT FALSE
391 );
392 COMMENT ON TABLE config.rule_max_fine IS $$
393 Circulation Max Fine rules
394
395 Each circulation is given a maximum fine based on one of
396 these rules.
397 $$;
398
399 CREATE TABLE config.rule_recurring_fine (
400         id                      SERIAL          PRIMARY KEY,
401         name                    TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
402         high                    NUMERIC(6,2)    NOT NULL,
403         normal                  NUMERIC(6,2)    NOT NULL,
404         low                     NUMERIC(6,2)    NOT NULL,
405         recurrence_interval     INTERVAL        NOT NULL DEFAULT '1 day'::INTERVAL,
406     grace_period       INTERVAL         NOT NULL DEFAULT '1 day'::INTERVAL
407 );
408 COMMENT ON TABLE config.rule_recurring_fine IS $$
409 Circulation Recurring Fine rules
410
411 Each circulation is given a recurring fine amount based on one of
412 these rules.  Note that it is recommended to run the fine generator
413 (from cron) at least as frequently as the lowest recurrence interval
414 used by your circulation rules so that accrued fines will be up
415 to date.
416 $$;
417
418
419 CREATE TABLE config.rule_age_hold_protect (
420         id      SERIAL          PRIMARY KEY,
421         name    TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
422         age     INTERVAL        NOT NULL,
423         prox    INT             NOT NULL
424 );
425 COMMENT ON TABLE config.rule_age_hold_protect IS $$
426 Hold Item Age Protection rules
427
428 A hold request can only capture new(ish) items when they are
429 within a particular proximity of the pickup_lib of the request.
430 The proximity ('prox' column) is calculated by counting
431 the number of tree edges between the pickup_lib and either the
432 owning_lib or circ_lib of the copy that could fulfill the hold,
433 as determined by the distance_is_from_owner value of the hold matrix
434 rule controlling the hold request.
435 $$;
436
437 CREATE TABLE config.copy_status (
438         id              SERIAL  PRIMARY KEY,
439         name            TEXT    NOT NULL UNIQUE,
440         holdable        BOOL    NOT NULL DEFAULT FALSE,
441         opac_visible    BOOL    NOT NULL DEFAULT FALSE,
442     copy_active  BOOL    NOT NULL DEFAULT FALSE,
443         restrict_copy_delete BOOL         NOT NULL DEFAULT FALSE,
444     is_available  BOOL    NOT NULL DEFAULT FALSE
445 );
446 COMMENT ON TABLE config.copy_status IS $$
447 Copy Statuses
448
449 The available copy statuses, and whether a copy in that
450 status is available for hold request capture.  0 (zero) is
451 the only special number in this set, meaning that the item
452 is available for immediate checkout, and is counted as available
453 in the OPAC.
454
455 Statuses with an ID below 100 are not removable, and have special
456 meaning in the code.  Do not change them except to translate the
457 textual name.
458
459 You may add and remove statuses above 100, and these can be used
460 to remove items from normal circulation without affecting the rest
461 of the copy's values or its location.
462 $$;
463
464 CREATE TABLE config.net_access_level (
465         id      SERIAL          PRIMARY KEY,
466         name    TEXT            NOT NULL UNIQUE
467 );
468 COMMENT ON TABLE config.net_access_level IS $$
469 Patron Network Access level
470
471 This will be used to inform the in-library firewall of how much
472 internet access the using patron should be allowed.
473 $$;
474
475
476 CREATE TABLE config.remote_account (
477     id          SERIAL  PRIMARY KEY,
478     label       TEXT    NOT NULL,
479     host        TEXT    NOT NULL,   -- name or IP, :port optional
480     username    TEXT,               -- optional, since we could default to $USER
481     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
482     account     TEXT,               -- aka profile or FTP "account" command
483     path        TEXT,               -- aka directory
484     owner       INT     NOT NULL,   -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
485     last_activity TIMESTAMP WITH TIME ZONE
486 );
487
488 CREATE TABLE config.marc21_rec_type_map (
489     code        TEXT    PRIMARY KEY,
490     type_val    TEXT    NOT NULL,
491     blvl_val    TEXT    NOT NULL
492 );
493
494 CREATE TABLE config.marc21_ff_pos_map (
495     id          SERIAL  PRIMARY KEY,
496     fixed_field TEXT    NOT NULL,
497     tag         TEXT    NOT NULL,
498     rec_type    TEXT    NOT NULL,
499     start_pos   INT     NOT NULL,
500     length      INT     NOT NULL,
501     default_val TEXT    NOT NULL DEFAULT ' '
502 );
503
504 CREATE TABLE config.marc21_physical_characteristic_type_map (
505     ptype_key   TEXT    PRIMARY KEY,
506     label       TEXT    NOT NULL -- I18N
507 );
508
509 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
510     id          SERIAL  PRIMARY KEY,
511     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
512     subfield    TEXT    NOT NULL,
513     start_pos   INT     NOT NULL,
514     length      INT     NOT NULL,
515     label       TEXT    NOT NULL -- I18N
516 );
517
518 CREATE TABLE config.marc21_physical_characteristic_value_map (
519     id              SERIAL  PRIMARY KEY,
520     value           TEXT    NOT NULL,
521     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
522     label           TEXT    NOT NULL -- I18N
523 );
524
525
526 CREATE TABLE config.z3950_source (
527     name                TEXT    PRIMARY KEY,
528     label               TEXT    NOT NULL UNIQUE,
529     host                TEXT    NOT NULL,
530     port                INT     NOT NULL,
531     db                  TEXT    NOT NULL,
532     record_format       TEXT    NOT NULL DEFAULT 'FI',
533     transmission_format TEXT    NOT NULL DEFAULT 'usmarc',
534     auth                BOOL    NOT NULL DEFAULT TRUE,
535     use_perm            INT     -- REFERENCES permission.perm_list (id)
536 );
537
538 COMMENT ON TABLE config.z3950_source IS $$
539 Z39.50 Sources
540
541 Each row in this table represents a database searchable via Z39.50.
542 $$;
543
544 COMMENT ON COLUMN config.z3950_source.record_format IS $$
545 Z39.50 element set.
546 $$;
547
548 COMMENT ON COLUMN config.z3950_source.transmission_format IS $$
549 Z39.50 preferred record syntax..
550 $$;
551
552 COMMENT ON COLUMN config.z3950_source.use_perm IS $$
553 If set, this permission is required for the source to be listed in the staff
554 client Z39.50 interface.  Similar to permission.grp_tree.application_perm.
555 $$;
556
557 CREATE TABLE config.z3950_attr (
558     id          SERIAL  PRIMARY KEY,
559     source      TEXT    NOT NULL REFERENCES config.z3950_source (name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
560     name        TEXT    NOT NULL,
561     label       TEXT    NOT NULL,
562     code        INT     NOT NULL,
563     format      INT     NOT NULL,
564     truncation  INT     NOT NULL DEFAULT 0,
565     CONSTRAINT z_code_format_once_per_source UNIQUE (code,format,source)
566 );
567
568 CREATE TABLE config.z3950_source_credentials (
569     id SERIAL PRIMARY KEY,
570     owner INTEGER NOT NULL, -- REFERENCES actor.org_unit(id),
571     source TEXT NOT NULL REFERENCES config.z3950_source(name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
572     username TEXT,
573     password TEXT,
574     CONSTRAINT czsc_source_once_per_lib UNIQUE (source, owner)
575 );
576
577 CREATE TABLE config.i18n_locale (
578     code        TEXT    PRIMARY KEY,
579     marc_code   TEXT    NOT NULL, -- should exist in config.coded_value_map WHERE ctype = 'item_lang'
580     name        TEXT    UNIQUE NOT NULL,
581     description TEXT,
582     rtl         BOOL    NOT NULL DEFAULT FALSE
583 );
584
585 CREATE TABLE config.i18n_core (
586     id              BIGSERIAL   PRIMARY KEY,
587     fq_field        TEXT        NOT NULL,
588     identity_value  TEXT        NOT NULL,
589     translation     TEXT        NOT NULL    REFERENCES config.i18n_locale (code) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
590     string          TEXT        NOT NULL
591 );
592
593 CREATE UNIQUE INDEX i18n_identity ON config.i18n_core (fq_field,identity_value,translation);
594
595 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
596 BEGIN
597
598     EXECUTE $$
599         UPDATE  config.i18n_core
600           SET   identity_value = $$ || quote_literal(new_ident) || $$ 
601           WHERE fq_field LIKE '$$ || hint || $$.%' 
602                 AND identity_value = $$ || quote_literal(old_ident) || $$::TEXT;$$;
603
604     RETURN;
605
606 END;
607 $_$ LANGUAGE PLPGSQL;
608
609 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
610 BEGIN
611     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
612     RETURN NEW;
613 END;
614 $_$ LANGUAGE PLPGSQL;
615
616 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
617 BEGIN
618     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
619     RETURN NEW;
620 END;
621 $_$ LANGUAGE PLPGSQL;
622
623 CREATE TABLE config.billing_type (
624     id              SERIAL  PRIMARY KEY,
625     name            TEXT    NOT NULL,
626     owner           INT     NOT NULL, -- REFERENCES actor.org_unit (id)
627     default_price   NUMERIC(6,2),
628     CONSTRAINT billing_type_once_per_lib UNIQUE (name, owner)
629 );
630
631 CREATE TABLE config.settings_group (
632     name    TEXT PRIMARY KEY,
633     label   TEXT UNIQUE NOT NULL -- I18N
634 );
635
636 CREATE TABLE config.org_unit_setting_type (
637     name            TEXT    PRIMARY KEY,
638     label           TEXT    UNIQUE NOT NULL,
639     grp             TEXT    REFERENCES config.settings_group (name),
640     description     TEXT,
641     datatype        TEXT    NOT NULL DEFAULT 'string',
642     fm_class        TEXT,
643     view_perm       INT,
644     update_perm     INT,
645     --
646     -- define valid datatypes
647     --
648     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
649     ( 'bool', 'integer', 'float', 'currency', 'interval',
650       'date', 'string', 'object', 'array', 'link' ) ),
651     --
652     -- fm_class is meaningful only for 'link' datatype
653     --
654     CONSTRAINT coust_no_empty_link CHECK
655     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
656       ( datatype <> 'link' AND fm_class IS NULL ) )
657 );
658
659 CREATE TABLE config.usr_setting_type (
660
661     name TEXT PRIMARY KEY,
662     opac_visible BOOL NOT NULL DEFAULT FALSE,
663     label TEXT UNIQUE NOT NULL,
664     description TEXT,
665     grp             TEXT    REFERENCES config.settings_group (name),
666     datatype TEXT NOT NULL DEFAULT 'string',
667     fm_class TEXT,
668     reg_default TEXT,
669
670     --
671     -- define valid datatypes
672     --
673     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
674     ( 'bool', 'integer', 'float', 'currency', 'interval',
675         'date', 'string', 'object', 'array', 'link' ) ),
676
677     --
678     -- fm_class is meaningful only for 'link' datatype
679     --
680     CONSTRAINT coust_no_empty_link CHECK
681     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
682         ( datatype <> 'link' AND fm_class IS NULL ) )
683
684 );
685
686 CREATE TABLE config.workstation_setting_type (
687     name            TEXT    PRIMARY KEY,
688     label           TEXT    UNIQUE NOT NULL,
689     grp             TEXT    REFERENCES config.settings_group (name),
690     description     TEXT,
691     datatype        TEXT    NOT NULL DEFAULT 'string',
692     fm_class        TEXT,
693     --
694     -- define valid datatypes
695     --
696     CONSTRAINT cwst_valid_datatype CHECK ( datatype IN
697     ( 'bool', 'integer', 'float', 'currency', 'interval',
698       'date', 'string', 'object', 'array', 'link' ) ),
699     --
700     -- fm_class is meaningful only for 'link' datatype
701     --
702     CONSTRAINT cwst_no_empty_link CHECK
703     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
704       ( datatype <> 'link' AND fm_class IS NULL ) )
705 );
706
707 -- Prevent setting types from being both user and workstation settings.
708 CREATE OR REPLACE FUNCTION config.setting_is_user_or_ws()
709 RETURNS TRIGGER AS $FUNC$
710 BEGIN
711
712     IF TG_TABLE_NAME = 'usr_setting_type' THEN
713         PERFORM TRUE FROM config.workstation_setting_type cwst
714             WHERE cwst.name = NEW.name;
715         IF NOT FOUND THEN
716             RETURN NULL;
717         END IF;
718     END IF;
719
720     IF TG_TABLE_NAME = 'workstation_setting_type' THEN
721         PERFORM TRUE FROM config.usr_setting_type cust
722             WHERE cust.name = NEW.name;
723         IF NOT FOUND THEN
724             RETURN NULL;
725         END IF;
726     END IF;
727
728     RAISE EXCEPTION 
729         '% Cannot be used as both a user setting and a workstation setting.', 
730         NEW.name;
731 END;
732 $FUNC$ LANGUAGE PLPGSQL STABLE;
733
734 CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws
735   AFTER INSERT OR UPDATE ON config.usr_setting_type
736   FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws();
737
738 CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws
739   AFTER INSERT OR UPDATE ON config.workstation_setting_type
740   FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws();
741
742
743
744 -- Some handy functions, based on existing ones, to provide optional ingest normalization
745
746 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
747         SELECT SUBSTRING($1,$2);
748 $func$ LANGUAGE SQL STRICT IMMUTABLE;
749
750 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
751         SELECT SUBSTRING($1,1,$2);
752 $func$ LANGUAGE SQL STRICT IMMUTABLE;
753
754 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
755         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
756 $func$ LANGUAGE SQL STRICT IMMUTABLE;
757
758 CREATE OR REPLACE FUNCTION public.approximate_date( TEXT, TEXT ) RETURNS TEXT AS $func$
759         SELECT REGEXP_REPLACE( $1, E'\\D', $2, 'g' );
760 $func$ LANGUAGE SQL STRICT IMMUTABLE;
761
762 CREATE OR REPLACE FUNCTION public.approximate_low_date( TEXT ) RETURNS TEXT AS $func$
763         SELECT approximate_date( $1, '0');
764 $func$ LANGUAGE SQL STRICT IMMUTABLE;
765
766 CREATE OR REPLACE FUNCTION public.approximate_high_date( TEXT ) RETURNS TEXT AS $func$
767         SELECT approximate_date( $1, '9');
768 $func$ LANGUAGE SQL STRICT IMMUTABLE;
769
770 CREATE OR REPLACE FUNCTION public.content_or_null( TEXT ) RETURNS TEXT AS $func$
771         SELECT CASE WHEN $1 ~ E'^\\s*$' THEN NULL ELSE $1 END
772 $func$ LANGUAGE SQL STRICT IMMUTABLE;
773
774 CREATE OR REPLACE FUNCTION public.integer_or_null( TEXT ) RETURNS TEXT AS $func$
775         SELECT CASE WHEN $1 ~ E'^\\d+$' THEN $1 ELSE NULL END
776 $func$ LANGUAGE SQL STRICT IMMUTABLE;
777
778 CREATE OR REPLACE FUNCTION public.force_to_isbn13( TEXT ) RETURNS TEXT AS $func$
779     use Business::ISBN;
780     use strict;
781     use warnings;
782
783     # Find the first ISBN, force it to ISBN13 and return it
784
785     my $input = shift;
786
787     foreach my $word (split(/\s/, $input)) {
788         my $isbn = Business::ISBN->new($word);
789
790         # First check the checksum; if it is not valid, fix it and add the original
791         # bad-checksum ISBN to the output
792         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
793             $isbn->fix_checksum();
794         }
795
796         # If we now have a valid ISBN, force it to ISBN13 and return it
797         return $isbn->as_isbn13->isbn if ($isbn && $isbn->is_valid());
798     }
799     return undef;
800 $func$ LANGUAGE PLPERLU;
801
802 COMMENT ON FUNCTION public.force_to_isbn13(TEXT) IS $$
803 Inspired by translate_isbn1013
804
805 The force_to_isbn13 function takes an input ISBN and returns the ISBN13
806 version without hypens and with a repaired checksum if the checksum was bad
807 $$;
808
809
810 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
811     use Business::ISBN;
812     use strict;
813     use warnings;
814
815     # For each ISBN found in a single string containing a set of ISBNs:
816     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
817     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
818
819     my $input = shift;
820     my $output = '';
821
822     foreach my $word (split(/\s/, $input)) {
823         my $isbn = Business::ISBN->new($word);
824
825         # First check the checksum; if it is not valid, fix it and add the original
826         # bad-checksum ISBN to the output
827         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
828             $output .= $isbn->isbn() . " ";
829             $isbn->fix_checksum();
830         }
831
832         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
833         # and add the normalized original ISBN to the output
834         if ($isbn && $isbn->is_valid()) {
835             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
836             $output .= $isbn->isbn . " ";
837
838             # If we successfully converted the ISBN to its counterpart, add the
839             # converted ISBN to the output as well
840             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
841         }
842     }
843     return $output if $output;
844
845     # If there were no valid ISBNs, just return the raw input
846     return $input;
847 $func$ LANGUAGE PLPERLU;
848
849 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
850 The translate_isbn1013 function takes an input ISBN and returns the
851 following in a single space-delimited string if the input ISBN is valid:
852   - The normalized input ISBN (hyphens stripped)
853   - The normalized input ISBN with a fixed checksum if the checksum was bad
854   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
855 $$;
856
857 -- And ... a table in which to register them
858
859 CREATE TABLE config.index_normalizer (
860         id              SERIAL  PRIMARY KEY,
861         name            TEXT    UNIQUE NOT NULL,
862         description     TEXT,
863         func            TEXT    NOT NULL,
864         param_count     INT     NOT NULL DEFAULT 0
865 );
866
867 CREATE TABLE config.metabib_field_index_norm_map (
868         id      SERIAL  PRIMARY KEY,
869         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
870         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
871         params  TEXT,
872         pos     INT     NOT NULL DEFAULT 0
873 );
874
875 CREATE TABLE config.record_attr_definition (
876     name        TEXT    PRIMARY KEY,
877     label       TEXT    NOT NULL, -- I18N
878     description TEXT,
879     multi       BOOL    NOT NULL DEFAULT TRUE,  -- will store all values from a record
880     filter      BOOL    NOT NULL DEFAULT TRUE,  -- becomes QP filter if true
881     sorter      BOOL    NOT NULL DEFAULT FALSE, -- becomes QP sort() axis if true
882     composite   BOOL    NOT NULL DEFAULT FALSE, -- its values are derived from others
883
884 -- For pre-extracted fields. Takes the first occurance, uses naive subfield ordering
885     tag         TEXT, -- LIKE format
886     sf_list     TEXT, -- pile-o-values, like 'abcd' for a and b and c and d
887
888 -- This is used for both tag/sf and xpath entries
889     joiner      TEXT,
890
891 -- For xpath-extracted attrs
892     xpath       TEXT,
893     format      TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
894     start_pos   INT,
895     string_len  INT,
896
897 -- For fixed fields
898     fixed_field TEXT, -- should exist in config.marc21_ff_pos_map.fixed_field
899
900 -- For phys-char fields
901     phys_char_sf    INT REFERENCES config.marc21_physical_characteristic_subfield_map (id),
902
903 -- Source of vocabulary terms for this record attribute;
904 -- typically will be a URI referring to a SKOS vocabulary
905     vocabulary  TEXT
906 );
907
908 CREATE TABLE config.record_attr_index_norm_map (
909     id      SERIAL  PRIMARY KEY,
910     attr    TEXT    NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
911     norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
912     params  TEXT,
913     pos     INT     NOT NULL DEFAULT 0
914 );
915
916 CREATE TABLE config.coded_value_map (
917     id              SERIAL  PRIMARY KEY,
918     ctype           TEXT    NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
919     code            TEXT    NOT NULL,
920     value           TEXT    NOT NULL,
921     description     TEXT,
922     opac_visible    BOOL    NOT NULL DEFAULT TRUE, -- For TPac selectors
923     search_label    TEXT,
924     is_simple       BOOL    NOT NULL DEFAULT FALSE,
925     concept_uri     TEXT    -- URI expressing the SKOS concept that the
926                             -- coded value represents
927 );
928
929 CREATE INDEX config_coded_value_map_ctype_idx ON config.coded_value_map (ctype);
930
931 CREATE VIEW config.language_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_lang';
932 CREATE VIEW config.bib_level_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'bib_level';
933 CREATE VIEW config.item_form_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_form';
934 CREATE VIEW config.item_type_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_type';
935 CREATE VIEW config.lit_form_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'lit_form';
936 CREATE VIEW config.audience_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'audience';
937 CREATE VIEW config.videorecording_format_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'vr_format';
938
939 CREATE OR REPLACE FUNCTION config.update_coded_value_map(in_ctype TEXT, in_code TEXT, in_value TEXT, in_description TEXT DEFAULT NULL, in_opac_visible BOOL DEFAULT NULL, in_search_label TEXT DEFAULT NULL, in_is_simple BOOL DEFAULT NULL, add_only BOOL DEFAULT FALSE) RETURNS VOID AS $f$
940 DECLARE
941     current_row config.coded_value_map%ROWTYPE;
942 BEGIN
943     -- Look for a current value
944     SELECT INTO current_row * FROM config.coded_value_map WHERE ctype = in_ctype AND code = in_code;
945     -- If we have one..
946     IF FOUND AND NOT add_only THEN
947         -- Update anything we were handed
948         current_row.value := COALESCE(current_row.value, in_value);
949         current_row.description := COALESCE(current_row.description, in_description);
950         current_row.opac_visible := COALESCE(current_row.opac_visible, in_opac_visible);
951         current_row.search_label := COALESCE(current_row.search_label, in_search_label);
952         current_row.is_simple := COALESCE(current_row.is_simple, in_is_simple);
953         UPDATE config.coded_value_map
954             SET
955                 value = current_row.value,
956                 description = current_row.description,
957                 opac_visible = current_row.opac_visible,
958                 search_label = current_row.search_label,
959                 is_simple = current_row.is_simple
960             WHERE id = current_row.id;
961     ELSE
962         INSERT INTO config.coded_value_map(ctype, code, value, description, opac_visible, search_label, is_simple) VALUES
963             (in_ctype, in_code, in_value, in_description, COALESCE(in_opac_visible, TRUE), in_search_label, COALESCE(in_is_simple, FALSE));
964     END IF;
965 END;
966 $f$ LANGUAGE PLPGSQL;
967
968 CREATE TABLE config.composite_attr_entry_definition(
969     coded_value INT  PRIMARY KEY NOT NULL REFERENCES config.coded_value_map (id) ON UPDATE CASCADE ON DELETE CASCADE,
970     definition  TEXT    NOT NULL -- JSON
971 );
972
973 -- List applied db patches that are deprecated by (and block the application of) my_db_patch
974 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecates ( my_db_patch TEXT ) RETURNS SETOF evergreen.patch AS $$
975     SELECT  DISTINCT l.version
976       FROM  config.upgrade_log l
977             JOIN config.db_patch_dependencies d ON (l.version = ANY(d.deprecates))
978       WHERE d.db_patch = $1
979 $$ LANGUAGE SQL;
980
981 -- List applied db patches that are superseded by (and block the application of) my_db_patch
982 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_supersedes ( my_db_patch TEXT ) RETURNS SETOF evergreen.patch AS $$
983     SELECT  DISTINCT l.version
984       FROM  config.upgrade_log l
985             JOIN config.db_patch_dependencies d ON (l.version = ANY(d.supersedes))
986       WHERE d.db_patch = $1
987 $$ LANGUAGE SQL;
988
989 -- List applied db patches that deprecates (and block the application of) my_db_patch
990 CREATE FUNCTION evergreen.upgrade_list_applied_deprecated ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
991     SELECT  db_patch
992       FROM  config.db_patch_dependencies
993       WHERE ARRAY[$1]::TEXT[] && deprecates
994 $$ LANGUAGE SQL;
995
996 -- List applied db patches that supersedes (and block the application of) my_db_patch
997 CREATE FUNCTION evergreen.upgrade_list_applied_superseded ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
998     SELECT  db_patch
999       FROM  config.db_patch_dependencies
1000       WHERE ARRAY[$1]::TEXT[] && supersedes
1001 $$ LANGUAGE SQL;
1002
1003 -- Make sure that no deprecated or superseded db patches are currently applied
1004 CREATE OR REPLACE FUNCTION evergreen.upgrade_verify_no_dep_conflicts ( my_db_patch TEXT ) RETURNS BOOL AS $$
1005     SELECT  COUNT(*) = 0
1006       FROM  (SELECT * FROM evergreen.upgrade_list_applied_deprecates( $1 )
1007                 UNION
1008              SELECT * FROM evergreen.upgrade_list_applied_supersedes( $1 )
1009                 UNION
1010              SELECT * FROM evergreen.upgrade_list_applied_deprecated( $1 )
1011                 UNION
1012              SELECT * FROM evergreen.upgrade_list_applied_superseded( $1 ))x
1013 $$ LANGUAGE SQL;
1014
1015 -- Raise an exception if there are, in fact, dep/sup conflict
1016 CREATE OR REPLACE FUNCTION evergreen.upgrade_deps_block_check ( my_db_patch TEXT, my_applied_to TEXT ) RETURNS BOOL AS $$
1017 DECLARE 
1018     deprecates TEXT;
1019     supersedes TEXT;
1020 BEGIN
1021     IF NOT evergreen.upgrade_verify_no_dep_conflicts( my_db_patch ) THEN
1022         SELECT  STRING_AGG(patch, ', ') INTO deprecates FROM evergreen.upgrade_list_applied_deprecates(my_db_patch);
1023         SELECT  STRING_AGG(patch, ', ') INTO supersedes FROM evergreen.upgrade_list_applied_supersedes(my_db_patch);
1024         RAISE EXCEPTION '
1025 Upgrade script % can not be applied:
1026   applied deprecated scripts %
1027   applied superseded scripts %
1028   deprecated by %
1029   superseded by %',
1030             my_db_patch,
1031             (SELECT ARRAY_AGG(patch) FROM evergreen.upgrade_list_applied_deprecates(my_db_patch)),
1032             (SELECT ARRAY_AGG(patch) FROM evergreen.upgrade_list_applied_supersedes(my_db_patch)),
1033             evergreen.upgrade_list_applied_deprecated(my_db_patch),
1034             evergreen.upgrade_list_applied_superseded(my_db_patch);
1035     END IF;
1036
1037     INSERT INTO config.upgrade_log (version, applied_to) VALUES (my_db_patch, my_applied_to);
1038     RETURN TRUE;
1039 END;
1040 $$ LANGUAGE PLPGSQL;
1041
1042 CREATE TABLE config.barcode_completion (
1043     id          SERIAL PRIMARY KEY,
1044     active      BOOL NOT NULL DEFAULT true,
1045     org_unit    INT NOT NULL, -- REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED,
1046     prefix      TEXT,
1047     suffix      TEXT,
1048     length      INT NOT NULL DEFAULT 0,
1049     padding     TEXT,
1050     padding_end BOOL NOT NULL DEFAULT false,
1051     asset       BOOL NOT NULL DEFAULT true,
1052     actor       BOOL NOT NULL DEFAULT true
1053 );
1054
1055 CREATE TYPE evergreen.barcode_set AS (type TEXT, id BIGINT, barcode TEXT);
1056
1057 -- Add support for logging, only keep the most recent five rows for each category. 
1058
1059
1060 CREATE TABLE config.org_unit_setting_type_log (
1061     id              BIGSERIAL   PRIMARY KEY,
1062     date_applied    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1063     org             INT,   --REFERENCES actor.org_unit (id),
1064     original_value  TEXT,
1065     new_value       TEXT,
1066     field_name      TEXT      REFERENCES config.org_unit_setting_type (name) DEFERRABLE INITIALLY DEFERRED
1067 );
1068
1069 COMMENT ON TABLE config.org_unit_setting_type_log IS $$
1070 Org Unit setting Logs
1071
1072 This table contains the most recent changes to each setting 
1073 in actor.org_unit_setting, allowing for mistakes to be undone.
1074 This is NOT meant to be an auditor, but rather an undo/redo.
1075 $$;
1076
1077 CREATE OR REPLACE FUNCTION evergreen.limit_oustl() RETURNS TRIGGER AS $oustl_limit$
1078     BEGIN
1079         -- Only keeps the most recent five settings changes.
1080         DELETE FROM config.org_unit_setting_type_log WHERE field_name = NEW.field_name AND org = NEW.org AND date_applied NOT IN 
1081         (SELECT date_applied FROM config.org_unit_setting_type_log WHERE field_name = NEW.field_name AND org = NEW.org ORDER BY date_applied DESC LIMIT 4);
1082         
1083         IF (TG_OP = 'UPDATE') THEN
1084             RETURN NEW;
1085         ELSIF (TG_OP = 'INSERT') THEN
1086             RETURN NEW;
1087         END IF;
1088         RETURN NULL;
1089     END;
1090 $oustl_limit$ LANGUAGE plpgsql;
1091
1092 CREATE TRIGGER limit_logs_oust
1093     BEFORE INSERT OR UPDATE ON config.org_unit_setting_type_log
1094     FOR EACH ROW EXECUTE PROCEDURE limit_oustl();
1095
1096 CREATE TABLE config.sms_carrier (
1097     id              SERIAL PRIMARY KEY,
1098     region          TEXT,
1099     name            TEXT,
1100     email_gateway   TEXT,
1101     active          BOOLEAN DEFAULT TRUE
1102 );
1103
1104 CREATE TYPE config.usr_activity_group AS ENUM ('authen','authz','circ','hold','search');
1105
1106 CREATE TABLE config.usr_activity_type (
1107     id          SERIAL                      PRIMARY KEY, 
1108     ewho        TEXT,
1109     ewhat       TEXT,
1110     ehow        TEXT,
1111     label       TEXT                        NOT NULL, -- i18n
1112     egroup      config.usr_activity_group   NOT NULL,
1113     enabled     BOOL                        NOT NULL DEFAULT TRUE,
1114     transient   BOOL                        NOT NULL DEFAULT TRUE,
1115     CONSTRAINT  one_of_wwh CHECK (COALESCE(ewho,ewhat,ehow) IS NOT NULL)
1116 );
1117
1118 CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type 
1119     (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,''));
1120
1121 CREATE TABLE config.filter_dialog_interface (
1122     key         TEXT                        PRIMARY KEY,
1123     description TEXT
1124 );  
1125
1126 CREATE TABLE config.filter_dialog_filter_set (
1127     id          SERIAL                      PRIMARY KEY,
1128     name        TEXT                        NOT NULL,
1129     owning_lib  INT                         NOT NULL, -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
1130     creator     INT                         NOT NULL, -- REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
1131     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
1132     interface   TEXT                        NOT NULL REFERENCES config.filter_dialog_interface (key) DEFERRABLE INITIALLY DEFERRED,
1133     filters     TEXT                        NOT NULL, -- CHECK (evergreen.is_json(filters))
1134     CONSTRAINT cfdfs_name_once_per_lib UNIQUE (name, owning_lib)
1135 );
1136
1137 CREATE TABLE config.best_hold_order(
1138     id          SERIAL      PRIMARY KEY,
1139     name        TEXT        UNIQUE,   -- i18n
1140     pprox       INT, -- copy capture <-> pickup lib prox
1141     hprox       INT, -- copy circ lib <-> request lib prox
1142     aprox       INT, -- copy circ lib <-> pickup lib ADJUSTED prox on ahcm
1143     approx      INT, -- copy capture <-> pickup lib ADJUSTED prox from function
1144     priority    INT, -- group hold priority
1145     cut         INT, -- cut-in-line
1146     depth       INT, -- selection depth
1147     htime       INT, -- time since last home-lib circ exceeds org-unit setting
1148     rtime       INT, -- request time
1149     shtime      INT  -- time since copy last trip home exceeds org-unit setting
1150 );
1151
1152 -- At least one of these columns must contain a non-null value
1153 ALTER TABLE config.best_hold_order ADD CHECK ((
1154     pprox IS NOT NULL OR
1155     hprox IS NOT NULL OR
1156     aprox IS NOT NULL OR
1157     priority IS NOT NULL OR
1158     cut IS NOT NULL OR
1159     depth IS NOT NULL OR
1160     htime IS NOT NULL OR
1161     rtime IS NOT NULL
1162 ));
1163
1164 CREATE OR REPLACE FUNCTION 
1165     evergreen.z3950_attr_name_is_valid() RETURNS TRIGGER AS $func$
1166     BEGIN
1167
1168       PERFORM * FROM config.z3950_attr WHERE name = NEW.z3950_attr_type;
1169
1170       IF FOUND THEN
1171         RETURN NULL;
1172       END IF;
1173
1174       RAISE EXCEPTION '% is not a valid Z39.50 attribute type', NEW.z3950_attr_type;
1175
1176     END;
1177 $func$ LANGUAGE PLPGSQL STABLE;
1178
1179 COMMENT ON FUNCTION evergreen.z3950_attr_name_is_valid() IS $$
1180 Used by a config.z3950_index_field_map constraint trigger
1181 to verify z3950_attr_type maps.
1182 $$;
1183
1184 -- drop these in down here since they reference config.metabib_field
1185 -- and config.record_attr_definition
1186 CREATE TABLE config.z3950_index_field_map (
1187     id              SERIAL  PRIMARY KEY,
1188     label           TEXT    NOT NULL, -- i18n
1189     metabib_field   INTEGER REFERENCES config.metabib_field(id) ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
1190     record_attr     TEXT    REFERENCES config.record_attr_definition(name),
1191     z3950_attr      INTEGER REFERENCES config.z3950_attr(id),
1192     z3950_attr_type TEXT,-- REFERENCES config.z3950_attr(name)
1193     CONSTRAINT metabib_field_or_record_attr CHECK (
1194         metabib_field IS NOT NULL OR 
1195         record_attr IS NOT NULL
1196     ),
1197     CONSTRAINT attr_or_attr_type CHECK (
1198         z3950_attr IS NOT NULL OR 
1199         z3950_attr_type IS NOT NULL
1200     )
1201 );
1202
1203 CREATE CONSTRAINT TRIGGER valid_z3950_attr_type AFTER INSERT OR UPDATE ON config.z3950_index_field_map
1204     DEFERRABLE INITIALLY DEFERRED FOR EACH ROW WHEN (NEW.z3950_attr_type IS NOT NULL)
1205     EXECUTE PROCEDURE evergreen.z3950_attr_name_is_valid();
1206
1207 CREATE TABLE config.marc_format (
1208     id                  SERIAL PRIMARY KEY,
1209     code                TEXT NOT NULL,
1210     name                TEXT NOT NULL
1211 );
1212 COMMENT ON TABLE config.marc_format IS $$
1213 List of MARC formats supported by this Evergreen
1214 database. This exists primarily as a hook for future
1215 support of UNIMARC, though whether that will ever
1216 happen remains to be seen.
1217 $$;
1218
1219 CREATE TYPE config.marc_record_type AS ENUM ('biblio', 'authority', 'serial');
1220
1221 CREATE TABLE config.marc_field (
1222     id                  SERIAL PRIMARY KEY,
1223     marc_format         INTEGER NOT NULL
1224                         REFERENCES config.marc_format (id) DEFERRABLE INITIALLY DEFERRED,
1225     marc_record_type    config.marc_record_type NOT NULL,
1226     tag                 CHAR(3) NOT NULL,
1227     name                TEXT,
1228     description         TEXT,
1229     fixed_field         BOOLEAN,
1230     repeatable          BOOLEAN,
1231     mandatory           BOOLEAN,
1232     hidden              BOOLEAN,
1233     owner               INTEGER -- REFERENCES actor.org_unit (id)
1234                         -- if the owner is null, the data about the field is
1235                         -- assumed to come from the controlling MARC standard
1236 );
1237
1238 COMMENT ON TABLE config.marc_field IS $$
1239 This table stores a list of MARC fields recognized by the Evergreen
1240 instance.  Note that we're not aiming for completely generic ISO2709
1241 support: we're assuming things like three characters for a tag,
1242 one-character subfield labels, two indicators per variable data field,
1243 and the like, all of which are technically specializations of ISO2709.
1244
1245 Of particular significance is the owner column; if it's set to a null
1246 value, the field definition is assumed to come from a national
1247 standards body; if it's set to a non-null value, the field definition
1248 is an OU-level addition to or override of the standard.
1249 $$;
1250
1251 CREATE INDEX config_marc_field_tag_idx ON config.marc_field (tag);
1252 CREATE INDEX config_marc_field_owner_idx ON config.marc_field (owner);
1253
1254 CREATE UNIQUE INDEX config_standard_marc_tags_are_unique
1255     ON config.marc_field(marc_format, marc_record_type, tag)
1256     WHERE owner IS NULL;
1257 ALTER TABLE config.marc_field
1258     ADD CONSTRAINT config_standard_marc_tags_are_fully_specified
1259     CHECK ((owner IS NOT NULL) OR
1260            (
1261                 owner IS NULL AND
1262                 repeatable IS NOT NULL AND
1263                 mandatory IS NOT NULL AND
1264                 hidden IS NOT NULL
1265            )
1266           );
1267
1268 CREATE TABLE config.marc_subfield (
1269     id                  SERIAL PRIMARY KEY,
1270     marc_format         INTEGER NOT NULL
1271                         REFERENCES config.marc_format (id) DEFERRABLE INITIALLY DEFERRED,
1272     marc_record_type    config.marc_record_type NOT NULL,
1273     tag                 CHAR(3) NOT NULL,
1274     code                CHAR(1) NOT NULL,
1275     description         TEXT,
1276     repeatable          BOOLEAN,
1277     mandatory           BOOLEAN,
1278     hidden              BOOLEAN,
1279     value_ctype         TEXT
1280                         REFERENCES config.record_attr_definition (name)
1281                             DEFERRABLE INITIALLY DEFERRED,
1282     owner               INTEGER -- REFERENCES actor.org_unit (id)
1283                         -- if the owner is null, the data about the subfield is
1284                         -- assumed to come from the controlling MARC standard
1285 );
1286
1287 COMMENT ON TABLE config.marc_subfield IS $$
1288 This table stores the list of subfields recognized by this Evergreen
1289 instance.  As with config.marc_field, of particular significance is the
1290 owner column; if it's set to a null value, the subfield definition is
1291 assumed to come from a national standards body; if it's set to a non-null
1292 value, the subfield definition is an OU-level addition to or override
1293 of the standard.
1294 $$;
1295
1296 CREATE INDEX config_marc_subfield_tag_code_idx ON config.marc_subfield (tag, code);
1297 CREATE UNIQUE INDEX config_standard_marc_subfields_are_unique
1298     ON config.marc_subfield(marc_format, marc_record_type, tag, code)
1299     WHERE owner IS NULL;
1300 ALTER TABLE config.marc_subfield
1301     ADD CONSTRAINT config_standard_marc_subfields_are_fully_specified
1302     CHECK ((owner IS NOT NULL) OR
1303            (
1304                 owner IS NULL AND
1305                 repeatable IS NOT NULL AND
1306                 mandatory IS NOT NULL AND
1307                 hidden IS NOT NULL
1308            )
1309           );
1310
1311 CREATE TABLE config.copy_tag_type (
1312     code            TEXT NOT NULL PRIMARY KEY,
1313     label           TEXT NOT NULL,
1314     owner           INTEGER NOT NULL -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
1315 );
1316
1317 CREATE INDEX config_copy_tag_type_owner_idx
1318     ON config.copy_tag_type (owner);
1319
1320 COMMIT;