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