]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/version-upgrade/2.3-2.4.0RC-upgrade-db.sql
LP#1205061: Add IF EXISTS to some DROP statements in 2.3-2.4 upgrade script
[working/Evergreen.git] / Open-ILS / src / sql / Pg / version-upgrade / 2.3-2.4.0RC-upgrade-db.sql
1 --Upgrade Script for 2.3 to 2.4.0RC
2 \set eg_version '''2.4.0RC'''
3 BEGIN;
4 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.4.0RC', :eg_version);
5 -- remove the Bypass hold capture during clear shelf process setting
6
7 SELECT evergreen.upgrade_deps_block_check('0739', :eg_version);
8
9
10 DELETE FROM actor.org_unit_setting WHERE name = 'circ.holds.clear_shelf.no_capture_holds';
11 DELETE FROM config.org_unit_setting_type_log WHERE field_name = 'circ.holds.clear_shelf.no_capture_holds';
12
13
14 DELETE FROM config.org_unit_setting_type WHERE name = 'circ.holds.clear_shelf.no_capture_holds';
15
16
17 SELECT evergreen.upgrade_deps_block_check('0741', :eg_version);
18
19 INSERT INTO permission.perm_list ( id, code, description ) VALUES (
20     540,
21     'ADMIN_TOOLBAR_FOR_ORG',
22     oils_i18n_gettext(
23         540,
24         'Allows a user to create, edit, and delete custom toolbars for org units',
25         'ppl',
26         'description'
27     )
28 ), (
29     541,
30     'ADMIN_TOOLBAR_FOR_WORKSTATION',
31     oils_i18n_gettext(
32         541,
33         'Allows a user to create, edit, and delete custom toolbars for workstations',
34         'ppl',
35         'description'
36     )
37 ), (
38     542,
39     'ADMIN_TOOLBAR_FOR_USER',
40     oils_i18n_gettext(
41         542,
42         'Allows a user to create, edit, and delete custom toolbars for users',
43         'ppl',
44         'description'
45     )
46 );
47
48
49 -- Evergreen DB patch 0743.schema.remove_tsearch2.sql
50 --
51 -- Enable native full-text search to be used, and drop TSearch2 extension
52 --
53
54 -- check whether patch can be applied
55 SELECT evergreen.upgrade_deps_block_check('0743', :eg_version);
56
57 -- FIXME: add/check SQL statements to perform the upgrade
58 -- First up, these functions depend on metabib.full_rec. They have to go for now.
59 DROP FUNCTION IF EXISTS biblio.flatten_marc(bigint);
60 DROP FUNCTION IF EXISTS biblio.flatten_marc(text);
61
62 -- These views depend on metabib.full_rec as well. Bye-bye!
63 DROP VIEW IF EXISTS reporter.old_super_simple_record;
64 DROP VIEW IF EXISTS reporter.simple_record;
65 DROP VIEW IF EXISTS reporter.classic_item_list;
66
67 \echo WARNING: The reporter.classic_item_list view was dropped if it existed.
68 \echo If you use that view, please run the example.reporter-extension.sql script
69 \echo to recreate it after rest of the schema upgrade is complete.
70
71 -- Now we can drop metabib.full_rec.
72 DROP VIEW IF EXISTS metabib.full_rec;
73
74 -- These indexes have to go. BEFORE we alter the tables, otherwise things take extra time when we alter the tables.
75 DROP INDEX metabib.metabib_author_field_entry_value_idx;
76 DROP INDEX metabib.metabib_identifier_field_entry_value_idx;
77 DROP INDEX metabib.metabib_keyword_field_entry_value_idx;
78 DROP INDEX metabib.metabib_series_field_entry_value_idx;
79 DROP INDEX metabib.metabib_subject_field_entry_value_idx;
80 DROP INDEX metabib.metabib_title_field_entry_value_idx;
81
82 -- Now grab all of the tsvector-enabled columns and switch them to the non-wrapper version of the type.
83 ALTER TABLE authority.full_rec ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
84 ALTER TABLE authority.simple_heading ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
85 ALTER TABLE metabib.real_full_rec ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
86 ALTER TABLE metabib.author_field_entry ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
87 ALTER TABLE metabib.browse_entry ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
88 ALTER TABLE metabib.identifier_field_entry ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
89 ALTER TABLE metabib.keyword_field_entry ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
90 ALTER TABLE metabib.series_field_entry ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
91 ALTER TABLE metabib.subject_field_entry ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
92 ALTER TABLE metabib.title_field_entry ALTER COLUMN index_vector TYPE pg_catalog.tsvector;
93
94 -- Make sure that tsearch2 exists as an extension (for a sufficiently
95 -- old Evergreen database, it might still be an unpackaged contrib).
96 CREATE EXTENSION IF NOT EXISTS tsearch2 SCHEMA public FROM unpackaged;
97 -- Halfway there! Goodbye tsearch2 extension!
98 DROP EXTENSION tsearch2;
99
100 -- Next up, re-creating all of the stuff we just dropped.
101
102 -- Indexes! Note to whomever: Do we even need these anymore?
103 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
104 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
105 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
106 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
107 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
108 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
109
110 -- metabib.full_rec, with insert/update/delete rules
111 CREATE OR REPLACE VIEW metabib.full_rec AS
112     SELECT  id,
113             record,
114             tag,
115             ind1,
116             ind2,
117             subfield,
118             SUBSTRING(value,1,1024) AS value,
119             index_vector
120       FROM  metabib.real_full_rec;
121
122 CREATE OR REPLACE RULE metabib_full_rec_insert_rule
123     AS ON INSERT TO metabib.full_rec
124     DO INSTEAD
125     INSERT INTO metabib.real_full_rec VALUES (
126         COALESCE(NEW.id, NEXTVAL('metabib.full_rec_id_seq'::REGCLASS)),
127         NEW.record,
128         NEW.tag,
129         NEW.ind1,
130         NEW.ind2,
131         NEW.subfield,
132         NEW.value,
133         NEW.index_vector
134     );
135
136 CREATE OR REPLACE RULE metabib_full_rec_update_rule
137     AS ON UPDATE TO metabib.full_rec
138     DO INSTEAD
139     UPDATE  metabib.real_full_rec SET
140         id = NEW.id,
141         record = NEW.record,
142         tag = NEW.tag,
143         ind1 = NEW.ind1,
144         ind2 = NEW.ind2,
145         subfield = NEW.subfield,
146         value = NEW.value,
147         index_vector = NEW.index_vector
148       WHERE id = OLD.id;
149
150 CREATE OR REPLACE RULE metabib_full_rec_delete_rule
151     AS ON DELETE TO metabib.full_rec
152     DO INSTEAD
153     DELETE FROM metabib.real_full_rec WHERE id = OLD.id;
154
155 -- reporter views that depended on metabib.full_rec are up next
156 CREATE OR REPLACE VIEW reporter.simple_record AS
157 SELECT  r.id,
158     s.metarecord,
159     r.fingerprint,
160     r.quality,
161     r.tcn_source,
162     r.tcn_value,
163     title.value AS title,
164     uniform_title.value AS uniform_title,
165     author.value AS author,
166     publisher.value AS publisher,
167     SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
168     series_title.value AS series_title,
169     series_statement.value AS series_statement,
170     summary.value AS summary,
171     ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
172     ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
173     ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
174     ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
175     ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
176     ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
177     ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
178     ARRAY((SELECT value FROM metabib.full_rec WHERE tag = '856' AND subfield IN ('3','y','u') AND record = r.id ORDER BY CASE WHEN subfield IN ('3','y') THEN 0 ELSE 1 END)) AS external_uri
179   FROM  biblio.record_entry r
180     JOIN metabib.metarecord_source_map s ON (s.source = r.id)
181     LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
182     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
183     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
184     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
185     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
186     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
187     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
188     LEFT JOIN metabib.full_rec series_title ON (r.id = series_title.record AND series_title.tag IN ('830','440') AND series_title.subfield = 'a')
189     LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
190     LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
191   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
192
193 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
194 SELECT  r.id,
195     r.fingerprint,
196     r.quality,
197     r.tcn_source,
198     r.tcn_value,
199     FIRST(title.value) AS title,
200     FIRST(author.value) AS author,
201     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
202     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
203     ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
204     ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn
205   FROM  biblio.record_entry r
206     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
207     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
208     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
209     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
210     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
211     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
212   GROUP BY 1,2,3,4,5;
213
214 -- And finally, the biblio functions. NOTE: I can't find the original source of the second one, so I skipped it as old cruft that was in our production DB.
215 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
216 DECLARE
217     bib biblio.record_entry%ROWTYPE;
218     output  metabib.full_rec%ROWTYPE;
219     field   RECORD;
220 BEGIN
221     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
222
223     FOR field IN SELECT * FROM vandelay.flatten_marc( bib.marc ) LOOP
224         output.record := rid;
225         output.ind1 := field.ind1;
226         output.ind2 := field.ind2;
227         output.tag := field.tag;
228         output.subfield := field.subfield;
229         output.value := field.value;
230
231         RETURN NEXT output;
232     END LOOP;
233 END;
234 $func$ LANGUAGE PLPGSQL;
235
236 -- Evergreen DB patch 0745.data.prewarn_expire_setting.sql
237 --
238 -- Configuration setting to warn staff when an account is about to expire
239 --
240
241 -- check whether patch can be applied
242 SELECT evergreen.upgrade_deps_block_check('0745', :eg_version);
243
244 INSERT INTO config.org_unit_setting_type
245     (name, grp, label, description, datatype)
246     VALUES (
247         'circ.patron_expires_soon_warning',
248         'circ',
249         oils_i18n_gettext(
250             'circ.patron_expires_soon_warning',
251             'Warn when patron account is about to expire',
252             'coust',
253             'label'
254         ),
255         oils_i18n_gettext(
256             'circ.patron_expires_soon_warning',
257             'Warn when patron account is about to expire. If set, the staff client displays a warning this many days before the expiry of a patron account. Value is in number of days, for example: 3 for 3 days.',
258             'coust',
259             'description'
260         ),
261         'integer'
262     );
263
264 -- LP1076399: Prevent reactivated holds from canceling immediately.
265 -- Set the expire_time to NULL on all frozen/suspended holds.
266
267 SELECT evergreen.upgrade_deps_block_check('0747', :eg_version);
268
269 UPDATE action.hold_request
270 SET expire_time = NULL
271 WHERE frozen = 't'; 
272
273
274 SELECT evergreen.upgrade_deps_block_check('0752', :eg_version);
275
276 INSERT INTO container.biblio_record_entry_bucket_type (code, label) VALUES ('url_verify', 'URL Verification Queue');
277
278 DROP SCHEMA IF EXISTS url_verify CASCADE;
279
280 CREATE SCHEMA url_verify;
281
282 CREATE TABLE url_verify.session (
283     id          SERIAL                      PRIMARY KEY,
284     name        TEXT                        NOT NULL,
285     owning_lib  INT                         NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
286     creator     INT                         NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
287     container   INT                         NOT NULL REFERENCES container.biblio_record_entry_bucket (id) DEFERRABLE INITIALLY DEFERRED,
288     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
289     search      TEXT                        NOT NULL,
290     CONSTRAINT uvs_name_once_per_lib UNIQUE (name, owning_lib)
291 );
292
293 CREATE TABLE url_verify.url_selector (
294     id      SERIAL  PRIMARY KEY,
295     xpath   TEXT    NOT NULL,
296     session INT     NOT NULL REFERENCES url_verify.session (id) DEFERRABLE INITIALLY DEFERRED,
297     CONSTRAINT tag_once_per_sess UNIQUE (xpath, session)
298 );
299
300 CREATE TABLE url_verify.url (
301     id              SERIAL  PRIMARY KEY,
302     redirect_from   INT     REFERENCES url_verify.url(id) DEFERRABLE INITIALLY DEFERRED,
303     item            INT     REFERENCES container.biblio_record_entry_bucket_item (id) DEFERRABLE INITIALLY DEFERRED,
304     url_selector    INT     REFERENCES url_verify.url_selector (id) DEFERRABLE INITIALLY DEFERRED,
305     session         INT     REFERENCES url_verify.session (id) DEFERRABLE INITIALLY DEFERRED,
306     tag             TEXT,
307     subfield        TEXT,
308     ord             INT,
309     full_url        TEXT    NOT NULL,
310     scheme          TEXT,
311     username        TEXT,
312     password        TEXT,
313     host            TEXT,
314     domain          TEXT,
315     tld             TEXT,
316     port            TEXT,
317     path            TEXT,
318     page            TEXT,
319     query           TEXT,
320     fragment        TEXT,
321     CONSTRAINT redirect_or_from_item CHECK (
322         redirect_from IS NOT NULL OR (
323             item         IS NOT NULL AND
324             url_selector IS NOT NULL AND
325             tag          IS NOT NULL AND
326             subfield     IS NOT NULL AND
327             ord          IS NOT NULL
328         )
329     )
330 );
331
332 CREATE TABLE url_verify.verification_attempt (
333     id          SERIAL                      PRIMARY KEY,
334     usr         INT                         NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
335     session     INT                         NOT NULL REFERENCES url_verify.session (id) DEFERRABLE INITIALLY DEFERRED,
336     start_time  TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
337     finish_time TIMESTAMP WITH TIME ZONE
338 );
339  
340 CREATE TABLE url_verify.url_verification (
341     id          SERIAL                      PRIMARY KEY,
342     url         INT                         NOT NULL REFERENCES url_verify.url (id) DEFERRABLE INITIALLY DEFERRED,
343     attempt     INT                         NOT NULL REFERENCES url_verify.verification_attempt (id) DEFERRABLE INITIALLY DEFERRED,
344     req_time    TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
345     res_time    TIMESTAMP WITH TIME ZONE, 
346     res_code    INT                         CHECK (res_code BETWEEN 100 AND 999), -- we know > 599 will never be valid HTTP code, but we use 9XX for other stuff
347     res_text    TEXT, 
348     redirect_to INT                         REFERENCES url_verify.url (id) DEFERRABLE INITIALLY DEFERRED -- if redirected
349 );
350
351 CREATE TABLE config.filter_dialog_interface (
352     key         TEXT                        PRIMARY KEY,
353     description TEXT
354 );
355
356 CREATE TABLE config.filter_dialog_filter_set (
357     id          SERIAL                      PRIMARY KEY,
358     name        TEXT                        NOT NULL,
359     owning_lib  INT                         NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
360     creator     INT                         NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
361     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
362     interface   TEXT                        NOT NULL REFERENCES config.filter_dialog_interface (key) DEFERRABLE INITIALLY DEFERRED,
363     filters     TEXT                        NOT NULL CHECK (is_json(filters)),
364     CONSTRAINT cfdfs_name_once_per_lib UNIQUE (name, owning_lib)
365 );
366  
367
368 SELECT evergreen.upgrade_deps_block_check('0753', :eg_version);
369
370 CREATE OR REPLACE FUNCTION url_verify.parse_url (url_in TEXT) RETURNS url_verify.url AS $$
371
372 use Rose::URI;
373
374 my $url_in = shift;
375 my $url = Rose::URI->new($url_in);
376
377 my %parts = map { $_ => $url->$_ } qw/scheme username password host port path query fragment/;
378
379 $parts{full_url} = $url_in;
380 ($parts{domain} = $parts{host}) =~ s/^[^.]+\.//;
381 ($parts{tld} = $parts{domain}) =~ s/(?:[^.]+\.)+//;
382 ($parts{page} = $parts{path}) =~ s#(?:[^/]*/)+##;
383
384 return \%parts;
385
386 $$ LANGUAGE PLPERLU;
387
388 CREATE OR REPLACE FUNCTION url_verify.ingest_url () RETURNS TRIGGER AS $$
389 DECLARE
390     tmp_row url_verify.url%ROWTYPE;
391 BEGIN
392     SELECT * INTO tmp_row FROM url_verify.parse_url(NEW.full_url);
393
394     NEW.scheme          := tmp_row.scheme;
395     NEW.username        := tmp_row.username;
396     NEW.password        := tmp_row.password;
397     NEW.host            := tmp_row.host;
398     NEW.domain          := tmp_row.domain;
399     NEW.tld             := tmp_row.tld;
400     NEW.port            := tmp_row.port;
401     NEW.path            := tmp_row.path;
402     NEW.page            := tmp_row.page;
403     NEW.query           := tmp_row.query;
404     NEW.fragment        := tmp_row.fragment;
405
406     RETURN NEW;
407 END;
408 $$ LANGUAGE PLPGSQL;
409
410 CREATE TRIGGER ingest_url_tgr
411     BEFORE INSERT ON url_verify.url
412     FOR EACH ROW EXECUTE PROCEDURE url_verify.ingest_url(); 
413
414 CREATE OR REPLACE FUNCTION url_verify.extract_urls ( session_id INT, item_id INT ) RETURNS INT AS $$
415 DECLARE
416     last_seen_tag TEXT;
417     current_tag TEXT;
418     current_sf TEXT;
419     current_url TEXT;
420     current_ord INT;
421     current_url_pos INT;
422     current_selector url_verify.url_selector%ROWTYPE;
423 BEGIN
424     current_ord := 1;
425
426     FOR current_selector IN SELECT * FROM url_verify.url_selector s WHERE s.session = session_id LOOP
427         current_url_pos := 1;
428         LOOP
429             SELECT  (XPATH(current_selector.xpath || '/text()', b.marc::XML))[current_url_pos]::TEXT INTO current_url
430               FROM  biblio.record_entry b
431                     JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id)
432               WHERE c.id = item_id;
433
434             EXIT WHEN current_url IS NULL;
435
436             SELECT  (XPATH(current_selector.xpath || '/../@tag', b.marc::XML))[current_url_pos]::TEXT INTO current_tag
437               FROM  biblio.record_entry b
438                     JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id)
439               WHERE c.id = item_id;
440
441             IF current_tag IS NULL THEN
442                 current_tag := last_seen_tag;
443             ELSE
444                 last_seen_tag := current_tag;
445             END IF;
446
447             SELECT  (XPATH(current_selector.xpath || '/@code', b.marc::XML))[current_url_pos]::TEXT INTO current_sf
448               FROM  biblio.record_entry b
449                     JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id)
450               WHERE c.id = item_id;
451
452             INSERT INTO url_verify.url (session, item, url_selector, tag, subfield, ord, full_url)
453               VALUES ( session_id, item_id, current_selector.id, current_tag, current_sf, current_ord, current_url);
454
455             current_url_pos := current_url_pos + 1;
456             current_ord := current_ord + 1;
457         END LOOP;
458     END LOOP;
459
460     RETURN current_ord - 1;
461 END;
462 $$ LANGUAGE PLPGSQL;
463
464
465
466 -- NOTE: beware the use of bare perm IDs in the update_perm's below and in 
467 -- the 950 seed data file.  Update before merge to match current perm IDs! XXX
468
469
470 SELECT evergreen.upgrade_deps_block_check('0754', :eg_version);
471
472 INSERT INTO permission.perm_list (id, code, description) 
473     VALUES ( 
474         543, 
475         'URL_VERIFY',
476         oils_i18n_gettext(
477             543, 
478             'Allows a user to process and verify ULSs', 
479             'ppl', 
480             'description'
481         )
482     );
483
484
485 INSERT INTO permission.perm_list (id, code, description) 
486     VALUES ( 
487         544, 
488         'URL_VERIFY_UPDATE_SETTINGS',
489         oils_i18n_gettext(
490             544, 
491             'Allows a user to configure URL verification org unit settings',
492             'ppl', 
493             'description'
494         )
495     );
496
497
498 INSERT INTO permission.perm_list (id, code, description) 
499     VALUES ( 
500         545, 
501         'SAVED_FILTER_DIALOG_FILTERS',
502         oils_i18n_gettext(
503             545, 
504             'Allows users to save and load sets of filters for filter dialogs, available in certain staff interfaces',
505             'ppl', 
506             'description'
507         )
508     );
509
510
511 INSERT INTO config.settings_group (name, label)
512     VALUES (
513         'url_verify',
514         oils_i18n_gettext(
515             'url_verify',
516             'URL Verify',
517             'csg',
518             'label'
519         )
520     );
521
522 INSERT INTO config.org_unit_setting_type
523     (name, grp, label, description, datatype, update_perm)
524     VALUES (
525         'url_verify.url_verification_delay',
526         'url_verify',
527         oils_i18n_gettext(
528             'url_verify.url_verification_delay',
529             'Number of seconds to wait between URL test attempts.',
530             'coust',
531             'label'
532         ),
533         oils_i18n_gettext(
534             'url_verify.url_verification_delay',
535             'Throttling mechanism for batch URL verification runs.  Each running process will wait this number of seconds after a URL test before performing the next.',
536             'coust',
537             'description'
538         ),
539         'integer',
540         544
541     );
542
543 INSERT INTO config.org_unit_setting_type
544     (name, grp, label, description, datatype, update_perm)
545     VALUES (
546         'url_verify.url_verification_max_redirects',
547         'url_verify',
548         oils_i18n_gettext(
549             'url_verify.url_verification_max_redirects',
550             'Maximum redirect lookups',
551             'coust',
552             'label'
553         ),
554         oils_i18n_gettext(
555             'url_verify.url_verification_max_redirects',
556             'For URLs returning 3XX redirects, this is the maximum number of redirects we will follow before giving up.',
557             'coust',
558             'description'
559         ),
560         'integer',
561         544
562     );
563
564 INSERT INTO config.org_unit_setting_type
565     (name, grp, label, description, datatype, update_perm)
566     VALUES (
567         'url_verify.url_verification_max_wait',
568         'url_verify',
569         oils_i18n_gettext(
570             'url_verify.url_verification_max_wait',
571             'Maximum wait time (in seconds) for a URL to lookup',
572             'coust',
573             'label'
574         ),
575         oils_i18n_gettext(
576             'url_verify.url_verification_max_wait',
577             'If we exceed the wait time, the URL is marked as a "timeout" and the system moves on to the next URL',
578             'coust',
579             'description'
580         ),
581         'integer',
582         544
583     );
584
585
586 INSERT INTO config.org_unit_setting_type
587     (name, grp, label, description, datatype, update_perm)
588     VALUES (
589         'url_verify.verification_batch_size',
590         'url_verify',
591         oils_i18n_gettext(
592             'url_verify.verification_batch_size',
593             'Number of URLs to test in parallel',
594             'coust',
595             'label'
596         ),
597         oils_i18n_gettext(
598             'url_verify.verification_batch_size',
599             'URLs are tested in batches.  This number defines the size of each batch and it directly relates to the number of back-end processes performing URL verification.',
600             'coust',
601             'description'
602         ),
603         'integer',
604         544
605     );
606
607
608 INSERT INTO config.filter_dialog_interface (key, description) VALUES (
609     'url_verify',
610     oils_i18n_gettext(
611         'url_verify',
612         'All Link Checker filter dialogs',
613         'cfdi',
614         'description'
615     )
616 );
617
618
619 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
620     'ui.grid_columns.url_verify.select_urls',
621     'gui',
622     FALSE,
623     oils_i18n_gettext(
624         'ui.grid_columns.url_verify.select_urls',
625         'Link Checker''s URL Selection interface''s saved columns',
626         'cust',
627         'label'
628     ),
629     oils_i18n_gettext(
630         'ui.grid_columns.url_verify.select_urls',
631         'Link Checker''s URL Selection interface''s saved columns',
632         'cust',
633         'description'
634     ),
635     'string'
636 );
637
638 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
639     'ui.grid_columns.url_verify.review_attempt',
640     'gui',
641     FALSE,
642     oils_i18n_gettext(
643         'ui.grid_columns.url_verify.review_attempt',
644         'Link Checker''s Review Attempt interface''s saved columns',
645         'cust',
646         'label'
647     ),
648     oils_i18n_gettext(
649         'ui.grid_columns.url_verify.review_attempt',
650         'Link Checker''s Review Attempt interface''s saved columns',
651         'cust',
652         'description'
653     ),
654     'string'
655 );
656
657
658
659
660 SELECT evergreen.upgrade_deps_block_check('0755', :eg_version);
661
662 INSERT INTO config.org_unit_setting_type
663     (name, label, description, grp, datatype, fm_class) VALUES
664 (
665     'acq.upload.default.create_po',
666     oils_i18n_gettext(
667         'acq.upload.default.create_po',
668         'Upload Create PO',
669         'coust',
670         'label'
671     ),
672      oils_i18n_gettext(
673         'acq.upload.default.create_po',
674         'Create a purchase order by default during ACQ file upload',
675         'coust',
676         'description'
677     ),
678    'acq',
679     'bool',
680     NULL
681 ), (
682     'acq.upload.default.activate_po',
683     oils_i18n_gettext(
684         'acq.upload.default.activate_po',
685         'Upload Activate PO',
686         'coust',
687         'label'
688     ),
689      oils_i18n_gettext(
690         'acq.upload.default.activate_po',
691         'Activate the purchase order by default during ACQ file upload',
692         'coust',
693         'description'
694     ),
695     'acq',
696     'bool',
697     NULL
698 ), (
699     'acq.upload.default.provider',
700     oils_i18n_gettext(
701         'acq.upload.default.provider',
702         'Upload Default Provider',
703         'coust',
704         'label'
705     ),
706      oils_i18n_gettext(
707         'acq.upload.default.provider',
708         'Default provider to use during ACQ file upload',
709         'coust',
710         'description'
711     ),
712     'acq',
713     'link',
714     'acqpro'
715 ), (
716     'acq.upload.default.vandelay.match_set',
717     oils_i18n_gettext(
718         'acq.upload.default.vandelay.match_set',
719         'Upload Default Match Set',
720         'coust',
721         'label'
722     ),
723      oils_i18n_gettext(
724         'acq.upload.default.vandelay.match_set',
725         'Default match set to use during ACQ file upload',
726         'coust',
727         'description'
728     ),
729     'acq',
730     'link',
731     'vms'
732 ), (
733     'acq.upload.default.vandelay.merge_profile',
734     oils_i18n_gettext(
735         'acq.upload.default.vandelay.merge_profile',
736         'Upload Default Merge Profile',
737         'coust',
738         'label'
739     ),
740      oils_i18n_gettext(
741         'acq.upload.default.vandelay.merge_profile',
742         'Default merge profile to use during ACQ file upload',
743         'coust',
744         'description'
745     ),
746     'acq',
747     'link',
748     'vmp'
749 ), (
750     'acq.upload.default.vandelay.import_non_matching',
751     oils_i18n_gettext(
752         'acq.upload.default.vandelay.import_non_matching',
753         'Upload Import Non Matching by Default',
754         'coust',
755         'label'
756     ),
757      oils_i18n_gettext(
758         'acq.upload.default.vandelay.import_non_matching',
759         'Import non-matching records by default during ACQ file upload',
760         'coust',
761         'description'
762     ),
763     'acq',
764     'bool',
765     NULL
766 ), (
767     'acq.upload.default.vandelay.merge_on_exact',
768     oils_i18n_gettext(
769         'acq.upload.default.vandelay.merge_on_exact',
770         'Upload Merge on Exact Match by Default',
771         'coust',
772         'label'
773     ),
774      oils_i18n_gettext(
775         'acq.upload.default.vandelay.merge_on_exact',
776         'Merge records on exact match by default during ACQ file upload',
777         'coust',
778         'description'
779     ),
780     'acq',
781     'bool',
782     NULL
783 ), (
784     'acq.upload.default.vandelay.merge_on_best',
785     oils_i18n_gettext(
786         'acq.upload.default.vandelay.merge_on_best',
787         'Upload Merge on Best Match by Default',
788         'coust',
789         'label'
790     ),
791      oils_i18n_gettext(
792         'acq.upload.default.vandelay.merge_on_best',
793         'Merge records on best match by default during ACQ file upload',
794         'coust',
795         'description'
796     ),
797     'acq',
798     'bool',
799     NULL
800 ), (
801     'acq.upload.default.vandelay.merge_on_single',
802     oils_i18n_gettext(
803         'acq.upload.default.vandelay.merge_on_single',
804         'Upload Merge on Single Match by Default',
805         'coust',
806         'label'
807     ),
808      oils_i18n_gettext(
809         'acq.upload.default.vandelay.merge_on_single',
810         'Merge records on single match by default during ACQ file upload',
811         'coust',
812         'description'
813     ),
814     'acq',
815     'bool',
816     NULL
817 ), (
818     'acq.upload.default.vandelay.quality_ratio',
819     oils_i18n_gettext(
820         'acq.upload.default.vandelay.quality_ratio',
821         'Upload Default Min. Quality Ratio',
822         'coust',
823         'label'
824     ),
825      oils_i18n_gettext(
826         'acq.upload.default.vandelay.quality_ratio',
827         'Default minimum quality ratio used during ACQ file upload',
828         'coust',
829         'description'
830     ),
831     'acq',
832     'integer',
833     NULL
834 ), (
835     'acq.upload.default.vandelay.low_quality_fall_thru_profile',
836     oils_i18n_gettext(
837         'acq.upload.default.vandelay.low_quality_fall_thru_profile',
838         'Upload Default Insufficient Quality Fall-Thru Profile',
839         'coust',
840         'label'
841     ),
842      oils_i18n_gettext(
843         'acq.upload.default.vandelay.low_quality_fall_thru_profile',
844         'Default low-quality fall through profile used during ACQ file upload',
845         'coust',
846         'description'
847     ),
848     'acq',
849     'link',
850     'vmp'
851 ), (
852     'acq.upload.default.vandelay.load_item_for_imported',
853     oils_i18n_gettext(
854         'acq.upload.default.vandelay.load_item_for_imported',
855         'Upload Load Items for Imported Records by Default',
856         'coust',
857         'label'
858     ),
859      oils_i18n_gettext(
860         'acq.upload.default.vandelay.load_item_for_imported',
861         'Load items for imported records by default during ACQ file upload',
862         'coust',
863         'description'
864     ),
865     'acq',
866     'bool',
867     NULL
868 );
869
870
871 SELECT evergreen.upgrade_deps_block_check('0756', :eg_version);
872
873 -- Drop some lingering old functions in search schema
874 DROP FUNCTION IF EXISTS search.staged_fts(INT,INT,TEXT,INT[],INT[],TEXT[],TEXT[],TEXT[],TEXT[],TEXT[],TEXT[],TEXT[],TEXT,TEXT,TEXT,TEXT[],TEXT,REAL,TEXT,BOOL,BOOL,BOOL,INT,INT,INT);
875 DROP FUNCTION IF EXISTS search.parse_search_args(TEXT);
876 DROP FUNCTION IF EXISTS search.explode_array(ANYARRAY);
877 DROP FUNCTION IF EXISTS search.pick_table(TEXT);
878
879 -- Now drop query_parser_fts and related
880 DROP FUNCTION IF EXISTS search.query_parser_fts(INT,INT,TEXT,INT[],INT[],INT,INT,INT,BOOL,BOOL,INT);
881 DROP TYPE IF EXISTS search.search_result;
882 DROP TYPE IF EXISTS search.search_args;
883
884
885 SELECT evergreen.upgrade_deps_block_check('0757', :eg_version);
886
887 SET search_path = public, pg_catalog;
888
889 DO $$
890 DECLARE
891 lang TEXT;
892 BEGIN
893 FOR lang IN SELECT substring(pptsd.dictname from '(.*)_stem$') AS lang FROM pg_catalog.pg_ts_dict pptsd JOIN pg_catalog.pg_namespace ppn ON ppn.oid = pptsd.dictnamespace
894 WHERE ppn.nspname = 'pg_catalog' AND pptsd.dictname LIKE '%_stem' LOOP
895 RAISE NOTICE 'FOUND LANGUAGE %', lang;
896
897 EXECUTE 'DROP TEXT SEARCH DICTIONARY IF EXISTS ' || lang || '_nostop CASCADE;
898 CREATE TEXT SEARCH DICTIONARY ' || lang || '_nostop (TEMPLATE=pg_catalog.snowball, language=''' || lang || ''');
899 COMMENT ON TEXT SEARCH DICTIONARY ' || lang || '_nostop IS ''' ||lang || ' snowball stemmer with no stopwords for ASCII words only.'';
900 CREATE TEXT SEARCH CONFIGURATION ' || lang || '_nostop ( COPY = pg_catalog.' || lang || ' );
901 ALTER TEXT SEARCH CONFIGURATION ' || lang || '_nostop ALTER MAPPING FOR word, hword, hword_part WITH pg_catalog.simple;
902 ALTER TEXT SEARCH CONFIGURATION ' || lang || '_nostop ALTER MAPPING FOR asciiword, asciihword, hword_asciipart WITH ' || lang || '_nostop;';
903
904 END LOOP;
905 END;
906 $$;
907 CREATE TEXT SEARCH CONFIGURATION keyword ( COPY = english_nostop );
908 CREATE TEXT SEARCH CONFIGURATION "default" ( COPY = english_nostop );
909
910 SET search_path = evergreen, public, pg_catalog;
911
912 ALTER TABLE config.metabib_class
913     ADD COLUMN a_weight NUMERIC  DEFAULT 1.0 NOT NULL,
914     ADD COLUMN b_weight NUMERIC  DEFAULT 0.4 NOT NULL,
915     ADD COLUMN c_weight NUMERIC  DEFAULT 0.2 NOT NULL,
916     ADD COLUMN d_weight NUMERIC  DEFAULT 0.1 NOT NULL;
917
918 CREATE TABLE config.ts_config_list (
919     id      TEXT PRIMARY KEY,
920     name    TEXT NOT NULL
921 );
922 COMMENT ON TABLE config.ts_config_list IS $$
923 Full Text Configs
924
925 A list of full text configs with names and descriptions.
926 $$;
927
928 CREATE TABLE config.metabib_class_ts_map (
929     id              SERIAL PRIMARY KEY,
930     field_class     TEXT NOT NULL REFERENCES config.metabib_class (name),
931     ts_config       TEXT NOT NULL REFERENCES config.ts_config_list (id),
932     active          BOOL NOT NULL DEFAULT TRUE,
933     index_weight    CHAR(1) NOT NULL DEFAULT 'C' CHECK (index_weight IN ('A','B','C','D')),
934     index_lang      TEXT NULL,
935     search_lang     TEXT NULL,
936     always          BOOL NOT NULL DEFAULT true
937 );
938 COMMENT ON TABLE config.metabib_class_ts_map IS $$
939 Text Search Configs for metabib class indexing
940
941 This table contains text search config definitions for
942 storing index_vector values.
943 $$;
944
945 CREATE TABLE config.metabib_field_ts_map (
946     id              SERIAL PRIMARY KEY,
947     metabib_field   INT NOT NULL REFERENCES config.metabib_field (id),
948     ts_config       TEXT NOT NULL REFERENCES config.ts_config_list (id),
949     active          BOOL NOT NULL DEFAULT TRUE,
950     index_weight    CHAR(1) NOT NULL DEFAULT 'C' CHECK (index_weight IN ('A','B','C','D')),
951     index_lang      TEXT NULL,
952     search_lang     TEXT NULL
953 );
954 COMMENT ON TABLE config.metabib_field_ts_map IS $$
955 Text Search Configs for metabib field indexing
956
957 This table contains text search config definitions for
958 storing index_vector values.
959 $$;
960
961 CREATE TABLE metabib.combined_identifier_field_entry (
962     record          BIGINT      NOT NULL,
963     metabib_field   INT         NULL,
964     index_vector    tsvector    NOT NULL
965 );
966 CREATE UNIQUE INDEX metabib_combined_identifier_field_entry_fakepk_idx ON metabib.combined_identifier_field_entry (record, COALESCE(metabib_field::TEXT,''));
967 CREATE INDEX metabib_combined_identifier_field_entry_index_vector_idx ON metabib.combined_identifier_field_entry USING GIST (index_vector);
968 CREATE INDEX metabib_combined_identifier_field_source_idx ON metabib.combined_identifier_field_entry (metabib_field);
969
970 CREATE TABLE metabib.combined_title_field_entry (
971         record          BIGINT          NOT NULL,
972         metabib_field           INT             NULL,
973         index_vector    tsvector        NOT NULL
974 );
975 CREATE UNIQUE INDEX metabib_combined_title_field_entry_fakepk_idx ON metabib.combined_title_field_entry (record, COALESCE(metabib_field::TEXT,''));
976 CREATE INDEX metabib_combined_title_field_entry_index_vector_idx ON metabib.combined_title_field_entry USING GIST (index_vector);
977 CREATE INDEX metabib_combined_title_field_source_idx ON metabib.combined_title_field_entry (metabib_field);
978
979 CREATE TABLE metabib.combined_author_field_entry (
980         record          BIGINT          NOT NULL,
981         metabib_field           INT             NULL,
982         index_vector    tsvector        NOT NULL
983 );
984 CREATE UNIQUE INDEX metabib_combined_author_field_entry_fakepk_idx ON metabib.combined_author_field_entry (record, COALESCE(metabib_field::TEXT,''));
985 CREATE INDEX metabib_combined_author_field_entry_index_vector_idx ON metabib.combined_author_field_entry USING GIST (index_vector);
986 CREATE INDEX metabib_combined_author_field_source_idx ON metabib.combined_author_field_entry (metabib_field);
987
988 CREATE TABLE metabib.combined_subject_field_entry (
989         record          BIGINT          NOT NULL,
990         metabib_field           INT             NULL,
991         index_vector    tsvector        NOT NULL
992 );
993 CREATE UNIQUE INDEX metabib_combined_subject_field_entry_fakepk_idx ON metabib.combined_subject_field_entry (record, COALESCE(metabib_field::TEXT,''));
994 CREATE INDEX metabib_combined_subject_field_entry_index_vector_idx ON metabib.combined_subject_field_entry USING GIST (index_vector);
995 CREATE INDEX metabib_combined_subject_field_source_idx ON metabib.combined_subject_field_entry (metabib_field);
996
997 CREATE TABLE metabib.combined_keyword_field_entry (
998         record          BIGINT          NOT NULL,
999         metabib_field           INT             NULL,
1000         index_vector    tsvector        NOT NULL
1001 );
1002 CREATE UNIQUE INDEX metabib_combined_keyword_field_entry_fakepk_idx ON metabib.combined_keyword_field_entry (record, COALESCE(metabib_field::TEXT,''));
1003 CREATE INDEX metabib_combined_keyword_field_entry_index_vector_idx ON metabib.combined_keyword_field_entry USING GIST (index_vector);
1004 CREATE INDEX metabib_combined_keyword_field_source_idx ON metabib.combined_keyword_field_entry (metabib_field);
1005
1006 CREATE TABLE metabib.combined_series_field_entry (
1007         record          BIGINT          NOT NULL,
1008         metabib_field           INT             NULL,
1009         index_vector    tsvector        NOT NULL
1010 );
1011 CREATE UNIQUE INDEX metabib_combined_series_field_entry_fakepk_idx ON metabib.combined_series_field_entry (record, COALESCE(metabib_field::TEXT,''));
1012 CREATE INDEX metabib_combined_series_field_entry_index_vector_idx ON metabib.combined_series_field_entry USING GIST (index_vector);
1013 CREATE INDEX metabib_combined_series_field_source_idx ON metabib.combined_series_field_entry (metabib_field);
1014
1015 CREATE OR REPLACE FUNCTION metabib.update_combined_index_vectors(bib_id BIGINT) RETURNS VOID AS $func$
1016 BEGIN
1017     DELETE FROM metabib.combined_keyword_field_entry WHERE record = bib_id;
1018     INSERT INTO metabib.combined_keyword_field_entry(record, metabib_field, index_vector)
1019         SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1020         FROM metabib.keyword_field_entry WHERE source = bib_id GROUP BY field;
1021     INSERT INTO metabib.combined_keyword_field_entry(record, metabib_field, index_vector)
1022         SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1023         FROM metabib.keyword_field_entry WHERE source = bib_id;
1024
1025     DELETE FROM metabib.combined_title_field_entry WHERE record = bib_id;
1026     INSERT INTO metabib.combined_title_field_entry(record, metabib_field, index_vector)
1027         SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1028         FROM metabib.title_field_entry WHERE source = bib_id GROUP BY field;
1029     INSERT INTO metabib.combined_title_field_entry(record, metabib_field, index_vector)
1030         SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1031         FROM metabib.title_field_entry WHERE source = bib_id;
1032
1033     DELETE FROM metabib.combined_author_field_entry WHERE record = bib_id;
1034     INSERT INTO metabib.combined_author_field_entry(record, metabib_field, index_vector)
1035         SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1036         FROM metabib.author_field_entry WHERE source = bib_id GROUP BY field;
1037     INSERT INTO metabib.combined_author_field_entry(record, metabib_field, index_vector)
1038         SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1039         FROM metabib.author_field_entry WHERE source = bib_id;
1040
1041     DELETE FROM metabib.combined_subject_field_entry WHERE record = bib_id;
1042     INSERT INTO metabib.combined_subject_field_entry(record, metabib_field, index_vector)
1043         SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1044         FROM metabib.subject_field_entry WHERE source = bib_id GROUP BY field;
1045     INSERT INTO metabib.combined_subject_field_entry(record, metabib_field, index_vector)
1046         SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1047         FROM metabib.subject_field_entry WHERE source = bib_id;
1048
1049     DELETE FROM metabib.combined_series_field_entry WHERE record = bib_id;
1050     INSERT INTO metabib.combined_series_field_entry(record, metabib_field, index_vector)
1051         SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1052         FROM metabib.series_field_entry WHERE source = bib_id GROUP BY field;
1053     INSERT INTO metabib.combined_series_field_entry(record, metabib_field, index_vector)
1054         SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1055         FROM metabib.series_field_entry WHERE source = bib_id;
1056
1057     DELETE FROM metabib.combined_identifier_field_entry WHERE record = bib_id;
1058     INSERT INTO metabib.combined_identifier_field_entry(record, metabib_field, index_vector)
1059         SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1060         FROM metabib.identifier_field_entry WHERE source = bib_id GROUP BY field;
1061     INSERT INTO metabib.combined_identifier_field_entry(record, metabib_field, index_vector)
1062         SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
1063         FROM metabib.identifier_field_entry WHERE source = bib_id;
1064
1065 END;
1066 $func$ LANGUAGE PLPGSQL;
1067
1068 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT, skip_facet BOOL DEFAULT FALSE, skip_browse BOOL DEFAULT FALSE, skip_search BOOL DEFAULT FALSE ) RETURNS VOID AS $func$
1069 DECLARE
1070     fclass          RECORD;
1071     ind_data        metabib.field_entry_template%ROWTYPE;
1072     mbe_row         metabib.browse_entry%ROWTYPE;
1073     mbe_id          BIGINT;
1074 BEGIN
1075     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
1076     IF NOT FOUND THEN
1077         IF NOT skip_search THEN
1078             FOR fclass IN SELECT * FROM config.metabib_class LOOP
1079                 -- RAISE NOTICE 'Emptying out %', fclass.name;
1080                 EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
1081             END LOOP;
1082         END IF;
1083         IF NOT skip_facet THEN
1084             DELETE FROM metabib.facet_entry WHERE source = bib_id;
1085         END IF;
1086         IF NOT skip_browse THEN
1087             DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
1088         END IF;
1089     END IF;
1090
1091     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
1092         IF ind_data.field < 0 THEN
1093             ind_data.field = -1 * ind_data.field;
1094         END IF;
1095
1096         IF ind_data.facet_field AND NOT skip_facet THEN
1097             INSERT INTO metabib.facet_entry (field, source, value)
1098                 VALUES (ind_data.field, ind_data.source, ind_data.value);
1099         END IF;
1100
1101         IF ind_data.browse_field AND NOT skip_browse THEN
1102             -- A caveat about this SELECT: this should take care of replacing
1103             -- old mbe rows when data changes, but not if normalization (by
1104             -- which I mean specifically the output of
1105             -- evergreen.oils_tsearch2()) changes.  It may or may not be
1106             -- expensive to add a comparison of index_vector to index_vector
1107             -- to the WHERE clause below.
1108             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = ind_data.value;
1109             IF FOUND THEN
1110                 mbe_id := mbe_row.id;
1111             ELSE
1112                 INSERT INTO metabib.browse_entry (value) VALUES
1113                     (metabib.browse_normalize(ind_data.value, ind_data.field));
1114                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
1115             END IF;
1116
1117             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
1118                 VALUES (mbe_id, ind_data.field, ind_data.source);
1119         END IF;
1120
1121         IF ind_data.search_field AND NOT skip_search THEN
1122             EXECUTE $$
1123                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
1124                     VALUES ($$ ||
1125                         quote_literal(ind_data.field) || $$, $$ ||
1126                         quote_literal(ind_data.source) || $$, $$ ||
1127                         quote_literal(ind_data.value) ||
1128                     $$);$$;
1129         END IF;
1130
1131     END LOOP;
1132
1133     IF NOT skip_search THEN
1134         PERFORM metabib.update_combined_index_vectors(bib_id);
1135     END IF;
1136
1137     RETURN;
1138 END;
1139 $func$ LANGUAGE PLPGSQL;
1140
1141 DROP FUNCTION IF EXISTS evergreen.oils_tsearch2() CASCADE;
1142 DROP FUNCTION IF EXISTS public.oils_tsearch2() CASCADE;
1143
1144 CREATE OR REPLACE FUNCTION public.oils_tsearch2 () RETURNS TRIGGER AS $$
1145 DECLARE
1146     normalizer      RECORD;
1147     value           TEXT := '';
1148     temp_vector     TEXT := '';
1149     ts_rec          RECORD;
1150     cur_weight      "char";
1151 BEGIN
1152     value := NEW.value;
1153     NEW.index_vector = ''::tsvector;
1154
1155     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
1156         FOR normalizer IN
1157             SELECT  n.func AS func,
1158                     n.param_count AS param_count,
1159                     m.params AS params
1160               FROM  config.index_normalizer n
1161                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
1162               WHERE field = NEW.field
1163               ORDER BY m.pos LOOP
1164                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
1165                     quote_literal( value ) ||
1166                     CASE
1167                         WHEN normalizer.param_count > 0
1168                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
1169                             ELSE ''
1170                         END ||
1171                     ')' INTO value;
1172
1173         END LOOP;
1174         NEW.value = value;
1175     END IF;
1176
1177     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
1178         value :=  ARRAY_TO_STRING(
1179             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
1180         );
1181         value := public.search_normalize(value);
1182         NEW.index_vector = to_tsvector(TG_ARGV[0]::regconfig, value);
1183     ELSIF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
1184         FOR ts_rec IN
1185             SELECT ts_config, index_weight
1186             FROM config.metabib_class_ts_map
1187             WHERE field_class = TG_ARGV[0]
1188                 AND index_lang IS NULL OR EXISTS (SELECT 1 FROM metabib.record_attr WHERE id = NEW.source AND index_lang IN(attrs->'item_lang',attrs->'language'))
1189                 AND always OR NOT EXISTS (SELECT 1 FROM config.metabib_field_ts_map WHERE metabib_field = NEW.field)
1190             UNION
1191             SELECT ts_config, index_weight
1192             FROM config.metabib_field_ts_map
1193             WHERE metabib_field = NEW.field
1194                AND index_lang IS NULL OR EXISTS (SELECT 1 FROM metabib.record_attr WHERE id = NEW.source AND index_lang IN(attrs->'item_lang',attrs->'language'))
1195             ORDER BY index_weight ASC
1196         LOOP
1197             IF cur_weight IS NOT NULL AND cur_weight != ts_rec.index_weight THEN
1198                 NEW.index_vector = NEW.index_vector || setweight(temp_vector::tsvector,cur_weight);
1199                 temp_vector = '';
1200             END IF;
1201             cur_weight = ts_rec.index_weight;
1202             SELECT INTO temp_vector temp_vector || ' ' || to_tsvector(ts_rec.ts_config::regconfig, value)::TEXT;
1203         END LOOP;
1204         NEW.index_vector = NEW.index_vector || setweight(temp_vector::tsvector,cur_weight);
1205     ELSE
1206         NEW.index_vector = to_tsvector(TG_ARGV[0]::regconfig, value);
1207     END IF;
1208
1209     RETURN NEW;
1210 END;
1211 $$ LANGUAGE PLPGSQL;
1212
1213 CREATE TRIGGER authority_full_rec_fti_trigger
1214     BEFORE UPDATE OR INSERT ON authority.full_rec
1215     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
1216
1217 CREATE TRIGGER authority_simple_heading_fti_trigger
1218     BEFORE UPDATE OR INSERT ON authority.simple_heading
1219     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
1220
1221 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
1222     BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
1223     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('identifier');
1224
1225 CREATE TRIGGER metabib_title_field_entry_fti_trigger
1226     BEFORE UPDATE OR INSERT ON metabib.title_field_entry
1227     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('title');
1228
1229 CREATE TRIGGER metabib_author_field_entry_fti_trigger
1230     BEFORE UPDATE OR INSERT ON metabib.author_field_entry
1231     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('author');
1232
1233 CREATE TRIGGER metabib_subject_field_entry_fti_trigger
1234     BEFORE UPDATE OR INSERT ON metabib.subject_field_entry
1235     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('subject');
1236
1237 CREATE TRIGGER metabib_keyword_field_entry_fti_trigger
1238     BEFORE UPDATE OR INSERT ON metabib.keyword_field_entry
1239     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
1240
1241 CREATE TRIGGER metabib_series_field_entry_fti_trigger
1242     BEFORE UPDATE OR INSERT ON metabib.series_field_entry
1243     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('series');
1244
1245 CREATE TRIGGER metabib_browse_entry_fti_trigger
1246     BEFORE INSERT OR UPDATE ON metabib.browse_entry
1247     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
1248
1249 CREATE TRIGGER metabib_full_rec_fti_trigger
1250     BEFORE UPDATE OR INSERT ON metabib.real_full_rec
1251     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('default');
1252
1253 INSERT INTO config.ts_config_list(id, name) VALUES
1254     ('simple','Non-Stemmed Simple'),
1255     ('danish_nostop','Danish Stemmed'),
1256     ('dutch_nostop','Dutch Stemmed'),
1257     ('english_nostop','English Stemmed'),
1258     ('finnish_nostop','Finnish Stemmed'),
1259     ('french_nostop','French Stemmed'),
1260     ('german_nostop','German Stemmed'),
1261     ('hungarian_nostop','Hungarian Stemmed'),
1262     ('italian_nostop','Italian Stemmed'),
1263     ('norwegian_nostop','Norwegian Stemmed'),
1264     ('portuguese_nostop','Portuguese Stemmed'),
1265     ('romanian_nostop','Romanian Stemmed'),
1266     ('russian_nostop','Russian Stemmed'),
1267     ('spanish_nostop','Spanish Stemmed'),
1268     ('swedish_nostop','Swedish Stemmed'),
1269     ('turkish_nostop','Turkish Stemmed');
1270
1271 INSERT INTO config.metabib_class_ts_map(field_class, ts_config, index_weight, always) VALUES
1272     ('keyword','simple','A',true),
1273     ('keyword','english_nostop','C',true),
1274     ('title','simple','A',true),
1275     ('title','english_nostop','C',true),
1276     ('author','simple','A',true),
1277     ('author','english_nostop','C',true),
1278     ('series','simple','A',true),
1279     ('series','english_nostop','C',true),
1280     ('subject','simple','A',true),
1281     ('subject','english_nostop','C',true),
1282     ('identifier','simple','A',true);
1283
1284 CREATE OR REPLACE FUNCTION evergreen.rel_bump(terms TEXT[], value TEXT, bumps TEXT[], mults NUMERIC[]) RETURNS NUMERIC AS
1285 $BODY$
1286 use strict;
1287 my ($terms,$value,$bumps,$mults) = @_;
1288
1289 my $retval = 1;
1290
1291 for (my $id = 0; $id < @$bumps; $id++) {
1292         if ($bumps->[$id] eq 'first_word') {
1293                 $retval *= $mults->[$id] if ($value =~ /^$terms->[0]/);
1294         } elsif ($bumps->[$id] eq 'full_match') {
1295                 my $fullmatch = join(' ', @$terms);
1296                 $retval *= $mults->[$id] if ($value =~ /^$fullmatch$/);
1297         } elsif ($bumps->[$id] eq 'word_order') {
1298                 my $wordorder = join('.*', @$terms);
1299                 $retval *= $mults->[$id] if ($value =~ /$wordorder/);
1300         }
1301 }
1302 return $retval;
1303 $BODY$ LANGUAGE plperlu IMMUTABLE STRICT COST 100;
1304
1305 /* ** This happens in the supplemental script **
1306
1307 UPDATE metabib.identifier_field_entry set value = value;
1308 UPDATE metabib.title_field_entry set value = value;
1309 UPDATE metabib.author_field_entry set value = value;
1310 UPDATE metabib.subject_field_entry set value = value;
1311 UPDATE metabib.keyword_field_entry set value = value;
1312 UPDATE metabib.series_field_entry set value = value;
1313
1314 SELECT metabib.update_combined_index_vectors(id)
1315     FROM biblio.record_entry
1316     WHERE NOT deleted;
1317
1318 */
1319
1320 SELECT evergreen.upgrade_deps_block_check('0758', :eg_version);
1321
1322 INSERT INTO config.settings_group (name, label) VALUES
1323     ('vandelay', 'Vandelay');
1324
1325 INSERT INTO config.org_unit_setting_type (name, grp, label, datatype, fm_class) VALUES
1326     ('vandelay.default_match_set', 'vandelay', 'Default Record Match Set', 'link', 'vms');
1327
1328
1329 SELECT evergreen.upgrade_deps_block_check('0759', :eg_version);
1330
1331 CREATE TABLE actor.org_unit_proximity_adjustment (
1332     id                  SERIAL   PRIMARY KEY,
1333     item_circ_lib       INT         REFERENCES actor.org_unit (id),
1334     item_owning_lib     INT         REFERENCES actor.org_unit (id),
1335     copy_location       INT         REFERENCES asset.copy_location (id),
1336     hold_pickup_lib     INT         REFERENCES actor.org_unit (id),
1337     hold_request_lib    INT         REFERENCES actor.org_unit (id),
1338     pos                 INT         NOT NULL DEFAULT 0,
1339     absolute_adjustment BOOL        NOT NULL DEFAULT FALSE,
1340     prox_adjustment     NUMERIC,
1341     circ_mod            TEXT,       -- REFERENCES config.circ_modifier (code),
1342     CONSTRAINT prox_adj_criterium CHECK (COALESCE(item_circ_lib::TEXT,item_owning_lib::TEXT,copy_location::TEXT,hold_pickup_lib::TEXT,hold_request_lib::TEXT,circ_mod) IS NOT NULL)
1343 );
1344 CREATE UNIQUE INDEX prox_adj_once_idx ON actor.org_unit_proximity_adjustment (item_circ_lib,item_owning_lib,copy_location,hold_pickup_lib,hold_request_lib,circ_mod);
1345 CREATE INDEX prox_adj_circ_lib_idx ON actor.org_unit_proximity_adjustment (item_circ_lib);
1346 CREATE INDEX prox_adj_owning_lib_idx ON actor.org_unit_proximity_adjustment (item_owning_lib);
1347 CREATE INDEX prox_adj_copy_location_idx ON actor.org_unit_proximity_adjustment (copy_location);
1348 CREATE INDEX prox_adj_pickup_lib_idx ON actor.org_unit_proximity_adjustment (hold_pickup_lib);
1349 CREATE INDEX prox_adj_request_lib_idx ON actor.org_unit_proximity_adjustment (hold_request_lib);
1350 CREATE INDEX prox_adj_circ_mod_idx ON actor.org_unit_proximity_adjustment (circ_mod);
1351
1352 CREATE OR REPLACE FUNCTION actor.org_unit_ancestors_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
1353     WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
1354             SELECT $1, 0
1355         UNION
1356             SELECT ou.parent_ou, ouad.distance+1
1357             FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON (ou.id = ouad.id)
1358             WHERE ou.parent_ou IS NOT NULL
1359     )
1360     SELECT * FROM org_unit_ancestors_distance;
1361 $$ LANGUAGE SQL STABLE ROWS 1;
1362
1363 CREATE OR REPLACE FUNCTION action.hold_copy_calculated_proximity(
1364     ahr_id INT,
1365     acp_id BIGINT,
1366     copy_context_ou INT DEFAULT NULL
1367     -- TODO maybe? hold_context_ou INT DEFAULT NULL.  This would optionally
1368     -- support an "ahprox" measurement: adjust prox between copy circ lib and
1369     -- hold request lib, but I'm unsure whether to use this theoretical
1370     -- argument only in the baseline calculation or later in the other
1371     -- queries in this function.
1372 ) RETURNS NUMERIC AS $f$
1373 DECLARE
1374     aoupa           actor.org_unit_proximity_adjustment%ROWTYPE;
1375     ahr             action.hold_request%ROWTYPE;
1376     acp             asset.copy%ROWTYPE;
1377     acn             asset.call_number%ROWTYPE;
1378     acl             asset.copy_location%ROWTYPE;
1379     baseline_prox   NUMERIC;
1380
1381     icl_list        INT[];
1382     iol_list        INT[];
1383     isl_list        INT[];
1384     hpl_list        INT[];
1385     hrl_list        INT[];
1386
1387 BEGIN
1388
1389     SELECT * INTO ahr FROM action.hold_request WHERE id = ahr_id;
1390     SELECT * INTO acp FROM asset.copy WHERE id = acp_id;
1391     SELECT * INTO acn FROM asset.call_number WHERE id = acp.call_number;
1392     SELECT * INTO acl FROM asset.copy_location WHERE id = acp.location;
1393
1394     IF copy_context_ou IS NULL THEN
1395         copy_context_ou := acp.circ_lib;
1396     END IF;
1397
1398     -- First, gather the baseline proximity of "here" to pickup lib
1399     SELECT prox INTO baseline_prox FROM actor.org_unit_proximity WHERE from_org = copy_context_ou AND to_org = ahr.pickup_lib;
1400
1401     -- Find any absolute adjustments, and set the baseline prox to that
1402     SELECT  adj.* INTO aoupa
1403       FROM  actor.org_unit_proximity_adjustment adj
1404             LEFT JOIN actor.org_unit_ancestors_distance(copy_context_ou) acp_cl ON (acp_cl.id = adj.item_circ_lib)
1405             LEFT JOIN actor.org_unit_ancestors_distance(acn.owning_lib) acn_ol ON (acn_ol.id = adj.item_owning_lib)
1406             LEFT JOIN actor.org_unit_ancestors_distance(acl.owning_lib) acl_ol ON (acn_ol.id = adj.copy_location)
1407             LEFT JOIN actor.org_unit_ancestors_distance(ahr.pickup_lib) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib)
1408             LEFT JOIN actor.org_unit_ancestors_distance(ahr.request_lib) ahr_rl ON (ahr_rl.id = adj.hold_request_lib)
1409       WHERE (adj.circ_mod IS NULL OR adj.circ_mod = acp.circ_modifier) AND
1410         absolute_adjustment AND
1411         COALESCE(acp_cl.id, acn_ol.id, acl_ol.id, ahr_pl.id, ahr_rl.id) IS NOT NULL
1412       ORDER BY
1413             COALESCE(acp_cl.distance,999)
1414                 + COALESCE(acn_ol.distance,999)
1415                 + COALESCE(acl_ol.distance,999)
1416                 + COALESCE(ahr_pl.distance,999)
1417                 + COALESCE(ahr_rl.distance,999),
1418             adj.pos
1419       LIMIT 1;
1420
1421     IF FOUND THEN
1422         baseline_prox := aoupa.prox_adjustment;
1423     END IF;
1424
1425     -- Now find any relative adjustments, and change the baseline prox based on them
1426     FOR aoupa IN
1427         SELECT  adj.* 
1428           FROM  actor.org_unit_proximity_adjustment adj
1429                 LEFT JOIN actor.org_unit_ancestors_distance(copy_context_ou) acp_cl ON (acp_cl.id = adj.item_circ_lib)
1430                 LEFT JOIN actor.org_unit_ancestors_distance(acn.owning_lib) acn_ol ON (acn_ol.id = adj.item_owning_lib)
1431                 LEFT JOIN actor.org_unit_ancestors_distance(acl.owning_lib) acl_ol ON (acn_ol.id = adj.copy_location)
1432                 LEFT JOIN actor.org_unit_ancestors_distance(ahr.pickup_lib) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib)
1433                 LEFT JOIN actor.org_unit_ancestors_distance(ahr.request_lib) ahr_rl ON (ahr_rl.id = adj.hold_request_lib)
1434           WHERE (adj.circ_mod IS NULL OR adj.circ_mod = acp.circ_modifier) AND
1435             NOT absolute_adjustment AND
1436             COALESCE(acp_cl.id, acn_ol.id, acl_ol.id, ahr_pl.id, ahr_rl.id) IS NOT NULL
1437     LOOP
1438         baseline_prox := baseline_prox + aoupa.prox_adjustment;
1439     END LOOP;
1440
1441     RETURN baseline_prox;
1442 END;
1443 $f$ LANGUAGE PLPGSQL;
1444
1445 ALTER TABLE actor.org_unit_proximity_adjustment
1446     ADD CONSTRAINT actor_org_unit_proximity_adjustment_circ_mod_fkey
1447     FOREIGN KEY (circ_mod) REFERENCES config.circ_modifier (code)
1448     DEFERRABLE INITIALLY DEFERRED;
1449
1450 ALTER TABLE action.hold_copy_map ADD COLUMN proximity NUMERIC;
1451
1452
1453 SELECT evergreen.upgrade_deps_block_check('0760', :eg_version);
1454
1455 CREATE TABLE config.best_hold_order(
1456     id          SERIAL      PRIMARY KEY,    -- (metadata)
1457     name        TEXT        UNIQUE,   -- i18n (metadata)
1458     pprox       INT, -- copy capture <-> pickup lib prox
1459     hprox       INT, -- copy circ lib <-> request lib prox
1460     aprox       INT, -- copy circ lib <-> pickup lib ADJUSTED prox on ahcm
1461     approx      INT, -- copy capture <-> pickup lib ADJUSTED prox from function
1462     priority    INT, -- group hold priority
1463     cut         INT, -- cut-in-line
1464     depth       INT, -- selection depth
1465     htime       INT, -- time since last home-lib circ exceeds org-unit setting
1466     rtime       INT, -- request time
1467     shtime      INT  -- time since copy last trip home exceeds org-unit setting
1468 );
1469
1470 -- At least one of these columns must contain a non-null value
1471 ALTER TABLE config.best_hold_order ADD CHECK ((
1472     pprox IS NOT NULL OR
1473     hprox IS NOT NULL OR
1474     aprox IS NOT NULL OR
1475     priority IS NOT NULL OR
1476     cut IS NOT NULL OR
1477     depth IS NOT NULL OR
1478     htime IS NOT NULL OR
1479     rtime IS NOT NULL
1480 ));
1481
1482 INSERT INTO config.best_hold_order (
1483     name,
1484     pprox, aprox, priority, cut, depth, rtime, htime, hprox
1485 ) VALUES (
1486     'Traditional',
1487     1, 2, 3, 4, 5, 6, 7, 8
1488 );
1489
1490 INSERT INTO config.best_hold_order (
1491     name,
1492     hprox, pprox, aprox, priority, cut, depth, rtime, htime
1493 ) VALUES (
1494     'Traditional with Holds-always-go-home',
1495     1, 2, 3, 4, 5, 6, 7, 8
1496 );
1497
1498 INSERT INTO config.best_hold_order (
1499     name,
1500     htime, hprox, pprox, aprox, priority, cut, depth, rtime
1501 ) VALUES (
1502     'Traditional with Holds-go-home',
1503     1, 2, 3, 4, 5, 6, 7, 8
1504 );
1505
1506 INSERT INTO config.best_hold_order (
1507     name,
1508     priority, cut, rtime, depth, pprox, hprox, aprox, htime
1509 ) VALUES (
1510     'FIFO',
1511     1, 2, 3, 4, 5, 6, 7, 8
1512 );
1513
1514 INSERT INTO config.best_hold_order (
1515     name,
1516     hprox, priority, cut, rtime, depth, pprox, aprox, htime
1517 ) VALUES (
1518     'FIFO with Holds-always-go-home',
1519     1, 2, 3, 4, 5, 6, 7, 8
1520 );
1521
1522 INSERT INTO config.best_hold_order (
1523     name,
1524     htime, priority, cut, rtime, depth, pprox, aprox, hprox
1525 ) VALUES (
1526     'FIFO with Holds-go-home',
1527     1, 2, 3, 4, 5, 6, 7, 8
1528 );
1529
1530 INSERT INTO permission.perm_list (
1531     id, code, description
1532 ) VALUES (
1533     546,
1534     'ADMIN_HOLD_CAPTURE_SORT',
1535     oils_i18n_gettext(
1536         546,
1537         'Allows a user to make changes to best-hold selection sort order',
1538         'ppl',
1539         'description'
1540     )
1541 );
1542
1543 INSERT INTO config.org_unit_setting_type (
1544     name, label, description, datatype, fm_class, update_perm, grp
1545 ) VALUES (
1546     'circ.hold_capture_order',
1547     oils_i18n_gettext(
1548         'circ.hold_capture_order',
1549         'Best-hold selection sort order',
1550         'coust',
1551         'label'
1552     ),
1553     oils_i18n_gettext(
1554         'circ.hold_capture_order',
1555         'Defines the sort order of holds when selecting a hold to fill using a given copy at capture time',
1556         'coust',
1557         'description'
1558     ),
1559     'link',
1560     'cbho',
1561     546,
1562     'holds'
1563 );
1564
1565 INSERT INTO config.org_unit_setting_type (
1566     name, label, description, datatype, update_perm, grp
1567 ) VALUES (
1568     'circ.hold_go_home_interval',
1569     oils_i18n_gettext(
1570         'circ.hold_go_home_interval',
1571         'Max foreign-circulation time',
1572         'coust',
1573         'label'
1574     ),
1575     oils_i18n_gettext(
1576         'circ.hold_go_home_interval',
1577         'Time a copy can spend circulating away from its circ lib before returning there to fill a hold (if one exists there)',
1578         'coust',
1579         'description'
1580     ),
1581     'interval',
1582     546,
1583     'holds'
1584 );
1585
1586 INSERT INTO actor.org_unit_setting (
1587     org_unit, name, value
1588 ) VALUES (
1589     (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
1590     'circ.hold_go_home_interval',
1591     '"6 months"'
1592 );
1593
1594 UPDATE actor.org_unit_setting SET
1595     name = 'circ.hold_capture_order',
1596     value = (SELECT id FROM config.best_hold_order WHERE name = 'FIFO')
1597 WHERE
1598     name = 'circ.holds_fifo' AND value ILIKE '%true%';
1599
1600
1601 SELECT evergreen.upgrade_deps_block_check('0762', :eg_version);
1602
1603 INSERT INTO config.internal_flag (name) VALUES ('ingest.skip_browse_indexing');
1604 INSERT INTO config.internal_flag (name) VALUES ('ingest.skip_search_indexing');
1605 INSERT INTO config.internal_flag (name) VALUES ('ingest.skip_facet_indexing');
1606
1607 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT, skip_facet BOOL DEFAULT FALSE, skip_browse BOOL DEFAULT FALSE, skip_search BOOL DEFAULT FALSE ) RETURNS VOID AS $func$
1608 DECLARE
1609     fclass          RECORD;
1610     ind_data        metabib.field_entry_template%ROWTYPE;
1611     mbe_row         metabib.browse_entry%ROWTYPE;
1612     mbe_id          BIGINT;
1613     b_skip_facet    BOOL;
1614     b_skip_browse   BOOL;
1615     b_skip_search   BOOL;
1616 BEGIN
1617
1618     SELECT COALESCE(NULLIF(skip_facet, FALSE), EXISTS (SELECT enabled FROM config.internal_flag WHERE name =  'ingest.skip_facet_indexing' AND enabled)) INTO b_skip_facet;
1619     SELECT COALESCE(NULLIF(skip_browse, FALSE), EXISTS (SELECT enabled FROM config.internal_flag WHERE name =  'ingest.skip_browse_indexing' AND enabled)) INTO b_skip_browse;
1620     SELECT COALESCE(NULLIF(skip_search, FALSE), EXISTS (SELECT enabled FROM config.internal_flag WHERE name =  'ingest.skip_search_indexing' AND enabled)) INTO b_skip_search;
1621
1622     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
1623     IF NOT FOUND THEN
1624         IF NOT b_skip_search THEN
1625             FOR fclass IN SELECT * FROM config.metabib_class LOOP
1626                 -- RAISE NOTICE 'Emptying out %', fclass.name;
1627                 EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
1628             END LOOP;
1629         END IF;
1630         IF NOT b_skip_facet THEN
1631             DELETE FROM metabib.facet_entry WHERE source = bib_id;
1632         END IF;
1633         IF NOT b_skip_browse THEN
1634             DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
1635         END IF;
1636     END IF;
1637
1638     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
1639         IF ind_data.field < 0 THEN
1640             ind_data.field = -1 * ind_data.field;
1641         END IF;
1642
1643         IF ind_data.facet_field AND NOT b_skip_facet THEN
1644             INSERT INTO metabib.facet_entry (field, source, value)
1645                 VALUES (ind_data.field, ind_data.source, ind_data.value);
1646         END IF;
1647
1648         IF ind_data.browse_field AND NOT b_skip_browse THEN
1649             -- A caveat about this SELECT: this should take care of replacing
1650             -- old mbe rows when data changes, but not if normalization (by
1651             -- which I mean specifically the output of
1652             -- evergreen.oils_tsearch2()) changes.  It may or may not be
1653             -- expensive to add a comparison of index_vector to index_vector
1654             -- to the WHERE clause below.
1655             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = ind_data.value;
1656             IF FOUND THEN
1657                 mbe_id := mbe_row.id;
1658             ELSE
1659                 INSERT INTO metabib.browse_entry (value) VALUES
1660                     (metabib.browse_normalize(ind_data.value, ind_data.field));
1661                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
1662             END IF;
1663
1664             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
1665                 VALUES (mbe_id, ind_data.field, ind_data.source);
1666         END IF;
1667
1668         IF ind_data.search_field AND NOT b_skip_search THEN
1669             EXECUTE $$
1670                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
1671                     VALUES ($$ ||
1672                         quote_literal(ind_data.field) || $$, $$ ||
1673                         quote_literal(ind_data.source) || $$, $$ ||
1674                         quote_literal(ind_data.value) ||
1675                     $$);$$;
1676         END IF;
1677
1678     END LOOP;
1679
1680     IF NOT b_skip_search THEN
1681         PERFORM metabib.update_combined_index_vectors(bib_id);
1682     END IF;
1683
1684     RETURN;
1685 END;
1686 $func$ LANGUAGE PLPGSQL;
1687
1688
1689 SELECT evergreen.upgrade_deps_block_check('0763', :eg_version);
1690
1691 INSERT INTO config.org_unit_setting_type (
1692     name, label, grp, datatype
1693 ) VALUES (
1694     'circ.fines.truncate_to_max_fine',
1695     'Truncate fines to max fine amount',
1696     'circ',
1697     'bool'
1698 );
1699
1700
1701
1702 SELECT evergreen.upgrade_deps_block_check('0765', :eg_version);
1703
1704 ALTER TABLE acq.provider
1705     ADD COLUMN default_copy_count INTEGER NOT NULL DEFAULT 0;
1706
1707
1708 SELECT evergreen.upgrade_deps_block_check('0768', :eg_version);
1709
1710 CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
1711 RETURNS INTEGER AS $$
1712     SELECT COALESCE(
1713
1714         -- lib matches search_lib
1715         (SELECT CASE WHEN $1 = $2 THEN -20000 END),
1716
1717         -- lib matches pref_lib
1718         (SELECT CASE WHEN $1 = $3 THEN -10000 END),
1719
1720
1721         -- pref_lib is a child of search_lib and lib is a child of pref lib.  
1722         (SELECT distance - 5000
1723             FROM actor.org_unit_descendants_distance($3) 
1724             WHERE id = $1 AND $3 IN (
1725                 SELECT id FROM actor.org_unit_descendants($2))),
1726
1727         -- lib is a child of search_lib
1728         (SELECT distance FROM actor.org_unit_descendants_distance($2) WHERE id = $1),
1729
1730         -- all others pay cash
1731         1000
1732     );
1733 $$ LANGUAGE SQL STABLE;
1734
1735
1736
1737
1738 SELECT evergreen.upgrade_deps_block_check('0769', :eg_version);
1739
1740 DROP FUNCTION IF EXISTS 
1741     evergreen.ranked_volumes(BIGINT, INT, INT, HSTORE, HSTORE, INT);
1742
1743 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
1744     bibid BIGINT, 
1745     ouid INT,
1746     depth INT DEFAULT NULL,
1747     slimit HSTORE DEFAULT NULL,
1748     soffset HSTORE DEFAULT NULL,
1749     pref_lib INT DEFAULT NULL,
1750     includes TEXT[] DEFAULT NULL::TEXT[]
1751 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
1752     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
1753         SELECT acn.id, aou.name, acn.label_sortkey,
1754             evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
1755             RANK() OVER w
1756         FROM asset.call_number acn
1757             JOIN asset.copy acp ON (acn.id = acp.call_number)
1758             JOIN actor.org_unit_descendants( $2, COALESCE(
1759                 $3, (
1760                     SELECT depth
1761                     FROM actor.org_unit_type aout
1762                         INNER JOIN actor.org_unit ou ON ou_type = aout.id
1763                     WHERE ou.id = $2
1764                 ), $6)
1765             ) AS aou ON (acp.circ_lib = aou.id)
1766         WHERE acn.record = $1
1767             AND acn.deleted IS FALSE
1768             AND acp.deleted IS FALSE
1769             AND CASE WHEN ('exclude_invisible_acn' = ANY($7)) THEN 
1770                 EXISTS (
1771                     SELECT 1 
1772                     FROM asset.opac_visible_copies 
1773                     WHERE copy_id = acp.id AND record = acn.record
1774                 ) ELSE TRUE END
1775         GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
1776         WINDOW w AS (
1777             ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
1778         )
1779     ) AS ua
1780     GROUP BY ua.id, ua.name, ua.label_sortkey
1781     ORDER BY rank, ua.name, ua.label_sortkey
1782     LIMIT ($4 -> 'acn')::INT
1783     OFFSET ($5 -> 'acn')::INT;
1784 $$
1785 LANGUAGE SQL STABLE;
1786
1787 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
1788     bid BIGINT,
1789     ouid INT,
1790     org TEXT,
1791     depth INT DEFAULT NULL,
1792     includes TEXT[] DEFAULT NULL::TEXT[],
1793     slimit HSTORE DEFAULT NULL,
1794     soffset HSTORE DEFAULT NULL,
1795     include_xmlns BOOL DEFAULT TRUE,
1796     pref_lib INT DEFAULT NULL
1797 )
1798 RETURNS XML AS $F$
1799      SELECT  XMLELEMENT(
1800                  name holdings,
1801                  XMLATTRIBUTES(
1802                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1803                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id,
1804                     (SELECT record_has_holdable_copy FROM asset.record_has_holdable_copy($1)) AS has_holdable
1805                  ),
1806                  XMLELEMENT(
1807                      name counts,
1808                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
1809                          SELECT  XMLELEMENT(
1810                                      name count,
1811                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
1812                                  )::text
1813                            FROM  asset.opac_ou_record_copy_count($2,  $1)
1814                                      UNION
1815                          SELECT  XMLELEMENT(
1816                                      name count,
1817                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
1818                                  )::text
1819                            FROM  asset.staff_ou_record_copy_count($2, $1)
1820                                      UNION
1821                          SELECT  XMLELEMENT(
1822                                      name count,
1823                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
1824                                  )::text
1825                            FROM  asset.opac_ou_record_copy_count($9,  $1)
1826                                      ORDER BY 1
1827                      )x)
1828                  ),
1829                  CASE 
1830                      WHEN ('bmp' = ANY ($5)) THEN
1831                         XMLELEMENT(
1832                             name monograph_parts,
1833                             (SELECT XMLAGG(bmp) FROM (
1834                                 SELECT  unapi.bmp( id, 'xml', 'monograph_part', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE)
1835                                   FROM  biblio.monograph_part
1836                                   WHERE record = $1
1837                             )x)
1838                         )
1839                      ELSE NULL
1840                  END,
1841                  XMLELEMENT(
1842                      name volumes,
1843                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
1844                         -- Physical copies
1845                         SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
1846                         FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9, $5) AS y
1847                         UNION ALL
1848                         -- Located URIs
1849                         SELECT unapi.acn(uris.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), 0, name, label_sortkey
1850                         FROM evergreen.located_uris($1, $2, $9) AS uris
1851                      )x)
1852                  ),
1853                  CASE WHEN ('ssub' = ANY ($5)) THEN 
1854                      XMLELEMENT(
1855                          name subscriptions,
1856                          (SELECT XMLAGG(ssub) FROM (
1857                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
1858                               FROM  serial.subscription
1859                               WHERE record_entry = $1
1860                         )x)
1861                      )
1862                  ELSE NULL END,
1863                  CASE WHEN ('acp' = ANY ($5)) THEN 
1864                      XMLELEMENT(
1865                          name foreign_copies,
1866                          (SELECT XMLAGG(acp) FROM (
1867                             SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
1868                               FROM  biblio.peer_bib_copy_map p
1869                                     JOIN asset.copy c ON (p.target_copy = c.id)
1870                               WHERE NOT c.deleted AND p.peer_record = $1
1871                             LIMIT ($6 -> 'acp')::INT
1872                             OFFSET ($7 -> 'acp')::INT
1873                         )x)
1874                      )
1875                  ELSE NULL END
1876              );
1877 $F$ LANGUAGE SQL STABLE;
1878
1879
1880
1881 SELECT evergreen.upgrade_deps_block_check('0771', :eg_version);
1882
1883 INSERT INTO action_trigger.hook (
1884         key,
1885         core_type,
1886         description,
1887         passive
1888     ) VALUES (
1889         'au.barred',
1890         'au',
1891         'A user was barred by staff',
1892         FALSE
1893     );
1894
1895 INSERT INTO action_trigger.hook (
1896         key,
1897         core_type,
1898         description,
1899         passive
1900     ) VALUES (
1901         'au.unbarred',
1902         'au',
1903         'A user was un-barred by staff',
1904         FALSE
1905     );
1906
1907 INSERT INTO action_trigger.validator (
1908         module, 
1909         description
1910     ) VALUES (
1911         'PatronBarred',
1912         'Tests if a patron is currently marked as barred'
1913     );
1914
1915 INSERT INTO action_trigger.validator (
1916         module, 
1917         description
1918     ) VALUES (
1919         'PatronNotBarred',
1920         'Tests if a patron is currently not marked as barred'
1921     );
1922
1923
1924 SELECT evergreen.upgrade_deps_block_check('0772', :eg_version);
1925
1926 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.preserve_on_delete');  -- defaults to false/off
1927
1928 DROP RULE protect_bib_rec_delete ON biblio.record_entry;
1929 CREATE RULE protect_bib_rec_delete AS
1930     ON DELETE TO biblio.record_entry DO INSTEAD (
1931         UPDATE biblio.record_entry
1932             SET deleted = TRUE
1933             WHERE OLD.id = biblio.record_entry.id
1934     );
1935
1936
1937 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
1938 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
1939 DECLARE
1940     transformed_xml TEXT;
1941     prev_xfrm       TEXT;
1942     normalizer      RECORD;
1943     xfrm            config.xml_transform%ROWTYPE;
1944     attr_value      TEXT;
1945     new_attrs       HSTORE := ''::HSTORE;
1946     attr_def        config.record_attr_definition%ROWTYPE;
1947 BEGIN
1948
1949     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
1950         PERFORM * FROM config.internal_flag WHERE
1951             name = 'ingest.metarecord_mapping.preserve_on_delete' AND enabled;
1952         IF NOT FOUND THEN
1953             -- One needs to keep these around to support searches
1954             -- with the #deleted modifier, so one should turn on the named
1955             -- internal flag for that functionality.
1956             DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id;
1957             DELETE FROM metabib.record_attr WHERE id = NEW.id;
1958         END IF;
1959
1960         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
1961         DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
1962         DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs
1963         RETURN NEW; -- and we're done
1964     END IF;
1965
1966     IF TG_OP = 'UPDATE' THEN -- re-ingest?
1967         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
1968
1969         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
1970             RETURN NEW;
1971         END IF;
1972     END IF;
1973
1974     -- Record authority linking
1975     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
1976     IF NOT FOUND THEN
1977         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
1978     END IF;
1979
1980     -- Flatten and insert the mfr data
1981     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
1982     IF NOT FOUND THEN
1983         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
1984
1985         -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
1986         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
1987         IF NOT FOUND THEN
1988             FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
1989
1990                 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
1991                     SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
1992                       FROM  (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
1993                       WHERE record = NEW.id
1994                             AND tag LIKE attr_def.tag
1995                             AND CASE
1996                                 WHEN attr_def.sf_list IS NOT NULL 
1997                                     THEN POSITION(subfield IN attr_def.sf_list) > 0
1998                                 ELSE TRUE
1999                                 END
2000                       GROUP BY tag
2001                       ORDER BY tag
2002                       LIMIT 1;
2003
2004                 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
2005                     attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
2006
2007                 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
2008
2009                     SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
2010             
2011                     -- See if we can skip the XSLT ... it's expensive
2012                     IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
2013                         -- Can't skip the transform
2014                         IF xfrm.xslt <> '---' THEN
2015                             transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
2016                         ELSE
2017                             transformed_xml := NEW.marc;
2018                         END IF;
2019             
2020                         prev_xfrm := xfrm.name;
2021                     END IF;
2022
2023                     IF xfrm.name IS NULL THEN
2024                         -- just grab the marcxml (empty) transform
2025                         SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
2026                         prev_xfrm := xfrm.name;
2027                     END IF;
2028
2029                     attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
2030
2031                 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
2032                     SELECT  m.value INTO attr_value
2033                       FROM  biblio.marc21_physical_characteristics(NEW.id) v
2034                             JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
2035                       WHERE v.subfield = attr_def.phys_char_sf
2036                       LIMIT 1; -- Just in case ...
2037
2038                 END IF;
2039
2040                 -- apply index normalizers to attr_value
2041                 FOR normalizer IN
2042                     SELECT  n.func AS func,
2043                             n.param_count AS param_count,
2044                             m.params AS params
2045                       FROM  config.index_normalizer n
2046                             JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
2047                       WHERE attr = attr_def.name
2048                       ORDER BY m.pos LOOP
2049                         EXECUTE 'SELECT ' || normalizer.func || '(' ||
2050                             COALESCE( quote_literal( attr_value ), 'NULL' ) ||
2051                             CASE
2052                                 WHEN normalizer.param_count > 0
2053                                     THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
2054                                     ELSE ''
2055                                 END ||
2056                             ')' INTO attr_value;
2057         
2058                 END LOOP;
2059
2060                 -- Add the new value to the hstore
2061                 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
2062
2063             END LOOP;
2064
2065             IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
2066                 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
2067             ELSE
2068                 UPDATE metabib.record_attr SET attrs = new_attrs WHERE id = NEW.id;
2069             END IF;
2070
2071         END IF;
2072     END IF;
2073
2074     -- Gather and insert the field entry data
2075     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
2076
2077     -- Located URI magic
2078     IF TG_OP = 'INSERT' THEN
2079         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
2080         IF NOT FOUND THEN
2081             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
2082         END IF;
2083     ELSE
2084         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
2085         IF NOT FOUND THEN
2086             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
2087         END IF;
2088     END IF;
2089
2090     -- (re)map metarecord-bib linking
2091     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
2092         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
2093         IF NOT FOUND THEN
2094             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
2095         END IF;
2096     ELSE -- we're doing an update, and we're not deleted, remap
2097         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
2098         IF NOT FOUND THEN
2099             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
2100         END IF;
2101     END IF;
2102
2103     RETURN NEW;
2104 END;
2105 $func$ LANGUAGE PLPGSQL;
2106
2107
2108 -- Evergreen DB patch xxxx.data.authority_thesaurus_update.sql
2109 --
2110
2111 -- check whether patch can be applied
2112 SELECT evergreen.upgrade_deps_block_check('0773', :eg_version);
2113
2114
2115 INSERT INTO authority.thesaurus (code, name, control_set) VALUES
2116     (' ', oils_i18n_gettext(' ','Alternate no attempt to code','at','name'), NULL);
2117
2118
2119
2120 SELECT evergreen.upgrade_deps_block_check('0774', :eg_version);
2121
2122 CREATE TABLE config.z3950_source_credentials (
2123     id SERIAL PRIMARY KEY,
2124     owner INTEGER NOT NULL REFERENCES actor.org_unit(id),
2125     source TEXT NOT NULL REFERENCES config.z3950_source(name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
2126     -- do some Z servers require a username but no password or vice versa?
2127     username TEXT,
2128     password TEXT,
2129     CONSTRAINT czsc_source_once_per_lib UNIQUE (source, owner)
2130 );
2131
2132 -- find the most relevant set of credentials for the Z source and org
2133 CREATE OR REPLACE FUNCTION config.z3950_source_credentials_lookup
2134         (source TEXT, owner INTEGER) 
2135         RETURNS config.z3950_source_credentials AS $$
2136
2137     SELECT creds.* 
2138     FROM config.z3950_source_credentials creds
2139         JOIN actor.org_unit aou ON (aou.id = creds.owner)
2140         JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
2141     WHERE creds.source = $1 AND creds.owner IN ( 
2142         SELECT id FROM actor.org_unit_ancestors($2) 
2143     )
2144     ORDER BY aout.depth DESC LIMIT 1;
2145
2146 $$ LANGUAGE SQL STABLE;
2147
2148 -- since we are not exposing config.z3950_source_credentials
2149 -- via the IDL, providing a stored proc gives us a way to
2150 -- set values in the table via cstore
2151 CREATE OR REPLACE FUNCTION config.z3950_source_credentials_apply
2152         (src TEXT, org INTEGER, uname TEXT, passwd TEXT) 
2153         RETURNS VOID AS $$
2154 BEGIN
2155     PERFORM 1 FROM config.z3950_source_credentials
2156         WHERE owner = org AND source = src;
2157
2158     IF FOUND THEN
2159         IF COALESCE(uname, '') = '' AND COALESCE(passwd, '') = '' THEN
2160             DELETE FROM config.z3950_source_credentials 
2161                 WHERE owner = org AND source = src;
2162         ELSE 
2163             UPDATE config.z3950_source_credentials 
2164                 SET username = uname, password = passwd
2165                 WHERE owner = org AND source = src;
2166         END IF;
2167     ELSE
2168         IF COALESCE(uname, '') <> '' OR COALESCE(passwd, '') <> '' THEN
2169             INSERT INTO config.z3950_source_credentials
2170                 (source, owner, username, password) 
2171                 VALUES (src, org, uname, passwd);
2172         END IF;
2173     END IF;
2174 END;
2175 $$ LANGUAGE PLPGSQL;
2176
2177
2178
2179
2180 SELECT evergreen.upgrade_deps_block_check('0775', :eg_version);
2181
2182 ALTER TABLE config.z3950_attr
2183     DROP CONSTRAINT z3950_attr_source_fkey,
2184     ADD CONSTRAINT z3950_attr_source_fkey 
2185         FOREIGN KEY (source) 
2186         REFERENCES config.z3950_source(name) 
2187         ON UPDATE CASCADE
2188         ON DELETE CASCADE
2189         DEFERRABLE INITIALLY DEFERRED;
2190
2191
2192 SELECT evergreen.upgrade_deps_block_check('0776', :eg_version);
2193
2194 ALTER TABLE acq.lineitem_attr
2195     ADD COLUMN order_ident BOOLEAN NOT NULL DEFAULT FALSE;
2196
2197 INSERT INTO permission.perm_list ( id, code, description ) VALUES (
2198     547, -- VERIFY
2199     'ACQ_ADD_LINEITEM_IDENTIFIER',
2200     oils_i18n_gettext(
2201         547,-- VERIFY
2202         'When granted, newly added lineitem identifiers will propagate to linked bib records',
2203         'ppl',
2204         'description'
2205     )
2206 );
2207
2208 INSERT INTO permission.perm_list ( id, code, description ) VALUES (
2209     548, -- VERIFY
2210     'ACQ_SET_LINEITEM_IDENTIFIER',
2211     oils_i18n_gettext(
2212         548,-- VERIFY
2213         'Allows staff to change the lineitem identifier',
2214         'ppl',
2215         'description'
2216     )
2217 );
2218
2219
2220 SELECT evergreen.upgrade_deps_block_check('0777', :eg_version);
2221
2222 -- Listed here for reference / ease of access.  The update
2223 -- is not applied here (see the WHERE clause).
2224 UPDATE action_trigger.event_definition SET template = 
2225 $$
2226 [%- USE date -%]
2227 [%
2228     # extract some commonly used variables
2229
2230     VENDOR_SAN = target.provider.san;
2231     VENDCODE = target.provider.edi_default.vendcode;
2232     VENDACCT = target.provider.edi_default.vendacct;
2233     ORG_UNIT_SAN = target.ordering_agency.mailing_address.san;
2234
2235     # set the vendor / provider
2236
2237     VENDOR_BT      = 0; # Baker & Taylor
2238     VENDOR_INGRAM  = 0;
2239     VENDOR_BRODART = 0;
2240     VENDOR_MW_TAPE = 0; # Midwest Tape
2241     VENDOR_RB      = 0; # Recorded Books
2242     VENDOR_ULS     = 0; # ULS
2243
2244     IF    VENDOR_SAN == '1556150'; VENDOR_BT = 1;
2245     ELSIF VENDOR_SAN == '1697684'; VENDOR_BRODART = 1;
2246     ELSIF VENDOR_SAN == '1697978'; VENDOR_INGRAM = 1;
2247     ELSIF VENDOR_SAN == '2549913'; VENDOR_MW_TAPE = 1;
2248     ELSIF VENDOR_SAN == '1113984'; VENDOR_RB = 1;
2249     ELSIF VENDOR_SAN == '1699342'; VENDOR_ULS = 1;
2250     END;
2251
2252     # if true, pass the PO name as a secondary identifier
2253     # RFF+LI:<name>/li_id
2254     INC_PO_NAME = 0;
2255     IF VENDOR_INGRAM;
2256         INC_PO_NAME = 1;
2257     END;
2258
2259     # GIR configuration --------------------------------------
2260
2261     INC_COPIES = 1; # copies on/off switch
2262     INC_FUND = 0;
2263     INC_CALLNUMBER = 0;
2264     INC_ITEM_TYPE = 1;
2265     INC_LOCATION = 0;
2266     INC_COLLECTION_CODE = 1;
2267     INC_OWNING_LIB = 1;
2268     INC_QUANTITY = 1;
2269     INC_COPY_ID = 0;
2270
2271     IF VENDOR_BT;
2272         INC_CALLNUMBER = 1;
2273     END;
2274
2275     IF VENDOR_BRODART;
2276         INC_FUND = 1;
2277     END;
2278
2279     IF VENDOR_MW_TAPE;
2280         INC_FUND = 1;
2281         INC_COLLECTION_CODE = 0;
2282         INC_ITEM_TYPE = 0;
2283     END;
2284
2285     # END GIR configuration ---------------------------------
2286
2287 -%]
2288 [%- BLOCK big_block -%]
2289 {
2290    "recipient":"[% VENDOR_SAN %]",
2291    "sender":"[% ORG_UNIT_SAN %]",
2292    "body": [{
2293      "ORDERS":[ "order", {
2294
2295         "po_number":[% target.id %],
2296
2297         [% IF INC_PO_NAME %]
2298         "po_name":"[% target.name | replace('\/', ' ') | replace('"', '\"') %]",
2299         [% END %]
2300
2301         "date":"[% date.format(date.now, '%Y%m%d') %]",
2302
2303         "buyer":[
2304             [% IF VENDOR_BT %]
2305                 {"id-qualifier": 91, "id":"[% ORG_UNIT_SAN %] [% VENDCODE %]"}
2306             [% ELSE %]
2307                 {"id":"[% ORG_UNIT_SAN %]"},
2308                 {"id-qualifier": 91, "id":"[% VENDACCT %]"}
2309             [% END %]
2310         ],
2311
2312         "vendor":[
2313             "[% VENDOR_SAN %]",
2314             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
2315         ],
2316
2317         "currency":"[% target.provider.currency_type %]",
2318                 
2319         "items":[
2320         [%- FOR li IN target.lineitems %]
2321         {
2322             "line_index":"[% li.id %]",
2323             "identifiers":[   
2324             [%- 
2325                 idval = '';
2326                 idqual = 'EN'; # default ISBN/UPC/EAN-13
2327                 ident_attr = helpers.get_li_order_ident(li.attributes);
2328                 IF ident_attr;
2329                     idname = ident_attr.attr_name;
2330                     idval = ident_attr.attr_value;
2331                     IF idname == 'isbn' AND idval.length != 13;
2332                         idqual = 'IB';
2333                     ELSIF idname == 'issn';
2334                         idqual = 'IS';
2335                     END;
2336                 ELSE;
2337                     idqual = 'IN';
2338                     idval = li.id;
2339                 END -%]
2340                 {"id-qualifier":"[% idqual %]","id":"[% idval %]"}
2341             ],
2342             "price":[% li.estimated_unit_price || '0.00' %],
2343             "desc":[
2344                 {"BTI":"[% helpers.get_li_attr_jedi('title',     '', li.attributes) %]"},
2345                 {"BPU":"[% helpers.get_li_attr_jedi('publisher', '', li.attributes) %]"},
2346                 {"BPD":"[% helpers.get_li_attr_jedi('pubdate',   '', li.attributes) %]"},
2347                 [% IF VENDOR_ULS -%]
2348                 {"BEN":"[% helpers.get_li_attr_jedi('edition',   '', li.attributes) %]"},
2349                 {"BAU":"[% helpers.get_li_attr_jedi('author',    '', li.attributes) %]"}
2350                 [%- ELSE -%]
2351                 {"BPH":"[% helpers.get_li_attr_jedi('pagination','', li.attributes) %]"}
2352                 [%- END %]
2353             ],
2354             [%- ftx_vals = []; 
2355                 FOR note IN li.lineitem_notes;
2356                     NEXT UNLESS note.vendor_public == 't'; 
2357                     ftx_vals.push(note.value); 
2358                 END; 
2359                 IF VENDOR_BRODART; # look for copy-level spec code
2360                     FOR lid IN li.lineitem_details;
2361                         IF lid.note;
2362                             spec_note = lid.note.match('spec code ([a-zA-Z0-9_])');
2363                             IF spec_note.0; ftx_vals.push(spec_note.0); END;
2364                         END;
2365                     END;
2366                 END; 
2367                 IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
2368
2369                 # BT & ULS want FTX+LIN for every LI, even if empty
2370                 IF ((VENDOR_BT OR VENDOR_ULS) AND ftx_vals.size == 0);
2371                     ftx_vals.unshift('');
2372                 END;  
2373             -%]
2374
2375             "free-text":[ 
2376                 [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
2377             ],            
2378
2379             "quantity":[% li.lineitem_details.size %],
2380
2381             [%- IF INC_COPIES -%]
2382             "copies" : [
2383                 [%- compressed_copies = [];
2384                     FOR lid IN li.lineitem_details;
2385                         fund = lid.fund.code;
2386                         item_type = lid.circ_modifier;
2387                         callnumber = lid.cn_label;
2388                         owning_lib = lid.owning_lib.shortname;
2389                         location = lid.location;
2390                         collection_code = lid.collection_code;
2391     
2392                         # when we have real copy data, treat it as authoritative for some fields
2393                         acp = lid.eg_copy_id;
2394                         IF acp;
2395                             item_type = acp.circ_modifier;
2396                             callnumber = acp.call_number.label;
2397                             location = acp.location.name;
2398                         END ;
2399
2400
2401                         # collapse like copies into groups w/ quantity
2402
2403                         found_match = 0;
2404                         IF !INC_COPY_ID; # INC_COPY_ID implies 1 copy per GIR
2405                             FOR copy IN compressed_copies;
2406                                 IF  (fund == copy.fund OR (!fund AND !copy.fund)) AND
2407                                     (item_type == copy.item_type OR (!item_type AND !copy.item_type)) AND
2408                                     (callnumber == copy.callnumber OR (!callnumber AND !copy.callnumber)) AND
2409                                     (owning_lib == copy.owning_lib OR (!owning_lib AND !copy.owning_lib)) AND
2410                                     (location == copy.location OR (!location AND !copy.location)) AND
2411                                     (collection_code == copy.collection_code OR (!collection_code AND !copy.collection_code));
2412
2413                                     copy.quantity = copy.quantity + 1;
2414                                     found_match = 1;
2415                                 END;
2416                             END;
2417                         END;
2418
2419                         IF !found_match;
2420                             compressed_copies.push({
2421                                 fund => fund,
2422                                 item_type => item_type,
2423                                 callnumber => callnumber,
2424                                 owning_lib => owning_lib,
2425                                 location => location,
2426                                 collection_code => collection_code,
2427                                 copy_id => lid.id, # for INC_COPY_ID
2428                                 quantity => 1
2429                             });
2430                         END;
2431                     END;
2432                     FOR copy IN compressed_copies;
2433
2434                     # If we assume owning_lib is required and set, 
2435                     # it is safe to prepend each following copy field w/ a ","
2436
2437                     # B&T EDI requires expected GIR fields to be 
2438                     # present regardless of whether a value exists.  
2439                     # some fields are required to have a value in ACQ, 
2440                     # though, so they are not forced into place below.
2441
2442                  %]{[%- IF INC_OWNING_LIB AND copy.owning_lib %] "owning_lib":"[% copy.owning_lib %]"[% END -%]
2443                     [%- IF INC_FUND AND copy.fund %],"fund":"[% copy.fund %]"[% END -%]
2444                     [%- IF INC_CALLNUMBER AND (VENDOR_BT OR copy.callnumber) %],"call_number":"[% copy.callnumber %]"[% END -%]
2445                     [%- IF INC_ITEM_TYPE AND (VENDOR_BT OR copy.item_type) %],"item_type":"[% copy.item_type %]"[% END -%]
2446                     [%- IF INC_LOCATION AND copy.location %],"copy_location":"[% copy.location %]"[% END -%]
2447                     [%- IF INC_COLLECTION_CODE AND (VENDOR_BT OR copy.collection_code) %],"collection_code":"[% copy.collection_code %]"[% END -%]
2448                     [%- IF INC_QUANTITY %],"quantity":"[% copy.quantity %]"[% END -%]
2449                     [%- IF INC_COPY_ID %],"copy_id":"[% copy.copy_id %]" [% END %]}[% ',' UNLESS loop.last -%]
2450                 [%- END -%] [%# FOR compressed_copies -%]
2451             ]
2452             [%- END -%] [%# IF INC_COPIES %]
2453
2454         }[% UNLESS loop.last %],[% END -%]
2455
2456         [% END %] [%# END lineitems %]
2457         ],
2458         "line_items":[% target.lineitems.size %]
2459      }]  [%# close ORDERS array %]
2460    }]    [%# close  body  array %]
2461 }
2462 [% END %]
2463 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
2464 $$
2465 WHERE ID = 23 AND FALSE; -- remove 'AND FALSE' to apply this update
2466
2467
2468 -- lineitem worksheet
2469 UPDATE action_trigger.event_definition SET template = 
2470 $$
2471 [%- USE date -%]
2472 [%-
2473     # find a lineitem attribute by name and optional type
2474     BLOCK get_li_attr;
2475         FOR attr IN li.attributes;
2476             IF attr.attr_name == attr_name;
2477                 IF !attr_type OR attr_type == attr.attr_type;
2478                     attr.attr_value;
2479                     LAST;
2480                 END;
2481             END;
2482         END;
2483     END
2484 -%]
2485
2486 <h2>Purchase Order [% target.id %]</h2>
2487 <br/>
2488 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
2489 <br/>
2490
2491 <style>
2492     table td { padding:5px; border:1px solid #aaa;}
2493     table { width:95%; border-collapse:collapse; }
2494     #vendor-notes { padding:5px; border:1px solid #aaa; }
2495 </style>
2496 <table id='vendor-table'>
2497   <tr>
2498     <td valign='top'>Vendor</td>
2499     <td>
2500       <div>[% target.provider.name %]</div>
2501       <div>[% target.provider.addresses.0.street1 %]</div>
2502       <div>[% target.provider.addresses.0.street2 %]</div>
2503       <div>[% target.provider.addresses.0.city %]</div>
2504       <div>[% target.provider.addresses.0.state %]</div>
2505       <div>[% target.provider.addresses.0.country %]</div>
2506       <div>[% target.provider.addresses.0.post_code %]</div>
2507     </td>
2508     <td valign='top'>Ship to / Bill to</td>
2509     <td>
2510       <div>[% target.ordering_agency.name %]</div>
2511       <div>[% target.ordering_agency.billing_address.street1 %]</div>
2512       <div>[% target.ordering_agency.billing_address.street2 %]</div>
2513       <div>[% target.ordering_agency.billing_address.city %]</div>
2514       <div>[% target.ordering_agency.billing_address.state %]</div>
2515       <div>[% target.ordering_agency.billing_address.country %]</div>
2516       <div>[% target.ordering_agency.billing_address.post_code %]</div>
2517     </td>
2518   </tr>
2519 </table>
2520
2521 <br/><br/>
2522 <fieldset id='vendor-notes'>
2523     <legend>Notes to the Vendor</legend>
2524     <ul>
2525     [% FOR note IN target.notes %]
2526         [% IF note.vendor_public == 't' %]
2527             <li>[% note.value %]</li>
2528         [% END %]
2529     [% END %]
2530     </ul>
2531 </fieldset>
2532 <br/><br/>
2533
2534 <table>
2535   <thead>
2536     <tr>
2537       <th>PO#</th>
2538       <th>ISBN or Item #</th>
2539       <th>Title</th>
2540       <th>Quantity</th>
2541       <th>Unit Price</th>
2542       <th>Line Total</th>
2543       <th>Notes</th>
2544     </tr>
2545   </thead>
2546   <tbody>
2547
2548   [% subtotal = 0 %]
2549   [% FOR li IN target.lineitems %]
2550
2551   <tr>
2552     [% count = li.lineitem_details.size %]
2553     [% price = li.estimated_unit_price %]
2554     [% litotal = (price * count) %]
2555     [% subtotal = subtotal + litotal %]
2556     [% 
2557         ident_attr = helpers.get_li_order_ident(li.attributes);
2558         SET ident_value = ident_attr.attr_value IF ident_attr;
2559     %]
2560     <td>[% target.id %]</td>
2561     <td>[% ident_value %]</td>
2562     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
2563     <td>[% count %]</td>
2564     <td>[% price %]</td>
2565     <td>[% litotal %]</td>
2566     <td>
2567         <ul>
2568         [% FOR note IN li.lineitem_notes %]
2569             [% IF note.vendor_public == 't' %]
2570                 <li>[% note.value %]</li>
2571             [% END %]
2572         [% END %]
2573         </ul>
2574     </td>
2575   </tr>
2576   [% END %]
2577   <tr>
2578     <td/><td/><td/><td/>
2579     <td>Subtotal</td>
2580     <td>[% subtotal %]</td>
2581   </tr>
2582   </tbody>
2583 </table>
2584
2585 <br/>
2586
2587 Total Line Item Count: [% target.lineitems.size %]
2588 $$
2589 WHERE ID = 4; -- PO HTML
2590
2591
2592 SELECT evergreen.upgrade_deps_block_check('0778', :eg_version);
2593
2594 CREATE OR REPLACE FUNCTION extract_marc_field_set
2595         (TEXT, BIGINT, TEXT, TEXT) RETURNS SETOF TEXT AS $$
2596 DECLARE
2597     query TEXT;
2598     output TEXT;
2599 BEGIN
2600     FOR output IN
2601         SELECT x.t FROM (
2602             SELECT id,t
2603                 FROM  oils_xpath_table(
2604                     'id', 'marc', $1, $3, 'id = ' || $2)
2605                 AS t(id int, t text))x
2606         LOOP
2607         IF $4 IS NOT NULL THEN
2608             SELECT INTO output (SELECT regexp_replace(output, $4, '', 'g'));
2609         END IF;
2610         RETURN NEXT output;
2611     END LOOP;
2612     RETURN;
2613 END;
2614 $$ LANGUAGE PLPGSQL IMMUTABLE;
2615
2616
2617 CREATE OR REPLACE FUNCTION 
2618         public.extract_acq_marc_field_set ( BIGINT, TEXT, TEXT) 
2619         RETURNS SETOF TEXT AS $$
2620         SELECT extract_marc_field_set('acq.lineitem', $1, $2, $3);
2621 $$ LANGUAGE SQL;
2622
2623
2624 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
2625 DECLARE
2626         value           TEXT;
2627         atype           TEXT;
2628         prov            INT;
2629         pos             INT;
2630         adef            RECORD;
2631         xpath_string    TEXT;
2632 BEGIN
2633         FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
2634
2635                 SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
2636
2637                 IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
2638                         IF (atype = 'lineitem_provider_attr_definition') THEN
2639                                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
2640                                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
2641                         END IF;
2642                         
2643                         IF (atype = 'lineitem_provider_attr_definition') THEN
2644                                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
2645                         ELSIF (atype = 'lineitem_marc_attr_definition') THEN
2646                                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
2647                         ELSIF (atype = 'lineitem_generated_attr_definition') THEN
2648                                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
2649                         END IF;
2650
2651             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
2652
2653             IF (adef.code = 'title' OR adef.code = 'author') THEN
2654                 -- title and author should not be split
2655                 -- FIXME: once oils_xpath can grok XPATH 2.0 functions, we can use
2656                 -- string-join in the xpath and remove this special case
2657                         SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
2658                         IF (value IS NOT NULL AND value <> '') THEN
2659                                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
2660                                     VALUES (NEW.id, adef.id, atype, adef.code, value);
2661                 END IF;
2662             ELSE
2663                 pos := 1;
2664                 LOOP
2665                     -- each application of the regex may produce multiple values
2666                     FOR value IN
2667                         SELECT * FROM extract_acq_marc_field_set(
2668                             NEW.id, xpath_string || '[' || pos || ']', adef.remove)
2669                         LOOP
2670
2671                         IF (value IS NOT NULL AND value <> '') THEN
2672                             INSERT INTO acq.lineitem_attr
2673                                 (lineitem, definition, attr_type, attr_name, attr_value)
2674                                 VALUES (NEW.id, adef.id, atype, adef.code, value);
2675                         ELSE
2676                             EXIT;
2677                         END IF;
2678                     END LOOP;
2679                     IF NOT FOUND THEN
2680                         EXIT;
2681                     END IF;
2682                     pos := pos + 1;
2683                END LOOP;
2684             END IF;
2685
2686                 END IF;
2687
2688         END LOOP;
2689
2690         RETURN NULL;
2691 END;
2692 $function$ LANGUAGE PLPGSQL;
2693
2694
2695 SELECT evergreen.upgrade_deps_block_check('0779', :eg_version);
2696
2697 CREATE TABLE vandelay.import_bib_trash_group(
2698     id SERIAL PRIMARY KEY,
2699     owner INT NOT NULL REFERENCES actor.org_unit(id),
2700     label TEXT NOT NULL, --i18n
2701     always_apply BOOLEAN NOT NULL DEFAULT FALSE,
2702         CONSTRAINT vand_import_bib_trash_grp_owner_label UNIQUE (owner, label)
2703 );
2704
2705 -- otherwise, the ALTER TABLE statement below
2706 -- will fail with pending trigger events.
2707 SET CONSTRAINTS ALL IMMEDIATE;
2708
2709 ALTER TABLE vandelay.import_bib_trash_fields
2710     -- allow null-able for now..
2711     ADD COLUMN grp INTEGER REFERENCES vandelay.import_bib_trash_group;
2712
2713 -- add any existing trash_fields to "Legacy" groups (one per unique field
2714 -- owner) as part of the upgrade, since grp is now required.
2715 -- note that vandelay.import_bib_trash_fields was never used before,
2716 -- so in most cases this should be a no-op.
2717
2718 INSERT INTO vandelay.import_bib_trash_group (owner, label)
2719     SELECT DISTINCT(owner), 'Legacy' FROM vandelay.import_bib_trash_fields;
2720
2721 UPDATE vandelay.import_bib_trash_fields field SET grp = tgroup.id
2722     FROM vandelay.import_bib_trash_group tgroup
2723     WHERE tgroup.owner = field.owner;
2724     
2725 ALTER TABLE vandelay.import_bib_trash_fields
2726     -- now that have values, we can make this non-null
2727     ALTER COLUMN grp SET NOT NULL,
2728     -- drop outdated constraint
2729     DROP CONSTRAINT vand_import_bib_trash_fields_idx,
2730     -- owner is implied by the grp
2731     DROP COLUMN owner, 
2732     -- make grp+field unique
2733     ADD CONSTRAINT vand_import_bib_trash_fields_once_per UNIQUE (grp, field);
2734
2735
2736 SELECT evergreen.upgrade_deps_block_check('0780', :eg_version);
2737
2738 ALTER TABLE acq.distribution_formula_entry
2739     ADD COLUMN fund INT REFERENCES acq.fund (id),
2740     ADD COLUMN circ_modifier TEXT REFERENCES config.circ_modifier (code),
2741     ADD COLUMN collection_code TEXT ;
2742
2743
2744 -- support option to roll distribution formula funds
2745 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
2746         old_year INTEGER,
2747         user_id INTEGER,
2748         org_unit_id INTEGER,
2749     encumb_only BOOL DEFAULT FALSE,
2750     include_desc BOOL DEFAULT TRUE
2751 ) RETURNS VOID AS $$
2752 DECLARE
2753 --
2754 new_fund    INT;
2755 new_year    INT := old_year + 1;
2756 org_found   BOOL;
2757 perm_ous    BOOL;
2758 xfer_amount NUMERIC := 0;
2759 roll_fund   RECORD;
2760 deb         RECORD;
2761 detail      RECORD;
2762 roll_distrib_forms BOOL;
2763 --
2764 BEGIN
2765         --
2766         -- Sanity checks
2767         --
2768         IF old_year IS NULL THEN
2769                 RAISE EXCEPTION 'Input year argument is NULL';
2770     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
2771         RAISE EXCEPTION 'Input year is out of range';
2772         END IF;
2773         --
2774         IF user_id IS NULL THEN
2775                 RAISE EXCEPTION 'Input user id argument is NULL';
2776         END IF;
2777         --
2778         IF org_unit_id IS NULL THEN
2779                 RAISE EXCEPTION 'Org unit id argument is NULL';
2780         ELSE
2781                 --
2782                 -- Validate the org unit
2783                 --
2784                 SELECT TRUE
2785                 INTO org_found
2786                 FROM actor.org_unit
2787                 WHERE id = org_unit_id;
2788                 --
2789                 IF org_found IS NULL THEN
2790                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
2791                 ELSIF encumb_only THEN
2792                         SELECT INTO perm_ous value::BOOL FROM
2793                         actor.org_unit_ancestor_setting(
2794                                 'acq.fund.allow_rollover_without_money', org_unit_id
2795                         );
2796                         IF NOT FOUND OR NOT perm_ous THEN
2797                                 RAISE EXCEPTION 'Encumbrance-only rollover not permitted at org %', org_unit_id;
2798                         END IF;
2799                 END IF;
2800         END IF;
2801         --
2802         -- Loop over the propagable funds to identify the details
2803         -- from the old fund plus the id of the new one, if it exists.
2804         --
2805         FOR roll_fund in
2806         SELECT
2807             oldf.id AS old_fund,
2808             oldf.org,
2809             oldf.name,
2810             oldf.currency_type,
2811             oldf.code,
2812                 oldf.rollover,
2813             newf.id AS new_fund_id
2814         FROM
2815         acq.fund AS oldf
2816         LEFT JOIN acq.fund AS newf
2817                 ON ( oldf.code = newf.code )
2818         WHERE
2819                     oldf.year = old_year
2820                 AND oldf.propagate
2821         AND newf.year = new_year
2822                 AND ( ( include_desc AND oldf.org IN ( SELECT id FROM actor.org_unit_descendants( org_unit_id ) ) )
2823                 OR (NOT include_desc AND oldf.org = org_unit_id ) )
2824         LOOP
2825                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
2826                 --
2827                 IF roll_fund.new_fund_id IS NULL THEN
2828                         --
2829                         -- The old fund hasn't been propagated yet.  Propagate it now.
2830                         --
2831                         INSERT INTO acq.fund (
2832                                 org,
2833                                 name,
2834                                 year,
2835                                 currency_type,
2836                                 code,
2837                                 rollover,
2838                                 propagate,
2839                                 balance_warning_percent,
2840                                 balance_stop_percent
2841                         ) VALUES (
2842                                 roll_fund.org,
2843                                 roll_fund.name,
2844                                 new_year,
2845                                 roll_fund.currency_type,
2846                                 roll_fund.code,
2847                                 true,
2848                                 true,
2849                                 roll_fund.balance_warning_percent,
2850                                 roll_fund.balance_stop_percent
2851                         )
2852                         RETURNING id INTO new_fund;
2853                 ELSE
2854                         new_fund = roll_fund.new_fund_id;
2855                 END IF;
2856                 --
2857                 -- Determine the amount to transfer
2858                 --
2859                 SELECT amount
2860                 INTO xfer_amount
2861                 FROM acq.fund_spent_balance
2862                 WHERE fund = roll_fund.old_fund;
2863                 --
2864                 IF xfer_amount <> 0 THEN
2865                         IF NOT encumb_only AND roll_fund.rollover THEN
2866                                 --
2867                                 -- Transfer balance from old fund to new
2868                                 --
2869                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
2870                                 --
2871                                 PERFORM acq.transfer_fund(
2872                                         roll_fund.old_fund,
2873                                         xfer_amount,
2874                                         new_fund,
2875                                         xfer_amount,
2876                                         user_id,
2877                                         'Rollover'
2878                                 );
2879                         ELSE
2880                                 --
2881                                 -- Transfer balance from old fund to the void
2882                                 --
2883                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
2884                                 --
2885                                 PERFORM acq.transfer_fund(
2886                                         roll_fund.old_fund,
2887                                         xfer_amount,
2888                                         NULL,
2889                                         NULL,
2890                                         user_id,
2891                                         'Rollover into the void'
2892                                 );
2893                         END IF;
2894                 END IF;
2895                 --
2896                 IF roll_fund.rollover THEN
2897                         --
2898                         -- Move any lineitems from the old fund to the new one
2899                         -- where the associated debit is an encumbrance.
2900                         --
2901                         -- Any other tables tying expenditure details to funds should
2902                         -- receive similar treatment.  At this writing there are none.
2903                         --
2904                         UPDATE acq.lineitem_detail
2905                         SET fund = new_fund
2906                         WHERE
2907                         fund = roll_fund.old_fund -- this condition may be redundant
2908                         AND fund_debit in
2909                         (
2910                                 SELECT id
2911                                 FROM acq.fund_debit
2912                                 WHERE
2913                                 fund = roll_fund.old_fund
2914                                 AND encumbrance
2915                         );
2916                         --
2917                         -- Move encumbrance debits from the old fund to the new fund
2918                         --
2919                         UPDATE acq.fund_debit
2920                         SET fund = new_fund
2921                         wHERE
2922                                 fund = roll_fund.old_fund
2923                                 AND encumbrance;
2924                 END IF;
2925
2926                 -- Rollover distribution formulae funds
2927                 SELECT INTO roll_distrib_forms value::BOOL FROM
2928                         actor.org_unit_ancestor_setting(
2929                                 'acq.fund.rollover_distrib_forms', org_unit_id
2930                         );
2931
2932                 IF roll_distrib_forms THEN
2933                         UPDATE acq.distribution_formula_entry 
2934                                 SET fund = roll_fund.new_fund_id
2935                                 WHERE fund = roll_fund.old_fund;
2936                 END IF;
2937
2938                 --
2939                 -- Mark old fund as inactive, now that we've closed it
2940                 --
2941                 UPDATE acq.fund
2942                 SET active = FALSE
2943                 WHERE id = roll_fund.old_fund;
2944         END LOOP;
2945 END;
2946 $$ LANGUAGE plpgsql;
2947
2948
2949
2950 SELECT evergreen.upgrade_deps_block_check('0781', :eg_version);
2951
2952 INSERT INTO config.org_unit_setting_type
2953     (name, label, description, grp, datatype) 
2954 VALUES (
2955     'acq.fund.rollover_distrib_forms',
2956     oils_i18n_gettext(
2957         'acq.fund.rollover_distrib_forms',
2958         'Rollover Distribution Formulae Funds',
2959         'coust',
2960         'label'
2961     ),
2962      oils_i18n_gettext(
2963         'acq.fund.rollover_distrib_forms',
2964         'During fiscal rollover, update distribution formalae to use new funds',
2965         'coust',
2966         'description'
2967     ),
2968     'acq',
2969     'bool'
2970 );
2971
2972
2973 -- No transaction needed. This can be run on a live, production server.
2974 SELECT evergreen.upgrade_deps_block_check('0782', :eg_version);
2975
2976 /* ** Handled by the supplemental script ** */
2977 -- On a heavily used system, user activity lookup is painful.  This is used
2978 -- on the patron display in the staff client.
2979 --
2980 -- Measured speed increase: ~2s -> .01s
2981 -- CREATE INDEX CONCURRENTLY usr_activity_usr_idx on actor.usr_activity (usr);
2982
2983 -- Finding open holds, often as a subquery within larger hold-related logic,
2984 -- can be sped up with the following.
2985 --
2986 -- Measured speed increase: ~3s -> .02s
2987 -- CREATE INDEX CONCURRENTLY hold_request_open_idx on action.hold_request (id) where cancel_time IS NULL AND fulfillment_time IS NULL;
2988
2989 -- Hold queue position is a particularly difficult thing to calculate
2990 -- efficiently.  Recent changes in the query structure now allow some
2991 -- optimization via indexing.  These do that.
2992 --
2993 -- Measured speed increase: ~6s -> ~0.4s
2994 -- CREATE INDEX CONCURRENTLY cp_available_by_circ_lib_idx on asset.copy (circ_lib) where status IN (0,7);
2995 -- CREATE INDEX CONCURRENTLY hold_request_current_copy_before_cap_idx on action.hold_request (current_copy) where capture_time IS NULL AND cancel_time IS NULL;
2996
2997 -- After heavy use, fetching EDI messages becomes time consuming.  The following
2998 -- index addresses that for large-data environments.
2999 -- 
3000 -- Measured speed increase: ~3s -> .1s
3001 -- CREATE INDEX CONCURRENTLY edi_message_account_status_idx on acq.edi_message (account,status);
3002
3003 -- After heavy use, fetching POs becomes time consuming.  The following
3004 -- index addresses that for large-data environments.
3005 -- 
3006 -- Measured speed increase: ~1.5s -> .1s
3007 -- CREATE INDEX CONCURRENTLY edi_message_po_idx on acq.edi_message (purchase_order);
3008
3009 -- Related to EDI messages, fetching of certain A/T events benefit from specific
3010 -- indexing.  This index is more general than necessary for the observed query
3011 -- but ends up speeding several other (already relatively fast) queries.
3012 --
3013 -- Measured speed increase: ~2s -> .06s
3014 -- CREATE INDEX CONCURRENTLY atev_def_state on action_trigger.event (event_def,state);
3015
3016 -- Retrieval of hold transit by hold id (for transit completion or cancelation)
3017 -- is slow in some query formulations.
3018 --
3019 -- Measured speed increase: ~.5s -> .1s
3020 -- CREATE INDEX CONCURRENTLY hold_transit_copy_hold_idx on action.hold_transit_copy (hold);
3021
3022
3023 SELECT evergreen.upgrade_deps_block_check('0785', :eg_version);
3024
3025 DROP INDEX actor.prox_adj_once_idx;
3026
3027 CREATE UNIQUE INDEX prox_adj_once_idx ON actor.org_unit_proximity_adjustment (
3028     COALESCE(item_circ_lib, -1),
3029     COALESCE(item_owning_lib, -1),
3030     COALESCE(copy_location, -1),
3031     COALESCE(hold_pickup_lib, -1),
3032     COALESCE(hold_request_lib, -1),
3033     COALESCE(circ_mod, ''),
3034     pos
3035 );
3036
3037
3038 --Check if we can apply the upgrade.
3039 SELECT evergreen.upgrade_deps_block_check('0786', :eg_version);
3040
3041
3042 CREATE TYPE search.search_result AS ( id BIGINT, rel NUMERIC, record INT, total INT, checked INT, visible INT, deleted INT, excluded INT );
3043 CREATE TYPE search.search_args AS ( id INT, field_class TEXT, field_name TEXT, table_alias TEXT, term TEXT, term_type TEXT );
3044
3045 CREATE OR REPLACE FUNCTION search.query_parser_fts (
3046
3047     param_search_ou INT,
3048     param_depth     INT,
3049     param_query     TEXT,
3050     param_statuses  INT[],
3051     param_locations INT[],
3052     param_offset    INT,
3053     param_check     INT,
3054     param_limit     INT,
3055     metarecord      BOOL,
3056     staff           BOOL,
3057     param_pref_ou   INT DEFAULT NULL
3058 ) RETURNS SETOF search.search_result AS $func$
3059 DECLARE
3060
3061     current_res         search.search_result%ROWTYPE;
3062     search_org_list     INT[];
3063     luri_org_list       INT[];
3064     tmp_int_list        INT[];
3065
3066     check_limit         INT;
3067     core_limit          INT;
3068     core_offset         INT;
3069     tmp_int             INT;
3070
3071     core_result         RECORD;
3072     core_cursor         REFCURSOR;
3073     core_rel_query      TEXT;
3074
3075     total_count         INT := 0;
3076     check_count         INT := 0;
3077     deleted_count       INT := 0;
3078     visible_count       INT := 0;
3079     excluded_count      INT := 0;
3080
3081 BEGIN
3082
3083     check_limit := COALESCE( param_check, 1000 );
3084     core_limit  := COALESCE( param_limit, 25000 );
3085     core_offset := COALESCE( param_offset, 0 );
3086
3087     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
3088
3089     IF param_search_ou > 0 THEN
3090         IF param_depth IS NOT NULL THEN
3091             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
3092         ELSE
3093             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
3094         END IF;
3095
3096         SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
3097
3098     ELSIF param_search_ou < 0 THEN
3099         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
3100
3101         FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
3102             SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
3103             luri_org_list := luri_org_list || tmp_int_list;
3104         END LOOP;
3105
3106         SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
3107
3108     ELSIF param_search_ou = 0 THEN
3109         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
3110     END IF;
3111
3112     IF param_pref_ou IS NOT NULL THEN
3113         SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors(param_pref_ou);
3114         luri_org_list := luri_org_list || tmp_int_list;
3115     END IF;
3116
3117     OPEN core_cursor FOR EXECUTE param_query;
3118
3119     LOOP
3120
3121         FETCH core_cursor INTO core_result;
3122         EXIT WHEN NOT FOUND;
3123         EXIT WHEN total_count >= core_limit;
3124
3125         total_count := total_count + 1;
3126
3127         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
3128
3129         check_count := check_count + 1;
3130
3131         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
3132         IF NOT FOUND THEN
3133             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
3134             deleted_count := deleted_count + 1;
3135             CONTINUE;
3136         END IF;
3137
3138         PERFORM 1
3139           FROM  biblio.record_entry b
3140                 JOIN config.bib_source s ON (b.source = s.id)
3141           WHERE s.transcendant
3142                 AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
3143
3144         IF FOUND THEN
3145             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
3146             visible_count := visible_count + 1;
3147
3148             current_res.id = core_result.id;
3149             current_res.rel = core_result.rel;
3150
3151             tmp_int := 1;
3152             IF metarecord THEN
3153                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
3154             END IF;
3155
3156             IF tmp_int = 1 THEN
3157                 current_res.record = core_result.records[1];
3158             ELSE
3159                 current_res.record = NULL;
3160             END IF;
3161
3162             RETURN NEXT current_res;
3163
3164             CONTINUE;
3165         END IF;
3166
3167         PERFORM 1
3168           FROM  asset.call_number cn
3169                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
3170                 JOIN asset.uri uri ON (map.uri = uri.id)
3171           WHERE NOT cn.deleted
3172                 AND cn.label = '##URI##'
3173                 AND uri.active
3174                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
3175                 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3176                 AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
3177           LIMIT 1;
3178
3179         IF FOUND THEN
3180             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
3181             visible_count := visible_count + 1;
3182
3183             current_res.id = core_result.id;
3184             current_res.rel = core_result.rel;
3185
3186             tmp_int := 1;
3187             IF metarecord THEN
3188                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
3189             END IF;
3190
3191             IF tmp_int = 1 THEN
3192                 current_res.record = core_result.records[1];
3193             ELSE
3194                 current_res.record = NULL;
3195             END IF;
3196
3197             RETURN NEXT current_res;
3198
3199             CONTINUE;
3200         END IF;
3201
3202         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
3203
3204             PERFORM 1
3205               FROM  asset.call_number cn
3206                     JOIN asset.copy cp ON (cp.call_number = cn.id)
3207               WHERE NOT cn.deleted
3208                     AND NOT cp.deleted
3209                     AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
3210                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3211                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3212               LIMIT 1;
3213
3214             IF NOT FOUND THEN
3215                 PERFORM 1
3216                   FROM  biblio.peer_bib_copy_map pr
3217                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
3218                   WHERE NOT cp.deleted
3219                         AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
3220                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
3221                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3222                   LIMIT 1;
3223
3224                 IF NOT FOUND THEN
3225                 -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
3226                     excluded_count := excluded_count + 1;
3227                     CONTINUE;
3228                 END IF;
3229             END IF;
3230
3231         END IF;
3232
3233         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
3234
3235             PERFORM 1
3236               FROM  asset.call_number cn
3237                     JOIN asset.copy cp ON (cp.call_number = cn.id)
3238               WHERE NOT cn.deleted
3239                     AND NOT cp.deleted
3240                     AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
3241                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3242                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3243               LIMIT 1;
3244
3245             IF NOT FOUND THEN
3246                 PERFORM 1
3247                   FROM  biblio.peer_bib_copy_map pr
3248                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
3249                   WHERE NOT cp.deleted
3250                         AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
3251                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
3252                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3253                   LIMIT 1;
3254
3255                 IF NOT FOUND THEN
3256                     -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
3257                     excluded_count := excluded_count + 1;
3258                     CONTINUE;
3259                 END IF;
3260             END IF;
3261
3262         END IF;
3263
3264         IF staff IS NULL OR NOT staff THEN
3265
3266             PERFORM 1
3267               FROM  asset.opac_visible_copies
3268               WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3269                     AND record IN ( SELECT * FROM unnest( core_result.records ) )
3270               LIMIT 1;
3271
3272             IF NOT FOUND THEN
3273                 PERFORM 1
3274                   FROM  biblio.peer_bib_copy_map pr
3275                         JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
3276                   WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3277                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
3278                   LIMIT 1;
3279
3280                 IF NOT FOUND THEN
3281
3282                     -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
3283                     excluded_count := excluded_count + 1;
3284                     CONTINUE;
3285                 END IF;
3286             END IF;
3287
3288         ELSE
3289
3290             PERFORM 1
3291               FROM  asset.call_number cn
3292                     JOIN asset.copy cp ON (cp.call_number = cn.id)
3293               WHERE NOT cn.deleted
3294                     AND NOT cp.deleted
3295                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3296                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3297               LIMIT 1;
3298
3299             IF NOT FOUND THEN
3300
3301                 PERFORM 1
3302                   FROM  biblio.peer_bib_copy_map pr
3303                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
3304                   WHERE NOT cp.deleted
3305                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3306                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
3307                   LIMIT 1;
3308
3309                 IF NOT FOUND THEN
3310
3311                     PERFORM 1
3312                       FROM  asset.call_number cn
3313                             JOIN asset.copy cp ON (cp.call_number = cn.id)
3314                       WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3315                             AND NOT cp.deleted
3316                       LIMIT 1;
3317
3318                     IF FOUND THEN
3319                         -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
3320                         excluded_count := excluded_count + 1;
3321                         CONTINUE;
3322                     END IF;
3323                 END IF;
3324
3325             END IF;
3326
3327         END IF;
3328
3329         visible_count := visible_count + 1;
3330
3331         current_res.id = core_result.id;
3332         current_res.rel = core_result.rel;
3333
3334         tmp_int := 1;
3335         IF metarecord THEN
3336             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
3337         END IF;
3338
3339         IF tmp_int = 1 THEN
3340             current_res.record = core_result.records[1];
3341         ELSE
3342             current_res.record = NULL;
3343         END IF;
3344
3345         RETURN NEXT current_res;
3346
3347         IF visible_count % 1000 = 0 THEN
3348             -- RAISE NOTICE ' % visible so far ... ', visible_count;
3349         END IF;
3350
3351     END LOOP;
3352
3353     current_res.id = NULL;
3354     current_res.rel = NULL;
3355     current_res.record = NULL;
3356     current_res.total = total_count;
3357     current_res.checked = check_count;
3358     current_res.deleted = deleted_count;
3359     current_res.visible = visible_count;
3360     current_res.excluded = excluded_count;
3361
3362     CLOSE core_cursor;
3363
3364     RETURN NEXT current_res;
3365
3366 END;
3367 $func$ LANGUAGE PLPGSQL;
3368
3369  
3370
3371 SELECT evergreen.upgrade_deps_block_check('0788', :eg_version);
3372
3373 -- New view including 264 as a potential tag for publisher and pubdate
3374 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
3375 SELECT  r.id,
3376     r.fingerprint,
3377     r.quality,
3378     r.tcn_source,
3379     r.tcn_value,
3380     FIRST(title.value) AS title,
3381     FIRST(author.value) AS author,
3382     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
3383     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
3384     ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
3385     ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn
3386   FROM  biblio.record_entry r
3387     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
3388     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
3389     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND (publisher.tag = '260' OR (publisher.tag = '264' AND publisher.ind2 = '1')) AND publisher.subfield = 'b')
3390     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND (pubdate.tag = '260' OR (pubdate.tag = '264' AND pubdate.ind2 = '1')) AND pubdate.subfield = 'c')
3391     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
3392     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
3393   GROUP BY 1,2,3,4,5;
3394
3395 -- Update reporter.materialized_simple_record with new 264-based values for publisher and pubdate
3396 DELETE FROM reporter.materialized_simple_record WHERE id IN (
3397     SELECT DISTINCT record FROM metabib.full_rec WHERE tag = '264' AND subfield IN ('b', 'c')
3398 );
3399
3400 INSERT INTO reporter.materialized_simple_record
3401     SELECT DISTINCT rossr.* FROM reporter.old_super_simple_record rossr INNER JOIN metabib.full_rec mfr ON mfr.record = rossr.id
3402         WHERE mfr.tag = '264' AND mfr.subfield IN ('b', 'c')
3403 ;
3404
3405 SELECT evergreen.upgrade_deps_block_check('0789', :eg_version);
3406 SELECT evergreen.upgrade_deps_block_check('0790', :eg_version);
3407
3408 ALTER TABLE config.metabib_class ADD COLUMN combined BOOL NOT NULL DEFAULT FALSE;
3409 UPDATE config.metabib_class SET combined = TRUE WHERE name = 'subject';
3410
3411
3412 --Check if we can apply the upgrade.
3413 SELECT evergreen.upgrade_deps_block_check('0791', :eg_version);
3414
3415
3416
3417 CREATE OR REPLACE FUNCTION search.query_parser_fts (
3418
3419     param_search_ou INT,
3420     param_depth     INT,
3421     param_query     TEXT,
3422     param_statuses  INT[],
3423     param_locations INT[],
3424     param_offset    INT,
3425     param_check     INT,
3426     param_limit     INT,
3427     metarecord      BOOL,
3428     staff           BOOL,
3429     deleted_search  BOOL,
3430     param_pref_ou   INT DEFAULT NULL
3431 ) RETURNS SETOF search.search_result AS $func$
3432 DECLARE
3433
3434     current_res         search.search_result%ROWTYPE;
3435     search_org_list     INT[];
3436     luri_org_list       INT[];
3437     tmp_int_list        INT[];
3438
3439     check_limit         INT;
3440     core_limit          INT;
3441     core_offset         INT;
3442     tmp_int             INT;
3443
3444     core_result         RECORD;
3445     core_cursor         REFCURSOR;
3446     core_rel_query      TEXT;
3447
3448     total_count         INT := 0;
3449     check_count         INT := 0;
3450     deleted_count       INT := 0;
3451     visible_count       INT := 0;
3452     excluded_count      INT := 0;
3453
3454 BEGIN
3455
3456     check_limit := COALESCE( param_check, 1000 );
3457     core_limit  := COALESCE( param_limit, 25000 );
3458     core_offset := COALESCE( param_offset, 0 );
3459
3460     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
3461
3462     IF param_search_ou > 0 THEN
3463         IF param_depth IS NOT NULL THEN
3464             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
3465         ELSE
3466             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
3467         END IF;
3468
3469         SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
3470
3471     ELSIF param_search_ou < 0 THEN
3472         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
3473
3474         FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
3475             SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
3476             luri_org_list := luri_org_list || tmp_int_list;
3477         END LOOP;
3478
3479         SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
3480
3481     ELSIF param_search_ou = 0 THEN
3482         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
3483     END IF;
3484
3485     IF param_pref_ou IS NOT NULL THEN
3486         SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors(param_pref_ou);
3487         luri_org_list := luri_org_list || tmp_int_list;
3488     END IF;
3489
3490     OPEN core_cursor FOR EXECUTE param_query;
3491
3492     LOOP
3493
3494         FETCH core_cursor INTO core_result;
3495         EXIT WHEN NOT FOUND;
3496         EXIT WHEN total_count >= core_limit;
3497
3498         total_count := total_count + 1;
3499
3500         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
3501
3502         check_count := check_count + 1;
3503
3504         IF NOT deleted_search THEN
3505
3506             PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
3507             IF NOT FOUND THEN
3508                 -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
3509                 deleted_count := deleted_count + 1;
3510                 CONTINUE;
3511             END IF;
3512
3513             PERFORM 1
3514               FROM  biblio.record_entry b
3515                     JOIN config.bib_source s ON (b.source = s.id)
3516               WHERE s.transcendant
3517                     AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
3518
3519             IF FOUND THEN
3520                 -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
3521                 visible_count := visible_count + 1;
3522
3523                 current_res.id = core_result.id;
3524                 current_res.rel = core_result.rel;
3525
3526                 tmp_int := 1;
3527                 IF metarecord THEN
3528                     SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
3529                 END IF;
3530
3531                 IF tmp_int = 1 THEN
3532                     current_res.record = core_result.records[1];
3533                 ELSE
3534                     current_res.record = NULL;
3535                 END IF;
3536
3537                 RETURN NEXT current_res;
3538
3539                 CONTINUE;
3540             END IF;
3541
3542             PERFORM 1
3543               FROM  asset.call_number cn
3544                     JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
3545                     JOIN asset.uri uri ON (map.uri = uri.id)
3546               WHERE NOT cn.deleted
3547                     AND cn.label = '##URI##'
3548                     AND uri.active
3549                     AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
3550                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3551                     AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
3552               LIMIT 1;
3553
3554             IF FOUND THEN
3555                 -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
3556                 visible_count := visible_count + 1;
3557
3558                 current_res.id = core_result.id;
3559                 current_res.rel = core_result.rel;
3560
3561                 tmp_int := 1;
3562                 IF metarecord THEN
3563                     SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
3564                 END IF;
3565
3566                 IF tmp_int = 1 THEN
3567                     current_res.record = core_result.records[1];
3568                 ELSE
3569                     current_res.record = NULL;
3570                 END IF;
3571
3572                 RETURN NEXT current_res;
3573
3574                 CONTINUE;
3575             END IF;
3576
3577             IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
3578
3579                 PERFORM 1
3580                   FROM  asset.call_number cn
3581                         JOIN asset.copy cp ON (cp.call_number = cn.id)
3582                   WHERE NOT cn.deleted
3583                         AND NOT cp.deleted
3584                         AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
3585                         AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3586                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3587                   LIMIT 1;
3588
3589                 IF NOT FOUND THEN
3590                     PERFORM 1
3591                       FROM  biblio.peer_bib_copy_map pr
3592                             JOIN asset.copy cp ON (cp.id = pr.target_copy)
3593                       WHERE NOT cp.deleted
3594                             AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
3595                             AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
3596                             AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3597                       LIMIT 1;
3598
3599                     IF NOT FOUND THEN
3600                     -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
3601                         excluded_count := excluded_count + 1;
3602                         CONTINUE;
3603                     END IF;
3604                 END IF;
3605
3606             END IF;
3607
3608             IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
3609
3610                 PERFORM 1
3611                   FROM  asset.call_number cn
3612                         JOIN asset.copy cp ON (cp.call_number = cn.id)
3613                   WHERE NOT cn.deleted
3614                         AND NOT cp.deleted
3615                         AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
3616                         AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3617                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3618                   LIMIT 1;
3619
3620                 IF NOT FOUND THEN
3621                     PERFORM 1
3622                       FROM  biblio.peer_bib_copy_map pr
3623                             JOIN asset.copy cp ON (cp.id = pr.target_copy)
3624                       WHERE NOT cp.deleted
3625                             AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
3626                             AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
3627                             AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3628                       LIMIT 1;
3629
3630                     IF NOT FOUND THEN
3631                         -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
3632                         excluded_count := excluded_count + 1;
3633                         CONTINUE;
3634                     END IF;
3635                 END IF;
3636
3637             END IF;
3638
3639             IF staff IS NULL OR NOT staff THEN
3640
3641                 PERFORM 1
3642                   FROM  asset.opac_visible_copies
3643                   WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3644                         AND record IN ( SELECT * FROM unnest( core_result.records ) )
3645                   LIMIT 1;
3646
3647                 IF NOT FOUND THEN
3648                     PERFORM 1
3649                       FROM  biblio.peer_bib_copy_map pr
3650                             JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
3651                       WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3652                             AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
3653                       LIMIT 1;
3654
3655                     IF NOT FOUND THEN
3656
3657                         -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
3658                         excluded_count := excluded_count + 1;
3659                         CONTINUE;
3660                     END IF;
3661                 END IF;
3662
3663             ELSE
3664
3665                 PERFORM 1
3666                   FROM  asset.call_number cn
3667                         JOIN asset.copy cp ON (cp.call_number = cn.id)
3668                   WHERE NOT cn.deleted
3669                         AND NOT cp.deleted
3670                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3671                         AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3672                   LIMIT 1;
3673
3674                 IF NOT FOUND THEN
3675
3676                     PERFORM 1
3677                       FROM  biblio.peer_bib_copy_map pr
3678                             JOIN asset.copy cp ON (cp.id = pr.target_copy)
3679                       WHERE NOT cp.deleted
3680                             AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
3681                             AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
3682                       LIMIT 1;
3683
3684                     IF NOT FOUND THEN
3685
3686                         PERFORM 1
3687                           FROM  asset.call_number cn
3688                                 JOIN asset.copy cp ON (cp.call_number = cn.id)
3689                           WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
3690                                 AND NOT cp.deleted
3691                           LIMIT 1;
3692
3693                         IF FOUND THEN
3694                             -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
3695                             excluded_count := excluded_count + 1;
3696                             CONTINUE;
3697                         END IF;
3698                     END IF;
3699
3700                 END IF;
3701
3702             END IF;
3703
3704         END IF;
3705
3706         visible_count := visible_count + 1;
3707
3708         current_res.id = core_result.id;
3709         current_res.rel = core_result.rel;
3710
3711         tmp_int := 1;
3712         IF metarecord THEN
3713             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
3714         END IF;
3715
3716         IF tmp_int = 1 THEN
3717             current_res.record = core_result.records[1];
3718         ELSE
3719             current_res.record = NULL;
3720         END IF;
3721
3722         RETURN NEXT current_res;
3723
3724         IF visible_count % 1000 = 0 THEN
3725             -- RAISE NOTICE ' % visible so far ... ', visible_count;
3726         END IF;
3727
3728     END LOOP;
3729
3730     current_res.id = NULL;
3731     current_res.rel = NULL;
3732     current_res.record = NULL;
3733     current_res.total = total_count;
3734     current_res.checked = check_count;
3735     current_res.deleted = deleted_count;
3736     current_res.visible = visible_count;
3737     current_res.excluded = excluded_count;
3738
3739     CLOSE core_cursor;
3740
3741     RETURN NEXT current_res;
3742
3743 END;
3744 $func$ LANGUAGE PLPGSQL;
3745
3746
3747 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
3748 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
3749 DECLARE
3750     transformed_xml TEXT;
3751     prev_xfrm       TEXT;
3752     normalizer      RECORD;
3753     xfrm            config.xml_transform%ROWTYPE;
3754     attr_value      TEXT;
3755     new_attrs       HSTORE := ''::HSTORE;
3756     attr_def        config.record_attr_definition%ROWTYPE;
3757 BEGIN
3758
3759     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
3760         PERFORM * FROM config.internal_flag WHERE
3761             name = 'ingest.metarecord_mapping.preserve_on_delete' AND enabled;
3762         IF NOT FOUND THEN
3763             -- One needs to keep these around to support searches
3764             -- with the #deleted modifier, so one should turn on the named
3765             -- internal flag for that functionality.
3766             DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id;
3767             DELETE FROM metabib.record_attr WHERE id = NEW.id;
3768         END IF;
3769
3770         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
3771         DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
3772         DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs
3773         RETURN NEW; -- and we're done
3774     END IF;
3775
3776     IF TG_OP = 'UPDATE' THEN -- re-ingest?
3777         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
3778
3779         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
3780             RETURN NEW;
3781         END IF;
3782     END IF;
3783
3784     -- Record authority linking
3785     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
3786     IF NOT FOUND THEN
3787         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
3788     END IF;
3789
3790     -- Flatten and insert the mfr data
3791     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
3792     IF NOT FOUND THEN
3793         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
3794
3795         -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
3796         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
3797         IF NOT FOUND THEN
3798             FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
3799
3800                 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
3801                     SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
3802                       FROM  (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
3803                       WHERE record = NEW.id
3804                             AND tag LIKE attr_def.tag
3805                             AND CASE
3806                                 WHEN attr_def.sf_list IS NOT NULL 
3807                                     THEN POSITION(subfield IN attr_def.sf_list) > 0
3808                                 ELSE TRUE
3809                                 END
3810                       GROUP BY tag
3811                       ORDER BY tag
3812                       LIMIT 1;
3813
3814                 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
3815                     attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
3816
3817                 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
3818
3819                     SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
3820             
3821                     -- See if we can skip the XSLT ... it's expensive
3822                     IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
3823                         -- Can't skip the transform
3824                         IF xfrm.xslt <> '---' THEN
3825                             transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
3826                         ELSE
3827                             transformed_xml := NEW.marc;
3828                         END IF;
3829             
3830                         prev_xfrm := xfrm.name;
3831                     END IF;
3832
3833                     IF xfrm.name IS NULL THEN
3834                         -- just grab the marcxml (empty) transform
3835                         SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
3836                         prev_xfrm := xfrm.name;
3837                     END IF;
3838
3839                     attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
3840
3841                 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
3842                     SELECT  m.value INTO attr_value
3843                       FROM  biblio.marc21_physical_characteristics(NEW.id) v
3844                             JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
3845                       WHERE v.subfield = attr_def.phys_char_sf
3846                       LIMIT 1; -- Just in case ...
3847
3848                 END IF;
3849
3850                 -- apply index normalizers to attr_value
3851                 FOR normalizer IN
3852                     SELECT  n.func AS func,
3853                             n.param_count AS param_count,
3854                             m.params AS params
3855                       FROM  config.index_normalizer n
3856                             JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
3857                       WHERE attr = attr_def.name
3858                       ORDER BY m.pos LOOP
3859                         EXECUTE 'SELECT ' || normalizer.func || '(' ||
3860                             COALESCE( quote_literal( attr_value ), 'NULL' ) ||
3861                             CASE
3862                                 WHEN normalizer.param_count > 0
3863                                     THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
3864                                     ELSE ''
3865                                 END ||
3866                             ')' INTO attr_value;
3867         
3868                 END LOOP;
3869
3870                 -- Add the new value to the hstore
3871                 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
3872
3873             END LOOP;
3874
3875             IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
3876                 DELETE FROM metabib.record_attr WHERE id = NEW.id;
3877                 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
3878             ELSE
3879                 UPDATE metabib.record_attr SET attrs = new_attrs WHERE id = NEW.id;
3880             END IF;
3881
3882         END IF;
3883     END IF;
3884
3885     -- Gather and insert the field entry data
3886     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
3887
3888     -- Located URI magic
3889     IF TG_OP = 'INSERT' THEN
3890         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
3891         IF NOT FOUND THEN
3892             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
3893         END IF;
3894     ELSE
3895         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
3896         IF NOT FOUND THEN
3897             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
3898         END IF;
3899     END IF;
3900
3901     -- (re)map metarecord-bib linking
3902     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
3903         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
3904         IF NOT FOUND THEN
3905             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
3906         END IF;
3907     ELSE -- we're doing an update, and we're not deleted, remap
3908         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
3909         IF NOT FOUND THEN
3910             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
3911         END IF;
3912     END IF;
3913
3914     RETURN NEW;
3915 END;
3916 $func$ LANGUAGE PLPGSQL;
3917  
3918 SELECT evergreen.upgrade_deps_block_check('0792', :eg_version);
3919
3920 UPDATE permission.perm_list SET code = 'URL_VERIFY_UPDATE_SETTINGS' WHERE id = 544 AND code = '544';
3921
3922
3923 SELECT evergreen.upgrade_deps_block_check('0793', :eg_version);
3924
3925 UPDATE config.best_hold_order
3926 SET
3927     approx = 1,
3928     pprox = 2,
3929     aprox = 3,
3930     priority = 4,
3931     cut = 5,
3932     depth = 6,
3933     rtime = 7,
3934     hprox = NULL,
3935     htime = NULL
3936 WHERE name = 'Traditional' AND
3937     pprox = 1 AND
3938     aprox = 2 AND
3939     priority = 3 AND
3940     cut = 4 AND
3941     depth = 5 AND
3942     rtime = 6 ;
3943
3944 UPDATE config.best_hold_order
3945 SET
3946     hprox = 1,
3947     approx = 2,
3948     pprox = 3,
3949     aprox = 4,
3950     priority = 5,
3951     cut = 6,
3952     depth = 7,
3953     rtime = 8,
3954     htime = NULL
3955 WHERE name = 'Traditional with Holds-always-go-home' AND
3956     hprox = 1 AND
3957     pprox = 2 AND
3958     aprox = 3 AND
3959     priority = 4 AND
3960     cut = 5 AND
3961     depth = 6 AND
3962     rtime = 7 AND
3963     htime = 8;
3964
3965 UPDATE config.best_hold_order
3966 SET
3967     htime = 1,
3968     approx = 2,
3969     pprox = 3,
3970     aprox = 4,
3971     priority = 5,
3972     cut = 6,
3973     depth = 7,
3974     rtime = 8,
3975     hprox = NULL
3976 WHERE name = 'Traditional with Holds-go-home' AND
3977     htime = 1 AND
3978     hprox = 2 AND
3979     pprox = 3 AND
3980     aprox = 4 AND
3981     priority = 5 AND
3982     cut = 6 AND
3983     depth = 7 AND
3984     rtime = 8 ;
3985
3986
3987 COMMIT;
3988
3989 -- These are from 0789, and can and should be run outside of a transaction
3990 CREATE TEXT SEARCH CONFIGURATION title ( COPY = english_nostop );
3991 CREATE TEXT SEARCH CONFIGURATION author ( COPY = english_nostop );
3992 CREATE TEXT SEARCH CONFIGURATION subject ( COPY = english_nostop );
3993 CREATE TEXT SEARCH CONFIGURATION series ( COPY = english_nostop );
3994 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = english_nostop );
3995
3996 \qecho Please run Open-ILS/src/sql/Pg/version-upgrade/2.3-2.4-supplemental.sh now, which contains additional required SQL to complete your Evergreen upgrade!
3997