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