LP1817332 Stamping ugprade: timezone examples
[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 ('1166', :eg_version); -- remingtron/gmcharlt/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     max_auto_renewals INTEGER
338 );
339 COMMENT ON TABLE config.rule_circ_duration IS $$
340 Circulation Duration rules
341
342 Each circulation is given a duration based on one of these rules.
343 $$;
344
345 CREATE TABLE config.hard_due_date (
346     id                  SERIAL      PRIMARY KEY,
347     name                TEXT        NOT NULL UNIQUE,
348     ceiling_date        TIMESTAMPTZ NOT NULL,
349     forceto             BOOL        NOT NULL,
350     owner               INT         NOT NULL   -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
351 );
352
353 CREATE TABLE config.hard_due_date_values (
354     id                  SERIAL      PRIMARY KEY,
355     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
356                                     DEFERRABLE INITIALLY DEFERRED,
357     ceiling_date        TIMESTAMPTZ NOT NULL,
358     active_date         TIMESTAMPTZ NOT NULL
359 );
360
361 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
362 DECLARE
363     temp_value  config.hard_due_date_values%ROWTYPE;
364     updated     INT := 0;
365 BEGIN
366     FOR temp_value IN
367       SELECT  DISTINCT ON (hard_due_date) *
368         FROM  config.hard_due_date_values
369         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
370         ORDER BY hard_due_date, active_date DESC -- Latest (nearest to us) active time
371    LOOP
372         UPDATE  config.hard_due_date
373           SET   ceiling_date = temp_value.ceiling_date
374           WHERE id = temp_value.hard_due_date
375                 AND ceiling_date <> temp_value.ceiling_date -- Time is equal if we've already updated the chdd
376                 AND temp_value.ceiling_date >= NOW(); -- Don't update ceiling dates to the past
377
378         IF FOUND THEN
379             updated := updated + 1;
380         END IF;
381     END LOOP;
382
383     RETURN updated;
384 END;
385 $func$ LANGUAGE plpgsql;
386
387 CREATE TABLE config.rule_max_fine (
388     id          SERIAL          PRIMARY KEY,
389     name        TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
390     amount      NUMERIC(6,2)    NOT NULL,
391     is_percent  BOOL            NOT NULL DEFAULT FALSE
392 );
393 COMMENT ON TABLE config.rule_max_fine IS $$
394 Circulation Max Fine rules
395
396 Each circulation is given a maximum fine based on one of
397 these rules.
398 $$;
399
400 CREATE TABLE config.rule_recurring_fine (
401         id                      SERIAL          PRIMARY KEY,
402         name                    TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
403         high                    NUMERIC(6,2)    NOT NULL,
404         normal                  NUMERIC(6,2)    NOT NULL,
405         low                     NUMERIC(6,2)    NOT NULL,
406         recurrence_interval     INTERVAL        NOT NULL DEFAULT '1 day'::INTERVAL,
407     grace_period       INTERVAL         NOT NULL DEFAULT '1 day'::INTERVAL
408 );
409 COMMENT ON TABLE config.rule_recurring_fine IS $$
410 Circulation Recurring Fine rules
411
412 Each circulation is given a recurring fine amount based on one of
413 these rules.  Note that it is recommended to run the fine generator
414 (from cron) at least as frequently as the lowest recurrence interval
415 used by your circulation rules so that accrued fines will be up
416 to date.
417 $$;
418
419
420 CREATE TABLE config.rule_age_hold_protect (
421         id      SERIAL          PRIMARY KEY,
422         name    TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
423         age     INTERVAL        NOT NULL,
424         prox    INT             NOT NULL
425 );
426 COMMENT ON TABLE config.rule_age_hold_protect IS $$
427 Hold Item Age Protection rules
428
429 A hold request can only capture new(ish) items when they are
430 within a particular proximity of the pickup_lib of the request.
431 The proximity ('prox' column) is calculated by counting
432 the number of tree edges between the pickup_lib and either the
433 owning_lib or circ_lib of the copy that could fulfill the hold,
434 as determined by the distance_is_from_owner value of the hold matrix
435 rule controlling the hold request.
436 $$;
437
438 CREATE TABLE config.copy_status (
439         id              SERIAL  PRIMARY KEY,
440         name            TEXT    NOT NULL UNIQUE,
441         holdable        BOOL    NOT NULL DEFAULT FALSE,
442         opac_visible    BOOL    NOT NULL DEFAULT FALSE,
443     copy_active  BOOL    NOT NULL DEFAULT FALSE,
444         restrict_copy_delete BOOL         NOT NULL DEFAULT FALSE,
445     is_available  BOOL    NOT NULL DEFAULT FALSE
446 );
447 COMMENT ON TABLE config.copy_status IS $$
448 Copy Statuses
449
450 The available copy statuses, and whether a copy in that
451 status is available for hold request capture.  0 (zero) is
452 the only special number in this set, meaning that the item
453 is available for immediate checkout, and is counted as available
454 in the OPAC.
455
456 Statuses with an ID below 100 are not removable, and have special
457 meaning in the code.  Do not change them except to translate the
458 textual name.
459
460 You may add and remove statuses above 100, and these can be used
461 to remove items from normal circulation without affecting the rest
462 of the copy's values or its location.
463 $$;
464
465 CREATE TABLE config.net_access_level (
466         id      SERIAL          PRIMARY KEY,
467         name    TEXT            NOT NULL UNIQUE
468 );
469 COMMENT ON TABLE config.net_access_level IS $$
470 Patron Network Access level
471
472 This will be used to inform the in-library firewall of how much
473 internet access the using patron should be allowed.
474 $$;
475
476
477 CREATE TABLE config.remote_account (
478     id          SERIAL  PRIMARY KEY,
479     label       TEXT    NOT NULL,
480     host        TEXT    NOT NULL,   -- name or IP, :port optional
481     username    TEXT,               -- optional, since we could default to $USER
482     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
483     account     TEXT,               -- aka profile or FTP "account" command
484     path        TEXT,               -- aka directory
485     owner       INT     NOT NULL,   -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
486     last_activity TIMESTAMP WITH TIME ZONE
487 );
488
489 CREATE TABLE config.marc21_rec_type_map (
490     code        TEXT    PRIMARY KEY,
491     type_val    TEXT    NOT NULL,
492     blvl_val    TEXT    NOT NULL
493 );
494
495 CREATE TABLE config.marc21_ff_pos_map (
496     id          SERIAL  PRIMARY KEY,
497     fixed_field TEXT    NOT NULL,
498     tag         TEXT    NOT NULL,
499     rec_type    TEXT    NOT NULL,
500     start_pos   INT     NOT NULL,
501     length      INT     NOT NULL,
502     default_val TEXT    NOT NULL DEFAULT ' '
503 );
504
505 CREATE TABLE config.marc21_physical_characteristic_type_map (
506     ptype_key   TEXT    PRIMARY KEY,
507     label       TEXT    NOT NULL -- I18N
508 );
509
510 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
511     id          SERIAL  PRIMARY KEY,
512     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
513     subfield    TEXT    NOT NULL,
514     start_pos   INT     NOT NULL,
515     length      INT     NOT NULL,
516     label       TEXT    NOT NULL -- I18N
517 );
518
519 CREATE TABLE config.marc21_physical_characteristic_value_map (
520     id              SERIAL  PRIMARY KEY,
521     value           TEXT    NOT NULL,
522     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
523     label           TEXT    NOT NULL -- I18N
524 );
525
526
527 CREATE TABLE config.z3950_source (
528     name                TEXT    PRIMARY KEY,
529     label               TEXT    NOT NULL UNIQUE,
530     host                TEXT    NOT NULL,
531     port                INT     NOT NULL,
532     db                  TEXT    NOT NULL,
533     record_format       TEXT    NOT NULL DEFAULT 'FI',
534     transmission_format TEXT    NOT NULL DEFAULT 'usmarc',
535     auth                BOOL    NOT NULL DEFAULT TRUE,
536     use_perm            INT     -- REFERENCES permission.perm_list (id)
537 );
538
539 COMMENT ON TABLE config.z3950_source IS $$
540 Z39.50 Sources
541
542 Each row in this table represents a database searchable via Z39.50.
543 $$;
544
545 COMMENT ON COLUMN config.z3950_source.record_format IS $$
546 Z39.50 element set.
547 $$;
548
549 COMMENT ON COLUMN config.z3950_source.transmission_format IS $$
550 Z39.50 preferred record syntax..
551 $$;
552
553 COMMENT ON COLUMN config.z3950_source.use_perm IS $$
554 If set, this permission is required for the source to be listed in the staff
555 client Z39.50 interface.  Similar to permission.grp_tree.application_perm.
556 $$;
557
558 CREATE TABLE config.z3950_attr (
559     id          SERIAL  PRIMARY KEY,
560     source      TEXT    NOT NULL REFERENCES config.z3950_source (name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
561     name        TEXT    NOT NULL,
562     label       TEXT    NOT NULL,
563     code        INT     NOT NULL,
564     format      INT     NOT NULL,
565     truncation  INT     NOT NULL DEFAULT 0,
566     CONSTRAINT z_code_format_once_per_source UNIQUE (code,format,source)
567 );
568
569 CREATE TABLE config.z3950_source_credentials (
570     id SERIAL PRIMARY KEY,
571     owner INTEGER NOT NULL, -- REFERENCES actor.org_unit(id),
572     source TEXT NOT NULL REFERENCES config.z3950_source(name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
573     username TEXT,
574     password TEXT,
575     CONSTRAINT czsc_source_once_per_lib UNIQUE (source, owner)
576 );
577
578 CREATE TABLE config.i18n_locale (
579     code        TEXT    PRIMARY KEY,
580     marc_code   TEXT    NOT NULL, -- should exist in config.coded_value_map WHERE ctype = 'item_lang'
581     name        TEXT    UNIQUE NOT NULL,
582     description TEXT,
583     rtl         BOOL    NOT NULL DEFAULT FALSE
584 );
585
586 CREATE TABLE config.i18n_core (
587     id              BIGSERIAL   PRIMARY KEY,
588     fq_field        TEXT        NOT NULL,
589     identity_value  TEXT        NOT NULL,
590     translation     TEXT        NOT NULL    REFERENCES config.i18n_locale (code) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
591     string          TEXT        NOT NULL
592 );
593
594 CREATE UNIQUE INDEX i18n_identity ON config.i18n_core (fq_field,identity_value,translation);
595
596 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
597 BEGIN
598
599     EXECUTE $$
600         UPDATE  config.i18n_core
601           SET   identity_value = $$ || quote_literal(new_ident) || $$ 
602           WHERE fq_field LIKE '$$ || hint || $$.%' 
603                 AND identity_value = $$ || quote_literal(old_ident) || $$::TEXT;$$;
604
605     RETURN;
606
607 END;
608 $_$ LANGUAGE PLPGSQL;
609
610 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
611 BEGIN
612     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
613     RETURN NEW;
614 END;
615 $_$ LANGUAGE PLPGSQL;
616
617 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
618 BEGIN
619     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
620     RETURN NEW;
621 END;
622 $_$ LANGUAGE PLPGSQL;
623
624 CREATE TABLE config.billing_type (
625     id              SERIAL  PRIMARY KEY,
626     name            TEXT    NOT NULL,
627     owner           INT     NOT NULL, -- REFERENCES actor.org_unit (id)
628     default_price   NUMERIC(6,2),
629     CONSTRAINT billing_type_once_per_lib UNIQUE (name, owner)
630 );
631
632 CREATE TABLE config.settings_group (
633     name    TEXT PRIMARY KEY,
634     label   TEXT UNIQUE NOT NULL -- I18N
635 );
636
637 CREATE TABLE config.org_unit_setting_type (
638     name            TEXT    PRIMARY KEY,
639     label           TEXT    UNIQUE NOT NULL,
640     grp             TEXT    REFERENCES config.settings_group (name),
641     description     TEXT,
642     datatype        TEXT    NOT NULL DEFAULT 'string',
643     fm_class        TEXT,
644     view_perm       INT,
645     update_perm     INT,
646     --
647     -- define valid datatypes
648     --
649     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
650     ( 'bool', 'integer', 'float', 'currency', 'interval',
651       'date', 'string', 'object', 'array', 'link' ) ),
652     --
653     -- fm_class is meaningful only for 'link' datatype
654     --
655     CONSTRAINT coust_no_empty_link CHECK
656     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
657       ( datatype <> 'link' AND fm_class IS NULL ) )
658 );
659
660 CREATE TABLE config.usr_setting_type (
661
662     name TEXT PRIMARY KEY,
663     opac_visible BOOL NOT NULL DEFAULT FALSE,
664     label TEXT UNIQUE NOT NULL,
665     description TEXT,
666     grp             TEXT    REFERENCES config.settings_group (name),
667     datatype TEXT NOT NULL DEFAULT 'string',
668     fm_class TEXT,
669     reg_default TEXT,
670
671     --
672     -- define valid datatypes
673     --
674     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
675     ( 'bool', 'integer', 'float', 'currency', 'interval',
676         'date', 'string', 'object', 'array', 'link' ) ),
677
678     --
679     -- fm_class is meaningful only for 'link' datatype
680     --
681     CONSTRAINT coust_no_empty_link CHECK
682     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
683         ( datatype <> 'link' AND fm_class IS NULL ) )
684
685 );
686
687 CREATE TABLE config.workstation_setting_type (
688     name            TEXT    PRIMARY KEY,
689     label           TEXT    UNIQUE NOT NULL,
690     grp             TEXT    REFERENCES config.settings_group (name),
691     description     TEXT,
692     datatype        TEXT    NOT NULL DEFAULT 'string',
693     fm_class        TEXT,
694     --
695     -- define valid datatypes
696     --
697     CONSTRAINT cwst_valid_datatype CHECK ( datatype IN
698     ( 'bool', 'integer', 'float', 'currency', 'interval',
699       'date', 'string', 'object', 'array', 'link' ) ),
700     --
701     -- fm_class is meaningful only for 'link' datatype
702     --
703     CONSTRAINT cwst_no_empty_link CHECK
704     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
705       ( datatype <> 'link' AND fm_class IS NULL ) )
706 );
707
708 -- Prevent setting types from being both user and workstation settings.
709 CREATE OR REPLACE FUNCTION config.setting_is_user_or_ws()
710 RETURNS TRIGGER AS $FUNC$
711 BEGIN
712
713     IF TG_TABLE_NAME = 'usr_setting_type' THEN
714         PERFORM TRUE FROM config.workstation_setting_type cwst
715             WHERE cwst.name = NEW.name;
716         IF NOT FOUND THEN
717             RETURN NULL;
718         END IF;
719     END IF;
720
721     IF TG_TABLE_NAME = 'workstation_setting_type' THEN
722         PERFORM TRUE FROM config.usr_setting_type cust
723             WHERE cust.name = NEW.name;
724         IF NOT FOUND THEN
725             RETURN NULL;
726         END IF;
727     END IF;
728
729     RAISE EXCEPTION 
730         '% Cannot be used as both a user setting and a workstation setting.', 
731         NEW.name;
732 END;
733 $FUNC$ LANGUAGE PLPGSQL STABLE;
734
735 CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws
736   AFTER INSERT OR UPDATE ON config.usr_setting_type
737   FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws();
738
739 CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws
740   AFTER INSERT OR UPDATE ON config.workstation_setting_type
741   FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws();
742
743
744
745 -- Some handy functions, based on existing ones, to provide optional ingest normalization
746
747 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
748         SELECT SUBSTRING($1,$2);
749 $func$ LANGUAGE SQL STRICT IMMUTABLE;
750
751 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
752         SELECT SUBSTRING($1,1,$2);
753 $func$ LANGUAGE SQL STRICT IMMUTABLE;
754
755 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
756         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
757 $func$ LANGUAGE SQL STRICT IMMUTABLE;
758
759 CREATE OR REPLACE FUNCTION public.approximate_date( TEXT, TEXT ) RETURNS TEXT AS $func$
760         SELECT REGEXP_REPLACE( $1, E'\\D', $2, 'g' );
761 $func$ LANGUAGE SQL STRICT IMMUTABLE;
762
763 CREATE OR REPLACE FUNCTION public.approximate_low_date( TEXT ) RETURNS TEXT AS $func$
764         SELECT approximate_date( $1, '0');
765 $func$ LANGUAGE SQL STRICT IMMUTABLE;
766
767 CREATE OR REPLACE FUNCTION public.approximate_high_date( TEXT ) RETURNS TEXT AS $func$
768         SELECT approximate_date( $1, '9');
769 $func$ LANGUAGE SQL STRICT IMMUTABLE;
770
771 CREATE OR REPLACE FUNCTION public.content_or_null( TEXT ) RETURNS TEXT AS $func$
772         SELECT CASE WHEN $1 ~ E'^\\s*$' THEN NULL ELSE $1 END
773 $func$ LANGUAGE SQL STRICT IMMUTABLE;
774
775 CREATE OR REPLACE FUNCTION public.integer_or_null( TEXT ) RETURNS TEXT AS $func$
776         SELECT CASE WHEN $1 ~ E'^\\d+$' THEN $1 ELSE NULL END
777 $func$ LANGUAGE SQL STRICT IMMUTABLE;
778
779 CREATE OR REPLACE FUNCTION public.force_to_isbn13( TEXT ) RETURNS TEXT AS $func$
780     use Business::ISBN;
781     use strict;
782     use warnings;
783
784     # Find the first ISBN, force it to ISBN13 and return it
785
786     my $input = shift;
787
788     foreach my $word (split(/\s/, $input)) {
789         my $isbn = Business::ISBN->new($word);
790
791         # First check the checksum; if it is not valid, fix it and add the original
792         # bad-checksum ISBN to the output
793         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
794             $isbn->fix_checksum();
795         }
796
797         # If we now have a valid ISBN, force it to ISBN13 and return it
798         return $isbn->as_isbn13->isbn if ($isbn && $isbn->is_valid());
799     }
800     return undef;
801 $func$ LANGUAGE PLPERLU;
802
803 COMMENT ON FUNCTION public.force_to_isbn13(TEXT) IS $$
804 Inspired by translate_isbn1013
805
806 The force_to_isbn13 function takes an input ISBN and returns the ISBN13
807 version without hypens and with a repaired checksum if the checksum was bad
808 $$;
809
810
811 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
812     use Business::ISBN;
813     use strict;
814     use warnings;
815
816     # For each ISBN found in a single string containing a set of ISBNs:
817     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
818     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
819
820     my $input = shift;
821     my $output = '';
822
823     foreach my $word (split(/\s/, $input)) {
824         my $isbn = Business::ISBN->new($word);
825
826         # First check the checksum; if it is not valid, fix it and add the original
827         # bad-checksum ISBN to the output
828         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
829             $output .= $isbn->isbn() . " ";
830             $isbn->fix_checksum();
831         }
832
833         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
834         # and add the normalized original ISBN to the output
835         if ($isbn && $isbn->is_valid()) {
836             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
837             $output .= $isbn->isbn . " ";
838
839             # If we successfully converted the ISBN to its counterpart, add the
840             # converted ISBN to the output as well
841             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
842         }
843     }
844     return $output if $output;
845
846     # If there were no valid ISBNs, just return the raw input
847     return $input;
848 $func$ LANGUAGE PLPERLU;
849
850 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
851 The translate_isbn1013 function takes an input ISBN and returns the
852 following in a single space-delimited string if the input ISBN is valid:
853   - The normalized input ISBN (hyphens stripped)
854   - The normalized input ISBN with a fixed checksum if the checksum was bad
855   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
856 $$;
857
858 -- And ... a table in which to register them
859
860 CREATE TABLE config.index_normalizer (
861         id              SERIAL  PRIMARY KEY,
862         name            TEXT    UNIQUE NOT NULL,
863         description     TEXT,
864         func            TEXT    NOT NULL,
865         param_count     INT     NOT NULL DEFAULT 0
866 );
867
868 CREATE TABLE config.metabib_field_index_norm_map (
869         id      SERIAL  PRIMARY KEY,
870         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
871         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
872         params  TEXT,
873         pos     INT     NOT NULL DEFAULT 0
874 );
875
876 CREATE TABLE config.record_attr_definition (
877     name        TEXT    PRIMARY KEY,
878     label       TEXT    NOT NULL, -- I18N
879     description TEXT,
880     multi       BOOL    NOT NULL DEFAULT TRUE,  -- will store all values from a record
881     filter      BOOL    NOT NULL DEFAULT TRUE,  -- becomes QP filter if true
882     sorter      BOOL    NOT NULL DEFAULT FALSE, -- becomes QP sort() axis if true
883     composite   BOOL    NOT NULL DEFAULT FALSE, -- its values are derived from others
884
885 -- For pre-extracted fields. Takes the first occurance, uses naive subfield ordering
886     tag         TEXT, -- LIKE format
887     sf_list     TEXT, -- pile-o-values, like 'abcd' for a and b and c and d
888
889 -- This is used for both tag/sf and xpath entries
890     joiner      TEXT,
891
892 -- For xpath-extracted attrs
893     xpath       TEXT,
894     format      TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
895     start_pos   INT,
896     string_len  INT,
897
898 -- For fixed fields
899     fixed_field TEXT, -- should exist in config.marc21_ff_pos_map.fixed_field
900
901 -- For phys-char fields
902     phys_char_sf    INT REFERENCES config.marc21_physical_characteristic_subfield_map (id),
903
904 -- Source of vocabulary terms for this record attribute;
905 -- typically will be a URI referring to a SKOS vocabulary
906     vocabulary  TEXT
907 );
908
909 CREATE TABLE config.record_attr_index_norm_map (
910     id      SERIAL  PRIMARY KEY,
911     attr    TEXT    NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
912     norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
913     params  TEXT,
914     pos     INT     NOT NULL DEFAULT 0
915 );
916
917 CREATE TABLE config.coded_value_map (
918     id              SERIAL  PRIMARY KEY,
919     ctype           TEXT    NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
920     code            TEXT    NOT NULL,
921     value           TEXT    NOT NULL,
922     description     TEXT,
923     opac_visible    BOOL    NOT NULL DEFAULT TRUE, -- For TPac selectors
924     search_label    TEXT,
925     is_simple       BOOL    NOT NULL DEFAULT FALSE,
926     concept_uri     TEXT    -- URI expressing the SKOS concept that the
927                             -- coded value represents
928 );
929
930 CREATE INDEX config_coded_value_map_ctype_idx ON config.coded_value_map (ctype);
931
932 CREATE VIEW config.language_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_lang';
933 CREATE VIEW config.bib_level_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'bib_level';
934 CREATE VIEW config.item_form_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_form';
935 CREATE VIEW config.item_type_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_type';
936 CREATE VIEW config.lit_form_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'lit_form';
937 CREATE VIEW config.audience_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'audience';
938 CREATE VIEW config.videorecording_format_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'vr_format';
939
940 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$
941 DECLARE
942     current_row config.coded_value_map%ROWTYPE;
943 BEGIN
944     -- Look for a current value
945     SELECT INTO current_row * FROM config.coded_value_map WHERE ctype = in_ctype AND code = in_code;
946     -- If we have one..
947     IF FOUND AND NOT add_only THEN
948         -- Update anything we were handed
949         current_row.value := COALESCE(current_row.value, in_value);
950         current_row.description := COALESCE(current_row.description, in_description);
951         current_row.opac_visible := COALESCE(current_row.opac_visible, in_opac_visible);
952         current_row.search_label := COALESCE(current_row.search_label, in_search_label);
953         current_row.is_simple := COALESCE(current_row.is_simple, in_is_simple);
954         UPDATE config.coded_value_map
955             SET
956                 value = current_row.value,
957                 description = current_row.description,
958                 opac_visible = current_row.opac_visible,
959                 search_label = current_row.search_label,
960                 is_simple = current_row.is_simple
961             WHERE id = current_row.id;
962     ELSE
963         INSERT INTO config.coded_value_map(ctype, code, value, description, opac_visible, search_label, is_simple) VALUES
964             (in_ctype, in_code, in_value, in_description, COALESCE(in_opac_visible, TRUE), in_search_label, COALESCE(in_is_simple, FALSE));
965     END IF;
966 END;
967 $f$ LANGUAGE PLPGSQL;
968
969 CREATE TABLE config.composite_attr_entry_definition(
970     coded_value INT  PRIMARY KEY NOT NULL REFERENCES config.coded_value_map (id) ON UPDATE CASCADE ON DELETE CASCADE,
971     definition  TEXT    NOT NULL -- JSON
972 );
973
974 -- List applied db patches that are deprecated by (and block the application of) my_db_patch
975 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecates ( my_db_patch TEXT ) RETURNS SETOF evergreen.patch AS $$
976     SELECT  DISTINCT l.version
977       FROM  config.upgrade_log l
978             JOIN config.db_patch_dependencies d ON (l.version = ANY(d.deprecates))
979       WHERE d.db_patch = $1
980 $$ LANGUAGE SQL;
981
982 -- List applied db patches that are superseded by (and block the application of) my_db_patch
983 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_supersedes ( my_db_patch TEXT ) RETURNS SETOF evergreen.patch AS $$
984     SELECT  DISTINCT l.version
985       FROM  config.upgrade_log l
986             JOIN config.db_patch_dependencies d ON (l.version = ANY(d.supersedes))
987       WHERE d.db_patch = $1
988 $$ LANGUAGE SQL;
989
990 -- List applied db patches that deprecates (and block the application of) my_db_patch
991 CREATE FUNCTION evergreen.upgrade_list_applied_deprecated ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
992     SELECT  db_patch
993       FROM  config.db_patch_dependencies
994       WHERE ARRAY[$1]::TEXT[] && deprecates
995 $$ LANGUAGE SQL;
996
997 -- List applied db patches that supersedes (and block the application of) my_db_patch
998 CREATE FUNCTION evergreen.upgrade_list_applied_superseded ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
999     SELECT  db_patch
1000       FROM  config.db_patch_dependencies
1001       WHERE ARRAY[$1]::TEXT[] && supersedes
1002 $$ LANGUAGE SQL;
1003
1004 -- Make sure that no deprecated or superseded db patches are currently applied
1005 CREATE OR REPLACE FUNCTION evergreen.upgrade_verify_no_dep_conflicts ( my_db_patch TEXT ) RETURNS BOOL AS $$
1006     SELECT  COUNT(*) = 0
1007       FROM  (SELECT * FROM evergreen.upgrade_list_applied_deprecates( $1 )
1008                 UNION
1009              SELECT * FROM evergreen.upgrade_list_applied_supersedes( $1 )
1010                 UNION
1011              SELECT * FROM evergreen.upgrade_list_applied_deprecated( $1 )
1012                 UNION
1013              SELECT * FROM evergreen.upgrade_list_applied_superseded( $1 ))x
1014 $$ LANGUAGE SQL;
1015
1016 -- Raise an exception if there are, in fact, dep/sup conflict
1017 CREATE OR REPLACE FUNCTION evergreen.upgrade_deps_block_check ( my_db_patch TEXT, my_applied_to TEXT ) RETURNS BOOL AS $$
1018 DECLARE 
1019     deprecates TEXT;
1020     supersedes TEXT;
1021 BEGIN
1022     IF NOT evergreen.upgrade_verify_no_dep_conflicts( my_db_patch ) THEN
1023         SELECT  STRING_AGG(patch, ', ') INTO deprecates FROM evergreen.upgrade_list_applied_deprecates(my_db_patch);
1024         SELECT  STRING_AGG(patch, ', ') INTO supersedes FROM evergreen.upgrade_list_applied_supersedes(my_db_patch);
1025         RAISE EXCEPTION '
1026 Upgrade script % can not be applied:
1027   applied deprecated scripts %
1028   applied superseded scripts %
1029   deprecated by %
1030   superseded by %',
1031             my_db_patch,
1032             (SELECT ARRAY_AGG(patch) FROM evergreen.upgrade_list_applied_deprecates(my_db_patch)),
1033             (SELECT ARRAY_AGG(patch) FROM evergreen.upgrade_list_applied_supersedes(my_db_patch)),
1034             evergreen.upgrade_list_applied_deprecated(my_db_patch),
1035             evergreen.upgrade_list_applied_superseded(my_db_patch);
1036     END IF;
1037
1038     INSERT INTO config.upgrade_log (version, applied_to) VALUES (my_db_patch, my_applied_to);
1039     RETURN TRUE;
1040 END;
1041 $$ LANGUAGE PLPGSQL;
1042
1043 CREATE TABLE config.barcode_completion (
1044     id          SERIAL PRIMARY KEY,
1045     active      BOOL NOT NULL DEFAULT true,
1046     org_unit    INT NOT NULL, -- REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED,
1047     prefix      TEXT,
1048     suffix      TEXT,
1049     length      INT NOT NULL DEFAULT 0,
1050     padding     TEXT,
1051     padding_end BOOL NOT NULL DEFAULT false,
1052     asset       BOOL NOT NULL DEFAULT true,
1053     actor       BOOL NOT NULL DEFAULT true
1054 );
1055
1056 CREATE TYPE evergreen.barcode_set AS (type TEXT, id BIGINT, barcode TEXT);
1057
1058 -- Add support for logging, only keep the most recent five rows for each category. 
1059
1060
1061 CREATE TABLE config.org_unit_setting_type_log (
1062     id              BIGSERIAL   PRIMARY KEY,
1063     date_applied    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1064     org             INT,   --REFERENCES actor.org_unit (id),
1065     original_value  TEXT,
1066     new_value       TEXT,
1067     field_name      TEXT      REFERENCES config.org_unit_setting_type (name) DEFERRABLE INITIALLY DEFERRED
1068 );
1069
1070 COMMENT ON TABLE config.org_unit_setting_type_log IS $$
1071 Org Unit setting Logs
1072
1073 This table contains the most recent changes to each setting 
1074 in actor.org_unit_setting, allowing for mistakes to be undone.
1075 This is NOT meant to be an auditor, but rather an undo/redo.
1076 $$;
1077
1078 CREATE OR REPLACE FUNCTION evergreen.limit_oustl() RETURNS TRIGGER AS $oustl_limit$
1079     BEGIN
1080         -- Only keeps the most recent five settings changes.
1081         DELETE FROM config.org_unit_setting_type_log WHERE field_name = NEW.field_name AND org = NEW.org AND date_applied NOT IN 
1082         (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);
1083         
1084         IF (TG_OP = 'UPDATE') THEN
1085             RETURN NEW;
1086         ELSIF (TG_OP = 'INSERT') THEN
1087             RETURN NEW;
1088         END IF;
1089         RETURN NULL;
1090     END;
1091 $oustl_limit$ LANGUAGE plpgsql;
1092
1093 CREATE TRIGGER limit_logs_oust
1094     BEFORE INSERT OR UPDATE ON config.org_unit_setting_type_log
1095     FOR EACH ROW EXECUTE PROCEDURE limit_oustl();
1096
1097 CREATE TABLE config.sms_carrier (
1098     id              SERIAL PRIMARY KEY,
1099     region          TEXT,
1100     name            TEXT,
1101     email_gateway   TEXT,
1102     active          BOOLEAN DEFAULT TRUE
1103 );
1104
1105 CREATE TYPE config.usr_activity_group AS ENUM ('authen','authz','circ','hold','search');
1106
1107 CREATE TABLE config.usr_activity_type (
1108     id          SERIAL                      PRIMARY KEY, 
1109     ewho        TEXT,
1110     ewhat       TEXT,
1111     ehow        TEXT,
1112     label       TEXT                        NOT NULL, -- i18n
1113     egroup      config.usr_activity_group   NOT NULL,
1114     enabled     BOOL                        NOT NULL DEFAULT TRUE,
1115     transient   BOOL                        NOT NULL DEFAULT TRUE,
1116     CONSTRAINT  one_of_wwh CHECK (COALESCE(ewho,ewhat,ehow) IS NOT NULL)
1117 );
1118
1119 CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type 
1120     (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,''));
1121
1122 CREATE TABLE config.filter_dialog_interface (
1123     key         TEXT                        PRIMARY KEY,
1124     description TEXT
1125 );  
1126
1127 CREATE TABLE config.filter_dialog_filter_set (
1128     id          SERIAL                      PRIMARY KEY,
1129     name        TEXT                        NOT NULL,
1130     owning_lib  INT                         NOT NULL, -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
1131     creator     INT                         NOT NULL, -- REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
1132     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
1133     interface   TEXT                        NOT NULL REFERENCES config.filter_dialog_interface (key) DEFERRABLE INITIALLY DEFERRED,
1134     filters     TEXT                        NOT NULL, -- CHECK (evergreen.is_json(filters))
1135     CONSTRAINT cfdfs_name_once_per_lib UNIQUE (name, owning_lib)
1136 );
1137
1138 CREATE TABLE config.best_hold_order(
1139     id          SERIAL      PRIMARY KEY,
1140     name        TEXT        UNIQUE,   -- i18n
1141     pprox       INT, -- copy capture <-> pickup lib prox
1142     hprox       INT, -- copy circ lib <-> request lib prox
1143     aprox       INT, -- copy circ lib <-> pickup lib ADJUSTED prox on ahcm
1144     approx      INT, -- copy capture <-> pickup lib ADJUSTED prox from function
1145     priority    INT, -- group hold priority
1146     cut         INT, -- cut-in-line
1147     depth       INT, -- selection depth
1148     htime       INT, -- time since last home-lib circ exceeds org-unit setting
1149     rtime       INT, -- request time
1150     shtime      INT  -- time since copy last trip home exceeds org-unit setting
1151 );
1152
1153 -- At least one of these columns must contain a non-null value
1154 ALTER TABLE config.best_hold_order ADD CHECK ((
1155     pprox IS NOT NULL OR
1156     hprox IS NOT NULL OR
1157     aprox IS NOT NULL OR
1158     priority IS NOT NULL OR
1159     cut IS NOT NULL OR
1160     depth IS NOT NULL OR
1161     htime IS NOT NULL OR
1162     rtime IS NOT NULL
1163 ));
1164
1165 CREATE OR REPLACE FUNCTION 
1166     evergreen.z3950_attr_name_is_valid() RETURNS TRIGGER AS $func$
1167     BEGIN
1168
1169       PERFORM * FROM config.z3950_attr WHERE name = NEW.z3950_attr_type;
1170
1171       IF FOUND THEN
1172         RETURN NULL;
1173       END IF;
1174
1175       RAISE EXCEPTION '% is not a valid Z39.50 attribute type', NEW.z3950_attr_type;
1176
1177     END;
1178 $func$ LANGUAGE PLPGSQL STABLE;
1179
1180 COMMENT ON FUNCTION evergreen.z3950_attr_name_is_valid() IS $$
1181 Used by a config.z3950_index_field_map constraint trigger
1182 to verify z3950_attr_type maps.
1183 $$;
1184
1185 -- drop these in down here since they reference config.metabib_field
1186 -- and config.record_attr_definition
1187 CREATE TABLE config.z3950_index_field_map (
1188     id              SERIAL  PRIMARY KEY,
1189     label           TEXT    NOT NULL, -- i18n
1190     metabib_field   INTEGER REFERENCES config.metabib_field(id) ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
1191     record_attr     TEXT    REFERENCES config.record_attr_definition(name),
1192     z3950_attr      INTEGER REFERENCES config.z3950_attr(id),
1193     z3950_attr_type TEXT,-- REFERENCES config.z3950_attr(name)
1194     CONSTRAINT metabib_field_or_record_attr CHECK (
1195         metabib_field IS NOT NULL OR 
1196         record_attr IS NOT NULL
1197     ),
1198     CONSTRAINT attr_or_attr_type CHECK (
1199         z3950_attr IS NOT NULL OR 
1200         z3950_attr_type IS NOT NULL
1201     )
1202 );
1203
1204 CREATE CONSTRAINT TRIGGER valid_z3950_attr_type AFTER INSERT OR UPDATE ON config.z3950_index_field_map
1205     DEFERRABLE INITIALLY DEFERRED FOR EACH ROW WHEN (NEW.z3950_attr_type IS NOT NULL)
1206     EXECUTE PROCEDURE evergreen.z3950_attr_name_is_valid();
1207
1208 CREATE TABLE config.marc_format (
1209     id                  SERIAL PRIMARY KEY,
1210     code                TEXT NOT NULL,
1211     name                TEXT NOT NULL
1212 );
1213 COMMENT ON TABLE config.marc_format IS $$
1214 List of MARC formats supported by this Evergreen
1215 database. This exists primarily as a hook for future
1216 support of UNIMARC, though whether that will ever
1217 happen remains to be seen.
1218 $$;
1219
1220 CREATE TYPE config.marc_record_type AS ENUM ('biblio', 'authority', 'serial');
1221
1222 CREATE TABLE config.marc_field (
1223     id                  SERIAL PRIMARY KEY,
1224     marc_format         INTEGER NOT NULL
1225                         REFERENCES config.marc_format (id) DEFERRABLE INITIALLY DEFERRED,
1226     marc_record_type    config.marc_record_type NOT NULL,
1227     tag                 CHAR(3) NOT NULL,
1228     name                TEXT,
1229     description         TEXT,
1230     fixed_field         BOOLEAN,
1231     repeatable          BOOLEAN,
1232     mandatory           BOOLEAN,
1233     hidden              BOOLEAN,
1234     owner               INTEGER -- REFERENCES actor.org_unit (id)
1235                         -- if the owner is null, the data about the field is
1236                         -- assumed to come from the controlling MARC standard
1237 );
1238
1239 COMMENT ON TABLE config.marc_field IS $$
1240 This table stores a list of MARC fields recognized by the Evergreen
1241 instance.  Note that we're not aiming for completely generic ISO2709
1242 support: we're assuming things like three characters for a tag,
1243 one-character subfield labels, two indicators per variable data field,
1244 and the like, all of which are technically specializations of ISO2709.
1245
1246 Of particular significance is the owner column; if it's set to a null
1247 value, the field definition is assumed to come from a national
1248 standards body; if it's set to a non-null value, the field definition
1249 is an OU-level addition to or override of the standard.
1250 $$;
1251
1252 CREATE INDEX config_marc_field_tag_idx ON config.marc_field (tag);
1253 CREATE INDEX config_marc_field_owner_idx ON config.marc_field (owner);
1254
1255 CREATE UNIQUE INDEX config_standard_marc_tags_are_unique
1256     ON config.marc_field(marc_format, marc_record_type, tag)
1257     WHERE owner IS NULL;
1258 ALTER TABLE config.marc_field
1259     ADD CONSTRAINT config_standard_marc_tags_are_fully_specified
1260     CHECK ((owner IS NOT NULL) OR
1261            (
1262                 owner IS NULL AND
1263                 repeatable IS NOT NULL AND
1264                 mandatory IS NOT NULL AND
1265                 hidden IS NOT NULL
1266            )
1267           );
1268
1269 CREATE TABLE config.marc_subfield (
1270     id                  SERIAL PRIMARY KEY,
1271     marc_format         INTEGER NOT NULL
1272                         REFERENCES config.marc_format (id) DEFERRABLE INITIALLY DEFERRED,
1273     marc_record_type    config.marc_record_type NOT NULL,
1274     tag                 CHAR(3) NOT NULL,
1275     code                CHAR(1) NOT NULL,
1276     description         TEXT,
1277     repeatable          BOOLEAN,
1278     mandatory           BOOLEAN,
1279     hidden              BOOLEAN,
1280     value_ctype         TEXT
1281                         REFERENCES config.record_attr_definition (name)
1282                             DEFERRABLE INITIALLY DEFERRED,
1283     owner               INTEGER -- REFERENCES actor.org_unit (id)
1284                         -- if the owner is null, the data about the subfield is
1285                         -- assumed to come from the controlling MARC standard
1286 );
1287
1288 COMMENT ON TABLE config.marc_subfield IS $$
1289 This table stores the list of subfields recognized by this Evergreen
1290 instance.  As with config.marc_field, of particular significance is the
1291 owner column; if it's set to a null value, the subfield definition is
1292 assumed to come from a national standards body; if it's set to a non-null
1293 value, the subfield definition is an OU-level addition to or override
1294 of the standard.
1295 $$;
1296
1297 CREATE INDEX config_marc_subfield_tag_code_idx ON config.marc_subfield (tag, code);
1298 CREATE UNIQUE INDEX config_standard_marc_subfields_are_unique
1299     ON config.marc_subfield(marc_format, marc_record_type, tag, code)
1300     WHERE owner IS NULL;
1301 ALTER TABLE config.marc_subfield
1302     ADD CONSTRAINT config_standard_marc_subfields_are_fully_specified
1303     CHECK ((owner IS NOT NULL) OR
1304            (
1305                 owner IS NULL AND
1306                 repeatable IS NOT NULL AND
1307                 mandatory IS NOT NULL AND
1308                 hidden IS NOT NULL
1309            )
1310           );
1311
1312 CREATE TABLE config.copy_tag_type (
1313     code            TEXT NOT NULL PRIMARY KEY,
1314     label           TEXT NOT NULL,
1315     owner           INTEGER NOT NULL -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
1316 );
1317
1318 CREATE INDEX config_copy_tag_type_owner_idx
1319     ON config.copy_tag_type (owner);
1320
1321 COMMIT;