From: Bill Erickson Date: Tue, 28 Aug 2018 19:57:37 +0000 (-0400) Subject: JBAS-1832 KCLS 2.12 -> 3.2 SQL upgrade X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=1a18bd73a3765cbc5286bb2e6ad86417b4755191;p=working%2FEvergreen.git JBAS-1832 KCLS 2.12 -> 3.2 SQL upgrade Signed-off-by: Bill Erickson --- diff --git a/KCLS/sql/schema/tools/pg-func-diffs.sh b/KCLS/sql/schema/tools/pg-func-diffs.sh index 3f93377739..237ab5680d 100755 --- a/KCLS/sql/schema/tools/pg-func-diffs.sh +++ b/KCLS/sql/schema/tools/pg-func-diffs.sh @@ -9,7 +9,7 @@ # TODO: getopts, database names via command line. more docs, etc. FUNCS=$1 PSQL_STOCK="psql -x -U evergreen -d evergreen" -PSQL_KCLS="psql -x -U evergreen -d eg_kcls_25" +PSQL_KCLS="psql -x -U evergreen -d eg_kcls_2_12" mkdir -p results diff --git a/kcls-2.12-3.2-db-upgrade-data.sql b/kcls-2.12-3.2-db-upgrade-data.sql new file mode 100644 index 0000000000..5a158b8db9 --- /dev/null +++ b/kcls-2.12-3.2-db-upgrade-data.sql @@ -0,0 +1,127 @@ + +---------------------------------------------------------------------------- +-- 2.12 -> 3.2 SQL DATA UPDATES +-- No global transaction here. +---------------------------------------------------------------------------- + +\set ON_ERROR_STOP on + +SET STATEMENT_TIMEOUT = 0; + +---------------------------------------------------------------------------- +-- POST UPGRADE DATA UPDATES. +---------------------------------------------------------------------------- + +-- TODO: EXTERNAL BIB FULL RE-INGEST + +-- TODO: run all in transaction for testing -- remove transactions later. + +BEGIN; + +DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON biblio.peer_bib_copy_map; +DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON biblio.record_entry; +DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON asset.copy; +DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON asset.call_number; +DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON asset.copy_location; +DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON serial.unit; +DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON config.copy_status; +DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON actor.org_unit; + +SELECT CLOCK_TIMESTAMP(), 'Updating copy visibility attr cache'; + +-- no insert wrapper required +-- Upgrade the data! +INSERT INTO asset.copy_vis_attr_cache (target_copy, record, vis_attr_vector) + SELECT cp.id, + cn.record, + asset.calculate_copy_visibility_attribute_set(cp.id) + FROM asset.copy cp + JOIN asset.call_number cn ON (cp.call_number = cn.id); + +CREATE TRIGGER z_opac_vis_mat_view_tgr BEFORE INSERT OR UPDATE + ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); +CREATE TRIGGER z_opac_vis_mat_view_tgr AFTER INSERT OR DELETE + ON biblio.peer_bib_copy_map FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); +CREATE TRIGGER z_opac_vis_mat_view_tgr AFTER UPDATE + ON asset.call_number FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); +CREATE TRIGGER z_opac_vis_mat_view_del_tgr BEFORE DELETE + ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); +CREATE TRIGGER z_opac_vis_mat_view_del_tgr BEFORE DELETE + ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); +CREATE TRIGGER z_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE + ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); +CREATE TRIGGER z_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE + ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); + +SELECT CLOCK_TIMESTAMP(), 'Making copy alert types active'; + +UPDATE config.copy_alert_type +SET active = TRUE +WHERE id IN (4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17); + + +-- Update keyword indexes for existing patrons +SELECT CLOCK_TIMESTAMP(), 'Updating patron name keyword vector'; + +UPDATE actor.usr SET name_kw_tsvector = + TO_TSVECTOR( + COALESCE(prefix, '') || ' ' || + COALESCE(first_given_name, '') || ' ' || + COALESCE(evergreen.unaccent_and_squash(first_given_name), '') || ' ' || + COALESCE(second_given_name, '') || ' ' || + COALESCE(evergreen.unaccent_and_squash(second_given_name), '') || ' ' || + COALESCE(family_name, '') || ' ' || + COALESCE(evergreen.unaccent_and_squash(family_name), '') || ' ' || + COALESCE(suffix, '') + ); + +-- Authority record re-ingest + +SELECT CLOCK_TIMESTAMP(), 'Reingesting authority records'; + +-- add the flag ingest.disable_authority_full_rec if it does not exist +INSERT INTO config.internal_flag (name, enabled) +SELECT 'ingest.disable_authority_full_rec', FALSE +WHERE NOT EXISTS (SELECT 1 FROM config.internal_flag +WHERE name = 'ingest.disable_authority_full_rec'); + +CREATE TEMPORARY TABLE internal_flag_state AS + SELECT name, enabled + FROM config.internal_flag + WHERE name in ( + 'ingest.reingest.force_on_same_marc', + 'ingest.disable_authority_auto_update', + 'ingest.disable_authority_full_rec' + ); + +UPDATE config.internal_flag +SET enabled = TRUE +WHERE name in ( + 'ingest.reingest.force_on_same_marc', + 'ingest.disable_authority_auto_update', + 'ingest.disable_authority_full_rec' +); + +ALTER TABLE authority.record_entry DISABLE TRIGGER a_marcxml_is_well_formed; +ALTER TABLE authority.record_entry DISABLE TRIGGER b_maintain_901; +ALTER TABLE authority.record_entry DISABLE TRIGGER c_maintain_control_numbers; +ALTER TABLE authority.record_entry DISABLE TRIGGER map_thesaurus_to_control_set; + +UPDATE authority.record_entry SET id = id WHERE NOT DELETED; + +ALTER TABLE authority.record_entry ENABLE TRIGGER a_marcxml_is_well_formed; +ALTER TABLE authority.record_entry ENABLE TRIGGER b_maintain_901; +ALTER TABLE authority.record_entry ENABLE TRIGGER c_maintain_control_numbers; +ALTER TABLE authority.record_entry ENABLE TRIGGER map_thesaurus_to_control_set; + +-- and restore +UPDATE config.internal_flag a +SET enabled = b.enabled +FROM internal_flag_state b +WHERE a.name = b.name; + + +COMMIT; +--ROLLBACK; + + diff --git a/kcls-2.12-3.2-db-upgrade.sql b/kcls-2.12-3.2-db-upgrade.sql new file mode 100644 index 0000000000..2b16a8ae5a --- /dev/null +++ b/kcls-2.12-3.2-db-upgrade.sql @@ -0,0 +1,21496 @@ + +BEGIN; + +---------------------------------------------------------------------------- +-- BEGIN 2.12.6 -> 3.0.0 +---------------------------------------------------------------------------- + +--Upgrade Script for 2.12.6 to 3.0.0 +--\set eg_version '''3.0.0''' +\set ON_ERROR_STOP on + +SET STATEMENT_TIMEOUT = 0; + +-- VERSION 1032 + +CREATE OR REPLACE VIEW action.all_circulation_combined_types AS + SELECT acirc.id AS id, + acirc.xact_start, + acirc.circ_lib, + acirc.circ_staff, + acirc.create_time, + ac_acirc.circ_modifier AS item_type, + 'regular_circ'::text AS circ_type + FROM action.circulation acirc, + asset.copy ac_acirc + WHERE acirc.target_copy = ac_acirc.id +UNION ALL + SELECT ancc.id::BIGINT AS id, + ancc.circ_time AS xact_start, + ancc.circ_lib, + ancc.staff AS circ_staff, + ancc.circ_time AS create_time, + cnct_ancc.name AS item_type, + 'non-cat_circ'::text AS circ_type + FROM action.non_cataloged_circulation ancc, + config.non_cataloged_type cnct_ancc + WHERE ancc.item_type = cnct_ancc.id +UNION ALL + SELECT aihu.id::BIGINT AS id, + aihu.use_time AS xact_start, + aihu.org_unit AS circ_lib, + aihu.staff AS circ_staff, + aihu.use_time AS create_time, + ac_aihu.circ_modifier AS item_type, + 'in-house_use'::text AS circ_type + FROM action.in_house_use aihu, + asset.copy ac_aihu + WHERE aihu.item = ac_aihu.id +UNION ALL + SELECT ancihu.id::BIGINT AS id, + ancihu.use_time AS xact_start, + ancihu.org_unit AS circ_lib, + ancihu.staff AS circ_staff, + ancihu.use_time AS create_time, + cnct_ancihu.name AS item_type, + 'non-cat_circ'::text AS circ_type + FROM action.non_cat_in_house_use ancihu, + config.non_cataloged_type cnct_ancihu + WHERE ancihu.item_type = cnct_ancihu.id +UNION ALL + SELECT aacirc.id AS id, + aacirc.xact_start, + aacirc.circ_lib, + aacirc.circ_staff, + aacirc.create_time, + ac_aacirc.circ_modifier AS item_type, + 'aged_circ'::text AS circ_type + FROM action.aged_circulation aacirc, + asset.copy ac_aacirc + WHERE aacirc.target_copy = ac_aacirc.id; + + +-- VERSION 1034 + +ALTER TABLE config.hold_matrix_matchpoint + ADD COLUMN description TEXT; + +ALTER TABLE config.circ_matrix_matchpoint + ADD COLUMN description TEXT; + +-- VERSION 1035 + +-- Per Lp bug 1684984, the config.internal_flag, +-- ingest.disable_metabib_field_entry, was made obsolete by the +-- addition of the ingest.skip_browse_indexing, +-- ingest.skip_search_indexing, and ingest.skip_facet_indexing flags. +-- Since it is not used in the database, we delete it. +DELETE FROM config.internal_flag +WHERE name = 'ingest.disable_metabib_field_entry'; + +-- VERSION 1036 + +CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$ +DECLARE + temp_value config.hard_due_date_values%ROWTYPE; + updated INT := 0; +BEGIN + FOR temp_value IN + SELECT DISTINCT ON (hard_due_date) * + FROM config.hard_due_date_values + WHERE active_date <= NOW() -- We've passed (or are at) the rollover time + ORDER BY hard_due_date, active_date DESC -- Latest (nearest to us) active time + LOOP + UPDATE config.hard_due_date + SET ceiling_date = temp_value.ceiling_date + WHERE id = temp_value.hard_due_date + AND ceiling_date <> temp_value.ceiling_date -- Time is equal if we've already updated the chdd + AND temp_value.ceiling_date >= NOW(); -- Don't update ceiling dates to the past + + IF FOUND THEN + updated := updated + 1; + END IF; + END LOOP; + + RETURN updated; +END; +$func$ LANGUAGE plpgsql; + + +-- VERSION 1041 + +--delete all instances from permission.grp_perm_map first +DELETE FROM permission.grp_perm_map where perm in +(select id from permission.perm_list where code='SET_CIRC_MISSING'); + +--delete all instances from permission.usr_perm_map too +DELETE FROM permission.usr_perm_map where perm in +(select id from permission.perm_list where code='SET_CIRC_MISSING'); + +--delete from permission.perm_list +DELETE FROM permission.perm_list where code='SET_CIRC_MISSING'; + + +-- VERSION 1042 + +ALTER TABLE asset.copy_location + ADD COLUMN url TEXT; + +-- SELECT evergreen.upgrade_deps_block_check('1043', :eg_version); + +-- SELECT evergreen.upgrade_deps_block_check('1044', :eg_version); + +-- INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1045', :eg_version); -- csharp/berick/gmcharlt + +ALTER TABLE action.transit_copy + ADD COLUMN cancel_time TIMESTAMPTZ; + +-- change "abort" to "cancel" in stock perm descriptions +UPDATE permission.perm_list + SET description = 'Allow a user to cancel a copy transit if the user is at the transit destination or source' + WHERE code = 'ABORT_TRANSIT' + AND description = 'Allow a user to abort a copy transit if the user is at the transit destination or source'; +UPDATE permission.perm_list + SET description = 'Allow a user to cancel a copy transit if the user is not at the transit source or dest' + WHERE code = 'ABORT_REMOTE_TRANSIT' + AND description = 'Allow a user to abort a copy transit if the user is not at the transit source or dest'; +UPDATE permission.perm_list + SET description = 'Allows a user to cancel a transit on a copy with status of LOST' + WHERE code = 'ABORT_TRANSIT_ON_LOST' + AND description = 'Allows a user to abort a transit on a copy with status of LOST'; +UPDATE permission.perm_list + SET description = 'Allows a user to cancel a transit on a copy with status of MISSING' + WHERE code = 'ABORT_TRANSIT_ON_MISSING' + AND description = 'Allows a user to abort a transit on a copy with status of MISSING'; + +-- SELECT evergreen.upgrade_deps_block_check('1046', :eg_version); -- phasefx/berick/gmcharlt + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT into config.org_unit_setting_type ( + name + ,grp + ,label + ,description + ,datatype +) VALUES ( ---------------------------------------- + 'webstaff.format.dates' + ,'gui' + ,oils_i18n_gettext( + 'webstaff.format.dates' + ,'Format Dates with this pattern' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.format.dates' + ,'Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010"). This will be used in areas where a date without a timestamp is sufficient, like Date of Birth.' + ,'coust' + ,'description' + ) + ,'string' +), ( ---------------------------------------- + 'webstaff.format.date_and_time' + ,'gui' + ,oils_i18n_gettext( + 'webstaff.format.date_and_time' + ,'Format Date+Time with this pattern' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.format.date_and_time' + ,'Format Date+Time with this pattern (examples: "yy-MM-dd h:m:s.SSS a" for "16-04-05 2:07:20.666 PM", "yyyy-dd-MMM HH:mm" for "2016-05-Apr 14:07"). This will be used in areas of the client where a date with a timestamp is needed, like Checkout, Due Date, or Record Created.' + ,'coust' + ,'description' + ) + ,'string' +); +END IF; END $INSERT$; + +UPDATE + config.org_unit_setting_type +SET + label = 'Deprecated: ' || label -- FIXME: Is this okay? +WHERE + name IN ('format.date','format.time') +; + +-- SELECT evergreen.upgrade_deps_block_check('1047', :eg_version); -- gmcharlt/stompro + +CREATE TABLE config.copy_tag_type ( + code TEXT NOT NULL PRIMARY KEY, + label TEXT NOT NULL, + owner INTEGER NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED +); + +CREATE INDEX config_copy_tag_type_owner_idx + ON config.copy_tag_type (owner); + +CREATE TABLE asset.copy_tag ( + id SERIAL PRIMARY KEY, + tag_type TEXT REFERENCES config.copy_tag_type (code) + ON UPDATE CASCADE ON DELETE CASCADE, + label TEXT NOT NULL, + value TEXT NOT NULL, + index_vector tsvector NOT NULL, + staff_note TEXT, + pub BOOLEAN DEFAULT TRUE, + owner INTEGER NOT NULL REFERENCES actor.org_unit (id) +); + +CREATE INDEX asset_copy_tag_label_idx + ON asset.copy_tag (label); +CREATE INDEX asset_copy_tag_label_lower_idx + ON asset.copy_tag (evergreen.lowercase(label)); +CREATE INDEX asset_copy_tag_index_vector_idx + ON asset.copy_tag + USING GIN(index_vector); +CREATE INDEX asset_copy_tag_tag_type_idx + ON asset.copy_tag (tag_type); +CREATE INDEX asset_copy_tag_owner_idx + ON asset.copy_tag (owner); + +CREATE OR REPLACE FUNCTION asset.set_copy_tag_value () RETURNS TRIGGER AS $$ +BEGIN + IF NEW.value IS NULL THEN + NEW.value = NEW.label; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE 'plpgsql'; + +-- name of following trigger chosen to ensure it runs first +CREATE TRIGGER asset_copy_tag_do_value + BEFORE INSERT OR UPDATE ON asset.copy_tag + FOR EACH ROW EXECUTE PROCEDURE asset.set_copy_tag_value(); +CREATE TRIGGER asset_copy_tag_fti_trigger + BEFORE UPDATE OR INSERT ON asset.copy_tag + FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('default'); + +CREATE TABLE asset.copy_tag_copy_map ( + id BIGSERIAL PRIMARY KEY, + copy BIGINT REFERENCES asset.copy (id) + ON UPDATE CASCADE ON DELETE CASCADE, + tag INTEGER REFERENCES asset.copy_tag (id) + ON UPDATE CASCADE ON DELETE CASCADE +); + +CREATE INDEX asset_copy_tag_copy_map_copy_idx + ON asset.copy_tag_copy_map (copy); +CREATE INDEX asset_copy_tag_copy_map_tag_idx + ON asset.copy_tag_copy_map (tag); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO config.copy_tag_type (code, label, owner) VALUES ('bookplate', 'Digital Bookplate', 1); + +INSERT INTO permission.perm_list ( id, code, description ) VALUES + ( 590, 'ADMIN_COPY_TAG_TYPES', oils_i18n_gettext( 590, + 'Administer copy tag types', 'ppl', 'description' )), + ( 591, 'ADMIN_COPY_TAG', oils_i18n_gettext( 591, + 'Administer copy tag', 'ppl', 'description' )) +; + +INSERT INTO config.org_unit_setting_type + (name, label, description, grp, datatype) +VALUES ( + 'opac.search.enable_bookplate_search', + oils_i18n_gettext( + 'opac.search.enable_bookplate_search', + 'Enable Digital Bookplate Search', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'opac.search.enable_bookplate_search', + 'If enabled, adds a "Digital Bookplate" option to the query type selectors in the public catalog for search on copy tags.', + 'coust', + 'description' + ), + 'opac', + 'bool' +); + +-- SELECT evergreen.upgrade_deps_block_check('1048', :eg_version); + +INSERT into config.org_unit_setting_type ( + name + ,grp + ,label + ,description + ,datatype +) VALUES ( ---------------------------------------- + 'webstaff.cat.label.font.family' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.font.family' + ,'Item Print Label Font Family' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.font.family' + ,'Set the preferred font family for item print labels. You can specify a list of CSS fonts, separated by commas, in order of preference; the system will use the first font it finds with a matching name. For example, "Arial, Helvetica, serif"' + ,'coust' + ,'description' + ) + ,'string' +), ( ---------------------------------------- + 'webstaff.cat.label.font.size' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.font.size' + ,'Item Print Label Font Size' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.font.size' + ,'Set the default font size for item print labels. Please include a unit of measurement that is valid CSS. For example, "12pt" or "16px" or "1em"' + ,'coust' + ,'description' + ) + ,'string' +), ( ---------------------------------------- + 'webstaff.cat.label.font.weight' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.font.weight' + ,'Item Print Label Font Weight' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.font.weight' + ,'Set the default font weight for item print labels. Please use the CSS specification for values for font-weight. For example, "normal", "bold", "bolder", or "lighter"' + ,'coust' + ,'description' + ) + ,'string' +), ( ---------------------------------------- + 'webstaff.cat.label.left_label.left_margin' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.left_label.left_margin' + ,'Item Print Label - Left Margin for Left Label' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.left_label.left_margin' + ,'Set the default left margin for the leftmost item print Label. Please include a unit of measurement that is valid CSS. For example, "1in" or "2.5cm"' + ,'coust' + ,'description' + ) + ,'string' +), ( ---------------------------------------- + 'webstaff.cat.label.right_label.left_margin' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.right_label.left_margin' + ,'Item Print Label - Left Margin for Right Label' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.right_label.left_margin' + ,'Set the default left margin for the rightmost item print label (or in other words, the desired space between the two labels). Please include a unit of measurement that is valid CSS. For example, "1in" or "2.5cm"' + ,'coust' + ,'description' + ) + ,'string' +), ( ---------------------------------------- + 'webstaff.cat.label.left_label.height' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.left_label.height' + ,'Item Print Label - Height for Left Label' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.left_label.height' + ,'Set the default height for the leftmost item print label. Please include a unit of measurement that is valid CSS. For example, "1in" or "2.5cm"' + ,'coust' + ,'description' + ) + ,'string' +), ( ---------------------------------------- + 'webstaff.cat.label.left_label.width' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.left_label.width' + ,'Item Print Label - Width for Left Label' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.left_label.width' + ,'Set the default width for the leftmost item print label. Please include a unit of measurement that is valid CSS. For example, "1in" or "2.5cm"' + ,'coust' + ,'description' + ) + ,'string' +), ( ---------------------------------------- + 'webstaff.cat.label.right_label.height' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.right_label.height' + ,'Item Print Label - Height for Right Label' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.right_label.height' + ,'Set the default height for the rightmost item print label. Please include a unit of measurement that is valid CSS. For example, "1in" or "2.5cm"' + ,'coust' + ,'description' + ) + ,'string' +), ( ---------------------------------------- + 'webstaff.cat.label.right_label.width' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.right_label.width' + ,'Item Print Label - Width for Right Label' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.right_label.width' + ,'Set the default width for the rightmost item print label. Please include a unit of measurement that is valid CSS. For example, "1in" or "2.5cm"' + ,'coust' + ,'description' + ) + ,'string' +), ( + 'webstaff.cat.label.inline_css' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.inline_css' + ,'Item Print Label - Inline CSS' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.inline_css' + ,'This setting allows you to inject arbitrary CSS into the item print label template. For example, ".printlabel { text-transform: uppercase; }"' + ,'coust' + ,'description' + ) + ,'string' +), ( + 'webstaff.cat.label.call_number_wrap_filter_height' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.call_number_wrap_filter_height' + ,'Item Print Label - Call Number Wrap Filter Height' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.call_number_wrap_filter_height' + ,'This setting is used to set the default height (in number of lines) to use for call number wrapping in the left print label.' + ,'coust' + ,'description' + ) + ,'integer' +), ( + 'webstaff.cat.label.call_number_wrap_filter_width' + ,'cat' + ,oils_i18n_gettext( + 'webstaff.cat.label.call_number_wrap_filter_width' + ,'Item Print Label - Call Number Wrap Filter Width' + ,'coust' + ,'label' + ) + ,oils_i18n_gettext( + 'webstaff.cat.label.call_number_wrap_filter_width' + ,'This setting is used to set the default width (in number of characters) to use for call number wrapping in the left print label.' + ,'coust' + ,'description' + ) + ,'integer' + + +); + +END IF; END $INSERT$; + +-- SELECT evergreen.upgrade_deps_block_check('1049', :eg_version); -- mmorgan/stompro/gmcharlt + +SELECT CLOCK_TIMESTAMP(), + 'Setting invalid age_protect and circ_as_type entries to NULL'; + +UPDATE asset.copy_template act +SET age_protect = NULL +FROM actor.org_unit aou +WHERE aou.id=act.owning_lib + AND act.age_protect NOT IN + ( + SELECT id FROM config.rule_age_hold_protect + ) +RETURNING act.id "Template ID", act.name "Template Name", + aou.shortname "Owning Lib", + 'Age Protection value reset to null.' "Description"; + +UPDATE asset.copy_template act +SET circ_as_type = NULL +FROM actor.org_unit aou +WHERE aou.id=act.owning_lib + AND act.circ_as_type NOT IN + ( + SELECT code FROM config.item_type_map + ) +RETURNING act.id "Template ID", act.name "Template Name", + aou.shortname "Owning Lib", + 'Circ as Type value reset to null.' as "Description"; + +\echo -----------End Serial Template Fix---------------- + +-- SELECT evergreen.upgrade_deps_block_check('1050', :eg_version); -- mmorgan/cesardv/gmcharlt + +CREATE OR REPLACE FUNCTION permission.usr_perms ( INT ) RETURNS SETOF permission.usr_perm_map AS $$ + SELECT DISTINCT ON (usr,perm) * + FROM ( + (SELECT * FROM permission.usr_perm_map WHERE usr = $1) + UNION ALL + (SELECT -p.id, $1 AS usr, p.perm, p.depth, p.grantable + FROM permission.grp_perm_map p + WHERE p.grp IN ( + SELECT (permission.grp_ancestors( + (SELECT profile FROM actor.usr WHERE id = $1) + )).id + ) + ) + UNION ALL + (SELECT -p.id, $1 AS usr, p.perm, p.depth, p.grantable + FROM permission.grp_perm_map p + WHERE p.grp IN (SELECT (permission.grp_ancestors(m.grp)).id FROM permission.usr_grp_map m WHERE usr = $1)) + ) AS x + ORDER BY 2, 3, 4 ASC, 5 DESC ; +$$ LANGUAGE SQL STABLE ROWS 10; + +-- SELECT evergreen.upgrade_deps_block_check('1051', :eg_version); + +CREATE OR REPLACE VIEW action.all_circulation_slim AS + SELECT + id, + usr, + xact_start, + xact_finish, + unrecovered, + target_copy, + circ_lib, + circ_staff, + checkin_staff, + checkin_lib, + renewal_remaining, + grace_period, + due_date, + stop_fines_time, + checkin_time, + create_time, + duration, + fine_interval, + recurring_fine, + max_fine, + phone_renewal, + desk_renewal, + opac_renewal, + duration_rule, + recurring_fine_rule, + max_fine_rule, + stop_fines, + workstation, + checkin_workstation, + copy_location, + checkin_scan_time, + parent_circ + FROM action.circulation +UNION ALL + SELECT + id, + NULL AS usr, + xact_start, + xact_finish, + unrecovered, + target_copy, + circ_lib, + circ_staff, + checkin_staff, + checkin_lib, + renewal_remaining, + grace_period, + due_date, + stop_fines_time, + checkin_time, + create_time, + duration, + fine_interval, + recurring_fine, + max_fine, + phone_renewal, + desk_renewal, + opac_renewal, + duration_rule, + recurring_fine_rule, + max_fine_rule, + stop_fines, + workstation, + checkin_workstation, + copy_location, + checkin_scan_time, + parent_circ + FROM action.aged_circulation +; + +DROP FUNCTION action.summarize_all_circ_chain(INTEGER); +DROP FUNCTION action.all_circ_chain(INTEGER); + +CREATE OR REPLACE FUNCTION action.all_circ_chain (ctx_circ_id INTEGER) + RETURNS SETOF action.all_circulation_slim AS $$ +DECLARE + tmp_circ action.all_circulation_slim%ROWTYPE; + circ_0 action.all_circulation_slim%ROWTYPE; +BEGIN + + SELECT INTO tmp_circ * FROM action.all_circulation_slim WHERE id = ctx_circ_id; + + IF tmp_circ IS NULL THEN + RETURN NEXT tmp_circ; + END IF; + circ_0 := tmp_circ; + + -- find the front of the chain + WHILE TRUE LOOP + SELECT INTO tmp_circ * FROM action.all_circulation_slim + WHERE id = tmp_circ.parent_circ; + IF tmp_circ IS NULL THEN + EXIT; + END IF; + circ_0 := tmp_circ; + END LOOP; + + -- now send the circs to the caller, oldest to newest + tmp_circ := circ_0; + WHILE TRUE LOOP + IF tmp_circ IS NULL THEN + EXIT; + END IF; + RETURN NEXT tmp_circ; + SELECT INTO tmp_circ * FROM action.all_circulation_slim + WHERE parent_circ = tmp_circ.id; + END LOOP; + +END; +$$ LANGUAGE 'plpgsql'; + +CREATE OR REPLACE FUNCTION action.summarize_all_circ_chain + (ctx_circ_id INTEGER) RETURNS action.circ_chain_summary AS $$ + +DECLARE + + -- first circ in the chain + circ_0 action.all_circulation_slim%ROWTYPE; + + -- last circ in the chain + circ_n action.all_circulation_slim%ROWTYPE; + + -- circ chain under construction + chain action.circ_chain_summary; + tmp_circ action.all_circulation_slim%ROWTYPE; + +BEGIN + + chain.num_circs := 0; + FOR tmp_circ IN SELECT * FROM action.all_circ_chain(ctx_circ_id) LOOP + + IF chain.num_circs = 0 THEN + circ_0 := tmp_circ; + END IF; + + chain.num_circs := chain.num_circs + 1; + circ_n := tmp_circ; + END LOOP; + + chain.start_time := circ_0.xact_start; + chain.last_stop_fines := circ_n.stop_fines; + chain.last_stop_fines_time := circ_n.stop_fines_time; + chain.last_checkin_time := circ_n.checkin_time; + chain.last_checkin_scan_time := circ_n.checkin_scan_time; + SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation; + SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation; + + IF chain.num_circs > 1 THEN + chain.last_renewal_time := circ_n.xact_start; + SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation; + END IF; + + RETURN chain; + +END; +$$ LANGUAGE 'plpgsql'; + +CREATE OR REPLACE FUNCTION rating.percent_time_circulating(badge_id INT) + RETURNS TABLE (record BIGINT, value NUMERIC) AS $f$ +DECLARE + badge rating.badge_with_orgs%ROWTYPE; +BEGIN + + SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id; + + PERFORM rating.precalc_bibs_by_copy(badge_id); + + DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN ( + SELECT id FROM precalc_filter_bib_list + INTERSECT + SELECT id FROM precalc_bibs_by_copy_list + ); + + ANALYZE precalc_copy_filter_bib_list; + + RETURN QUERY + SELECT bib, + SUM(COALESCE(circ_time,0))::NUMERIC / SUM(age)::NUMERIC + FROM (SELECT cn.record AS bib, + cp.id, + EXTRACT( EPOCH FROM AGE(cp.active_date) ) + 1 AS age, + SUM( -- time copy spent circulating + EXTRACT( + EPOCH FROM + AGE( + COALESCE(circ.checkin_time, circ.stop_fines_time, NOW()), + circ.xact_start + ) + ) + )::NUMERIC AS circ_time + FROM asset.copy cp + JOIN precalc_copy_filter_bib_list c ON (cp.id = c.copy) + JOIN asset.call_number cn ON (cn.id = cp.call_number) + LEFT JOIN action.all_circulation_slim circ ON ( + circ.target_copy = cp.id + AND stop_fines NOT IN ( + 'LOST', + 'LONGOVERDUE', + 'CLAIMSRETURNED', + 'LONGOVERDUE' + ) + AND NOT ( + checkin_time IS NULL AND + stop_fines = 'MAXFINES' + ) + ) + WHERE cn.owning_lib = ANY (badge.orgs) + AND cp.active_date IS NOT NULL + -- Next line requires that copies with no circs (circ.id IS NULL) also not be deleted + AND ((circ.id IS NULL AND NOT cp.deleted) OR circ.id IS NOT NULL) + GROUP BY 1,2,3 + ) x + GROUP BY 1; +END; +$f$ LANGUAGE PLPGSQL STRICT; + +-- SELECT evergreen.upgrade_deps_block_check('1052', :eg_version); + +CREATE OR REPLACE FUNCTION rating.inhouse_over_time(badge_id INT) + RETURNS TABLE (record BIGINT, value NUMERIC) AS $f$ +DECLARE + badge rating.badge_with_orgs%ROWTYPE; + iage INT := 1; + iint INT := NULL; + iscale NUMERIC := NULL; +BEGIN + + SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id; + + IF badge.horizon_age IS NULL THEN + RAISE EXCEPTION 'Badge "%" with id % requires a horizon age but has none.', + badge.name, + badge.id; + END IF; + + PERFORM rating.precalc_bibs_by_copy(badge_id); + + DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN ( + SELECT id FROM precalc_filter_bib_list + INTERSECT + SELECT id FROM precalc_bibs_by_copy_list + ); + + ANALYZE precalc_copy_filter_bib_list; + + iint := EXTRACT(EPOCH FROM badge.importance_interval); + IF badge.importance_age IS NOT NULL THEN + iage := (EXTRACT(EPOCH FROM badge.importance_age) / iint)::INT; + END IF; + + -- if iscale is smaller than 1, scaling slope will be shallow ... BEWARE! + iscale := COALESCE(badge.importance_scale, 1.0); + + RETURN QUERY + SELECT bib, + SUM( uses * GREATEST( iscale * (iage - cage), 1.0 )) + FROM ( + SELECT cn.record AS bib, + (1 + EXTRACT(EPOCH FROM AGE(u.use_time)) / iint)::INT AS cage, + COUNT(u.id)::INT AS uses + FROM action.in_house_use u + JOIN precalc_copy_filter_bib_list cf ON (u.item = cf.copy) + JOIN asset.copy cp ON (cp.id = u.item) + JOIN asset.call_number cn ON (cn.id = cp.call_number) + WHERE u.use_time >= NOW() - badge.horizon_age + AND cn.owning_lib = ANY (badge.orgs) + GROUP BY 1, 2 + ) x + GROUP BY 1; +END; +$f$ LANGUAGE PLPGSQL STRICT; + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT INTO rating.popularity_parameter (id, name, func, require_horizon,require_percentile) VALUES + (18,'In-House Use Over Time', 'rating.inhouse_over_time', TRUE, TRUE); +END IF; END $INSERT$; + + +-- SELECT evergreen.upgrade_deps_block_check('1053', :eg_version); + +CREATE OR REPLACE FUNCTION rating.org_unit_count(badge_id INT) + RETURNS TABLE (record INT, value NUMERIC) AS $f$ +DECLARE + badge rating.badge_with_orgs%ROWTYPE; +BEGIN + + SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id; + + PERFORM rating.precalc_bibs_by_copy(badge_id); + + DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN ( + SELECT id FROM precalc_filter_bib_list + INTERSECT + SELECT id FROM precalc_bibs_by_copy_list + ); + ANALYZE precalc_copy_filter_bib_list; + + -- Use circ rather than owning lib here as that means "on the shelf at..." + RETURN QUERY + SELECT f.id::INT AS bib, + COUNT(DISTINCT cp.circ_lib)::NUMERIC + FROM asset.copy cp + JOIN precalc_copy_filter_bib_list f ON (cp.id = f.copy) + WHERE cp.circ_lib = ANY (badge.orgs) GROUP BY 1; + +END; +$f$ LANGUAGE PLPGSQL STRICT; + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT INTO rating.popularity_parameter (id, name, func, require_percentile) VALUES + (17,'Circulation Library Count', 'rating.org_unit_count', TRUE); + +-- SELECT evergreen.upgrade_deps_block_check('1054', :eg_version); + +INSERT into config.org_unit_setting_type +( name, grp, label, description, datatype ) VALUES + +( 'lib.timezone', 'lib', + oils_i18n_gettext('lib.timezone', + 'Library time zone', + 'coust', 'label'), + oils_i18n_gettext('lib.timezone', + 'Define the time zone in which a library physically resides', + 'coust', 'description'), + 'string'); +END IF; END $INSERT$; + +ALTER TABLE actor.org_unit_closed ADD COLUMN full_day BOOLEAN DEFAULT FALSE; +ALTER TABLE actor.org_unit_closed ADD COLUMN multi_day BOOLEAN DEFAULT FALSE; + +UPDATE actor.org_unit_closed SET multi_day = TRUE + WHERE close_start::DATE <> close_end::DATE; + +UPDATE actor.org_unit_closed SET full_day = TRUE + WHERE close_start::DATE = close_end::DATE + AND SUBSTRING(close_start::time::text FROM 1 FOR 8) = '00:00:00' + AND SUBSTRING(close_end::time::text FROM 1 FOR 8) = '23:59:59'; + +CREATE OR REPLACE FUNCTION action.push_circ_due_time () RETURNS TRIGGER AS $$ +DECLARE + proper_tz TEXT := COALESCE( + oils_json_to_text(( + SELECT value + FROM actor.org_unit_ancestor_setting('lib.timezone',NEW.circ_lib) + LIMIT 1 + )), + CURRENT_SETTING('timezone') + ); +BEGIN + + IF (EXTRACT(EPOCH FROM NEW.duration)::INT % EXTRACT(EPOCH FROM '1 day'::INTERVAL)::INT) = 0 -- day-granular duration + AND SUBSTRING((NEW.due_date AT TIME ZONE proper_tz)::TIME::TEXT FROM 1 FOR 8) <> '23:59:59' THEN -- has not yet been pushed + NEW.due_date = ((NEW.due_date AT TIME ZONE proper_tz)::DATE + '1 day'::INTERVAL - '1 second'::INTERVAL) || ' ' || proper_tz; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + +-- INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1056', :eg_version); -- miker/gmcharlt + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO permission.perm_list (id,code,description) VALUES (592,'CONTAINER_BATCH_UPDATE','Allow batch update via buckets'); + +INSERT INTO container.user_bucket_type (code,label) SELECT code,label FROM container.copy_bucket_type where code = 'staff_client'; +END IF; END $INSERT$; + + +CREATE TABLE action.fieldset_group ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + create_time TIMESTAMPTZ NOT NULL DEFAULT NOW(), + complete_time TIMESTAMPTZ, + container INT, -- Points to a container of some type ... + container_type TEXT, -- One of 'biblio_record_entry', 'user', 'call_number', 'copy' + can_rollback BOOL DEFAULT TRUE, + rollback_group INT REFERENCES action.fieldset_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + rollback_time TIMESTAMPTZ, + creator INT NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + owning_lib INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED +); + +ALTER TABLE action.fieldset ADD COLUMN fieldset_group INT REFERENCES action.fieldset_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE action.fieldset ADD COLUMN error_msg TEXT; +ALTER TABLE container.biblio_record_entry_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE container.user_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE container.call_number_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE container.copy_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; + +UPDATE query.stored_query SET id = id + 1000 WHERE id < 1000; +UPDATE query.from_relation SET id = id + 1000 WHERE id < 1000; +UPDATE query.expression SET id = id + 1000 WHERE id < 1000; + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +PERFORM SETVAL('query.stored_query_id_seq', 1, FALSE); +PERFORM SETVAL('query.from_relation_id_seq', 1, FALSE); +PERFORM SETVAL('query.expression_id_seq', 1, FALSE); + +INSERT INTO query.bind_variable (name,type,description,label) + SELECT 'bucket','number','ID of the bucket to pull items from','Bucket ID' + WHERE NOT EXISTS (SELECT 1 FROM query.bind_variable WHERE name = 'bucket'); + +-- Assumes completely empty 'query' schema +INSERT INTO query.stored_query (type, use_distinct) VALUES ('SELECT', TRUE); -- 1 + +INSERT INTO query.from_relation (type, table_name, class_name, table_alias) VALUES ('RELATION', 'container.user_bucket_item', 'cubi', 'cubi'); -- 1 +UPDATE query.stored_query SET from_clause = 1; + +INSERT INTO query.expr_xcol (table_alias, column_name) VALUES ('cubi', 'target_user'); -- 1 +INSERT INTO query.select_item (stored_query,seq_no,expression) VALUES (1,1,1); + +INSERT INTO query.expr_xcol (table_alias, column_name) VALUES ('cubi', 'bucket'); -- 2 +INSERT INTO query.expr_xbind (bind_variable) VALUES ('bucket'); -- 3 + +INSERT INTO query.expr_xop (left_operand, operator, right_operand) VALUES (2, '=', 3); -- 4 +UPDATE query.stored_query SET where_clause = 4; + +PERFORM SETVAL('query.stored_query_id_seq', 1000, TRUE) FROM query.stored_query; +PERFORM SETVAL('query.from_relation_id_seq', 1000, TRUE) FROM query.from_relation; +PERFORM SETVAL('query.expression_id_seq', 10000, TRUE) FROM query.expression; + +END IF; END $INSERT$; + +CREATE OR REPLACE FUNCTION action.apply_fieldset( + fieldset_id IN INT, -- id from action.fieldset + table_name IN TEXT, -- table to be updated + pkey_name IN TEXT, -- name of primary key column in that table + query IN TEXT -- query constructed by qstore (for query-based + -- fieldsets only; otherwise null +) +RETURNS TEXT AS $$ +DECLARE + statement TEXT; + where_clause TEXT; + fs_status TEXT; + fs_pkey_value TEXT; + fs_query TEXT; + sep CHAR; + status_code TEXT; + msg TEXT; + fs_id INT; + fsg_id INT; + update_count INT; + cv RECORD; + fs_obj action.fieldset%ROWTYPE; + fs_group action.fieldset_group%ROWTYPE; + rb_row RECORD; +BEGIN + -- Sanity checks + IF fieldset_id IS NULL THEN + RETURN 'Fieldset ID parameter is NULL'; + END IF; + IF table_name IS NULL THEN + RETURN 'Table name parameter is NULL'; + END IF; + IF pkey_name IS NULL THEN + RETURN 'Primary key name parameter is NULL'; + END IF; + + SELECT + status, + quote_literal( pkey_value ) + INTO + fs_status, + fs_pkey_value + FROM + action.fieldset + WHERE + id = fieldset_id; + + -- + -- Build the WHERE clause. This differs according to whether it's a + -- single-row fieldset or a query-based fieldset. + -- + IF query IS NULL AND fs_pkey_value IS NULL THEN + RETURN 'Incomplete fieldset: neither a primary key nor a query available'; + ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN + fs_query := rtrim( query, ';' ); + where_clause := 'WHERE ' || pkey_name || ' IN ( ' + || fs_query || ' )'; + ELSIF query IS NULL AND fs_pkey_value IS NOT NULL THEN + where_clause := 'WHERE ' || pkey_name || ' = '; + IF pkey_name = 'id' THEN + where_clause := where_clause || fs_pkey_value; + ELSIF pkey_name = 'code' THEN + where_clause := where_clause || quote_literal(fs_pkey_value); + ELSE + RETURN 'Only know how to handle "id" and "code" pkeys currently, received ' || pkey_name; + END IF; + ELSE -- both are not null + RETURN 'Ambiguous fieldset: both a primary key and a query provided'; + END IF; + + IF fs_status IS NULL THEN + RETURN 'No fieldset found for id = ' || fieldset_id; + ELSIF fs_status = 'APPLIED' THEN + RETURN 'Fieldset ' || fieldset_id || ' has already been applied'; + END IF; + + SELECT * INTO fs_obj FROM action.fieldset WHERE id = fieldset_id; + SELECT * INTO fs_group FROM action.fieldset_group WHERE id = fs_obj.fieldset_group; + + IF fs_group.can_rollback THEN + -- This is part of a non-rollback group. We need to record the current values for future rollback. + + INSERT INTO action.fieldset_group (can_rollback, name, creator, owning_lib, container, container_type) + VALUES (FALSE, 'ROLLBACK: '|| fs_group.name, fs_group.creator, fs_group.owning_lib, fs_group.container, fs_group.container_type); + + fsg_id := CURRVAL('action.fieldset_group_id_seq'); + + FOR rb_row IN EXECUTE 'SELECT * FROM ' || table_name || ' ' || where_clause LOOP + IF pkey_name = 'id' THEN + fs_pkey_value := rb_row.id; + ELSIF pkey_name = 'code' THEN + fs_pkey_value := rb_row.code; + ELSE + RETURN 'Only know how to handle "id" and "code" pkeys currently, received ' || pkey_name; + END IF; + INSERT INTO action.fieldset (fieldset_group,owner,owning_lib,status,classname,name,pkey_value) + VALUES (fsg_id, fs_obj.owner, fs_obj.owning_lib, 'PENDING', fs_obj.classname, fs_obj.name || ' ROLLBACK FOR ' || fs_pkey_value, fs_pkey_value); + + fs_id := CURRVAL('action.fieldset_id_seq'); + sep := ''; + FOR cv IN + SELECT DISTINCT col + FROM action.fieldset_col_val + WHERE fieldset = fieldset_id + LOOP + EXECUTE 'INSERT INTO action.fieldset_col_val (fieldset, col, val) ' || + 'SELECT '|| fs_id || ', '||quote_literal(cv.col)||', '||cv.col||' FROM '||table_name||' WHERE '||pkey_name||' = '||fs_pkey_value; + END LOOP; + END LOOP; + END IF; + + statement := 'UPDATE ' || table_name || ' SET'; + + sep := ''; + FOR cv IN + SELECT col, + val + FROM action.fieldset_col_val + WHERE fieldset = fieldset_id + LOOP + statement := statement || sep || ' ' || cv.col + || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' ); + sep := ','; + END LOOP; + + IF sep = '' THEN + RETURN 'Fieldset ' || fieldset_id || ' has no column values defined'; + END IF; + statement := statement || ' ' || where_clause; + + -- + -- Execute the update + -- + BEGIN + EXECUTE statement; + GET DIAGNOSTICS update_count = ROW_COUNT; + + IF update_count = 0 THEN + RAISE data_exception; + END IF; + + IF fsg_id IS NOT NULL THEN + UPDATE action.fieldset_group SET rollback_group = fsg_id WHERE id = fs_group.id; + END IF; + + IF fs_group.id IS NOT NULL THEN + UPDATE action.fieldset_group SET complete_time = now() WHERE id = fs_group.id; + END IF; + + UPDATE action.fieldset SET status = 'APPLIED', applied_time = now() WHERE id = fieldset_id; + + EXCEPTION WHEN data_exception THEN + msg := 'No eligible rows found for fieldset ' || fieldset_id; + UPDATE action.fieldset SET status = 'ERROR', applied_time = now() WHERE id = fieldset_id; + RETURN msg; + + END; + + RETURN msg; + +EXCEPTION WHEN OTHERS THEN + msg := 'Unable to apply fieldset ' || fieldset_id || ': ' || sqlerrm; + UPDATE action.fieldset SET status = 'ERROR', applied_time = now() WHERE id = fieldset_id; + RETURN msg; + +END; +$$ LANGUAGE plpgsql; + +-- INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1057', :eg_version); -- miker/gmcharlt/kmlussier + +-- Thist change drops a needless join and saves 10-15% in time cost +CREATE OR REPLACE FUNCTION search.facets_for_record_set(ignore_facet_classes text[], hits bigint[]) RETURNS TABLE(id integer, value text, count bigint) +AS $f$ + SELECT id, value, count + FROM ( + SELECT mfae.field AS id, + mfae.value, + COUNT(DISTINCT mfae.source), + row_number() OVER ( + PARTITION BY mfae.field ORDER BY COUNT(DISTINCT mfae.source) DESC + ) AS rownum + FROM metabib.facet_entry mfae + JOIN config.metabib_field cmf ON (cmf.id = mfae.field) + WHERE mfae.source = ANY ($2) + AND cmf.facet_field + AND cmf.field_class NOT IN (SELECT * FROM unnest($1)) + GROUP by 1, 2 + ) all_facets + WHERE rownum <= ( + SELECT COALESCE( + (SELECT value::INT FROM config.global_flag WHERE name = 'search.max_facets_per_field' AND enabled), + 1000 + ) + ); +$f$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION unapi.metabib_virtual_record_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ +DECLARE + layout unapi.bre_output_layout%ROWTYPE; + transform config.xml_transform%ROWTYPE; + item_format TEXT; + tmp_xml TEXT; + xmlns_uri TEXT := 'http://open-ils.org/spec/feed-xml/v1'; + ouid INT; + element_list TEXT[]; +BEGIN + + IF org = '-' OR org IS NULL THEN + SELECT shortname INTO org FROM evergreen.org_top(); + END IF; + + SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org; + SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format; + + IF layout.name IS NULL THEN + RETURN NULL::XML; + END IF; + + SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform; + xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri); + + -- Gather the bib xml + SELECT XMLAGG( unapi.mmr(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i; + + IF layout.title_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title; + END IF; + + IF layout.description_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description; + END IF; + + IF layout.creator_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator; + END IF; + + IF layout.update_ts_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts; + END IF; + + IF unapi_url IS NOT NULL THEN + EXECUTE $$SELECT XMLCONCAT( XMLELEMENT( name link, XMLATTRIBUTES( 'http://www.w3.org/1999/xhtml' AS xmlns, 'unapi-server' AS rel, $1 AS href, 'unapi' AS title)), $2)$$ INTO tmp_xml USING unapi_url, tmp_xml::XML; + END IF; + + IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF; + + element_list := regexp_split_to_array(layout.feed_top,E'\\.'); + FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP + EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML; + END LOOP; + + RETURN tmp_xml::XML; +END; +$F$ LANGUAGE PLPGSQL STABLE; + +CREATE TABLE asset.copy_vis_attr_cache ( + id BIGSERIAL PRIMARY KEY, + record BIGINT NOT NULL, -- No FKEYs, managed by user triggers. + target_copy BIGINT NOT NULL, + vis_attr_vector INT[] +); +CREATE INDEX copy_vis_attr_cache_record_idx ON asset.copy_vis_attr_cache (record); +CREATE INDEX copy_vis_attr_cache_copy_idx ON asset.copy_vis_attr_cache (target_copy); + +ALTER TABLE biblio.record_entry ADD COLUMN vis_attr_vector INT[]; + +CREATE OR REPLACE FUNCTION search.calculate_visibility_attribute ( value INT, attr TEXT ) RETURNS INT AS $f$ +SELECT ((CASE $2 + + WHEN 'luri_org' THEN 0 -- "b" attr + WHEN 'bib_source' THEN 1 -- "b" attr + + WHEN 'copy_flags' THEN 0 -- "c" attr + WHEN 'owning_lib' THEN 1 -- "c" attr + WHEN 'circ_lib' THEN 2 -- "c" attr + WHEN 'status' THEN 3 -- "c" attr + WHEN 'location' THEN 4 -- "c" attr + WHEN 'location_group' THEN 5 -- "c" attr + + END) << 28 ) | $1; + +/* copy_flags bit positions, LSB-first: + + 0: asset.copy.opac_visible + + + When adding flags, you must update asset.all_visible_flags() + + Because bib and copy values are stored separately, we can reuse + shifts, saving us some space. We could probably take back a bit + too, but I'm not sure its worth squeezing that last one out. We'd + be left with just 2 slots for copy attrs, rather than 10. +*/ + +$f$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION search.calculate_visibility_attribute_list ( attr TEXT, value INT[] ) RETURNS INT[] AS $f$ + SELECT ARRAY_AGG(search.calculate_visibility_attribute(x, $1)) FROM UNNEST($2) AS X; +$f$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION search.calculate_visibility_attribute_test ( attr TEXT, value INT[], negate BOOL DEFAULT FALSE ) RETURNS TEXT AS $f$ + SELECT CASE WHEN $3 THEN '!' ELSE '' END || '(' || ARRAY_TO_STRING(search.calculate_visibility_attribute_list($1,$2),'|') || ')'; +$f$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION asset.calculate_copy_visibility_attribute_set ( copy_id BIGINT ) RETURNS INT[] AS $f$ +DECLARE + copy_row asset.copy%ROWTYPE; + lgroup_map asset.copy_location_group_map%ROWTYPE; + attr_set INT[]; +BEGIN + SELECT * INTO copy_row FROM asset.copy WHERE id = copy_id; + + attr_set := attr_set || search.calculate_visibility_attribute(copy_row.opac_visible::INT, 'copy_flags'); + attr_set := attr_set || search.calculate_visibility_attribute(copy_row.circ_lib, 'circ_lib'); + attr_set := attr_set || search.calculate_visibility_attribute(copy_row.status, 'status'); + attr_set := attr_set || search.calculate_visibility_attribute(copy_row.location, 'location'); + + SELECT ARRAY_APPEND( + attr_set, + search.calculate_visibility_attribute(owning_lib, 'owning_lib') + ) INTO attr_set + FROM asset.call_number + WHERE id = copy_row.call_number; + + FOR lgroup_map IN SELECT * FROM asset.copy_location_group_map WHERE location = copy_row.location LOOP + attr_set := attr_set || search.calculate_visibility_attribute(lgroup_map.lgroup, 'location_group'); + END LOOP; + + RETURN attr_set; +END; +$f$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION biblio.calculate_bib_visibility_attribute_set ( bib_id BIGINT ) RETURNS INT[] AS $f$ +DECLARE + bib_row biblio.record_entry%ROWTYPE; + cn_row asset.call_number%ROWTYPE; + attr_set INT[]; +BEGIN + SELECT * INTO bib_row FROM biblio.record_entry WHERE id = bib_id; + + IF bib_row.source IS NOT NULL THEN + attr_set := attr_set || search.calculate_visibility_attribute(bib_row.source, 'bib_source'); + END IF; + + FOR cn_row IN + SELECT cn.* + FROM asset.call_number cn + JOIN asset.uri_call_number_map m ON (cn.id = m.call_number) + JOIN asset.uri u ON (u.id = m.uri) + WHERE cn.record = bib_id + AND cn.label = '##URI##' + AND u.active + LOOP + attr_set := attr_set || search.calculate_visibility_attribute(cn_row.owning_lib, 'luri_org'); + END LOOP; + + RETURN attr_set; +END; +$f$ LANGUAGE PLPGSQL; + +-- SELECT evergreen.upgrade_deps_block_check('1076', :eg_version); -- miker/gmcharlt + +CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$ +DECLARE + ocn asset.call_number%ROWTYPE; + ncn asset.call_number%ROWTYPE; + cid BIGINT; +BEGIN + + IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN -- Only needs ON INSERT OR DELETE, so handle separately + IF TG_OP = 'INSERT' THEN + INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES ( + NEW.peer_record, + NEW.target_copy, + asset.calculate_copy_visibility_attribute_set(NEW.target_copy) + ); + + RETURN NEW; + ELSIF TG_OP = 'DELETE' THEN + DELETE FROM asset.copy_vis_attr_cache + WHERE record = NEW.peer_record AND target_copy = NEW.target_copy; + + RETURN OLD; + END IF; + END IF; + + IF TG_OP = 'INSERT' THEN -- Handles ON INSERT. ON UPDATE is below. + IF TG_TABLE_NAME IN ('copy', 'unit') THEN + SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number; + INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES ( + ncn.record, + NEW.id, + asset.calculate_copy_visibility_attribute_set(NEW.id) + ); + ELSIF TG_TABLE_NAME = 'record_entry' THEN + NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id); + END IF; + + RETURN NEW; + END IF; + + -- handle items first, since with circulation activity + -- their statuses change frequently + IF TG_TABLE_NAME IN ('copy', 'unit') THEN -- This handles ON UPDATE OR DELETE. ON INSERT above + + IF TG_OP = 'DELETE' THEN -- Shouldn't get here, normally + DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id; + RETURN OLD; + END IF; + + SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number; + + IF OLD.deleted <> NEW.deleted THEN + IF NEW.deleted THEN + DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id; + ELSE + INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES ( + ncn.record, + NEW.id, + asset.calculate_copy_visibility_attribute_set(NEW.id) + ); + END IF; + + RETURN NEW; + ELSIF OLD.call_number <> NEW.call_number THEN + SELECT * INTO ocn FROM asset.call_number cn WHERE id = OLD.call_number; + + IF ncn.record <> ocn.record THEN + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(ncn.record) + WHERE id = ocn.record; + + -- We have to use a record-specific WHERE clause + -- to avoid modifying the entries for peer-bib copies. + UPDATE asset.copy_vis_attr_cache + SET target_copy = NEW.id, + record = ncn.record + WHERE target_copy = OLD.id + AND record = ocn.record; + END IF; + END IF; + + IF OLD.location <> NEW.location OR + OLD.status <> NEW.status OR + OLD.opac_visible <> NEW.opac_visible OR + OLD.circ_lib <> NEW.circ_lib + THEN + -- Any of these could change visibility, but + -- we'll save some queries and not try to calculate + -- the change directly. We want to update peer-bib + -- entries in this case, unlike above. + UPDATE asset.copy_vis_attr_cache + SET target_copy = NEW.id, + vis_attr_vector = asset.calculate_copy_visibility_attribute_set(NEW.id) + WHERE target_copy = OLD.id; + + END IF; + + ELSIF TG_TABLE_NAME = 'call_number' THEN -- Only ON UPDATE. Copy handler will deal with ON INSERT OR DELETE. + + IF OLD.record <> NEW.record THEN + IF NEW.label = '##URI##' THEN + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record) + WHERE id = OLD.record; + + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(NEW.record) + WHERE id = NEW.record; + END IF; + + UPDATE asset.copy_vis_attr_cache + SET record = NEW.record, + vis_attr_vector = asset.calculate_copy_visibility_attribute_set(target_copy) + WHERE target_copy IN (SELECT id FROM asset.copy WHERE call_number = NEW.id) + AND record = OLD.record; + + ELSIF OLD.owning_lib <> NEW.owning_lib THEN + UPDATE asset.copy_vis_attr_cache + SET vis_attr_vector = asset.calculate_copy_visibility_attribute_set(target_copy) + WHERE target_copy IN (SELECT id FROM asset.copy WHERE call_number = NEW.id) + AND record = NEW.record; + + IF NEW.label = '##URI##' THEN + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record) + WHERE id = OLD.record; + END IF; + END IF; + + ELSIF TG_TABLE_NAME = 'record_entry' THEN -- Only handles ON UPDATE OR DELETE + + IF TG_OP = 'DELETE' THEN -- Shouldn't get here, normally + DELETE FROM asset.copy_vis_attr_cache WHERE record = OLD.id; + RETURN OLD; + ELSIF OLD.source <> NEW.source THEN + NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id); + END IF; + + END IF; + + RETURN NEW; +END; +$func$ LANGUAGE PLPGSQL; + + +-- Helper functions for use in constructing searches -- + +CREATE OR REPLACE FUNCTION asset.all_visible_flags () RETURNS TEXT AS $f$ + SELECT '(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(1 << x, 'copy_flags')),'&') || ')' + FROM GENERATE_SERIES(0,0) AS x; -- increment as new flags are added. +$f$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION asset.visible_orgs (otype TEXT) RETURNS TEXT AS $f$ + SELECT '(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, $1)),'|') || ')' + FROM actor.org_unit + WHERE opac_visible; +$f$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION asset.invisible_orgs (otype TEXT) RETURNS TEXT AS $f$ + SELECT '!(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, $1)),'|') || ')' + FROM actor.org_unit + WHERE NOT opac_visible; +$f$ LANGUAGE SQL STABLE; + +-- Bib-oriented defaults for search +CREATE OR REPLACE FUNCTION asset.bib_source_default () RETURNS TEXT AS $f$ + SELECT '(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, 'bib_source')),'|') || ')' + FROM config.bib_source + WHERE transcendant; +$f$ LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION asset.luri_org_default () RETURNS TEXT AS $f$ + SELECT * FROM asset.invisible_orgs('luri_org'); +$f$ LANGUAGE SQL STABLE; + +-- Copy-oriented defaults for search +CREATE OR REPLACE FUNCTION asset.location_group_default () RETURNS TEXT AS $f$ + SELECT '!(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, 'location_group')),'|') || ')' + FROM asset.copy_location_group + WHERE NOT opac_visible; +$f$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION asset.location_default () RETURNS TEXT AS $f$ + SELECT '!(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, 'location')),'|') || ')' + FROM asset.copy_location + WHERE NOT opac_visible; +$f$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION asset.status_default () RETURNS TEXT AS $f$ + SELECT '!(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, 'status')),'|') || ')' + FROM config.copy_status + WHERE NOT opac_visible; +$f$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION asset.owning_lib_default () RETURNS TEXT AS $f$ + SELECT * FROM asset.invisible_orgs('owning_lib'); +$f$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION asset.circ_lib_default () RETURNS TEXT AS $f$ + SELECT * FROM asset.invisible_orgs('circ_lib'); +$f$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION asset.patron_default_visibility_mask () RETURNS TABLE (b_attrs TEXT, c_attrs TEXT) AS $f$ +DECLARE + copy_flags TEXT; -- "c" attr + + owning_lib TEXT; -- "c" attr + circ_lib TEXT; -- "c" attr + status TEXT; -- "c" attr + location TEXT; -- "c" attr + location_group TEXT; -- "c" attr + + luri_org TEXT; -- "b" attr + bib_sources TEXT; -- "b" attr +BEGIN + copy_flags := asset.all_visible_flags(); -- Will always have at least one + + owning_lib := NULLIF(asset.owning_lib_default(),'!()'); + + circ_lib := NULLIF(asset.circ_lib_default(),'!()'); + status := NULLIF(asset.status_default(),'!()'); + location := NULLIF(asset.location_default(),'!()'); + location_group := NULLIF(asset.location_group_default(),'!()'); + + luri_org := NULLIF(asset.luri_org_default(),'!()'); + bib_sources := NULLIF(asset.bib_source_default(),'()'); + + RETURN QUERY SELECT + '('||ARRAY_TO_STRING( + ARRAY[luri_org,bib_sources], + '|' + )||')', + '('||ARRAY_TO_STRING( + ARRAY[copy_flags,owning_lib,circ_lib,status,location,location_group]::TEXT[], + '&' + )||')'; +END; +$f$ LANGUAGE PLPGSQL STABLE ROWS 1; + +CREATE OR REPLACE FUNCTION metabib.suggest_browse_entries(raw_query_text text, search_class text, headline_opts text, visibility_org integer, query_limit integer, normalization integer) + RETURNS TABLE(value text, field integer, buoyant_and_class_match boolean, field_match boolean, field_weight integer, rank real, buoyant boolean, match text) +AS $f$ +DECLARE + prepared_query_texts TEXT[]; + query TSQUERY; + plain_query TSQUERY; + opac_visibility_join TEXT; + search_class_join TEXT; + r_fields RECORD; +BEGIN + prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text); + + query := TO_TSQUERY('keyword', prepared_query_texts[1]); + plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]); + + visibility_org := NULLIF(visibility_org,-1); + IF visibility_org IS NOT NULL THEN + PERFORM FROM actor.org_unit WHERE id = visibility_org AND parent_ou IS NULL; + IF FOUND THEN + opac_visibility_join := ''; + ELSE + opac_visibility_join := ' + JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = x.source) + JOIN vm ON (acvac.vis_attr_vector @@ + (vm.c_attrs || $$&$$ || + search.calculate_visibility_attribute_test( + $$circ_lib$$, + (SELECT ARRAY_AGG(id) FROM actor.org_unit_descendants($4)) + ) + )::query_int + ) +'; + END IF; + ELSE + opac_visibility_join := ''; + END IF; + + -- The following determines whether we only provide suggestsons matching + -- the user's selected search_class, or whether we show other suggestions + -- too. The reason for MIN() is that for search_classes like + -- 'title|proper|uniform' you would otherwise get multiple rows. The + -- implication is that if title as a class doesn't have restrict, + -- nor does the proper field, but the uniform field does, you're going + -- to get 'false' for your overall evaluation of 'should we restrict?' + -- To invert that, change from MIN() to MAX(). + + SELECT + INTO r_fields + MIN(cmc.restrict::INT) AS restrict_class, + MIN(cmf.restrict::INT) AS restrict_field + FROM metabib.search_class_to_registered_components(search_class) + AS _registered (field_class TEXT, field INT) + JOIN + config.metabib_class cmc ON (cmc.name = _registered.field_class) + LEFT JOIN + config.metabib_field cmf ON (cmf.id = _registered.field); + + -- evaluate 'should we restrict?' + IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN + search_class_join := ' + JOIN + metabib.search_class_to_registered_components($2) + AS _registered (field_class TEXT, field INT) ON ( + (_registered.field IS NULL AND + _registered.field_class = cmf.field_class) OR + (_registered.field = cmf.id) + ) + '; + ELSE + search_class_join := ' + LEFT JOIN + metabib.search_class_to_registered_components($2) + AS _registered (field_class TEXT, field INT) ON ( + _registered.field_class = cmc.name + ) + '; + END IF; + + RETURN QUERY EXECUTE ' +WITH vm AS ( SELECT * FROM asset.patron_default_visibility_mask() ), + mbe AS (SELECT * FROM metabib.browse_entry WHERE index_vector @@ $1 LIMIT 10000) +SELECT DISTINCT + x.value, + x.id, + x.push, + x.restrict, + x.weight, + x.ts_rank_cd, + x.buoyant, + TS_HEADLINE(value, $7, $3) + FROM (SELECT DISTINCT + mbe.value, + cmf.id, + cmc.buoyant AND _registered.field_class IS NOT NULL AS push, + _registered.field = cmf.id AS restrict, + cmf.weight, + TS_RANK_CD(mbe.index_vector, $1, $6), + cmc.buoyant, + mbedm.source + FROM metabib.browse_entry_def_map mbedm + JOIN mbe ON (mbe.id = mbedm.entry) + JOIN config.metabib_field cmf ON (cmf.id = mbedm.def) + JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name) + ' || search_class_join || ' + ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC + LIMIT 1000) AS x + ' || opac_visibility_join || ' + ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC + LIMIT $5 +' -- sic, repeat the order by clause in the outer select too + USING + query, search_class, headline_opts, + visibility_org, query_limit, normalization, plain_query + ; + + -- sort order: + -- buoyant AND chosen class = match class + -- chosen field = match field + -- field weight + -- rank + -- buoyancy + -- value itself + +END; +$f$ LANGUAGE plpgsql ROWS 10; + +/* COMMENT OUT THE FIRST UPDATE SO WE THERE IS NOT NEED TO PATCH BOTH +CREATE OR REPLACE FUNCTION metabib.browse(search_field integer[], browse_term text, context_org integer DEFAULT NULL::integer, context_loc_group integer DEFAULT NULL::integer, staff boolean DEFAULT false, pivot_id bigint DEFAULT NULL::bigint, result_limit integer DEFAULT 10) + RETURNS SETOF metabib.flat_browse_entry_appearance +AS $f$ +DECLARE + core_query TEXT; + back_query TEXT; + forward_query TEXT; + pivot_sort_value TEXT; + pivot_sort_fallback TEXT; + context_locations INT[]; + browse_superpage_size INT; + results_skipped INT := 0; + back_limit INT; + back_to_pivot INT; + forward_limit INT; + forward_to_pivot INT; +BEGIN + -- First, find the pivot if we were given a browse term but not a pivot. + IF pivot_id IS NULL THEN + pivot_id := metabib.browse_pivot(search_field, browse_term); + END IF; + + SELECT INTO pivot_sort_value, pivot_sort_fallback + sort_value, value FROM metabib.browse_entry WHERE id = pivot_id; + + -- Bail if we couldn't find a pivot. + IF pivot_sort_value IS NULL THEN + RETURN; + END IF; + + -- Transform the context_loc_group argument (if any) (logc at the + -- TPAC layer) into a form we'll be able to use. + IF context_loc_group IS NOT NULL THEN + SELECT INTO context_locations ARRAY_AGG(location) + FROM asset.copy_location_group_map + WHERE lgroup = context_loc_group; + END IF; + + -- Get the configured size of browse superpages. + SELECT INTO browse_superpage_size COALESCE(value::INT,100) -- NULL ok + FROM config.global_flag + WHERE enabled AND name = 'opac.browse.holdings_visibility_test_limit'; + + -- First we're going to search backward from the pivot, then we're going + -- to search forward. In each direction, we need two limits. At the + -- lesser of the two limits, we delineate the edge of the result set + -- we're going to return. At the greater of the two limits, we find the + -- pivot value that would represent an offset from the current pivot + -- at a distance of one "page" in either direction, where a "page" is a + -- result set of the size specified in the "result_limit" argument. + -- + -- The two limits in each direction make four derived values in total, + -- and we calculate them now. + back_limit := CEIL(result_limit::FLOAT / 2); + back_to_pivot := result_limit; + forward_limit := result_limit / 2; + forward_to_pivot := result_limit - 1; + + -- This is the meat of the SQL query that finds browse entries. We'll + -- pass this to a function which uses it with a cursor, so that individual + -- rows may be fetched in a loop until some condition is satisfied, without + -- waiting for a result set of fixed size to be collected all at once. + core_query := ' +SELECT mbe.id, + mbe.value, + mbe.sort_value + FROM metabib.browse_entry mbe + WHERE ( + EXISTS ( -- are there any bibs using this mbe via the requested fields? + SELECT 1 + FROM metabib.browse_entry_def_map mbedm + WHERE mbedm.entry = mbe.id AND mbedm.def = ANY(' || quote_literal(search_field) || ') + ) OR EXISTS ( -- are there any authorities using this mbe via the requested fields? + SELECT 1 + FROM metabib.browse_entry_simple_heading_map mbeshm + JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id ) + JOIN authority.control_set_auth_field_metabib_field_map_refs map ON ( + ash.atag = map.authority_field + AND map.metabib_field = ANY(' || quote_literal(search_field) || ') + ) + WHERE mbeshm.entry = mbe.id + ) + ) AND '; + + -- This is the variant of the query for browsing backward. + back_query := core_query || + ' mbe.sort_value <= ' || quote_literal(pivot_sort_value) || + ' ORDER BY mbe.sort_value DESC, mbe.value DESC LIMIT 1000'; + + -- This variant browses forward. + forward_query := core_query || + ' mbe.sort_value > ' || quote_literal(pivot_sort_value) || + ' ORDER BY mbe.sort_value, mbe.value LIMIT 1000'; + + -- We now call the function which applies a cursor to the provided + -- queries, stopping at the appropriate limits and also giving us + -- the next page's pivot. + RETURN QUERY + SELECT * FROM metabib.staged_browse( + back_query, search_field, context_org, context_locations, + staff, browse_superpage_size, TRUE, back_limit, back_to_pivot + ) UNION + SELECT * FROM metabib.staged_browse( + forward_query, search_field, context_org, context_locations, + staff, browse_superpage_size, FALSE, forward_limit, forward_to_pivot + ) ORDER BY row_number DESC; +END; $f$ LANGUAGE plpgsql ROWS 10; + +CREATE OR REPLACE FUNCTION metabib.staged_browse(query text, fields integer[], context_org integer, context_locations integer[], staff boolean, browse_superpage_size integer, count_up_from_zero boolean, result_limit integer, next_pivot_pos integer) + RETURNS SETOF metabib.flat_browse_entry_appearance +AS $f$ +DECLARE + curs REFCURSOR; + rec RECORD; + qpfts_query TEXT; + aqpfts_query TEXT; + afields INT[]; + bfields INT[]; + result_row metabib.flat_browse_entry_appearance%ROWTYPE; + results_skipped INT := 0; + row_counter INT := 0; + row_number INT; + slice_start INT; + slice_end INT; + full_end INT; + all_records BIGINT[]; + all_brecords BIGINT[]; + all_arecords BIGINT[]; + superpage_of_records BIGINT[]; + superpage_size INT; + c_tests TEXT := ''; + b_tests TEXT := ''; + c_orgs INT[]; +BEGIN + IF count_up_from_zero THEN + row_number := 0; + ELSE + row_number := -1; + END IF; + + IF NOT staff THEN + SELECT x.c_attrs, x.b_attrs INTO c_tests, b_tests FROM asset.patron_default_visibility_mask() x; + END IF; + + IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF; + IF b_tests <> '' THEN b_tests := b_tests || '&'; END IF; + + SELECT ARRAY_AGG(id) INTO c_orgs FROM actor.org_unit_descendants(context_org); + + c_tests := c_tests || search.calculate_visibility_attribute_test('circ_lib',c_orgs) + || '&' || search.calculate_visibility_attribute_test('owning_lib',c_orgs); + + PERFORM 1 FROM config.internal_flag WHERE enabled AND name = 'opac.located_uri.act_as_copy'; + IF FOUND THEN + b_tests := b_tests || search.calculate_visibility_attribute_test( + 'luri_org', + (SELECT ARRAY_AGG(id) FROM actor.org_unit_full_path(context_org) x) + ); + ELSE + b_tests := b_tests || search.calculate_visibility_attribute_test( + 'luri_org', + (SELECT ARRAY_AGG(id) FROM actor.org_unit_ancestors(context_org) x) + ); + END IF; + + IF context_locations THEN + IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF; + c_tests := c_tests || search.calculate_visibility_attribute_test('location',context_locations); + END IF; + + OPEN curs NO SCROLL FOR EXECUTE query; + + LOOP + FETCH curs INTO rec; + IF NOT FOUND THEN + IF result_row.pivot_point IS NOT NULL THEN + RETURN NEXT result_row; + END IF; + RETURN; + END IF; + + -- Gather aggregate data based on the MBE row we're looking at now, authority axis + SELECT INTO all_arecords, result_row.sees, afields + ARRAY_AGG(DISTINCT abl.bib), -- bibs to check for visibility + STRING_AGG(DISTINCT aal.source::TEXT, $$,$$), -- authority record ids + ARRAY_AGG(DISTINCT map.metabib_field) -- authority-tag-linked CMF rows + + FROM metabib.browse_entry_simple_heading_map mbeshm + JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id ) + JOIN authority.authority_linking aal ON ( ash.record = aal.source ) + JOIN authority.bib_linking abl ON ( aal.target = abl.authority ) + JOIN authority.control_set_auth_field_metabib_field_map_refs map ON ( + ash.atag = map.authority_field + AND map.metabib_field = ANY(fields) + ) + WHERE mbeshm.entry = rec.id; + + -- Gather aggregate data based on the MBE row we're looking at now, bib axis + SELECT INTO all_brecords, result_row.authorities, bfields + ARRAY_AGG(DISTINCT source), + STRING_AGG(DISTINCT authority::TEXT, $$,$$), + ARRAY_AGG(DISTINCT def) + FROM metabib.browse_entry_def_map + WHERE entry = rec.id + AND def = ANY(fields); + + SELECT INTO result_row.fields STRING_AGG(DISTINCT x::TEXT, $$,$$) FROM UNNEST(afields || bfields) x; + + result_row.sources := 0; + result_row.asources := 0; + + -- Bib-linked vis checking + IF ARRAY_UPPER(all_brecords,1) IS NOT NULL THEN + + SELECT INTO result_row.sources COUNT(DISTINCT b.id) + FROM biblio.record_entry b + JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id) + WHERE b.id = ANY(all_brecords[1:browse_superpage_size]) + AND ( + acvac.vis_attr_vector @@ c_tests::query_int + OR b.vis_attr_vector @@ b_tests::query_int + ); + + result_row.accurate := TRUE; + + END IF; + + -- Authority-linked vis checking + IF ARRAY_UPPER(all_arecords,1) IS NOT NULL THEN + + SELECT INTO result_row.asources COUNT(DISTINCT b.id) + FROM biblio.record_entry b + JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id) + WHERE b.id = ANY(all_arecords[1:browse_superpage_size]) + AND ( + acvac.vis_attr_vector @@ c_tests::query_int + OR b.vis_attr_vector @@ b_tests::query_int + ); + + result_row.aaccurate := TRUE; + + END IF; + + IF result_row.sources > 0 OR result_row.asources > 0 THEN + + -- The function that calls this function needs row_number in order + -- to correctly order results from two different runs of this + -- functions. + result_row.row_number := row_number; + + -- Now, if row_counter is still less than limit, return a row. If + -- not, but it is less than next_pivot_pos, continue on without + -- returning actual result rows until we find + -- that next pivot, and return it. + + IF row_counter < result_limit THEN + result_row.browse_entry := rec.id; + result_row.value := rec.value; + + RETURN NEXT result_row; + ELSE + result_row.browse_entry := NULL; + result_row.authorities := NULL; + result_row.fields := NULL; + result_row.value := NULL; + result_row.sources := NULL; + result_row.sees := NULL; + result_row.accurate := NULL; + result_row.aaccurate := NULL; + result_row.pivot_point := rec.id; + + IF row_counter >= next_pivot_pos THEN + RETURN NEXT result_row; + RETURN; + END IF; + END IF; + + IF count_up_from_zero THEN + row_number := row_number + 1; + ELSE + row_number := row_number - 1; + END IF; + + -- row_counter is different from row_number. + -- It simply counts up from zero so that we know when + -- we've reached our limit. + row_counter := row_counter + 1; + END IF; + END LOOP; +END; +$f$ LANGUAGE plpgsql ROWS 10; +*/ + +CREATE OR REPLACE FUNCTION asset.opac_ou_record_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$ +DECLARE + ans RECORD; + trans INT; +BEGIN + SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid; + + FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP + RETURN QUERY + WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x), + available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available), + mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x) + SELECT ans.depth, + ans.id, + COUNT( av.id ), + SUM( (cp.status = ANY (available_statuses.ids))::INT ), + COUNT( av.id ), + trans + FROM mask, + available_statuses, + org_list, + asset.copy_vis_attr_cache av + JOIN asset.copy cp ON (cp.id = av.target_copy AND av.record = rid) + WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int + GROUP BY 1,2,6; + + IF NOT FOUND THEN + RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans; + END IF; + + END LOOP; + + RETURN; +END; +$f$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION asset.opac_lasso_record_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$ +DECLARE + ans RECORD; + trans INT; +BEGIN + SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid; + + FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP + RETURN QUERY + WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x), + available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available), + mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x) + SELECT -1, + ans.id, + COUNT( av.id ), + SUM( (cp.status = ANY (available_statuses.ids))::INT ), + COUNT( av.id ), + trans + FROM mask, + org_list, + asset.copy_vis_attr_cache av + JOIN asset.copy cp ON (cp.id = av.target_copy AND av.record = rid) + WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int + GROUP BY 1,2,6; + + IF NOT FOUND THEN + RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans; + END IF; + + END LOOP; + + RETURN; +END; +$f$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION asset.opac_ou_metarecord_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$ +DECLARE + ans RECORD; + trans INT; +BEGIN + SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid; + + FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP + RETURN QUERY + WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x), + available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available), + mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x) + SELECT ans.depth, + ans.id, + COUNT( av.id ), + SUM( (cp.status = ANY (available_statuses.ids))::INT ), + COUNT( av.id ), + trans + FROM mask, + org_list, + available_statuses, + asset.copy_vis_attr_cache av + JOIN asset.copy cp ON (cp.id = av.target_copy) + JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record) + WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int + GROUP BY 1,2,6; + + IF NOT FOUND THEN + RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans; + END IF; + + END LOOP; + + RETURN; +END; +$f$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION asset.opac_lasso_metarecord_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$ +DECLARE + ans RECORD; + trans INT; +BEGIN + SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid; + + FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP + RETURN QUERY + WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x), + available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available), + mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x) + SELECT -1, + ans.id, + COUNT( av.id ), + SUM( (cp.status = ANY (available_statuses.ids))::INT ), + COUNT( av.id ), + trans + FROM mask, + org_list, + available_statuses, + asset.copy_vis_attr_cache av + JOIN asset.copy cp ON (cp.id = av.target_copy) + JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record) + WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int + GROUP BY 1,2,6; + + IF NOT FOUND THEN + RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans; + END IF; + + END LOOP; + + RETURN; +END; +$f$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION unapi.mmr_mra ( + obj_id BIGINT, + format TEXT, + ename TEXT, + includes TEXT[], + org TEXT, + depth INT DEFAULT NULL, + slimit HSTORE DEFAULT NULL, + soffset HSTORE DEFAULT NULL, + include_xmlns BOOL DEFAULT TRUE, + pref_lib INT DEFAULT NULL +) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name attributes, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@mmr/' || $1 AS metarecord + ), + (SELECT XMLAGG(foo.y) + FROM ( + WITH sourcelist AS ( + WITH aou AS (SELECT COALESCE(id, (evergreen.org_top()).id) AS id FROM actor.org_unit WHERE shortname = $5 LIMIT 1), + basevm AS (SELECT c_attrs FROM asset.patron_default_visibility_mask()), + circvm AS (SELECT search.calculate_visibility_attribute_test('circ_lib', ARRAY_AGG(aoud.id)) AS mask + FROM aou, LATERAL actor.org_unit_descendants(aou.id, $6) aoud) + SELECT source + FROM aou, circvm, basevm, metabib.metarecord_source_map mmsm + WHERE mmsm.metarecord = $1 AND ( + EXISTS ( + SELECT 1 + FROM circvm, basevm, asset.copy_vis_attr_cache acvac + WHERE acvac.vis_attr_vector @@ (basevm.c_attrs || '&' || circvm.mask)::query_int + AND acvac.record = mmsm.source + ) + OR EXISTS (SELECT 1 FROM evergreen.located_uris(source, aou.id, $10) LIMIT 1) + OR EXISTS (SELECT 1 FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = mmsm.source) + ) + ) + SELECT cmra.aid, + XMLELEMENT( + name field, + XMLATTRIBUTES( + cmra.attr AS name, + cmra.value AS "coded-value", + cmra.aid AS "cvmid", + rad.composite, + rad.multi, + rad.filter, + rad.sorter, + cmra.source_list + ), + cmra.value + ) + FROM ( + SELECT DISTINCT aid, attr, value, STRING_AGG(x.id::TEXT, ',') AS source_list + FROM ( + SELECT v.source AS id, + c.id AS aid, + c.ctype AS attr, + c.code AS value + FROM metabib.record_attr_vector_list v + JOIN config.coded_value_map c ON ( c.id = ANY( v.vlist ) ) + ) AS x + JOIN sourcelist ON (x.id = sourcelist.source) + GROUP BY 1, 2, 3 + ) AS cmra + JOIN config.record_attr_definition rad ON (cmra.attr = rad.name) + UNION ALL + SELECT umra.aid, + XMLELEMENT( + name field, + XMLATTRIBUTES( + umra.attr AS name, + rad.composite, + rad.multi, + rad.filter, + rad.sorter + ), + umra.value + ) + FROM ( + SELECT DISTINCT aid, attr, value + FROM ( + SELECT v.source AS id, + m.id AS aid, + m.attr AS attr, + m.value AS value + FROM metabib.record_attr_vector_list v + JOIN metabib.uncontrolled_record_attr_value m ON ( m.id = ANY( v.vlist ) ) + ) AS x + JOIN sourcelist ON (x.id = sourcelist.source) + ) AS umra + JOIN config.record_attr_definition rad ON (umra.attr = rad.name) + ORDER BY 1 + + )foo(id,y) + ) + ) +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION evergreen.ranked_volumes( + bibid BIGINT[], + ouid INT, + depth INT DEFAULT NULL, + slimit HSTORE DEFAULT NULL, + soffset HSTORE DEFAULT NULL, + pref_lib INT DEFAULT NULL, + includes TEXT[] DEFAULT NULL::TEXT[] +) RETURNS TABLE(id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$ + WITH RECURSIVE ou_depth AS ( + SELECT COALESCE( + $3, + ( + SELECT depth + FROM actor.org_unit_type aout + INNER JOIN actor.org_unit ou ON ou_type = aout.id + WHERE ou.id = $2 + ) + ) AS depth + ), descendant_depth AS ( + SELECT ou.id, + ou.parent_ou, + out.depth + FROM actor.org_unit ou + JOIN actor.org_unit_type out ON (out.id = ou.ou_type) + JOIN anscestor_depth ad ON (ad.id = ou.id), + ou_depth + WHERE ad.depth = ou_depth.depth + UNION ALL + SELECT ou.id, + ou.parent_ou, + out.depth + FROM actor.org_unit ou + JOIN actor.org_unit_type out ON (out.id = ou.ou_type) + JOIN descendant_depth ot ON (ot.id = ou.parent_ou) + ), anscestor_depth AS ( + SELECT ou.id, + ou.parent_ou, + out.depth + FROM actor.org_unit ou + JOIN actor.org_unit_type out ON (out.id = ou.ou_type) + WHERE ou.id = $2 + UNION ALL + SELECT ou.id, + ou.parent_ou, + out.depth + FROM actor.org_unit ou + JOIN actor.org_unit_type out ON (out.id = ou.ou_type) + JOIN anscestor_depth ot ON (ot.parent_ou = ou.id) + ), descendants as ( + SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id) + ) + + SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM ( + SELECT acn.id, owning_lib.name, acn.label_sortkey, + evergreen.rank_cp(acp), + RANK() OVER w + FROM asset.call_number acn + JOIN asset.copy acp ON (acn.id = acp.call_number) + JOIN descendants AS aou ON (acp.circ_lib = aou.id) + JOIN actor.org_unit AS owning_lib ON (acn.owning_lib = owning_lib.id) + WHERE acn.record = ANY ($1) + AND acn.deleted IS FALSE + AND acp.deleted IS FALSE + AND CASE WHEN ('exclude_invisible_acn' = ANY($7)) THEN + EXISTS ( + WITH basevm AS (SELECT c_attrs FROM asset.patron_default_visibility_mask()), + circvm AS (SELECT search.calculate_visibility_attribute_test('circ_lib', ARRAY[acp.circ_lib]) AS mask) + SELECT 1 + FROM basevm, circvm, asset.copy_vis_attr_cache acvac + WHERE acvac.vis_attr_vector @@ (basevm.c_attrs || '&' || circvm.mask)::query_int + AND acvac.target_copy = acp.id + AND acvac.record = acn.record + ) ELSE TRUE END + GROUP BY acn.id, evergreen.rank_cp(acp), owning_lib.name, acn.label_sortkey, aou.id + WINDOW w AS ( + ORDER BY + COALESCE( + CASE WHEN aou.id = $2 THEN -20000 END, + CASE WHEN aou.id = $6 THEN -10000 END, + (SELECT distance - 5000 + FROM actor.org_unit_descendants_distance($6) as x + WHERE x.id = aou.id AND $6 IN ( + SELECT q.id FROM actor.org_unit_descendants($2) as q)), + (SELECT e.distance FROM actor.org_unit_descendants_distance($2) as e WHERE e.id = aou.id), + 1000 + ), + evergreen.rank_cp(acp) + ) + ) AS ua + GROUP BY ua.id, ua.name, ua.label_sortkey + ORDER BY rank, ua.name, ua.label_sortkey + LIMIT ($4 -> 'acn')::INT + OFFSET ($5 -> 'acn')::INT; +$$ LANGUAGE SQL STABLE ROWS 10; + + +-- Evergreen DB patch XXXX.schema.action-trigger.event_definition.sms_preminder.sql +-- +-- New action trigger event definition: 3 Day Courtesy Notice by SMS +-- + +-- check whether patch can be applied +-- SELECT evergreen.upgrade_deps_block_check('1058', :eg_version); -- mccanna/csharp/gmcharlt + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, + validator, reactor, delay, max_delay, delay_field, group_field, template) + VALUES (54, FALSE, 1, + '3 Day Courtesy Notice by SMS', + 'checkout.due', + 'CircIsOpen', 'SendSMS', '-3 days', '-2 days', 'due_date', 'usr', +$$ +[%- USE date -%] +[%- user = target.0.usr -%] +[%- homelib = user.home_ou -%] +[%- sms_number = helpers.get_user_setting(user.id, 'opac.default_sms_notify') -%] +[%- sms_carrier = helpers.get_user_setting(user.id, 'opac.default_sms_carrier') -%] +From: [%- helpers.get_org_setting(homelib.id, 'org.bounced_emails') || homelib.email || params.sender_email || default_sender %] +To: [%- helpers.get_sms_gateway_email(sms_carrier,sms_number) %] +Subject: Library Materials Due Soon + +You have items due soon: + +[% FOR circ IN target %] +[%- copy_details = helpers.get_copy_bib_basics(circ.target_copy.id) -%] +[% copy_details.title FILTER ucfirst %] by [% copy_details.author FILTER ucfirst %] due on [% date.format(helpers.format_date(circ.due_date), '%m-%d-%Y') %] + +[% END %] + +$$); + +INSERT INTO action_trigger.environment (event_def, path) VALUES + (54, 'circ_lib.billing_address'), + (54, 'target_copy.call_number'), + (54, 'usr'), + (54, 'usr.home_ou'); + +END IF; END $INSERT$; + + +-- check whether patch can be applied +-- SELECT evergreen.upgrade_deps_block_check('1059', :eg_version); --Stompro/DPearl/kmlussier + +CREATE OR REPLACE VIEW reporter.old_super_simple_record AS +SELECT r.id, + r.fingerprint, + r.quality, + r.tcn_source, + r.tcn_value, + CONCAT_WS(' ', FIRST(title.value),FIRST(title_np.val)) AS title, + FIRST(author.value) AS author, + STRING_AGG(DISTINCT publisher.value, ', ') AS publisher, + STRING_AGG(DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$), ', ') AS pubdate, + CASE WHEN ARRAY_AGG( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) = '{NULL}' + THEN NULL + ELSE ARRAY_AGG( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) + END AS isbn, + CASE WHEN ARRAY_AGG( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) = '{NULL}' + THEN NULL + ELSE ARRAY_AGG( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) + END AS issn + FROM biblio.record_entry r + LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a') + LEFT JOIN ( -- Grab 245 N and P subfields in the order that they appear. + SELECT b.record, string_agg(val, ' ') AS val FROM ( + SELECT title_np.record, title_np.value AS val + FROM metabib.full_rec title_np + WHERE + title_np.tag = '245' + AND title_np.subfield IN ('p','n') + ORDER BY title_np.id + ) b + GROUP BY 1 + ) title_np ON (title_np.record=r.id) + LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a') + 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') + 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') + LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z')) + LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a') + GROUP BY 1,2,3,4,5; + + + -- Remove trigger on biblio.record_entry + SELECT reporter.disable_materialized_simple_record_trigger(); + + -- Rebuild reporter.materialized_simple_record + SELECT reporter.enable_materialized_simple_record_trigger(); + + +-- SELECT evergreen.upgrade_deps_block_check('1060', :eg_version); + +DROP VIEW IF EXISTS extend_reporter.copy_count_per_org; + +CREATE OR REPLACE VIEW extend_reporter.copy_count_per_org AS + SELECT acn.record AS bibid, + ac.circ_lib, + acn.owning_lib, + max(ac.edit_date) AS last_edit_time, + min(ac.deleted::integer) AS has_only_deleted_copies, + count( + CASE + WHEN ac.deleted THEN ac.id + ELSE NULL::bigint + END) AS deleted_count, + count( + CASE + WHEN NOT ac.deleted THEN ac.id + ELSE NULL::bigint + END) AS visible_count, + count(*) AS total_count + FROM asset.call_number acn, + asset.copy ac + WHERE ac.call_number = acn.id + GROUP BY acn.record, acn.owning_lib, ac.circ_lib; + + +-- SELECT evergreen.upgrade_deps_block_check('1061', :eg_version); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO config.org_unit_setting_type + (name, label, description, grp, datatype) +VALUES ( + 'ui.staff.max_recent_patrons', + oils_i18n_gettext( + 'ui.staff.max_recent_patrons', + 'Number of Retrievable Recent Patrons', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'ui.staff.max_recent_patrons', + 'Number of most recently accessed patrons that can be re-retrieved ' || + 'in the staff client. A value of 0 or less disables the feature. Defaults to 1.', + 'coust', + 'description' + ), + 'circ', + 'integer' +); + +END IF; END $INSERT$; + +-- SELECT evergreen.upgrade_deps_block_check('1062', :eg_version); + +CREATE TABLE acq.edi_attr ( + key TEXT PRIMARY KEY, + label TEXT NOT NULL UNIQUE +); + +CREATE TABLE acq.edi_attr_set ( + id SERIAL PRIMARY KEY, + label TEXT NOT NULL UNIQUE +); + +CREATE TABLE acq.edi_attr_set_map ( + id SERIAL PRIMARY KEY, + attr_set INTEGER NOT NULL REFERENCES acq.edi_attr_set(id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + attr TEXT NOT NULL REFERENCES acq.edi_attr(key) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + CONSTRAINT edi_attr_set_map_attr_once UNIQUE (attr_set, attr) +); + +-- An attr_set is not strictly required, since some edi_accounts/vendors +-- may not need to apply any attributes. +ALTER TABLE acq.edi_account + ADD COLUMN attr_set INTEGER REFERENCES acq.edi_attr_set(id), + ADD COLUMN use_attrs BOOLEAN NOT NULL DEFAULT FALSE; + + +-- SELECT evergreen.upgrade_deps_block_check('1063', :eg_version); + +DO $temp$ +DECLARE + r RECORD; +BEGIN + + FOR r IN SELECT t.table_schema AS sname, + t.table_name AS tname, + t.column_name AS colname, + t.constraint_name + FROM information_schema.referential_constraints ref + JOIN information_schema.key_column_usage t USING (constraint_schema,constraint_name) + WHERE ref.unique_constraint_schema = 'asset' + AND ref.unique_constraint_name = 'copy_pkey' + LOOP + + EXECUTE 'ALTER TABLE '||r.sname||'.'||r.tname||' DROP CONSTRAINT '||r.constraint_name||';'; + + EXECUTE ' + CREATE OR REPLACE FUNCTION evergreen.'||r.sname||'_'||r.tname||'_'||r.colname||'_inh_fkey() RETURNS TRIGGER AS $f$ + BEGIN + PERFORM 1 FROM asset.copy WHERE id = NEW.'||r.colname||'; + IF NOT FOUND THEN + RAISE foreign_key_violation USING MESSAGE = FORMAT( + $$Referenced asset.copy id not found, '||r.colname||':%s$$, NEW.'||r.colname||' + ); + END IF; + RETURN NEW; + END; + $f$ LANGUAGE PLPGSQL VOLATILE COST 50; + '; + + EXECUTE ' + CREATE CONSTRAINT TRIGGER inherit_'||r.constraint_name||' + AFTER UPDATE OR INSERT OR DELETE ON '||r.sname||'.'||r.tname||' + DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.'||r.sname||'_'||r.tname||'_'||r.colname||'_inh_fkey(); + '; + END LOOP; +END +$temp$; + + + +-- SELECT evergreen.upgrade_deps_block_check('1064', :eg_version); + +ALTER TABLE serial.issuance DROP CONSTRAINT IF EXISTS issuance_caption_and_pattern_fkey; + +-- Using NOT VALID and VALIDATE CONSTRAINT limits the impact to concurrent work. +-- For details, see: https://www.postgresql.org/docs/current/static/sql-altertable.html + +ALTER TABLE serial.issuance ADD CONSTRAINT issuance_caption_and_pattern_fkey + FOREIGN KEY (caption_and_pattern) + REFERENCES serial.caption_and_pattern (id) + ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED + NOT VALID; + +ALTER TABLE serial.issuance VALIDATE CONSTRAINT issuance_caption_and_pattern_fkey; + + + +-- SELECT evergreen.upgrade_deps_block_check('1065', :eg_version); + +CREATE TABLE serial.pattern_template ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + pattern_code TEXT NOT NULL, + owning_lib INTEGER REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED, + share_depth INTEGER NOT NULL DEFAULT 0 +); +CREATE INDEX serial_pattern_template_name_idx ON serial.pattern_template (evergreen.lowercase(name)); + +CREATE OR REPLACE FUNCTION serial.pattern_templates_visible_to(org_unit INT) RETURNS SETOF serial.pattern_template AS $func$ +BEGIN + RETURN QUERY SELECT * + FROM serial.pattern_template spt + WHERE ( + SELECT ARRAY_AGG(id) + FROM actor.org_unit_descendants(spt.owning_lib, spt.share_depth) + ) @@ org_unit::TEXT::QUERY_INT; +END; +$func$ LANGUAGE PLPGSQL; + + +-- SELECT evergreen.upgrade_deps_block_check('1066', :eg_version); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO permission.perm_list ( id, code, description ) VALUES + ( 593, 'ADMIN_SERIAL_PATTERN_TEMPLATE', oils_i18n_gettext( 593, + 'Administer serial prediction pattern templates', 'ppl', 'description' )) +; + +INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable) + SELECT + pgt.id, perm.id, aout.depth, FALSE + FROM + permission.grp_tree pgt, + permission.perm_list perm, + actor.org_unit_type aout + WHERE + pgt.name = 'Serials' AND + aout.name = 'System' AND + perm.code IN ( + 'ADMIN_SERIAL_PATTERN_TEMPLATE' + ); + + +-- SELECT evergreen.upgrade_deps_block_check('1067', :eg_version); + +INSERT INTO acq.edi_attr (key, label) VALUES + ('INCLUDE_PO_NAME', + oils_i18n_gettext('INCLUDE_PO_NAME', + 'Orders Include PO Name', 'aea', 'label')), + ('INCLUDE_COPIES', + oils_i18n_gettext('INCLUDE_COPIES', + 'Orders Include Copy Data', 'aea', 'label')), + ('INCLUDE_FUND', + oils_i18n_gettext('INCLUDE_FUND', + 'Orders Include Copy Funds', 'aea', 'label')), + ('INCLUDE_CALL_NUMBER', + oils_i18n_gettext('INCLUDE_CALL_NUMBER', + 'Orders Include Copy Call Numbers', 'aea', 'label')), + ('INCLUDE_ITEM_TYPE', + oils_i18n_gettext('INCLUDE_ITEM_TYPE', + 'Orders Include Copy Item Types', 'aea', 'label')), + ('INCLUDE_ITEM_BARCODE', + oils_i18n_gettext('INCLUDE_ITEM_BARCODE', + 'Orders Include Copy Barcodes', 'aea', 'label')), + ('INCLUDE_LOCATION', + oils_i18n_gettext('INCLUDE_LOCATION', + 'Orders Include Copy Locations', 'aea', 'label')), + ('INCLUDE_COLLECTION_CODE', + oils_i18n_gettext('INCLUDE_COLLECTION_CODE', + 'Orders Include Copy Collection Codes', 'aea', 'label')), + ('INCLUDE_OWNING_LIB', + oils_i18n_gettext('INCLUDE_OWNING_LIB', + 'Orders Include Copy Owning Library', 'aea', 'label')), + ('USE_ID_FOR_OWNING_LIB', + oils_i18n_gettext('USE_ID_FOR_OWNING_LIB', + 'Emit Owning Library ID Rather Than Short Name. Takes effect only if INCLUDE_OWNING_LIB is in use', 'aea', 'label')), + ('INCLUDE_QUANTITY', + oils_i18n_gettext('INCLUDE_QUANTITY', + 'Orders Include Copy Quantities', 'aea', 'label')), + ('INCLUDE_COPY_ID', + oils_i18n_gettext('INCLUDE_COPY_ID', + 'Orders Include Copy IDs', 'aea', 'label')), + ('BUYER_ID_INCLUDE_VENDCODE', + oils_i18n_gettext('BUYER_ID_INCLUDE_VENDCODE', + 'Buyer ID Qualifier Includes Vendcode', 'aea', 'label')), + ('BUYER_ID_ONLY_VENDCODE', + oils_i18n_gettext('BUYER_ID_ONLY_VENDCODE', + 'Buyer ID Qualifier Only Contains Vendcode', 'aea', 'label')), + ('INCLUDE_BIB_EDITION', + oils_i18n_gettext('INCLUDE_BIB_EDITION', + 'Order Lineitems Include Edition Info', 'aea', 'label')), + ('INCLUDE_BIB_AUTHOR', + oils_i18n_gettext('INCLUDE_BIB_AUTHOR', + 'Order Lineitems Include Author Info', 'aea', 'label')), + ('INCLUDE_BIB_PAGINATION', + oils_i18n_gettext('INCLUDE_BIB_PAGINATION', + 'Order Lineitems Include Pagination Info', 'aea', 'label')), + ('COPY_SPEC_CODES', + oils_i18n_gettext('COPY_SPEC_CODES', + 'Order Lineitem Notes Include Copy Spec Codes', 'aea', 'label')), + ('INCLUDE_EMPTY_IMD_VALUES', + oils_i18n_gettext('INCLUDE_EMPTY_IMD_VALUES', + 'Lineitem Title, Author, etc. Fields Are Present Even if Empty', 'aea', 'label')), + ('INCLUDE_EMPTY_LI_NOTE', + oils_i18n_gettext('INCLUDE_EMPTY_LI_NOTE', + 'Order Lineitem Notes Always Present (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_CALL_NUMBER', + oils_i18n_gettext('INCLUDE_EMPTY_CALL_NUMBER', + 'Order Copies Always Include Call Number (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_ITEM_TYPE', + oils_i18n_gettext('INCLUDE_EMPTY_ITEM_TYPE', + 'Order Copies Always Include Item Type (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_LOCATION', + oils_i18n_gettext('INCLUDE_EMPTY_LOCATION', + 'Order Copies Always Include Location (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_COLLECTION_CODE', + oils_i18n_gettext('INCLUDE_EMPTY_COLLECTION_CODE', + 'Order Copies Always Include Collection Code (Even if Empty)', 'aea', 'label')), + ('LINEITEM_IDENT_VENDOR_NUMBER', + oils_i18n_gettext('LINEITEM_IDENT_VENDOR_NUMBER', + 'Lineitem Identifier Fields (LIN/PIA) Use Vendor-Encoded ID Value When Available', 'aea', 'label')), + ('LINEITEM_REF_ID_ONLY', + oils_i18n_gettext('LINEITEM_REF_ID_ONLY', + 'Lineitem Reference Field (RFF) Uses Lineitem ID Only', 'aea', 'label')) + +; + +INSERT INTO acq.edi_attr_set (id, label) VALUES (1, 'Ingram Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (2, 'Baker & Taylor Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (3, 'Brodart Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (4, 'Midwest Tape Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (5, 'ULS Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (6, 'Recorded Books Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (7, 'Midwest Library Service'); + +-- carve out space for mucho defaults +PERFORM SETVAL('acq.edi_attr_set_id_seq'::TEXT, 1000); + +INSERT INTO acq.edi_attr_set_map (attr_set, attr) VALUES + + -- Ingram + (1, 'INCLUDE_PO_NAME'), + (1, 'INCLUDE_COPIES'), + (1, 'INCLUDE_ITEM_TYPE'), + (1, 'INCLUDE_COLLECTION_CODE'), + (1, 'INCLUDE_OWNING_LIB'), + (1, 'INCLUDE_QUANTITY'), + (1, 'INCLUDE_BIB_PAGINATION'), + + -- B&T + (2, 'INCLUDE_COPIES'), + (2, 'INCLUDE_ITEM_TYPE'), + (2, 'INCLUDE_COLLECTION_CODE'), + (2, 'INCLUDE_CALL_NUMBER'), + (2, 'INCLUDE_OWNING_LIB'), + (2, 'INCLUDE_QUANTITY'), + (2, 'INCLUDE_BIB_PAGINATION'), + (2, 'BUYER_ID_INCLUDE_VENDCODE'), + (2, 'INCLUDE_EMPTY_LI_NOTE'), + (2, 'INCLUDE_EMPTY_CALL_NUMBER'), + (2, 'INCLUDE_EMPTY_ITEM_TYPE'), + (2, 'INCLUDE_EMPTY_COLLECTION_CODE'), + (2, 'INCLUDE_EMPTY_LOCATION'), + (2, 'LINEITEM_IDENT_VENDOR_NUMBER'), + (2, 'LINEITEM_REF_ID_ONLY'), + + -- Brodart + (3, 'INCLUDE_COPIES'), + (3, 'INCLUDE_FUND'), + (3, 'INCLUDE_ITEM_TYPE'), + (3, 'INCLUDE_COLLECTION_CODE'), + (3, 'INCLUDE_OWNING_LIB'), + (3, 'INCLUDE_QUANTITY'), + (3, 'INCLUDE_BIB_PAGINATION'), + (3, 'COPY_SPEC_CODES'), + + -- Midwest + (4, 'INCLUDE_COPIES'), + (4, 'INCLUDE_FUND'), + (4, 'INCLUDE_OWNING_LIB'), + (4, 'INCLUDE_QUANTITY'), + (4, 'INCLUDE_BIB_PAGINATION'), + + -- ULS + (5, 'INCLUDE_COPIES'), + (5, 'INCLUDE_ITEM_TYPE'), + (5, 'INCLUDE_COLLECTION_CODE'), + (5, 'INCLUDE_OWNING_LIB'), + (5, 'INCLUDE_QUANTITY'), + (5, 'INCLUDE_BIB_AUTHOR'), + (5, 'INCLUDE_BIB_EDITION'), + (5, 'INCLUDE_EMPTY_LI_NOTE'), + + -- Recorded Books + (6, 'INCLUDE_COPIES'), + (6, 'INCLUDE_ITEM_TYPE'), + (6, 'INCLUDE_COLLECTION_CODE'), + (6, 'INCLUDE_OWNING_LIB'), + (6, 'INCLUDE_QUANTITY'), + (6, 'INCLUDE_BIB_PAGINATION'), + + -- Midwest Library Service + (7, 'INCLUDE_BIB_AUTHOR'), + (7, 'INCLUDE_BIB_EDITION'), + (7, 'BUYER_ID_ONLY_VENDCODE'), + (7, 'INCLUDE_EMPTY_IMD_VALUES') +; + + +-- SELECT evergreen.upgrade_deps_block_check('1068', :eg_version); --miker/gmcharlt/kmlussier + +INSERT INTO config.xml_transform (name,namespace_uri,prefix,xslt) VALUES ('mads21','http://www.loc.gov/mads/v2','mads21',$XSLT$ + + + + + + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + + + + ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ + + + + !'()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ + + + 0123456789ABCDEF + + + + + + + + + + + + + + + + + + + + + + + + + + + abcdefghijklmnopqrstuvwxyz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .:,;/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .:,;/] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warning: string contains a character + that is out of range! Substituting "?". + 63 + + + + + + + + + + + + + + + + + + + + + + + + + + + + naf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + direct + + + + + indirect + + + + + not applicable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Converted from MARCXML to MADS version 2.0 (Revision 2.13) + + + + + + + + + + + earlier rules + + + + + aacr1 + + + + + aacr2 + + + + + aacr2 compatible + + + + + other rules + + + + + + + + + + + + + + + + + + + + + + + n + n + fghkdlmor + + + + + p + p + fghkdlmor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cdn + + + + + + + + + + + aq + + + + + + + + + + + + acdenq + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bc + + + + + + + + + + + + + + + + + + + + + + + yes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + a + + - + + st + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + abcdxyz + - + + + + + + + + abcdxyz + - + + + + + + + + abcdxyz + - + + + + + + + + + + abcdxyz + - + + + + + + + abcdxyz5 + - + + + + + + + + + + abcdxyz5 + - + + + + + + + + + + abcdxyz5 + - + + + + + + + + + + abcdxyz5 + - + + + + + + + + + + abcdxyz5 + - + + + + + + + + + + abcdxyz5 + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ab + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + nonpublic + + + source + + + notFound + + + history + + + subject example + + + deleted heading information + + + application history + + + + + + + + + + + ab + + + + + ai + + + + + + + + + + + + + + + z3 + + + + + + + + + + + + earlier + + + later + + + parentOrg + + + broader + + + narrower + + + other + + + other + + + + other + + + + equivalent + + + + + + + + + + + + acronym + + + other + + + other + + + + + other + + + + + + + + + + + + naf + + + + + lcsh + + + + + lacnaf + + + + + lcsh + + + + cash + + + naf + + + lcsh + + + + lacnaf + + + + + cash + + + + + lcshcl + + + + + nlmnaf + + + + + nalnaf + + + + + aat + + + + sears + + + rvm + + + + + + + + + naf + + + + + lcsh + + + + + lacnaf + + + + + lcsh + + + + + lcsh + + + + + mesh + + + + + nal + + + + + cash + + + + + naf + + + + lcsh + + + lacnaf + + + cash + + + lcsh + + + naf + + + lacnaf + + + lcsh + + + cash + + + lcshcl + + + nlmnaf + + + nalnaf + + + rvm + + + + + + + + lacnaf + + + naf + + + lcsh + + + mesh + + + nal + + + cash + + + + +$XSLT$); + +END IF; END $INSERT$; + + +-- SELECT evergreen.upgrade_deps_block_check('1069', :eg_version); --gmcharlt/kmlussier + +-- subset of types listed in https://www.loc.gov/marc/authority/ad1xx3xx.html +-- for now, ignoring subdivisions +CREATE TYPE authority.heading_type AS ENUM ( + 'personal_name', + 'corporate_name', + 'meeting_name', + 'uniform_title', + 'named_event', + 'chronological_term', + 'topical_term', + 'geographic_name', + 'genre_form_term', + 'medium_of_performance_term' +); + +CREATE TYPE authority.variant_heading_type AS ENUM ( + 'abbreviation', + 'acronym', + 'translation', + 'expansion', + 'other', + 'hidden' +); + +CREATE TYPE authority.related_heading_type AS ENUM ( + 'earlier', + 'later', + 'parent organization', + 'broader', + 'narrower', + 'equivalent', + 'other' +); + +CREATE TYPE authority.heading_purpose AS ENUM ( + 'main', + 'variant', + 'related' +); + +CREATE TABLE authority.heading_field ( + id SERIAL PRIMARY KEY, + heading_type authority.heading_type NOT NULL, + heading_purpose authority.heading_purpose NOT NULL, + label TEXT NOT NULL, + format TEXT NOT NULL REFERENCES config.xml_transform (name) DEFAULT 'mads21', + heading_xpath TEXT NOT NULL, + component_xpath TEXT NOT NULL, + type_xpath TEXT NULL, -- to extract related or variant type + thesaurus_xpath TEXT NULL, + thesaurus_override_xpath TEXT NULL, + joiner TEXT NULL +); + +CREATE TABLE authority.heading_field_norm_map ( + id SERIAL PRIMARY KEY, + field INT NOT NULL REFERENCES authority.heading_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + norm INT NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + params TEXT, + pos INT NOT NULL DEFAULT 0 +); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO authority.heading_field(heading_type, heading_purpose, label, heading_xpath, component_xpath, type_xpath, thesaurus_xpath, thesaurus_override_xpath) VALUES + ( 'topical_term', 'main', 'Main Topical Term', '/mads21:mads/mads21:authority', '//mads21:topic', NULL, '/mads21:mads/mads21:authority/mads21:topic[1]/@authority', NULL ) +,( 'topical_term', 'variant', 'Variant Topical Term', '/mads21:mads/mads21:variant', '//mads21:topic', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:topic[1]/@authority', '//mads21:topic[1]/@authority') +,( 'topical_term', 'related', 'Related Topical Term', '/mads21:mads/mads21:related', '//mads21:topic', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:topic[1]/@authority', '//mads21:topic[1]/@authority') +,( 'personal_name', 'main', 'Main Personal Name', '/mads21:mads/mads21:authority', '//mads21:name[@type="personal"]', NULL, NULL, NULL ) +,( 'personal_name', 'variant', 'Variant Personal Name', '/mads21:mads/mads21:variant', '//mads21:name[@type="personal"]', NULL, NULL, NULL ) +,( 'personal_name', 'related', 'Related Personal Name', '/mads21:mads/mads21:related', '//mads21:name[@type="personal"]', '/mads21:related/@type', NULL, NULL ) +,( 'corporate_name', 'main', 'Main Corporate name', '/mads21:mads/mads21:authority', '//mads21:name[@type="corporate"]', NULL, NULL, NULL ) +,( 'corporate_name', 'variant', 'Variant Corporate Name', '/mads21:mads/mads21:variant', '//mads21:name[@type="corporate"]', NULL, NULL, NULL ) +,( 'corporate_name', 'related', 'Related Corporate Name', '/mads21:mads/mads21:related', '//mads21:name[@type="corporate"]', '/mads21:related/@type', NULL, NULL ) +,( 'meeting_name', 'main', 'Main Meeting name', '/mads21:mads/mads21:authority', '//mads21:name[@type="conference"]', NULL, NULL, NULL ) +,( 'meeting_name', 'variant', 'Variant Meeting Name', '/mads21:mads/mads21:variant', '//mads21:name[@type="conference"]', NULL, NULL, NULL ) +,( 'meeting_name', 'related', 'Related Meeting Name', '/mads21:mads/mads21:related', '//mads21:name[@type="meeting"]', '/mads21:related/@type', NULL, NULL ) +,( 'geographic_name', 'main', 'Main Geographic Term', '/mads21:mads/mads21:authority', '//mads21:geographic', NULL, '/mads21:mads/mads21:authority/mads21:geographic[1]/@authority', NULL ) +,( 'geographic_name', 'variant', 'Variant Geographic Term', '/mads21:mads/mads21:variant', '//mads21:geographic', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:geographic[1]/@authority', '//mads21:geographic[1]/@authority') +,( 'geographic_name', 'related', 'Related Geographic Term', '/mads21:mads/mads21:related', '//mads21:geographic', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:geographic[1]/@authority', '//mads21:geographic[1]/@authority') +,( 'genre_form_term', 'main', 'Main Genre/Form Term', '/mads21:mads/mads21:authority', '//mads21:genre', NULL, '/mads21:mads/mads21:authority/mads21:genre[1]/@authority', NULL ) +,( 'genre_form_term', 'variant', 'Variant Genre/Form Term', '/mads21:mads/mads21:variant', '//mads21:genre', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:genre[1]/@authority', '//mads21:genre[1]/@authority') +,( 'genre_form_term', 'related', 'Related Genre/Form Term', '/mads21:mads/mads21:related', '//mads21:genre', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:genre[1]/@authority', '//mads21:genre[1]/@authority') +,( 'chronological_term', 'main', 'Main Chronological Term', '/mads21:mads/mads21:authority', '//mads21:temporal', NULL, '/mads21:mads/mads21:authority/mads21:temporal[1]/@authority', NULL ) +,( 'chronological_term', 'variant', 'Variant Chronological Term', '/mads21:mads/mads21:variant', '//mads21:temporal', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:temporal[1]/@authority', '//mads21:temporal[1]/@authority') +,( 'chronological_term', 'related', 'Related Chronological Term', '/mads21:mads/mads21:related', '//mads21:temporal', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:temporal[1]/@authority', '//mads21:temporal[1]/@authority') +,( 'uniform_title', 'main', 'Main Uniform Title', '/mads21:mads/mads21:authority', '//mads21:title', NULL, '/mads21:mads/mads21:authority/mads21:title[1]/@authority', NULL ) +,( 'uniform_title', 'variant', 'Variant Uniform Title', '/mads21:mads/mads21:variant', '//mads21:title', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:title[1]/@authority', '//mads21:title[1]/@authority') +,( 'uniform_title', 'related', 'Related Uniform Title', '/mads21:mads/mads21:related', '//mads21:title', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:title[1]/@authority', '//mads21:title[1]/@authority') +; + +-- NACO normalize all the things +INSERT INTO authority.heading_field_norm_map (field, norm, pos) +SELECT id, 1, 0 +FROM authority.heading_field; + +END IF; END $INSERT$; + +CREATE TYPE authority.heading AS ( + field INT, + type authority.heading_type, + purpose authority.heading_purpose, + variant_type authority.variant_heading_type, + related_type authority.related_heading_type, + thesaurus TEXT, + heading TEXT, + normalized_heading TEXT +); + +CREATE OR REPLACE FUNCTION authority.extract_headings(marc TEXT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$ +DECLARE + idx authority.heading_field%ROWTYPE; + xfrm config.xml_transform%ROWTYPE; + prev_xfrm TEXT; + transformed_xml TEXT; + heading_node TEXT; + heading_node_list TEXT[]; + component_node TEXT; + component_node_list TEXT[]; + raw_text TEXT; + normalized_text TEXT; + normalizer RECORD; + curr_text TEXT; + joiner TEXT; + type_value TEXT; + base_thesaurus TEXT := NULL; + output_row authority.heading; +BEGIN + + -- Loop over the indexing entries + FOR idx IN SELECT * FROM authority.heading_field WHERE restrict IS NULL OR id = ANY (restrict) ORDER BY format LOOP + + output_row.field := idx.id; + output_row.type := idx.heading_type; + output_row.purpose := idx.heading_purpose; + + joiner := COALESCE(idx.joiner, ' '); + + SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format; + + -- See if we can skip the XSLT ... it's expensive + IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN + -- Can't skip the transform + IF xfrm.xslt <> '---' THEN + transformed_xml := oils_xslt_process(marc, xfrm.xslt); + ELSE + transformed_xml := marc; + END IF; + + prev_xfrm := xfrm.name; + END IF; + + IF idx.thesaurus_xpath IS NOT NULL THEN + base_thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + END IF; + + heading_node_list := oils_xpath( idx.heading_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + + FOR heading_node IN SELECT x FROM unnest(heading_node_list) AS x LOOP + + CONTINUE WHEN heading_node !~ E'^\\s*<'; + + output_row.variant_type := NULL; + output_row.related_type := NULL; + output_row.thesaurus := NULL; + output_row.heading := NULL; + + IF idx.heading_purpose = 'variant' AND idx.type_xpath IS NOT NULL THEN + type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + BEGIN + output_row.variant_type := type_value; + EXCEPTION WHEN invalid_text_representation THEN + RAISE NOTICE 'Do not recognize variant heading type %', type_value; + END; + END IF; + IF idx.heading_purpose = 'related' AND idx.type_xpath IS NOT NULL THEN + type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + BEGIN + output_row.related_type := type_value; + EXCEPTION WHEN invalid_text_representation THEN + RAISE NOTICE 'Do not recognize related heading type %', type_value; + END; + END IF; + + IF idx.thesaurus_override_xpath IS NOT NULL THEN + output_row.thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_override_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + END IF; + IF output_row.thesaurus IS NULL THEN + output_row.thesaurus := base_thesaurus; + END IF; + + raw_text := NULL; + + -- now iterate over components of heading + component_node_list := oils_xpath( idx.component_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + FOR component_node IN SELECT x FROM unnest(component_node_list) AS x LOOP + -- XXX much of this should be moved into oils_xpath_string... + curr_text := ARRAY_TO_STRING(evergreen.array_remove_item_by_value(evergreen.array_remove_item_by_value( + oils_xpath( '//text()', -- get the content of all the nodes within the main selected node + REGEXP_REPLACE( component_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space + ), ' '), ''), -- throw away morally empty (bankrupt?) strings + joiner + ); + + CONTINUE WHEN curr_text IS NULL OR curr_text = ''; + + IF raw_text IS NOT NULL THEN + raw_text := raw_text || joiner; + END IF; + + raw_text := COALESCE(raw_text,'') || curr_text; + END LOOP; + + IF raw_text IS NOT NULL THEN + output_row.heading := raw_text; + normalized_text := raw_text; + + FOR normalizer IN + SELECT n.func AS func, + n.param_count AS param_count, + m.params AS params + FROM config.index_normalizer n + JOIN authority.heading_field_norm_map m ON (m.norm = n.id) + WHERE m.field = idx.id + ORDER BY m.pos LOOP + + EXECUTE 'SELECT ' || normalizer.func || '(' || + quote_literal( normalized_text ) || + CASE + WHEN normalizer.param_count > 0 + THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'') + ELSE '' + END || + ')' INTO normalized_text; + + END LOOP; + + output_row.normalized_heading := normalized_text; + + RETURN NEXT output_row; + END IF; + END LOOP; + + END LOOP; +END; +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION authority.extract_headings(rid BIGINT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$ +DECLARE + auth authority.record_entry%ROWTYPE; + output_row authority.heading; +BEGIN + -- Get the record + SELECT INTO auth * FROM authority.record_entry WHERE id = rid; + + RETURN QUERY SELECT * FROM authority.extract_headings(auth.marc, restrict); +END; +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION authority.simple_heading_set( marcxml TEXT ) RETURNS SETOF authority.simple_heading AS $func$ +DECLARE + res authority.simple_heading%ROWTYPE; + acsaf authority.control_set_authority_field%ROWTYPE; + heading_row authority.heading%ROWTYPE; + tag_used TEXT; + nfi_used TEXT; + sf TEXT; + cset INT; + heading_text TEXT; + joiner_text TEXT; + sort_text TEXT; + tmp_text TEXT; + tmp_xml TEXT; + first_sf BOOL; + auth_id INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT; +BEGIN + + SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id; + + IF cset IS NULL THEN + SELECT control_set INTO cset + FROM authority.control_set_authority_field + WHERE tag IN ( SELECT UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[])) + LIMIT 1; + END IF; + + res.record := auth_id; + res.thesaurus := authority.extract_thesaurus(marcxml); + + FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP + res.atag := acsaf.id; + + IF acsaf.heading_field IS NULL THEN + tag_used := acsaf.tag; + nfi_used := acsaf.nfi; + joiner_text := COALESCE(acsaf.joiner, ' '); + + FOR tmp_xml IN SELECT UNNEST(XPATH('//*[@tag="'||tag_used||'"]', marcxml::XML)::TEXT[]) LOOP + + heading_text := COALESCE( + oils_xpath_string('./*[contains("'||acsaf.display_sf_list||'",@code)]', tmp_xml, joiner_text), + '' + ); + + IF nfi_used IS NOT NULL THEN + + sort_text := SUBSTRING( + heading_text FROM + COALESCE( + NULLIF( + REGEXP_REPLACE( + oils_xpath_string('./@ind'||nfi_used, tmp_xml::TEXT), + $$\D+$$, + '', + 'g' + ), + '' + )::INT, + 0 + ) + 1 + ); + + ELSE + sort_text := heading_text; + END IF; + + IF heading_text IS NOT NULL AND heading_text <> '' THEN + res.value := heading_text; + res.sort_value := public.naco_normalize(sort_text); + res.index_vector = to_tsvector('keyword'::regconfig, res.sort_value); + RETURN NEXT res; + END IF; + + END LOOP; + ELSE + FOR heading_row IN SELECT * FROM authority.extract_headings(marcxml, ARRAY[acsaf.heading_field]) LOOP + res.value := heading_row.heading; + res.sort_value := heading_row.normalized_heading; + res.index_vector = to_tsvector('keyword'::regconfig, res.sort_value); + RETURN NEXT res; + END LOOP; + END IF; + END LOOP; + + RETURN; +END; +$func$ LANGUAGE PLPGSQL STABLE STRICT; + +ALTER TABLE authority.control_set_authority_field ADD COLUMN heading_field INTEGER REFERENCES authority.heading_field(id); + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '100' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'personal_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '400' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'personal_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '500' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'personal_name'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '110' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'corporate_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '410' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'corporate_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '510' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'corporate_name'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '111' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'meeting_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '411' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'meeting_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '511' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'meeting_name'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '130' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'uniform_title'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '430' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'uniform_title'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '530' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'uniform_title'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '150' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'topical_term'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '450' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'topical_term'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '550' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'topical_term'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '151' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'geographic_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '451' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'geographic_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '551' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'geographic_name'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '155' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'genre_form_term'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '455' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'genre_form_term'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '555' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'genre_form_term'; + + +-- SELECT evergreen.upgrade_deps_block_check('1070', :eg_version); --miker/gmcharlt/kmlussier + +CREATE TRIGGER thes_code_tracking_trigger + AFTER UPDATE ON authority.thesaurus + FOR EACH ROW EXECUTE PROCEDURE oils_i18n_code_tracking('at'); + +ALTER TABLE authority.thesaurus ADD COLUMN short_code TEXT, ADD COLUMN uri TEXT; + +DELETE FROM authority.thesaurus WHERE control_set = 1 AND code NOT IN ('n',' ','|'); +UPDATE authority.thesaurus SET short_code = code; + +CREATE TEMP TABLE thesauri (code text, uri text, name text, xlate hstore); +COPY thesauri (code, uri, name, xlate) FROM STDIN; +migfg http://id.loc.gov/vocabulary/genreFormSchemes/migfg Moving image genre-form guide +reveal http://id.loc.gov/vocabulary/genreFormSchemes/reveal REVEAL: fiction indexing and genre headings +dct http://id.loc.gov/vocabulary/genreFormSchemes/dct Dublin Core list of resource types +gmgpc http://id.loc.gov/vocabulary/genreFormSchemes/gmgpc Thesaurus for graphic materials: TGM II, Genre and physical characteristic terms +rbgenr http://id.loc.gov/vocabulary/genreFormSchemes/rbgenr Genre terms: a thesaurus for use in rare book and special collections cataloguing +sgp http://id.loc.gov/vocabulary/genreFormSchemes/sgp Svenska genrebeteckningar fr periodika "sv"=>"Svenska genrebeteckningar fr periodika" +estc http://id.loc.gov/vocabulary/genreFormSchemes/estc Eighteenth century short title catalogue, the cataloguing rules. New ed. +ftamc http://id.loc.gov/vocabulary/genreFormSchemes/ftamc Form terms for archival and manuscripts control +alett http://id.loc.gov/vocabulary/genreFormSchemes/alett An alphabetical list of English text types +gtlm http://id.loc.gov/vocabulary/genreFormSchemes/gtlm Genre terms for law materials: a thesaurus +rbprov http://id.loc.gov/vocabulary/genreFormSchemes/rbprov Provenance evidence: a thesaurus for use in rare book and special collections cataloging +rbbin http://id.loc.gov/vocabulary/genreFormSchemes/rbbin Binding terms: a thesaurus for use in rare book and special collections cataloguing +fbg http://id.loc.gov/vocabulary/genreFormSchemes/fbg Films by genre /dd> +isbdmedia http://id.loc.gov/vocabulary/genreFormSchemes/isbdmedia ISBD Area 0 [media] +marccategory http://id.loc.gov/vocabulary/genreFormSchemes/marccategory MARC form category term list +gnd-music http://id.loc.gov/vocabulary/genreFormSchemes/gnd-music Gemeinsame Normdatei: Musikalische Ausgabeform +proysen http://id.loc.gov/vocabulary/genreFormSchemes/proysen Prøysen: emneord for Prøysen-bibliografien +rdacarrier http://id.loc.gov/vocabulary/genreFormSchemes/rdacarrier Term and code list for RDA carrier types +gnd http://id.loc.gov/vocabulary/genreFormSchemes/gnd Gemeinsame Normdatei +cjh http://id.loc.gov/vocabulary/genreFormSchemes/cjh Center for Jewish History thesaurus +rbpri http://id.loc.gov/vocabulary/genreFormSchemes/rbpri Printing & publishing evidence: a thesaurus for use in rare book and special collections cataloging +fgtpcm http://id.loc.gov/vocabulary/genreFormSchemes/fgtpcm Form/genre terms for printed cartoon material +rbpub http://id.loc.gov/vocabulary/genreFormSchemes/rbpub Printing and publishing evidence: a thesaurus for use in rare book and special collections cataloging +gmd http://id.loc.gov/vocabulary/genreFormSchemes/gmd Anglo-American Cataloguing Rules general material designation +rbpap http://id.loc.gov/vocabulary/genreFormSchemes/rbpap Paper terms: a thesaurus for use in rare book and special collections cataloging +rdamedia http://id.loc.gov/vocabulary/genreFormSchemes/rdamedia Term and code list for RDA media types +marcsmd http://id.loc.gov/vocabulary/genreFormSchemes/marcsmd MARC specific material form term list +saogf http://id.loc.gov/vocabulary/genreFormSchemes/saogf Svenska ämnesord - Genre/Form "sv"=>"Svenska ämnesord - Genre/Form" +lcgft http://id.loc.gov/vocabulary/genreFormSchemes/lcgft Library of Congress genre/form terms for library and archival materials +muzeukv http://id.loc.gov/vocabulary/genreFormSchemes/muzeukv MuzeVideo UK DVD and UMD film genre classification +mim http://id.loc.gov/vocabulary/genreFormSchemes/mim Moving image materials: genre terms +nmc http://id.loc.gov/vocabulary/genreFormSchemes/nmc Revised nomenclature for museum cataloging: a revised and expanded version of Robert C. Chenhall's system for classifying man-made objects +gnd-content http://id.loc.gov/vocabulary/genreFormSchemes/gnd-content Gemeinsame Normdatei: Beschreibung des Inhalts +bgtchm http://id.loc.gov/vocabulary/genreFormSchemes/bgtchm Basic genre terms for cultural heritage materials +gsafd http://id.loc.gov/vocabulary/genreFormSchemes/gsafd Guidelines on subject access to individual works of fiction, drama, etc +marcform http://id.loc.gov/vocabulary/genreFormSchemes/marcform MARC form of item term list +marcgt http://id.loc.gov/vocabulary/genreFormSchemes/marcgt MARC genre terms +barngf http://id.loc.gov/vocabulary/genreFormSchemes/barngf Svenska ämnesord för barn - Genre/Form "sv"=>"Svenska ämnesord för barn - Genre/Form" +ngl http://id.loc.gov/vocabulary/genreFormSchemes/ngl Newspaper genre list +rvmgf http://id.loc.gov/vocabulary/genreFormSchemes/rvmgf Thésaurus des descripteurs de genre/forme de l'Université Laval "fr"=>"Thésaurus des descripteurs de genre/forme de l'Université Laval" +tgfbne http://id.loc.gov/vocabulary/genreFormSchemes/tgfbne Términos de género/forma de la Biblioteca Nacional de España +nbdbgf http://id.loc.gov/vocabulary/genreFormSchemes/nbdbgf NBD Biblion Genres Fictie +rbtyp http://id.loc.gov/vocabulary/genreFormSchemes/rbtyp Type evidence: a thesaurus for use in rare book and special collections cataloging +radfg http://id.loc.gov/vocabulary/genreFormSchemes/radfg Radio form / genre terms guide +gnd-carrier http://id.loc.gov/vocabulary/genreFormSchemes/gnd-carrier Gemeinsame Normdatei: Datenträgertyp +gatbeg http://id.loc.gov/vocabulary/genreFormSchemes/gatbeg Gattungsbegriffe "de"=>"Gattungsbegriffe" +rdacontent http://id.loc.gov/vocabulary/genreFormSchemes/rdacontent Term and code list for RDA content types +isbdcontent http://id.loc.gov/vocabulary/genreFormSchemes/isbdcontent ISBD Area 0 [content] +nimafc http://id.loc.gov/vocabulary/genreFormSchemes/nimafc NIMA form codes +amg http://id.loc.gov/vocabulary/genreFormSchemes/amg Audiovisual material glossary +local http://id.loc.gov/vocabulary/subjectSchemes/local Locally assigned term +taika http://id.loc.gov/vocabulary/subjectSchemes/taika Taideteollisuuden asiasanasto "fi"=>"Taideteollisuuden asiasanasto" +nasat http://id.loc.gov/vocabulary/subjectSchemes/nasat NASA thesaurus +rswkaf http://id.loc.gov/vocabulary/subjectSchemes/rswkaf Alternativform zum Hauptschlagwort "de"=>"Alternativform zum Hauptschlagwort" +jhpk http://id.loc.gov/vocabulary/subjectSchemes/jhpk Język haseł przedmiotowych KABA "pl"=>"Język haseł przedmiotowych KABA" +asrcrfcd http://id.loc.gov/vocabulary/subjectSchemes/asrcrfcd Australian Standard Research Classification: Research Fields, Courses and Disciplines (RFCD) classification +bt http://id.loc.gov/vocabulary/subjectSchemes/bt Bioethics thesaurus +lcstt http://id.loc.gov/vocabulary/subjectSchemes/lcstt List of Chinese subject terms +netc http://id.loc.gov/vocabulary/subjectSchemes/netc National Emergency Training Center Thesaurus (NETC) +aat http://id.loc.gov/vocabulary/subjectSchemes/aat Art & architecture thesaurus +bet http://id.loc.gov/vocabulary/subjectSchemes/bet British education thesaurus +ncjt http://id.loc.gov/vocabulary/subjectSchemes/ncjt National criminal justice thesaurus +samisk http://id.loc.gov/vocabulary/subjectSchemes/samisk Sami bibliography "no"=>"Sámi bibliografia = Samisk bibliografi (Norge)" +tips http://id.loc.gov/vocabulary/subjectSchemes/tips Tesauro ISOC de psicología "es"=>"Tesauro ISOC de psicología" +ukslc http://id.loc.gov/vocabulary/subjectSchemes/ukslc UK Standard Library Categories +tekord http://id.loc.gov/vocabulary/subjectSchemes/tekord TEK-ord : UBiTs emneordliste for arkitektur, realfag, og teknolog "no"=>"TEK-ord : UBiTs emneordliste for arkitektur, realfag, og teknolog" +umitrist http://id.loc.gov/vocabulary/subjectSchemes/umitrist University of Michigan Transportation Research Institute structured thesaurus +wgst http://id.loc.gov/vocabulary/subjectSchemes/wgst Washington GILS Subject Tree +rasuqam http://id.loc.gov/vocabulary/subjectSchemes/rasuqam Répertoire d'autorités-sujet de l'UQAM "fr"=>"Répertoire d'autorités-sujet de l'UQAM" +ntids http://id.loc.gov/vocabulary/subjectSchemes/ntids Norske tidsskrifter 1700-1820: emneord "no"=>"Norske tidsskrifter 1700-1820: emneord" +kaa http://id.loc.gov/vocabulary/subjectSchemes/kaa Kasvatusalan asiasanasto "fi"=>"Kasvatusalan asiasanasto" +yso http://id.loc.gov/vocabulary/subjectSchemes/yso YSO - Yleinen suomalainen ontologia "fi"=>"YSO - Yleinen suomalainen ontologia" +gcipmedia http://id.loc.gov/vocabulary/subjectSchemes/gcipmedia GAMECIP - Computer Game Media Formats (GAMECIP (Game Metadata and Citation Project)) +inspect http://id.loc.gov/vocabulary/subjectSchemes/inspect INSPEC thesaurus +ordnok http://id.loc.gov/vocabulary/subjectSchemes/ordnok Ordnokkelen: tesaurus for kulturminnevern "no"=>"Ordnokkelen: tesaurus for kulturminnevern" +helecon http://id.loc.gov/vocabulary/subjectSchemes/helecon Asiasanasto HELECON-tietikantoihin "fi"=>"Asiasanasto HELECON-tietikantoihin" +dltlt http://id.loc.gov/vocabulary/subjectSchemes/dltlt Cuddon, J. A. A dictionary of literary terms and literary theory +csapa http://id.loc.gov/vocabulary/subjectSchemes/csapa "Controlled vocabulary" in Pollution abstracts +gtt http://id.loc.gov/vocabulary/subjectSchemes/gtt GOO-trefwoorden thesaurus "nl"=>"GOO-trefwoorden thesaurus" +iescs http://id.loc.gov/vocabulary/subjectSchemes/iescs International energy subject categories and scope +itrt http://id.loc.gov/vocabulary/subjectSchemes/itrt International Thesaurus of Refugee Terminology +sanb http://id.loc.gov/vocabulary/subjectSchemes/sanb South African national bibliography authority file +blmlsh http://id.loc.gov/vocabulary/subjectSchemes/blmlsh British Library - Map library subject headings +bhb http://id.loc.gov/vocabulary/subjectSchemes/bhb Bibliography of the Hebrew Book +csh http://id.loc.gov/vocabulary/subjectSchemes/csh Kapsner, Oliver Leonard. Catholic subject headings +fire http://id.loc.gov/vocabulary/subjectSchemes/fire FireTalk, IFSI thesaurus +jlabsh http://id.loc.gov/vocabulary/subjectSchemes/jlabsh Basic subject headings "ja"=>"Kihon kenmei hyômokuhyô" +udc http://id.loc.gov/vocabulary/subjectSchemes/udc Universal decimal classification +lcshac http://id.loc.gov/vocabulary/subjectSchemes/lcshac Children's subject headings in Library of Congress subject headings: supplementary vocabularies +geonet http://id.loc.gov/vocabulary/subjectSchemes/geonet NGA GEOnet Names Server (GNS) +humord http://id.loc.gov/vocabulary/subjectSchemes/humord HUMORD "no"=>"HUMORD" +no-ubo-mr http://id.loc.gov/vocabulary/subjectSchemes/no-ubo-mr Menneskerettighets-tesaurus "no"=>"Menneskerettighets-tesaurus" +sgce http://id.loc.gov/vocabulary/subjectSchemes/sgce COBISS.SI General List of subject headings (English subject headings) "sl"=>"SploÅ¡ni geslovnik COBISS.SI" +kdm http://id.loc.gov/vocabulary/subjectSchemes/kdm Khung dê muc hê thông thông tin khoa hoc và ky thuât quôc gia "vi"=>"Khung dê muc hê thông thông tin khoa hoc và ky thuât quôc gia" +thesoz http://id.loc.gov/vocabulary/subjectSchemes/thesoz Thesaurus for the Social Sciences +asth http://id.loc.gov/vocabulary/subjectSchemes/asth Astronomy thesaurus +muzeukc http://id.loc.gov/vocabulary/subjectSchemes/muzeukc MuzeMusic UK classical music classification +norbok http://id.loc.gov/vocabulary/subjectSchemes/norbok Norbok: emneord i Norsk bokfortegnelse "no"=>"Norbok: emneord i Norsk bokfortegnelse" +masa http://id.loc.gov/vocabulary/subjectSchemes/masa Museoalan asiasanasto "fi"=>"Museoalan asiasanasto" +conorsi http://id.loc.gov/vocabulary/subjectSchemes/conorsi CONOR.SI (name authority file) (Maribor, Slovenia: Institut informacijskih znanosti (IZUM)) +eurovocen http://id.loc.gov/vocabulary/subjectSchemes/eurovocen Eurovoc thesaurus (English) +kto http://id.loc.gov/vocabulary/subjectSchemes/kto KTO - Kielitieteen ontologia "fi"=>"KTO - Kielitieteen ontologia" +muzvukci http://id.loc.gov/vocabulary/subjectSchemes/muzvukci MuzeVideo UK contributor index +kaunokki http://id.loc.gov/vocabulary/subjectSchemes/kaunokki Kaunokki: kaunokirjallisuuden asiasanasto "fi"=>"Kaunokki: kaunokirjallisuuden asiasanasto" +maotao http://id.loc.gov/vocabulary/subjectSchemes/maotao MAO/TAO - Ontologi för museibranschen och Konstindustriella ontologin "fi"=>"MAO/TAO - Ontologi för museibranschen och Konstindustriella ontologin" +psychit http://id.loc.gov/vocabulary/subjectSchemes/psychit Thesaurus of psychological index terms. +tlsh http://id.loc.gov/vocabulary/subjectSchemes/tlsh Subject heading authority list +csalsct http://id.loc.gov/vocabulary/subjectSchemes/csalsct CSA life sciences collection thesaurus +ciesiniv http://id.loc.gov/vocabulary/subjectSchemes/ciesiniv CIESIN indexing vocabulary +ebfem http://id.loc.gov/vocabulary/subjectSchemes/ebfem Encabezamientos bilingües de la Fundación Educativa Ana G. Mendez +mero http://id.loc.gov/vocabulary/subjectSchemes/mero MERO - Merenkulkualan ontologia "fi"=>"MERO - Merenkulkualan ontologia" +mmm http://id.loc.gov/vocabulary/subjectSchemes/mmm "Subject key" in Marxism and the mass media +pascal http://id.loc.gov/vocabulary/subjectSchemes/pascal PASCAL database classification scheme "fr"=>"Base de donneés PASCAL: plan de classement" +chirosh http://id.loc.gov/vocabulary/subjectSchemes/chirosh Chiropractic Subject Headings +cilla http://id.loc.gov/vocabulary/subjectSchemes/cilla Cilla: specialtesaurus för musik "fi"=>"Cilla: specialtesaurus för musik" +aiatsisl http://id.loc.gov/vocabulary/subjectSchemes/aiatsisl AIATSIS language thesaurus +nskps http://id.loc.gov/vocabulary/subjectSchemes/nskps Priručnik za izradu predmetnog kataloga u Nacionalnoj i sveučiliÅ¡noj knjičnici u Zagrebu "hr"=>"Priručnik za izradu predmetnog kataloga u Nacionalnoj i sveučiliÅ¡noj knjičnici u Zagrebu" +lctgm http://id.loc.gov/vocabulary/subjectSchemes/lctgm Thesaurus for graphic materials: TGM I, Subject terms +muso http://id.loc.gov/vocabulary/subjectSchemes/muso MUSO - Ontologi för musik "fi"=>"MUSO - Ontologi för musik" +blcpss http://id.loc.gov/vocabulary/subjectSchemes/blcpss COMPASS subject authority system +fast http://id.loc.gov/vocabulary/subjectSchemes/fast Faceted application of subject terminology +bisacmt http://id.loc.gov/vocabulary/subjectSchemes/bisacmt BISAC Merchandising Themes +lapponica http://id.loc.gov/vocabulary/subjectSchemes/lapponica Lapponica "fi"=>"Lapponica" +juho http://id.loc.gov/vocabulary/subjectSchemes/juho JUHO - Julkishallinnon ontologia "fi"=>"JUHO - Julkishallinnon ontologia" +idas http://id.loc.gov/vocabulary/subjectSchemes/idas ID-Archivschlüssel "de"=>"ID-Archivschlüssel" +tbjvp http://id.loc.gov/vocabulary/subjectSchemes/tbjvp Tesauro de la Biblioteca Dr. Jorge Villalobos Padilla, S.J. "es"=>"Tesauro de la Biblioteca Dr. Jorge Villalobos Padilla, S.J." +test http://id.loc.gov/vocabulary/subjectSchemes/test Thesaurus of engineering and scientific terms +finmesh http://id.loc.gov/vocabulary/subjectSchemes/finmesh FinMeSH "fi"=>"FinMeSH" +kssbar http://id.loc.gov/vocabulary/subjectSchemes/kssbar Klassifikationssystem for svenska bibliotek. Ämnesordregister. Alfabetisk del "sv"=>"Klassifikationssystem for svenska bibliotek. Ämnesordregister. Alfabetisk del" +kupu http://id.loc.gov/vocabulary/subjectSchemes/kupu Maori Wordnet "mi"=>"He puna kupu" +rpe http://id.loc.gov/vocabulary/subjectSchemes/rpe Rubricator on economics "ru"=>"Rubrikator po ekonomike" +dit http://id.loc.gov/vocabulary/subjectSchemes/dit Defense intelligence thesaurus +she http://id.loc.gov/vocabulary/subjectSchemes/she SHE: subject headings for engineering +idszbzna http://id.loc.gov/vocabulary/subjectSchemes/idszbzna Thesaurus IDS Nebis Zentralbibliothek Zürich, Nordamerika-Bibliothek "de"=>"Thesaurus IDS Nebis Zentralbibliothek Zürich, Nordamerika-Bibliothek" +msc http://id.loc.gov/vocabulary/subjectSchemes/msc Mathematical subject classification +muzeukn http://id.loc.gov/vocabulary/subjectSchemes/muzeukn MuzeMusic UK non-classical music classification +ipsp http://id.loc.gov/vocabulary/subjectSchemes/ipsp Defense intelligence production schedule. +sthus http://id.loc.gov/vocabulary/subjectSchemes/sthus Subject Taxonomy of the History of U.S. Foreign Relations +poliscit http://id.loc.gov/vocabulary/subjectSchemes/poliscit Political science thesaurus II +qtglit http://id.loc.gov/vocabulary/subjectSchemes/qtglit A queer thesaurus : an international thesaurus of gay and lesbian index terms +unbist http://id.loc.gov/vocabulary/subjectSchemes/unbist UNBIS thesaurus +gcipplatform http://id.loc.gov/vocabulary/subjectSchemes/gcipplatform GAMECIP - Computer Game Platforms (GAMECIP (Game Metadata and Citation Project)) +puho http://id.loc.gov/vocabulary/subjectSchemes/puho PUHO - Puolustushallinnon ontologia "fi"=>"PUHO - Puolustushallinnon ontologia" +thub http://id.loc.gov/vocabulary/subjectSchemes/thub Thesaurus de la Universitat de Barcelona "ca"=>"Thesaurus de la Universitat de Barcelona" +ndlsh http://id.loc.gov/vocabulary/subjectSchemes/ndlsh National Diet Library list of subject headings "ja"=>"Koktsu Kokkai Toshokan kenmei hyômokuhyô" +czenas http://id.loc.gov/vocabulary/subjectSchemes/czenas CZENAS thesaurus: a list of subject terms used in the National Library of the Czech Republic "cs"=>"Soubor vĕcných autorit Národní knihovny ČR" +idszbzzh http://id.loc.gov/vocabulary/subjectSchemes/idszbzzh Thesaurus IDS Nebis Zentralbibliothek Zürich, Handschriftenabteilung "de"=>"Thesaurus IDS Nebis Zentralbibliothek Zürich, Handschriftenabteilung" +unbisn http://id.loc.gov/vocabulary/subjectSchemes/unbisn UNBIS name authority list (New York, NY: Dag Hammarskjld Library, United Nations; : Chadwyck-Healey) +rswk http://id.loc.gov/vocabulary/subjectSchemes/rswk Regeln für den Schlagwortkatalog "de"=>"Regeln für den Schlagwortkatalog" +larpcal http://id.loc.gov/vocabulary/subjectSchemes/larpcal Lista de assuntos referente ao programa de cadastramento automatizado de livros da USP "pt"=>"Lista de assuntos referente ao programa de cadastramento automatizado de livros da USP" +biccbmc http://id.loc.gov/vocabulary/subjectSchemes/biccbmc BIC Children's Books Marketing Classifications +kulo http://id.loc.gov/vocabulary/subjectSchemes/kulo KULO - Kulttuurien tutkimuksen ontologia "fi"=>"KULO - Kulttuurien tutkimuksen ontologia" +popinte http://id.loc.gov/vocabulary/subjectSchemes/popinte POPIN thesaurus: population multilingual thesaurus +tisa http://id.loc.gov/vocabulary/subjectSchemes/tisa Villagrá Rubio, Angel. Tesauro ISOC de sociología autores "es"=>"Villagrá Rubio, Angel. Tesauro ISOC de sociología autores" +atg http://id.loc.gov/vocabulary/subjectSchemes/atg Agricultural thesaurus and glossary +eflch http://id.loc.gov/vocabulary/subjectSchemes/eflch E4Libraries Category Headings +maaq http://id.loc.gov/vocabulary/subjectSchemes/maaq Madâkhil al-asmâ' al-'arabîyah al-qadîmah "ar"=>"Madâkhil al-asmâ' al-'arabîyah al-qadîmah" +rvmgd http://id.loc.gov/vocabulary/subjectSchemes/rvmgd Thésaurus des descripteurs de groupes démographiques de l'Université Laval "fr"=>"Thésaurus des descripteurs de groupes démographiques de l'Université Laval" +csahssa http://id.loc.gov/vocabulary/subjectSchemes/csahssa "Controlled vocabulary" in Health and safety science abstracts +sigle http://id.loc.gov/vocabulary/subjectSchemes/sigle SIGLE manual, Part 2, Subject category list +blnpn http://id.loc.gov/vocabulary/subjectSchemes/blnpn British Library newspaper place names +asrctoa http://id.loc.gov/vocabulary/subjectSchemes/asrctoa Australian Standard Research Classification: Type of Activity (TOA) classification +lcdgt http://id.loc.gov/vocabulary/subjectSchemes/lcdgt Library of Congress demographic group term and code List +bokbas http://id.loc.gov/vocabulary/subjectSchemes/bokbas Bokbasen "no"=>"Bokbasen" +gnis http://id.loc.gov/vocabulary/subjectSchemes/gnis Geographic Names Information System (GNIS) +nbiemnfag http://id.loc.gov/vocabulary/subjectSchemes/nbiemnfag NBIs emneordsliste for faglitteratur "no"=>"NBIs emneordsliste for faglitteratur" +nlgaf http://id.loc.gov/vocabulary/subjectSchemes/nlgaf Archeio Kathierōmenōn Epikephalidōn "el"=>"Archeio Kathierōmenōn Epikephalidōn" +bhashe http://id.loc.gov/vocabulary/subjectSchemes/bhashe BHA, Bibliography of the history of art, subject headings/English +tsht http://id.loc.gov/vocabulary/subjectSchemes/tsht Thesaurus of subject headings for television +scbi http://id.loc.gov/vocabulary/subjectSchemes/scbi Soggettario per i cataloghi delle biblioteche italiane "it"=>"Soggettario per i cataloghi delle biblioteche italiane" +valo http://id.loc.gov/vocabulary/subjectSchemes/valo VALO - Fotografiska ontologin "fi"=>"VALO - Fotografiska ontologin" +wpicsh http://id.loc.gov/vocabulary/subjectSchemes/wpicsh WPIC Library thesaurus of subject headings +aktp http://id.loc.gov/vocabulary/subjectSchemes/aktp Alphavētikos Katalogos Thematikōn Perigrapheōn "el"=>"Alphavētikos Katalogos Thematikōn Perigrapheōn" +stw http://id.loc.gov/vocabulary/subjectSchemes/stw STW Thesaurus for Economics "de"=>"Standard-Thesaurus Wirtschaft" +mesh http://id.loc.gov/vocabulary/subjectSchemes/mesh Medical subject headings +ica http://id.loc.gov/vocabulary/subjectSchemes/ica Index of Christian art +emnmus http://id.loc.gov/vocabulary/subjectSchemes/emnmus Emneord for musikkdokument i EDB-kataloger "no"=>"Emneord for musikkdokument i EDB-kataloger" +sao http://id.loc.gov/vocabulary/subjectSchemes/sao Svenska ämnesord "sv"=>"Svenska ämnesord" +sgc http://id.loc.gov/vocabulary/subjectSchemes/sgc COBISS.SI General List of subject headings (Slovenian subject headings) "sl"=>"SploÅ¡ni geslovnik COBISS.SI" +bib1814 http://id.loc.gov/vocabulary/subjectSchemes/bib1814 1814-bibliografi: emneord for 1814-bibliografi "no"=>"1814-bibliografi: emneord for 1814-bibliografi" +bjornson http://id.loc.gov/vocabulary/subjectSchemes/bjornson Bjornson: emneord for Bjornsonbibliografien "no"=>"Bjornson: emneord for Bjornsonbibliografien" +liito http://id.loc.gov/vocabulary/subjectSchemes/liito LIITO - Liiketoimintaontologia "fi"=>"LIITO - Liiketoimintaontologia" +apaist http://id.loc.gov/vocabulary/subjectSchemes/apaist APAIS thesaurus: a list of subject terms used in the Australian Public Affairs Information Service +itglit http://id.loc.gov/vocabulary/subjectSchemes/itglit International thesaurus of gay and lesbian index terms (Chicago?: Thesaurus Committee, Gay and Lesbian Task Force, American Library Association) +ntcsd http://id.loc.gov/vocabulary/subjectSchemes/ntcsd "National Translations Center secondary descriptors" in National Translation Center primary subject classification and secondary descriptor +scisshl http://id.loc.gov/vocabulary/subjectSchemes/scisshl SCIS subject headings +opms http://id.loc.gov/vocabulary/subjectSchemes/opms Opetusministeriön asiasanasto "fi"=>"Opetusministeriön asiasanasto" +ttka http://id.loc.gov/vocabulary/subjectSchemes/ttka Teologisen tiedekunnan kirjaston asiasanasto "fi"=>"Teologisen tiedekunnan kirjaston asiasanasto" +watrest http://id.loc.gov/vocabulary/subjectSchemes/watrest Thesaurus of water resources terms: a collection of water resources and related terms for use in indexing technical information +ysa http://id.loc.gov/vocabulary/subjectSchemes/ysa Yleinen suomalainen asiasanasto "fi"=>"Yleinen suomalainen asiasanasto" +kitu http://id.loc.gov/vocabulary/subjectSchemes/kitu Kirjallisuudentutkimuksen asiasanasto "fi"=>"Kirjallisuudentutkimuksen asiasanasto" +sk http://id.loc.gov/vocabulary/subjectSchemes/sk 'Zhong guo gu ji shan ban shu zong mu' fen lei biao "zh"=>"'Zhong guo gu ji shan ban shu zong mu' fen lei biao" +aiatsisp http://id.loc.gov/vocabulary/subjectSchemes/aiatsisp AIATSIS place thesaurus +ram http://id.loc.gov/vocabulary/subjectSchemes/ram RAMEAU: répertoire d'authorité de matières encyclopédique unifié "fr"=>"RAMEAU: répertoire d'authorité de matières encyclopédique unifié" +aedoml http://id.loc.gov/vocabulary/subjectSchemes/aedoml Listado de encabezamientos de materia de música "es"=>"Listado de encabezamientos de materia de música" +ated http://id.loc.gov/vocabulary/subjectSchemes/ated Australian Thesaurus of Education Descriptors (ATED) +cabt http://id.loc.gov/vocabulary/subjectSchemes/cabt CAB thesaurus (Slough [England]: Commonwealth Agricultural Bureaux) +kassu http://id.loc.gov/vocabulary/subjectSchemes/kassu Kassu - Kasvien suomenkieliset nimet "fi"=>"Kassu - Kasvien suomenkieliset nimet" +nbdbt http://id.loc.gov/vocabulary/subjectSchemes/nbdbt NBD Biblion Trefwoordenthesaurus "nl"=>"NBD Biblion Trefwoordenthesaurus" +jhpb http://id.loc.gov/vocabulary/subjectSchemes/jhpb Język haseł przedmiotowych Biblioteki Narodowej "pl"=>"Język haseł przedmiotowych Biblioteki Narodowej" +bidex http://id.loc.gov/vocabulary/subjectSchemes/bidex Bilindex: a bilingual Spanish-English subject heading list +ccsa http://id.loc.gov/vocabulary/subjectSchemes/ccsa Catalogue collectif suisse des affiches "fr"=>"Catalogue collectif suisse des affiches" +noraf http://id.loc.gov/vocabulary/subjectSchemes/noraf Norwegian Authority File +kito http://id.loc.gov/vocabulary/subjectSchemes/kito KITO - Kirjallisuudentutkimuksen ontologia "fi"=>"KITO - Kirjallisuudentutkimuksen ontologia" +tho http://id.loc.gov/vocabulary/subjectSchemes/tho Thesauros Hellēnikōn Oron "el"=>"Thesauros Hellēnikōn Oron" +pmont http://id.loc.gov/vocabulary/subjectSchemes/pmont Powerhouse Museum Object Name Thesaurus +ssg http://id.loc.gov/vocabulary/subjectSchemes/ssg SploÅ¡ni slovenski geslovnik "sl"=>"SploÅ¡ni slovenski geslovnik" +huc http://id.loc.gov/vocabulary/subjectSchemes/huc U.S. Geological Survey water-supply paper 2294: hydrologic basins unit codes +isis http://id.loc.gov/vocabulary/subjectSchemes/isis "Classification scheme" in Isis +ibsen http://id.loc.gov/vocabulary/subjectSchemes/ibsen Ibsen: emneord for Den internasjonale Ibsen-bibliografien "no"=>"Ibsen: emneord for Den internasjonale Ibsen-bibliografien" +lacnaf http://id.loc.gov/vocabulary/subjectSchemes/lacnaf Library and Archives Canada name authority file +swemesh http://id.loc.gov/vocabulary/subjectSchemes/swemesh Swedish MeSH "sv"=>"Svenska MeSH" +hamsun http://id.loc.gov/vocabulary/subjectSchemes/hamsun Hamsun: emneord for Hamsunbibliografien "no"=>"Hamsun: emneord for Hamsunbibliografien" +qrma http://id.loc.gov/vocabulary/subjectSchemes/qrma List of Arabic subject headings "ar"=>"Qâ'imat ru'ûs al-mawdûât al-'Arabîyah" +qrmak http://id.loc.gov/vocabulary/subjectSchemes/qrmak Qâ'imat ru'ûs al-mawdû'ât al-'Arabîyah al-qiyâsîyah al-maktabât wa-marâkaz al-ma'lûmât wa-qawâid al-bayânât "ar"=>"Qâ'imat ru'ûs al-mawdû'ât al-'Arabîyah al-qiyâsîyah al-maktabât wa-marâkaz al-ma'lûmât wa-qawâid al-bayânât" +ceeus http://id.loc.gov/vocabulary/subjectSchemes/ceeus Counties and equivalent entities of the United States its possessions, and associated areas +taxhs http://id.loc.gov/vocabulary/subjectSchemes/taxhs A taxonomy or human services: a conceptual framework with standardized terminology and definitions for the field +noram http://id.loc.gov/vocabulary/subjectSchemes/noram Noram: emneord for Norsk-amerikansk samling "no"=>"Noram: emneord for Norsk-amerikansk samling" +eurovocfr http://id.loc.gov/vocabulary/subjectSchemes/eurovocfr Eurovoc thesaurus (French) +jurivoc http://id.loc.gov/vocabulary/subjectSchemes/jurivoc JURIVOC +agrifors http://id.loc.gov/vocabulary/subjectSchemes/agrifors AGRIFOREST-sanasto "fi"=>"AGRIFOREST-sanasto" +noubojur http://id.loc.gov/vocabulary/subjectSchemes/noubojur Thesaurus of Law "no"=>"Thesaurus of Law" +pha http://id.loc.gov/vocabulary/subjectSchemes/pha Puolostushallinnon asiasanasto "fi"=>"Puolostushallinnon asiasanasto" +ddcrit http://id.loc.gov/vocabulary/subjectSchemes/ddcrit DDC retrieval and indexing terminology; posting terms with hierarchy and KWOC +mar http://id.loc.gov/vocabulary/subjectSchemes/mar Merenkulun asiasanasto "fi"=>"Merenkulun asiasanasto" +sbt http://id.loc.gov/vocabulary/subjectSchemes/sbt Soggettario Sistema Bibliotecario Ticinese "it"=>"Soggettario Sistema Bibliotecario Ticinese" +nzggn http://id.loc.gov/vocabulary/subjectSchemes/nzggn New Zealand gazetteer of official geographic names (New Zealand Geographic Board Ngā Pou Taunaha o Aotearoa (NZGB)) +kta http://id.loc.gov/vocabulary/subjectSchemes/kta Kielitieteen asiasanasto "fi"=>"Kielitieteen asiasanasto" +snt http://id.loc.gov/vocabulary/subjectSchemes/snt Sexual nomenclature : a thesaurus +francis http://id.loc.gov/vocabulary/subjectSchemes/francis FRANCIS database classification scheme "fr"=>"Base de donneés FRANCIS: plan de classement" +eurovocsl http://id.loc.gov/vocabulary/subjectSchemes/eurovocsl Eurovoc thesaurus "sl"=>"Eurovoc thesaurus" +idszbzes http://id.loc.gov/vocabulary/subjectSchemes/idszbzes Thesaurus IDS Nebis Bibliothek Englisches Seminar der Universität Zürich "de"=>"Thesaurus IDS Nebis Bibliothek Englisches Seminar der Universität Zürich" +nlmnaf http://id.loc.gov/vocabulary/subjectSchemes/nlmnaf National Library of Medicine name authority file +rugeo http://id.loc.gov/vocabulary/subjectSchemes/rugeo Natsional'nyi normativnyi fail geograficheskikh nazvanii Rossiiskoi Federatsii "ru"=>"Natsional'nyi normativnyi fail geograficheskikh nazvanii Rossiiskoi Federatsii" +sipri http://id.loc.gov/vocabulary/subjectSchemes/sipri SIPRI library thesaurus +kkts http://id.loc.gov/vocabulary/subjectSchemes/kkts Katalogos Kathierōmenōn Typōn Syllogikou Katalogou Demosion Vivliothekon "el"=>"Katalogos Kathierōmenōn Typōn Syllogikou Katalogou Demosion Vivliothekon" +tucua http://id.loc.gov/vocabulary/subjectSchemes/tucua Thesaurus for use in college and university archives +pmbok http://id.loc.gov/vocabulary/subjectSchemes/pmbok Guide to the project management body of knowledge (PMBOK Guide) +agrovoc http://id.loc.gov/vocabulary/subjectSchemes/agrovoc AGROVOC multilingual agricultural thesaurus +nal http://id.loc.gov/vocabulary/subjectSchemes/nal National Agricultural Library subject headings +lnmmbr http://id.loc.gov/vocabulary/subjectSchemes/lnmmbr Lietuvos nacionalines Martyno Mazvydo bibliotekos rubrikynas "lt"=>"Lietuvos nacionalines Martyno Mazvydo bibliotekos rubrikynas" +vmj http://id.loc.gov/vocabulary/subjectSchemes/vmj Vedettes-matière jeunesse "fr"=>"Vedettes-matière jeunesse" +ddcut http://id.loc.gov/vocabulary/subjectSchemes/ddcut Dewey Decimal Classification user terms +eks http://id.loc.gov/vocabulary/subjectSchemes/eks Eduskunnan kirjaston asiasanasto "fi"=>"Eduskunnan kirjaston asiasanasto" +wot http://id.loc.gov/vocabulary/subjectSchemes/wot A Women's thesaurus +noubomn http://id.loc.gov/vocabulary/subjectSchemes/noubomn University of Oslo Library Thesaurus of Science "no"=>"University of Oslo Library Thesaurus of Science" +idszbzzg http://id.loc.gov/vocabulary/subjectSchemes/idszbzzg Thesaurus IDS Nebis Zentralbibliothek Zürich, Graphische Sammlung "de"=>"Thesaurus IDS Nebis Zentralbibliothek Zürich, Graphische Sammlung" +precis http://id.loc.gov/vocabulary/subjectSchemes/precis PRECIS: a manual of concept analysis and subject indexing +cstud http://id.loc.gov/vocabulary/subjectSchemes/cstud Classificatieschema's Bibliotheek TU Delft "nl"=>"Classificatieschema's Bibliotheek TU Delft" +nlgkk http://id.loc.gov/vocabulary/subjectSchemes/nlgkk Katalogos kathierōmenōn onomatōn physikōn prosōpōn "el"=>"Katalogos kathierōmenōn onomatōn physikōn prosōpōn" +pmt http://id.loc.gov/vocabulary/subjectSchemes/pmt Project management terminology. Newtown Square, PA: Project Management Institute +ericd http://id.loc.gov/vocabulary/subjectSchemes/ericd Thesaurus of ERIC descriptors +rvm http://id.loc.gov/vocabulary/subjectSchemes/rvm Répertoire de vedettes-matière "fr"=>"Répertoire de vedettes-matière" +sfit http://id.loc.gov/vocabulary/subjectSchemes/sfit Svenska filminstitutets tesaurus "sv"=>"Svenska filminstitutets tesaurus" +trtsa http://id.loc.gov/vocabulary/subjectSchemes/trtsa Teatterin ja tanssin asiasanasto "fi"=>"Teatterin ja tanssin asiasanasto" +ulan http://id.loc.gov/vocabulary/subjectSchemes/ulan Union list of artist names +unescot http://id.loc.gov/vocabulary/subjectSchemes/unescot UNESCO thesaurus "fr"=>"Thésaurus de l'UNESCO","es"=>"Tesauro de la UNESCO" +koko http://id.loc.gov/vocabulary/subjectSchemes/koko KOKO-ontologia "fi"=>"KOKO-ontologia" +msh http://id.loc.gov/vocabulary/subjectSchemes/msh Trimboli, T., and Martyn S. Marianist subject headings +trt http://id.loc.gov/vocabulary/subjectSchemes/trt Transportation resource thesaurus +agrovocf http://id.loc.gov/vocabulary/subjectSchemes/agrovocf AGROVOC thésaurus agricole multilingue "fr"=>"AGROVOC thésaurus agricole multilingue" +aucsh http://id.loc.gov/vocabulary/subjectSchemes/aucsh Arabic Union Catalog Subject Headings "ar"=>"Qâ'imat ru'ûs mawdû'ât al-fahras al-'Arabîyah al-mowahad" +ddcri http://id.loc.gov/vocabulary/subjectSchemes/ddcri Dewey Decimal Classification Relative Index +est http://id.loc.gov/vocabulary/subjectSchemes/est International energy: subject thesaurus (: International Energy Agency, Energy Technology Data Exchange) +lua http://id.loc.gov/vocabulary/subjectSchemes/lua Liikunnan ja urheilun asiasanasto "fi"=>"Liikunnan ja urheilun asiasanasto" +mipfesd http://id.loc.gov/vocabulary/subjectSchemes/mipfesd Macrothesaurus for information processing in the field of economic and social development +rurkp http://id.loc.gov/vocabulary/subjectSchemes/rurkp Predmetnye rubriki Rossiiskoi knizhnoi palaty "ru"=>"Predmetnye rubriki Rossiiskoi knizhnoi palaty" +albt http://id.loc.gov/vocabulary/subjectSchemes/albt Arbetslivsbibliotekets tesaurus "sv"=>"Arbetslivsbibliotekets tesaurus" +fmesh http://id.loc.gov/vocabulary/subjectSchemes/fmesh Liste systématique et liste permutée des descripteurs français MeSH "fr"=>"Liste systématique et liste permutée des descripteurs français MeSH" +bicssc http://id.loc.gov/vocabulary/subjectSchemes/bicssc BIC standard subject categories +cctf http://id.loc.gov/vocabulary/subjectSchemes/cctf Carto-Canadiana thésaurus - Français "fr"=>"Carto-Canadiana thésaurus - Français" +reo http://id.loc.gov/vocabulary/subjectSchemes/reo Māori Subject Headings thesaurus "mi"=>"Ngā Ūpoko Tukutuku" +icpsr http://id.loc.gov/vocabulary/subjectSchemes/icpsr ICPSR controlled vocabulary system +kao http://id.loc.gov/vocabulary/subjectSchemes/kao KVINNSAM ämnesordsregister "sv"=>"KVINNSAM ämnesordsregister" +asrcseo http://id.loc.gov/vocabulary/subjectSchemes/asrcseo Australian Standard Research Classification: Socio-Economic Objective (SEO) classification +georeft http://id.loc.gov/vocabulary/subjectSchemes/georeft GeoRef thesaurus +cct http://id.loc.gov/vocabulary/subjectSchemes/cct Chinese Classified Thesaurus "zh"=>"Zhong guo fen lei zhu ti ci biao" +dcs http://id.loc.gov/vocabulary/subjectSchemes/dcs Health Sciences Descriptors "es"=>"Descriptores en Ciencias de la Salud","pt"=>"Descritores em Ciências da Saúde" +musa http://id.loc.gov/vocabulary/subjectSchemes/musa Musiikin asiasanasto: erikoissanasto "fi"=>"Musiikin asiasanasto: erikoissanasto" +ntissc http://id.loc.gov/vocabulary/subjectSchemes/ntissc NTIS subject categories +idszbz http://id.loc.gov/vocabulary/subjectSchemes/idszbz Thesaurus IDS Nebis Zentralbibliothek Zürich "de"=>"Thesaurus IDS Nebis Zentralbibliothek Zürich" +tlka http://id.loc.gov/vocabulary/subjectSchemes/tlka Investigació, Procés Tècnicn kirjaston asiasanasto "fi"=>"Investigació, Procés Tècnicn kirjaston asiasanasto" +usaidt http://id.loc.gov/vocabulary/subjectSchemes/usaidt USAID thesaurus: Keywords used to index documents included in the USAID Development Experience System. +embne http://id.loc.gov/vocabulary/subjectSchemes/embne Encabezamientos de Materia de la Biblioteca Nacional de España "es"=>"Encabezamientos de Materia de la Biblioteca Nacional de España" +vcaadu http://id.loc.gov/vocabulary/subjectSchemes/vcaadu Vocabulario controlado de arquitectura, arte, diseño y urbanismo "es"=>"Vocabulario controlado de arquitectura, arte, diseño y urbanismo" +ntcpsc http://id.loc.gov/vocabulary/subjectSchemes/ntcpsc "National Translations Center primary subject classification" in National Translations Center primary subject classification and secondary descriptors +quiding http://id.loc.gov/vocabulary/subjectSchemes/quiding Quiding, Nils Herman. Svenskt allmänt författningsregister för tiden frÃ¥n Ã¥r 1522 till och med Ã¥r 1862 "sv"=>"Quiding, Nils Herman. Svenskt allmänt författningsregister för tiden frÃ¥n Ã¥r 1522 till och med Ã¥r 1862" +allars http://id.loc.gov/vocabulary/subjectSchemes/allars Allärs: allmän tesaurus pä svenska "fi"=>"Allärs: allmän tesaurus pä svenska" +ogst http://id.loc.gov/vocabulary/subjectSchemes/ogst Oregon GILS Subject Tree (Oregon: Oregon State Library and Oregon Information Resource Management Division (IRMD)) +bella http://id.loc.gov/vocabulary/subjectSchemes/bella Bella: specialtesaurus för skönlitteratur "fi"=>"Bella: specialtesaurus för skönlitteratur" +bibalex http://id.loc.gov/vocabulary/subjectSchemes/bibalex Bibliotheca Alexandrina name and subject authority file +pepp http://id.loc.gov/vocabulary/subjectSchemes/pepp The Princeton encyclopedia of poetry and poetics +hkcan http://id.loc.gov/vocabulary/subjectSchemes/hkcan Hong Kong Chinese Authority File (Name) - HKCAN +dissao http://id.loc.gov/vocabulary/subjectSchemes/dissao "Dissertation abstracts online" in Search tools: the guide to UNI/Data Courier Online +ltcsh http://id.loc.gov/vocabulary/subjectSchemes/ltcsh Land Tenure Center Library list of subject headings +mpirdes http://id.loc.gov/vocabulary/subjectSchemes/mpirdes Macrothesaurus para el procesamiento de la información relativa al desarrollo económico y social "es"=>"Macrothesaurus para el procesamiento de la información relativa al desarrollo económico y social" +asft http://id.loc.gov/vocabulary/subjectSchemes/asft Aquatic sciences and fisheries thesaurus +naf http://id.loc.gov/vocabulary/subjectSchemes/naf NACO authority file +nimacsc http://id.loc.gov/vocabulary/subjectSchemes/nimacsc NIMA cartographic subject categories +khib http://id.loc.gov/vocabulary/subjectSchemes/khib Emneord, KHiB Biblioteket "no"=>"Emneord, KHiB Biblioteket" +cdcng http://id.loc.gov/vocabulary/subjectSchemes/cdcng Catalogage des documents cartographiques: forme et structure des vedettes noms géographiques - NF Z 44-081 "fr"=>"Catalogage des documents cartographiques: forme et structure des vedettes noms géographiques - NF Z 44-081" +afset http://id.loc.gov/vocabulary/subjectSchemes/afset American Folklore Society Ethnographic Thesaurus +erfemn http://id.loc.gov/vocabulary/subjectSchemes/erfemn Erfaringskompetanses emneord "no"=>"Erfaringskompetanses emneord" +sbiao http://id.loc.gov/vocabulary/subjectSchemes/sbiao Svenska barnboksinstitutets ämnesordslista "sv"=>"Svenska barnboksinstitutets ämnesordslista" +socio http://id.loc.gov/vocabulary/subjectSchemes/socio Sociological Abstracts Thesaurus +bisacrt http://id.loc.gov/vocabulary/subjectSchemes/bisacrt BISAC Regional Themes +eum http://id.loc.gov/vocabulary/subjectSchemes/eum Eesti uldine märksonastik "et"=>"Eesti uldine märksonastik" +kula http://id.loc.gov/vocabulary/subjectSchemes/kula Kulttuurien tutkimuksen asiasanasto "fi"=>"Kulttuurien tutkimuksen asiasanasto" +odlt http://id.loc.gov/vocabulary/subjectSchemes/odlt Baldick, C. The Oxford dictionary of literary terms +rerovoc http://id.loc.gov/vocabulary/subjectSchemes/rerovoc Indexation matiéres RERO autoritès "fr"=>"Indexation matiéres RERO autoritès" +tsr http://id.loc.gov/vocabulary/subjectSchemes/tsr TSR-ontologia "fi"=>"TSR-ontologia" +czmesh http://id.loc.gov/vocabulary/subjectSchemes/czmesh Czech MeSH "cs"=>"Czech MeSH" +dltt http://id.loc.gov/vocabulary/subjectSchemes/dltt Quinn, E. A dictionary of literary and thematic terms +idsbb http://id.loc.gov/vocabulary/subjectSchemes/idsbb Thesaurus IDS Basel Bern "de"=>"Thesaurus IDS Basel Bern" +inist http://id.loc.gov/vocabulary/subjectSchemes/inist INIS: thesaurus +idszbzzk http://id.loc.gov/vocabulary/subjectSchemes/idszbzzk Thesaurus IDS Nebis Zentralbibliothek Zürich, Kartensammlung "de"=>"Thesaurus IDS Nebis Zentralbibliothek Zürich, Kartensammlung" +tesa http://id.loc.gov/vocabulary/subjectSchemes/tesa Tesauro Agrícola "es"=>"Tesauro Agrícola" +liv http://id.loc.gov/vocabulary/subjectSchemes/liv Legislative indexing vocabulary +collett http://id.loc.gov/vocabulary/subjectSchemes/collett Collett-bibliografi: litteratur av og om Camilla Collett "no"=>"Collett-bibliografi: litteratur av og om Camilla Collett" +nsbncf http://id.loc.gov/vocabulary/subjectSchemes/nsbncf Nuovo Soggettario "it"=>"Nuovo Soggettario" +ipat http://id.loc.gov/vocabulary/subjectSchemes/ipat IPA thesaurus and frequency list +skon http://id.loc.gov/vocabulary/subjectSchemes/skon Att indexera skönlitteratur: Ämnesordslista, vuxenlitteratur "sv"=>"Att indexera skönlitteratur: Ämnesordslista, vuxenlitteratur" +renib http://id.loc.gov/vocabulary/subjectSchemes/renib Renib "es"=>"Renib" +hrvmesh http://id.loc.gov/vocabulary/subjectSchemes/hrvmesh Croatian MeSH / Hrvatski MeSH "no"=>"Croatian MeSH / Hrvatski MeSH" +swd http://id.loc.gov/vocabulary/subjectSchemes/swd Schlagwortnormdatei "de"=>"Schlagwortnormdatei" +aass http://id.loc.gov/vocabulary/subjectSchemes/aass "Asian American Studies Library subject headings" in A Guide for establishing Asian American core collections +cht http://id.loc.gov/vocabulary/subjectSchemes/cht Chicano thesaurus for indexing Chicano materials in Chicano periodical index +galestne http://id.loc.gov/vocabulary/subjectSchemes/galestne Gale Group subject thesaurus and named entity vocabulary +nlgsh http://id.loc.gov/vocabulary/subjectSchemes/nlgsh Katalogos Hellēnikōn thematikōn epikephalidōn "el"=>"Katalogos Hellēnikōn thematikōn epikephalidōn" +hoidokki http://id.loc.gov/vocabulary/subjectSchemes/hoidokki Hoitotieteellinen asiasanasto +vffyl http://id.loc.gov/vocabulary/subjectSchemes/vffyl Vocabulario de la Biblioteca Central de la FFyL "es"=>"Vocabulario de la Biblioteca Central de la FFyL" +kubikat http://id.loc.gov/vocabulary/subjectSchemes/kubikat kubikat "de"=>"kubikat" +waqaf http://id.loc.gov/vocabulary/subjectSchemes/waqaf Maknas Uloom Al Waqaf "ar"=>"Maknas Uloom Al Waqaf" +hapi http://id.loc.gov/vocabulary/subjectSchemes/hapi HAPI thesaurus and name authority, 1970-2000 +drama http://id.loc.gov/vocabulary/subjectSchemes/drama Drama: specialtesaurus för teater och dans +sosa http://id.loc.gov/vocabulary/subjectSchemes/sosa Sociaalialan asiasanasto "fi"=>"Sociaalialan asiasanasto" +ilpt http://id.loc.gov/vocabulary/subjectSchemes/ilpt Index to legal periodicals: thesaurus +nicem http://id.loc.gov/vocabulary/subjectSchemes/nicem NICEM subject headings and classification system +qlsp http://id.loc.gov/vocabulary/subjectSchemes/qlsp Queens Library Spanish language subject headings +eet http://id.loc.gov/vocabulary/subjectSchemes/eet European education thesaurus +nalnaf http://id.loc.gov/vocabulary/subjectSchemes/nalnaf National Agricultural Library name authority file +eclas http://id.loc.gov/vocabulary/subjectSchemes/eclas ECLAS thesaurus +agrovocs http://id.loc.gov/vocabulary/subjectSchemes/agrovocs AGROVOC tesauro agrícola multilingée "es"=>"AGROVOC tesauro agrícola multilingée" +shbe http://id.loc.gov/vocabulary/subjectSchemes/shbe Subject headings in business and economics "sv"=>"Subject headings in business and economics" +barn http://id.loc.gov/vocabulary/subjectSchemes/barn Svenska ämnesord för barn "sv"=>"Svenska ämnesord för barn" +bhammf http://id.loc.gov/vocabulary/subjectSchemes/bhammf BHA, Bibliographie d'histoire de l'art, mots-matière/français "fr"=>"BHA, Bibliographie d'histoire de l'art, mots-matière/français" +gccst http://id.loc.gov/vocabulary/subjectSchemes/gccst Government of Canada core subject thesaurus (Gatineau : Library and Archives Canada) +fnhl http://id.loc.gov/vocabulary/subjectSchemes/fnhl First Nations House of Learning Subject Headings +kauno http://id.loc.gov/vocabulary/subjectSchemes/kauno KAUNO - Kaunokki-ontologin "fi"=>"KAUNO - Kaunokki-ontologin" +dtict http://id.loc.gov/vocabulary/subjectSchemes/dtict Defense Technical Information Center thesaurus +mech http://id.loc.gov/vocabulary/subjectSchemes/mech Iskanje po zbirki MECH "sl"=>"Iskanje po zbirki MECH" +jupo http://id.loc.gov/vocabulary/subjectSchemes/jupo JUPO - Julkisen hallinnon palveluontologia "fi"=>"JUPO - Julkisen hallinnon palveluontologia" +ktpt http://id.loc.gov/vocabulary/subjectSchemes/ktpt Kirjasto- ja tietopalvelualan tesaurus "fi"=>"Kirjasto- ja tietopalvelualan tesaurus" +aiatsiss http://id.loc.gov/vocabulary/subjectSchemes/aiatsiss AIATSIS subject Thesaurus +lcac http://id.loc.gov/vocabulary/subjectSchemes/lcac Library of Congress Annotated Children's Cataloging Program subject headings +lemac http://id.loc.gov/vocabulary/subjectSchemes/lemac Llista d'encapçalaments de matèria en català "ca"=>"Llista d'encapçalaments de matèria en català" +lemb http://id.loc.gov/vocabulary/subjectSchemes/lemb Lista de encabezamientos de materia para bibliotecas "es"=>"Lista de encabezamientos de materia para bibliotecas" +henn http://id.loc.gov/vocabulary/subjectSchemes/henn Hennepin County Library cumulative authority list +mtirdes http://id.loc.gov/vocabulary/subjectSchemes/mtirdes Macrothésaurus pour le traitement de l'information relative au développement économique et social "fr"=>"Macrothésaurus pour le traitement de l'information relative au développement économique et social" +cash http://id.loc.gov/vocabulary/subjectSchemes/cash Canadian subject headings +nznb http://id.loc.gov/vocabulary/subjectSchemes/nznb New Zealand national bibliographic +prvt http://id.loc.gov/vocabulary/subjectSchemes/prvt Patent- och registreringsverkets tesaurus "sv"=>"Patent- och registreringsverkets tesaurus" +scgdst http://id.loc.gov/vocabulary/subjectSchemes/scgdst Subject categorization guide for defense science and technology +gem http://id.loc.gov/vocabulary/subjectSchemes/gem GEM controlled vocabularies +lcsh http://id.loc.gov/vocabulary/subjectSchemes/lcsh Library of Congress subject headings +rero http://id.loc.gov/vocabulary/subjectSchemes/rero Indexation matires RERO "fr"=>"Indexation matires RERO" +peri http://id.loc.gov/vocabulary/subjectSchemes/peri Perinnetieteiden asiasanasto "fi"=>"Perinnetieteiden asiasanasto" +shsples http://id.loc.gov/vocabulary/subjectSchemes/shsples Encabezamientos de materia para bibliotecas escolares y públicas "es"=>"Encabezamientos de materia para bibliotecas escolares y públicas" +slem http://id.loc.gov/vocabulary/subjectSchemes/slem Sears: lista de encabezamientos de materia "es"=>"Sears: lista de encabezamientos de materia" +afo http://id.loc.gov/vocabulary/subjectSchemes/afo AFO - Viikin kampuskirjaston ontologia "fi"=>"AFO - Viikin kampuskirjaston ontologia" +gst http://id.loc.gov/vocabulary/subjectSchemes/gst Gay studies thesaurus: a controlled vocabulary for indexing and accessing materials of relevance to gay culture, history, politics and psychology +hlasstg http://id.loc.gov/vocabulary/subjectSchemes/hlasstg HLAS subject term glossary +iest http://id.loc.gov/vocabulary/subjectSchemes/iest International energy: subject thesaurus +pkk http://id.loc.gov/vocabulary/subjectSchemes/pkk Predmetnik za katoliÅ¡ke knjižnice "sl"=>"Predmetnik za katoliÅ¡ke knjižnice" +atla http://id.loc.gov/vocabulary/subjectSchemes/atla Religion indexes: thesaurus +scot http://id.loc.gov/vocabulary/subjectSchemes/scot Schools Online Thesaurus (ScOT) +smda http://id.loc.gov/vocabulary/subjectSchemes/smda Smithsonian National Air and Space Museum Directory of Airplanes +solstad http://id.loc.gov/vocabulary/subjectSchemes/solstad Solstad: emneord for Solstadbibliografien "no"=>"Solstad: emneord for Solstadbibliografien" +abne http://id.loc.gov/vocabulary/subjectSchemes/abne Autoridades de la Biblioteca Nacional de España "es"=>"Autoridades de la Biblioteca Nacional de España" +spines http://id.loc.gov/vocabulary/subjectSchemes/spines Tesauro SPINES: un vocabulario controlado y estructurado para el tratamiento de información sobre ciencia y tecnología para el desarrollo "es"=>"Tesauro SPINES: un vocabulario controlado y estructurado para el tratamiento de información sobre ciencia y tecnología para el desarrollo" +ktta http://id.loc.gov/vocabulary/subjectSchemes/ktta Käsi - ja taideteollisuuden asiasanasto "fi"=>"Käsi - ja taideteollisuuden asiasanasto" +ccte http://id.loc.gov/vocabulary/subjectSchemes/ccte Carto-Canadiana thesaurus - English +pmcsg http://id.loc.gov/vocabulary/subjectSchemes/pmcsg Combined standards glossary +bisacsh http://id.loc.gov/vocabulary/subjectSchemes/bisacsh BISAC Subject Headings +fssh http://id.loc.gov/vocabulary/subjectSchemes/fssh FamilySearch Subject Headings (FamilySearch) +tasmas http://id.loc.gov/vocabulary/subjectSchemes/tasmas Tesaurus de Asuntos Sociales del Ministerio de Asuntos Sociales de España "es"=>"Tesaurus de Asuntos Sociales del Ministerio de Asuntos Sociales de España" +tero http://id.loc.gov/vocabulary/subjectSchemes/tero TERO - Terveyden ja hyvinvoinnin ontologia "fi"=>"TERO - Terveyden ja hyvinvoinnin ontologia" +rma http://id.loc.gov/vocabulary/subjectSchemes/rma Ru'us al-mawdu'at al-'Arabiyah "ar"=>"Ru'us al-mawdu'at al-'Arabiyah" +tgn http://id.loc.gov/vocabulary/subjectSchemes/tgn Getty thesaurus of geographic names +tha http://id.loc.gov/vocabulary/subjectSchemes/tha Barcala de Moyano, Graciela G., Cristina Voena. Tesauro de Historia Argentina "es"=>"Barcala de Moyano, Graciela G., Cristina Voena. Tesauro de Historia Argentina" +ttll http://id.loc.gov/vocabulary/subjectSchemes/ttll Roggau, Zunilda. Tell. Tesauro de lengua y literatura "es"=>"Roggau, Zunilda. Tell. Tesauro de lengua y literatura" +sears http://id.loc.gov/vocabulary/subjectSchemes/sears Sears list of subject headings +csht http://id.loc.gov/vocabulary/subjectSchemes/csht Chinese subject headings +\. + +-- ' ...blah + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT INTO authority.thesaurus (code, uri, name, control_set) + SELECT code, uri, name, 1 FROM thesauri; +END IF; END $INSERT$; + + +UPDATE authority.thesaurus SET short_code = 'a' WHERE code = 'lcsh'; +UPDATE authority.thesaurus SET short_code = 'b' WHERE code = 'lcshac'; +UPDATE authority.thesaurus SET short_code = 'c' WHERE code = 'mesh'; +UPDATE authority.thesaurus SET short_code = 'd' WHERE code = 'nal'; +UPDATE authority.thesaurus SET short_code = 'k' WHERE code = 'cash'; +UPDATE authority.thesaurus SET short_code = 'r' WHERE code = 'aat'; +UPDATE authority.thesaurus SET short_code = 's' WHERE code = 'sears'; +UPDATE authority.thesaurus SET short_code = 'v' WHERE code = 'rvm'; + +UPDATE authority.thesaurus + SET short_code = 'z' + WHERE short_code IS NULL + AND control_set = 1; + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT INTO config.i18n_core (fq_field, identity_value, translation, string ) + SELECT 'at.name', t.code, xlate->key, xlate->value + FROM thesauri t + JOIN LATERAL each(t.xlate) AS xlate ON TRUE + WHERE NOT EXISTS + (SELECT id + FROM config.i18n_core + WHERE fq_field = 'at.name' + AND identity_value = t.code + AND translation = xlate->key) + AND t.xlate IS NOT NULL + AND t.name <> (xlate->value); +END IF; END $INSERT$; + + +CREATE OR REPLACE FUNCTION authority.extract_thesaurus( marcxml TEXT ) RETURNS TEXT AS $func$ +DECLARE + thes_code TEXT; +BEGIN + thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj'); + IF thes_code IS NULL THEN + thes_code := '|'; + ELSIF thes_code = 'z' THEN + thes_code := COALESCE( oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml), 'z' ); + ELSE + SELECT code INTO thes_code FROM authority.thesaurus WHERE short_code = thes_code; + IF NOT FOUND THEN + thes_code := '|'; -- default + END IF; + END IF; + RETURN thes_code; +END; +$func$ LANGUAGE PLPGSQL STABLE STRICT; + +CREATE OR REPLACE FUNCTION authority.map_thesaurus_to_control_set () RETURNS TRIGGER AS $func$ +BEGIN + IF NEW.control_set IS NULL THEN + SELECT control_set INTO NEW.control_set + FROM authority.thesaurus + WHERE code = authority.extract_thesaurus(NEW.marc); + END IF; + + RETURN NEW; +END; +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$ +BEGIN + DELETE FROM authority.rec_descriptor WHERE record = auth_id; + INSERT INTO authority.rec_descriptor (record, record_status, encoding_level, thesaurus) + SELECT auth_id, + vandelay.marc21_extract_fixed_field(marc,'RecStat'), + vandelay.marc21_extract_fixed_field(marc,'ELvl'), + authority.extract_thesaurus(marc) + FROM authority.record_entry + WHERE id = auth_id; + RETURN; +END; +$func$ LANGUAGE PLPGSQL; + + + +-- SELECT evergreen.upgrade_deps_block_check('1071', :eg_version); --gmcharlt/kmlussier + +CREATE OR REPLACE FUNCTION metabib.staged_browse(query text, fields integer[], context_org integer, context_locations integer[], staff boolean, browse_superpage_size integer, count_up_from_zero boolean, result_limit integer, next_pivot_pos integer) + RETURNS SETOF metabib.flat_browse_entry_appearance +AS $f$ +DECLARE + curs REFCURSOR; + rec RECORD; + qpfts_query TEXT; + aqpfts_query TEXT; + afields INT[]; + bfields INT[]; + result_row metabib.flat_browse_entry_appearance%ROWTYPE; + results_skipped INT := 0; + row_counter INT := 0; + row_number INT; + slice_start INT; + slice_end INT; + full_end INT; + all_records BIGINT[]; + all_brecords BIGINT[]; + all_arecords BIGINT[]; + superpage_of_records BIGINT[]; + superpage_size INT; + c_tests TEXT := ''; + b_tests TEXT := ''; + c_orgs INT[]; + unauthorized_entry RECORD; +BEGIN + IF count_up_from_zero THEN + row_number := 0; + ELSE + row_number := -1; + END IF; + + IF NOT staff THEN + SELECT x.c_attrs, x.b_attrs INTO c_tests, b_tests FROM asset.patron_default_visibility_mask() x; + END IF; + + IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF; + IF b_tests <> '' THEN b_tests := b_tests || '&'; END IF; + + SELECT ARRAY_AGG(id) INTO c_orgs FROM actor.org_unit_descendants(context_org); + + c_tests := c_tests || search.calculate_visibility_attribute_test('circ_lib',c_orgs) + || '&' || search.calculate_visibility_attribute_test('owning_lib',c_orgs); + + PERFORM 1 FROM config.internal_flag WHERE enabled AND name = 'opac.located_uri.act_as_copy'; + IF FOUND THEN + b_tests := b_tests || search.calculate_visibility_attribute_test( + 'luri_org', + (SELECT ARRAY_AGG(id) FROM actor.org_unit_full_path(context_org) x) + ); + ELSE + b_tests := b_tests || search.calculate_visibility_attribute_test( + 'luri_org', + (SELECT ARRAY_AGG(id) FROM actor.org_unit_ancestors(context_org) x) + ); + END IF; + + IF context_locations THEN + IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF; + c_tests := c_tests || search.calculate_visibility_attribute_test('location',context_locations); + END IF; + + OPEN curs NO SCROLL FOR EXECUTE query; + + LOOP + FETCH curs INTO rec; + IF NOT FOUND THEN + IF result_row.pivot_point IS NOT NULL THEN + RETURN NEXT result_row; + END IF; + RETURN; + END IF; + + --Is unauthorized? + SELECT INTO unauthorized_entry * + FROM metabib.browse_entry_simple_heading_map mbeshm + INNER JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id ) + INNER JOIN authority.control_set_authority_field acsaf ON ( acsaf.id = ash.atag ) + JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field) + WHERE mbeshm.entry = rec.id + AND ahf.heading_purpose = 'variant'; + + -- Gather aggregate data based on the MBE row we're looking at now, authority axis + IF (unauthorized_entry.record IS NOT NULL) THEN + --unauthorized term belongs to an auth linked to a bib? + SELECT INTO all_arecords, result_row.sees, afields + ARRAY_AGG(DISTINCT abl.bib), + STRING_AGG(DISTINCT abl.authority::TEXT, $$,$$), + ARRAY_AGG(DISTINCT map.metabib_field) + FROM authority.bib_linking abl + INNER JOIN authority.control_set_auth_field_metabib_field_map_refs map ON ( + map.authority_field = unauthorized_entry.atag + AND map.metabib_field = ANY(fields) + ) + WHERE abl.authority = unauthorized_entry.record; + ELSE + --do usual procedure + SELECT INTO all_arecords, result_row.sees, afields + ARRAY_AGG(DISTINCT abl.bib), -- bibs to check for visibility + STRING_AGG(DISTINCT aal.source::TEXT, $$,$$), -- authority record ids + ARRAY_AGG(DISTINCT map.metabib_field) -- authority-tag-linked CMF rows + + FROM metabib.browse_entry_simple_heading_map mbeshm + JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id ) + JOIN authority.authority_linking aal ON ( ash.record = aal.source ) + JOIN authority.bib_linking abl ON ( aal.target = abl.authority ) + JOIN authority.control_set_auth_field_metabib_field_map_refs map ON ( + ash.atag = map.authority_field + AND map.metabib_field = ANY(fields) + ) + JOIN authority.control_set_authority_field acsaf ON ( + map.authority_field = acsaf.id + ) + JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field) + WHERE mbeshm.entry = rec.id + AND ahf.heading_purpose = 'variant'; + + END IF; + + -- Gather aggregate data based on the MBE row we're looking at now, bib axis + SELECT INTO all_brecords, result_row.authorities, bfields + ARRAY_AGG(DISTINCT source), + STRING_AGG(DISTINCT authority::TEXT, $$,$$), + ARRAY_AGG(DISTINCT def) + FROM metabib.browse_entry_def_map + WHERE entry = rec.id + AND def = ANY(fields); + + SELECT INTO result_row.fields STRING_AGG(DISTINCT x::TEXT, $$,$$) FROM UNNEST(afields || bfields) x; + + result_row.sources := 0; + result_row.asources := 0; + + -- Bib-linked vis checking + IF ARRAY_UPPER(all_brecords,1) IS NOT NULL THEN + + SELECT INTO result_row.sources COUNT(DISTINCT b.id) + FROM biblio.record_entry b + JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id) + WHERE b.id = ANY(all_brecords[1:browse_superpage_size]) + AND ( + acvac.vis_attr_vector @@ c_tests::query_int + OR b.vis_attr_vector @@ b_tests::query_int + ); + + result_row.accurate := TRUE; + + END IF; + + -- Authority-linked vis checking + IF ARRAY_UPPER(all_arecords,1) IS NOT NULL THEN + + SELECT INTO result_row.asources COUNT(DISTINCT b.id) + FROM biblio.record_entry b + JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id) + WHERE b.id = ANY(all_arecords[1:browse_superpage_size]) + AND ( + acvac.vis_attr_vector @@ c_tests::query_int + OR b.vis_attr_vector @@ b_tests::query_int + ); + + result_row.aaccurate := TRUE; + + END IF; + + IF result_row.sources > 0 OR result_row.asources > 0 THEN + + -- The function that calls this function needs row_number in order + -- to correctly order results from two different runs of this + -- functions. + result_row.row_number := row_number; + + -- Now, if row_counter is still less than limit, return a row. If + -- not, but it is less than next_pivot_pos, continue on without + -- returning actual result rows until we find + -- that next pivot, and return it. + + IF row_counter < result_limit THEN + result_row.browse_entry := rec.id; + result_row.value := rec.value; + + RETURN NEXT result_row; + ELSE + result_row.browse_entry := NULL; + result_row.authorities := NULL; + result_row.fields := NULL; + result_row.value := NULL; + result_row.sources := NULL; + result_row.sees := NULL; + result_row.accurate := NULL; + result_row.aaccurate := NULL; + result_row.pivot_point := rec.id; + + IF row_counter >= next_pivot_pos THEN + RETURN NEXT result_row; + RETURN; + END IF; + END IF; + + IF count_up_from_zero THEN + row_number := row_number + 1; + ELSE + row_number := row_number - 1; + END IF; + + -- row_counter is different from row_number. + -- It simply counts up from zero so that we know when + -- we've reached our limit. + row_counter := row_counter + 1; + END IF; + END LOOP; +END; +$f$ LANGUAGE plpgsql ROWS 10; + +CREATE OR REPLACE FUNCTION metabib.browse(search_field integer[], browse_term text, context_org integer DEFAULT NULL::integer, context_loc_group integer DEFAULT NULL::integer, staff boolean DEFAULT false, pivot_id bigint DEFAULT NULL::bigint, result_limit integer DEFAULT 10) + RETURNS SETOF metabib.flat_browse_entry_appearance +AS $f$ +DECLARE + core_query TEXT; + back_query TEXT; + forward_query TEXT; + pivot_sort_value TEXT; + pivot_sort_fallback TEXT; + context_locations INT[]; + browse_superpage_size INT; + results_skipped INT := 0; + back_limit INT; + back_to_pivot INT; + forward_limit INT; + forward_to_pivot INT; +BEGIN + -- First, find the pivot if we were given a browse term but not a pivot. + IF pivot_id IS NULL THEN + pivot_id := metabib.browse_pivot(search_field, browse_term); + END IF; + + SELECT INTO pivot_sort_value, pivot_sort_fallback + sort_value, value FROM metabib.browse_entry WHERE id = pivot_id; + + -- Bail if we couldn't find a pivot. + IF pivot_sort_value IS NULL THEN + RETURN; + END IF; + + -- Transform the context_loc_group argument (if any) (logc at the + -- TPAC layer) into a form we'll be able to use. + IF context_loc_group IS NOT NULL THEN + SELECT INTO context_locations ARRAY_AGG(location) + FROM asset.copy_location_group_map + WHERE lgroup = context_loc_group; + END IF; + + -- Get the configured size of browse superpages. + SELECT INTO browse_superpage_size COALESCE(value::INT,100) -- NULL ok + FROM config.global_flag + WHERE enabled AND name = 'opac.browse.holdings_visibility_test_limit'; + + -- First we're going to search backward from the pivot, then we're going + -- to search forward. In each direction, we need two limits. At the + -- lesser of the two limits, we delineate the edge of the result set + -- we're going to return. At the greater of the two limits, we find the + -- pivot value that would represent an offset from the current pivot + -- at a distance of one "page" in either direction, where a "page" is a + -- result set of the size specified in the "result_limit" argument. + -- + -- The two limits in each direction make four derived values in total, + -- and we calculate them now. + back_limit := CEIL(result_limit::FLOAT / 2); + back_to_pivot := result_limit; + forward_limit := result_limit / 2; + forward_to_pivot := result_limit - 1; + + -- This is the meat of the SQL query that finds browse entries. We'll + -- pass this to a function which uses it with a cursor, so that individual + -- rows may be fetched in a loop until some condition is satisfied, without + -- waiting for a result set of fixed size to be collected all at once. + core_query := ' +SELECT mbe.id, + mbe.value, + mbe.sort_value + FROM metabib.browse_entry mbe + WHERE ( + EXISTS ( -- are there any bibs using this mbe via the requested fields? + SELECT 1 + FROM metabib.browse_entry_def_map mbedm + WHERE mbedm.entry = mbe.id AND mbedm.def = ANY(' || quote_literal(search_field) || ') + ) OR EXISTS ( -- are there any authorities using this mbe via the requested fields? + SELECT 1 + FROM metabib.browse_entry_simple_heading_map mbeshm + JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id ) + JOIN authority.control_set_auth_field_metabib_field_map_refs map ON ( + ash.atag = map.authority_field + AND map.metabib_field = ANY(' || quote_literal(search_field) || ') + ) + JOIN authority.control_set_authority_field acsaf ON ( + map.authority_field = acsaf.id + ) + JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field) + WHERE mbeshm.entry = mbe.id + AND ahf.heading_purpose IN (' || $$'variant'$$ || ') + -- and authority that variant is coming from is linked to a bib + AND EXISTS ( + SELECT 1 + FROM metabib.browse_entry_def_map mbedm2 + WHERE mbedm2.authority = ash.record AND mbedm2.def = ANY(' || quote_literal(search_field) || ') + ) + ) + ) AND '; + + -- This is the variant of the query for browsing backward. + back_query := core_query || + ' mbe.sort_value <= ' || quote_literal(pivot_sort_value) || + ' ORDER BY mbe.sort_value DESC, mbe.value DESC LIMIT 1000'; + + -- This variant browses forward. + forward_query := core_query || + ' mbe.sort_value > ' || quote_literal(pivot_sort_value) || + ' ORDER BY mbe.sort_value, mbe.value LIMIT 1000'; + + -- We now call the function which applies a cursor to the provided + -- queries, stopping at the appropriate limits and also giving us + -- the next page's pivot. + RETURN QUERY + SELECT * FROM metabib.staged_browse( + back_query, search_field, context_org, context_locations, + staff, browse_superpage_size, TRUE, back_limit, back_to_pivot + ) UNION + SELECT * FROM metabib.staged_browse( + forward_query, search_field, context_org, context_locations, + staff, browse_superpage_size, FALSE, forward_limit, forward_to_pivot + ) ORDER BY row_number DESC; + +END; +$f$ LANGUAGE plpgsql ROWS 10; + + +-- SELECT evergreen.upgrade_deps_block_check('1072', :eg_version); --gmcharlt/kmlussier + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO config.global_flag (name, label, enabled) VALUES ( + 'opac.show_related_headings_in_browse', + oils_i18n_gettext( + 'opac.show_related_headings_in_browse', + 'Display related headings (see-also) in browse', + 'cgf', + 'label' + ), + TRUE +); + +END IF; END $INSERT$; + +-- SELECT evergreen.upgrade_deps_block_check('1073', :eg_version); + +ALTER TABLE config.metabib_field + ADD COLUMN display_xpath TEXT, + ADD COLUMN display_field BOOL NOT NULL DEFAULT FALSE; + +CREATE TABLE config.display_field_map ( + name TEXT PRIMARY KEY, + field INTEGER REFERENCES config.metabib_field (id), + multi BOOLEAN DEFAULT FALSE +); + +CREATE TABLE metabib.display_entry ( + id BIGSERIAL PRIMARY KEY, + source BIGINT NOT NULL REFERENCES biblio.record_entry (id), + field INT NOT NULL REFERENCES config.metabib_field (id), + value TEXT NOT NULL +); + +CREATE INDEX metabib_display_entry_field_idx ON metabib.display_entry (field); +CREATE INDEX metabib_display_entry_source_idx ON metabib.display_entry (source); + +-- one row per display entry fleshed with field info +CREATE VIEW metabib.flat_display_entry AS + SELECT + mde.source, + cdfm.name, + cdfm.multi, + cmf.label, + cmf.id AS field, + mde.value + FROM metabib.display_entry mde + JOIN config.metabib_field cmf ON (cmf.id = mde.field) + JOIN config.display_field_map cdfm ON (cdfm.field = mde.field) +; + +-- like flat_display_entry except values are compressed +-- into one row per display_field_map and JSON-ified. +CREATE VIEW metabib.compressed_display_entry AS + SELECT + source, + name, + multi, + label, + field, + CASE WHEN multi THEN + TO_JSON(ARRAY_AGG(value)) + ELSE + TO_JSON(MIN(value)) + END AS value + FROM metabib.flat_display_entry + GROUP BY 1, 2, 3, 4, 5 +; + +-- TODO: expand to encompass all well-known fields +CREATE VIEW metabib.wide_display_entry AS + SELECT + bre.id AS source, + COALESCE(mcde_title.value, 'null') AS title, + COALESCE(mcde_author.value, 'null') AS author, + COALESCE(mcde_subject.value, 'null') AS subject, + COALESCE(mcde_creators.value, 'null') AS creators, + COALESCE(mcde_isbn.value, 'null') AS isbn + -- ensure one row per bre regardless of any display fields + FROM biblio.record_entry bre + LEFT JOIN metabib.compressed_display_entry mcde_title + ON (bre.id = mcde_title.source AND mcde_title.name = 'title') + LEFT JOIN metabib.compressed_display_entry mcde_author + ON (bre.id = mcde_author.source AND mcde_author.name = 'author') + LEFT JOIN metabib.compressed_display_entry mcde_subject + ON (bre.id = mcde_subject.source AND mcde_subject.name = 'subject') + LEFT JOIN metabib.compressed_display_entry mcde_creators + ON (bre.id = mcde_creators.source AND mcde_creators.name = 'creators') + LEFT JOIN metabib.compressed_display_entry mcde_isbn + ON (bre.id = mcde_isbn.source AND mcde_isbn.name = 'isbn') +; + + +CREATE OR REPLACE FUNCTION metabib.display_field_normalize_trigger () + RETURNS TRIGGER AS $$ +DECLARE + normalizer RECORD; + display_field_text TEXT; +BEGIN + display_field_text := NEW.value; + + FOR normalizer IN + SELECT n.func AS func, + n.param_count AS param_count, + m.params AS params + FROM config.index_normalizer n + JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id) + WHERE m.field = NEW.field AND m.pos < 0 + ORDER BY m.pos LOOP + + EXECUTE 'SELECT ' || normalizer.func || '(' || + quote_literal( display_field_text ) || + CASE + WHEN normalizer.param_count > 0 + THEN ',' || REPLACE(REPLACE(BTRIM( + normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'') + ELSE '' + END || + ')' INTO display_field_text; + + END LOOP; + + NEW.value = display_field_text; + + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + +CREATE TRIGGER display_field_normalize_tgr + BEFORE UPDATE OR INSERT ON metabib.display_entry + FOR EACH ROW EXECUTE PROCEDURE metabib.display_field_normalize_trigger(); + +CREATE OR REPLACE FUNCTION evergreen.display_field_force_nfc() + RETURNS TRIGGER AS $$ +BEGIN + NEW.value := force_unicode_normal_form(NEW.value,'NFC'); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + +CREATE TRIGGER display_field_force_nfc_tgr + BEFORE UPDATE OR INSERT ON metabib.display_entry + FOR EACH ROW EXECUTE PROCEDURE evergreen.display_field_force_nfc(); + +ALTER TYPE metabib.field_entry_template ADD ATTRIBUTE display_field BOOL; + +DROP FUNCTION metabib.reingest_metabib_field_entries(BIGINT, BOOL, BOOL, BOOL); +DROP FUNCTION biblio.extract_metabib_field_entry(BIGINT); +DROP FUNCTION biblio.extract_metabib_field_entry(BIGINT, TEXT); + +CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( + rid BIGINT, + default_joiner TEXT, + field_types TEXT[], + only_fields INT[] +) RETURNS SETOF metabib.field_entry_template AS $func$ +DECLARE + bib biblio.record_entry%ROWTYPE; + idx config.metabib_field%ROWTYPE; + xfrm config.xml_transform%ROWTYPE; + prev_xfrm TEXT; + transformed_xml TEXT; + xml_node TEXT; + xml_node_list TEXT[]; + facet_text TEXT; + display_text TEXT; + browse_text TEXT; + sort_value TEXT; + raw_text TEXT; + curr_text TEXT; + joiner TEXT := default_joiner; -- XXX will index defs supply a joiner? + authority_text TEXT; + authority_link BIGINT; + output_row metabib.field_entry_template%ROWTYPE; + process_idx BOOL; +BEGIN + + -- Start out with no field-use bools set + output_row.browse_field = FALSE; + output_row.facet_field = FALSE; + output_row.display_field = FALSE; + output_row.search_field = FALSE; + + -- Get the record + SELECT INTO bib * FROM biblio.record_entry WHERE id = rid; + + -- Loop over the indexing entries + FOR idx IN SELECT * FROM config.metabib_field WHERE id = ANY (only_fields) ORDER BY format LOOP + + process_idx := FALSE; + IF idx.display_field AND 'display' = ANY (field_types) THEN process_idx = TRUE; END IF; + IF idx.browse_field AND 'browse' = ANY (field_types) THEN process_idx = TRUE; END IF; + IF idx.search_field AND 'search' = ANY (field_types) THEN process_idx = TRUE; END IF; + IF idx.facet_field AND 'facet' = ANY (field_types) THEN process_idx = TRUE; END IF; + CONTINUE WHEN process_idx = FALSE; + + joiner := COALESCE(idx.joiner, default_joiner); + + SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format; + + -- See if we can skip the XSLT ... it's expensive + IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN + -- Can't skip the transform + IF xfrm.xslt <> '---' THEN + transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt); + ELSE + transformed_xml := bib.marc; + END IF; + + prev_xfrm := xfrm.name; + END IF; + + xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + + raw_text := NULL; + FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP + CONTINUE WHEN xml_node !~ E'^\\s*<'; + + -- XXX much of this should be moved into oils_xpath_string... + curr_text := ARRAY_TO_STRING(evergreen.array_remove_item_by_value(evergreen.array_remove_item_by_value( + oils_xpath( '//text()', -- get the content of all the nodes within the main selected node + REGEXP_REPLACE( xml_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space + ), ' '), ''), -- throw away morally empty (bankrupt?) strings + joiner + ); + + CONTINUE WHEN curr_text IS NULL OR curr_text = ''; + + IF raw_text IS NOT NULL THEN + raw_text := raw_text || joiner; + END IF; + + raw_text := COALESCE(raw_text,'') || curr_text; + + -- autosuggest/metabib.browse_entry + IF idx.browse_field THEN + + IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN + browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + ELSE + browse_text := curr_text; + END IF; + + IF idx.browse_sort_xpath IS NOT NULL AND + idx.browse_sort_xpath <> '' THEN + + sort_value := oils_xpath_string( + idx.browse_sort_xpath, xml_node, joiner, + ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] + ); + ELSE + sort_value := browse_text; + END IF; + + output_row.field_class = idx.field_class; + output_row.field = idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g')); + + -- output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g')); + -- outer regexp_replace keeps all '.' expect the last one. + -- inner regexp_replace removes all connecting whitespace and replaces it with a single space + output_row.value = BTRIM(REGEXP_REPLACE(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g'), E'\\.$', '', 'g')); + -- /KCLS + + output_row.sort_value := + public.naco_normalize(sort_value); + + output_row.authority := NULL; + + IF idx.authority_xpath IS NOT NULL AND idx.authority_xpath <> '' THEN + authority_text := oils_xpath_string( + idx.authority_xpath, xml_node, joiner, + ARRAY[ + ARRAY[xfrm.prefix, xfrm.namespace_uri], + ARRAY['xlink','http://www.w3.org/1999/xlink'] + ] + ); + + IF authority_text ~ '^\d+$' THEN + authority_link := authority_text::BIGINT; + PERFORM * FROM authority.record_entry WHERE id = authority_link; + IF FOUND THEN + output_row.authority := authority_link; + END IF; + END IF; + + END IF; + + output_row.browse_field = TRUE; + -- Returning browse rows with search_field = true for search+browse + -- configs allows us to retain granularity of being able to search + -- browse fields with "starts with" type operators (for example, for + -- titles of songs in music albums) + IF idx.search_field THEN + output_row.search_field = TRUE; + END IF; + RETURN NEXT output_row; + output_row.browse_field = FALSE; + output_row.search_field = FALSE; + output_row.sort_value := NULL; + END IF; + + -- insert raw node text for faceting + IF idx.facet_field THEN + + IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN + facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + ELSE + facet_text := curr_text; + END IF; + + output_row.field_class = idx.field_class; + output_row.field = -1 * idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g')); + + output_row.facet_field = TRUE; + RETURN NEXT output_row; + output_row.facet_field = FALSE; + END IF; + + -- insert raw node text for display + IF idx.display_field THEN + + IF idx.display_xpath IS NOT NULL AND idx.display_xpath <> '' THEN + display_text := oils_xpath_string( idx.display_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + ELSE + display_text := curr_text; + END IF; + + output_row.field_class = idx.field_class; + output_row.field = -1 * idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(display_text, E'\\s+', ' ', 'g')); + + output_row.display_field = TRUE; + RETURN NEXT output_row; + output_row.display_field = FALSE; + END IF; + + END LOOP; + + CONTINUE WHEN raw_text IS NULL OR raw_text = ''; + + -- insert combined node text for searching + IF idx.search_field THEN + output_row.field_class = idx.field_class; + -- KCLS + -- TODO: move these mods into configs / search normalizers + IF idx.field_class = 'identifier' AND idx.name = 'bibcn' THEN + output_row.field_class = 'call_number'; + output_row.browse_field = TRUE; + output_row.sort_value = public.naco_normalize_keep_decimal(raw_text,''); + output_row.value = raw_text; + ELSE + output_row.field_class = idx.field_class; + output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g')); + END IF; + -- /KCLS + output_row.field = idx.id; + output_row.source = rid; + -- KCLS -- value is set above + -- output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g')); + -- /KCLS + + output_row.search_field = TRUE; + RETURN NEXT output_row; + output_row.search_field = FALSE; + END IF; + + END LOOP; + +END; + +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( + bib_id BIGINT, + skip_facet BOOL DEFAULT FALSE, + skip_display BOOL DEFAULT FALSE, + skip_browse BOOL DEFAULT FALSE, + skip_search BOOL DEFAULT FALSE, + only_fields INT[] DEFAULT '{}'::INT[] +) RETURNS VOID AS $func$ +DECLARE + fclass RECORD; + ind_data metabib.field_entry_template%ROWTYPE; + mbe_row metabib.browse_entry%ROWTYPE; + mbe_id BIGINT; + b_skip_facet BOOL; + b_skip_display BOOL; + b_skip_browse BOOL; + b_skip_search BOOL; + value_prepped TEXT; + field_list INT[] := only_fields; + field_types TEXT[] := '{}'::TEXT[]; +BEGIN + + IF field_list = '{}'::INT[] THEN + SELECT ARRAY_AGG(id) INTO field_list FROM config.metabib_field; + END IF; + + 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; + SELECT COALESCE(NULLIF(skip_display, FALSE), EXISTS (SELECT enabled FROM config.internal_flag WHERE name = 'ingest.skip_display_indexing' AND enabled)) INTO b_skip_display; + 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; + 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; + + IF NOT b_skip_facet THEN field_types := field_types || '{facet}'; END IF; + IF NOT b_skip_display THEN field_types := field_types || '{display}'; END IF; + IF NOT b_skip_browse THEN field_types := field_types || '{browse}'; END IF; + IF NOT b_skip_search THEN field_types := field_types || '{search}'; END IF; + + PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled; + IF NOT FOUND THEN + IF NOT b_skip_search THEN + FOR fclass IN SELECT * FROM config.metabib_class LOOP + -- RAISE NOTICE 'Emptying out %', fclass.name; + EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id; + END LOOP; + END IF; + IF NOT b_skip_facet THEN + DELETE FROM metabib.facet_entry WHERE source = bib_id; + END IF; + IF NOT b_skip_display THEN + DELETE FROM metabib.display_entry WHERE source = bib_id; + END IF; + IF NOT b_skip_browse THEN + DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id; + END IF; + END IF; + + FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id, ' ', field_types, field_list ) LOOP + + -- don't store what has been normalized away + CONTINUE WHEN ind_data.value IS NULL; + + IF ind_data.field < 0 THEN + ind_data.field = -1 * ind_data.field; + END IF; + + IF ind_data.facet_field AND NOT b_skip_facet THEN + INSERT INTO metabib.facet_entry (field, source, value) + VALUES (ind_data.field, ind_data.source, ind_data.value); + END IF; + + IF ind_data.display_field AND NOT b_skip_display THEN + INSERT INTO metabib.display_entry (field, source, value) + VALUES (ind_data.field, ind_data.source, ind_data.value); + END IF; + + + IF ind_data.browse_field AND NOT b_skip_browse THEN + -- A caveat about this SELECT: this should take care of replacing + -- old mbe rows when data changes, but not if normalization (by + -- which I mean specifically the output of + -- evergreen.oils_tsearch2()) changes. It may or may not be + -- expensive to add a comparison of index_vector to index_vector + -- to the WHERE clause below. + + CONTINUE WHEN ind_data.sort_value IS NULL; + + value_prepped := metabib.browse_normalize(ind_data.value, ind_data.field); + SELECT INTO mbe_row * FROM metabib.browse_entry + WHERE value = value_prepped AND sort_value = ind_data.sort_value; + + IF FOUND THEN + mbe_id := mbe_row.id; + ELSE + INSERT INTO metabib.browse_entry + ( value, sort_value ) VALUES + ( value_prepped, ind_data.sort_value ); + + mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS); + END IF; + + INSERT INTO metabib.browse_entry_def_map (entry, def, source, authority) + VALUES (mbe_id, ind_data.field, ind_data.source, ind_data.authority); + END IF; + + IF ind_data.search_field AND NOT b_skip_search THEN + -- Avoid inserting duplicate rows + EXECUTE 'SELECT 1 FROM metabib.' || ind_data.field_class || + '_field_entry WHERE field = $1 AND source = $2 AND value = $3' + INTO mbe_id USING ind_data.field, ind_data.source, ind_data.value; + -- RAISE NOTICE 'Search for an already matching row returned %', mbe_id; + IF mbe_id IS NULL THEN + EXECUTE $$ + INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value) + VALUES ($$ || + quote_literal(ind_data.field) || $$, $$ || + quote_literal(ind_data.source) || $$, $$ || + quote_literal(ind_data.value) || + $$);$$; + END IF; + END IF; + + END LOOP; + + IF NOT b_skip_search THEN + PERFORM metabib.update_combined_index_vectors(bib_id); + END IF; + + RETURN; +END; +$func$ LANGUAGE PLPGSQL; + +-- AFTER UPDATE OR INSERT trigger for biblio.record_entry +CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$ +DECLARE + tmp_bool BOOL; +BEGIN + + IF NEW.deleted THEN -- If this bib is deleted + + PERFORM * FROM config.internal_flag WHERE + name = 'ingest.metarecord_mapping.preserve_on_delete' AND enabled; + + tmp_bool := FOUND; -- Just in case this is changed by some other statement + + PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint, TRUE, tmp_bool ); + + IF NOT tmp_bool THEN + -- One needs to keep these around to support searches + -- with the #deleted modifier, so one should turn on the named + -- internal flag for that functionality. + DELETE FROM metabib.record_attr_vector_list WHERE source = NEW.id; + END IF; + + DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible + DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items + -- DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs + -- KCLS + -- DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs + DELETE FROM metabib.browse_author_entry_def_map WHERE source = NEW.id; + DELETE FROM metabib.browse_series_entry_def_map WHERE source = NEW.id; + DELETE FROM metabib.browse_subject_entry_def_map WHERE source = NEW.id; + DELETE FROM metabib.browse_title_entry_def_map WHERE source = NEW.id; + DELETE FROM metabib.browse_call_number_entry_def_map WHERE source = NEW.id; + -- /KCLS + RETURN NEW; -- and we're done + END IF; + + IF TG_OP = 'UPDATE' THEN -- re-ingest? + PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled; + + IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change + RETURN NEW; + END IF; + END IF; + + -- Record authority linking + PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled; + IF NOT FOUND THEN + PERFORM biblio.map_authority_linking( NEW.id, NEW.marc ); + END IF; + + -- Flatten and insert the mfr data + PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled; + IF NOT FOUND THEN + PERFORM metabib.reingest_metabib_full_rec(NEW.id); + + -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields + PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled; + IF NOT FOUND THEN + PERFORM metabib.reingest_record_attributes(NEW.id, NULL, NEW.marc, TG_OP = 'INSERT' OR OLD.deleted); + END IF; + END IF; + + -- Gather and insert the field entry data + PERFORM metabib.reingest_metabib_field_entries(NEW.id); + + -- Located URI magic + PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled; + IF NOT FOUND THEN PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor ); END IF; + + -- (re)map metarecord-bib linking + IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag + PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled; + IF NOT FOUND THEN + PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint ); + END IF; + ELSE -- we're doing an update, and we're not deleted, remap + PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled; + IF NOT FOUND THEN + PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint ); + END IF; + END IF; + + RETURN NEW; +END; +$func$ LANGUAGE PLPGSQL; + + +-- SELECT evergreen.upgrade_deps_block_check('1074', :eg_version); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO config.internal_flag (name, enabled) + VALUES ('ingest.skip_display_indexing', FALSE); + +-- Adds seed data to replace (for now) values from the 'mvr' class + +UPDATE config.metabib_field SET display_field = TRUE WHERE id IN (6, 8, 16, 18); + +INSERT INTO config.metabib_field ( id, field_class, name, label, + format, xpath, display_field, display_xpath ) VALUES + (37, 'author', 'creator', oils_i18n_gettext(37, 'All Creators', 'cmf', 'label'), + 'mods32', $$//mods32:mods/mods32:name[mods32:role/mods32:roleTerm[text()='creator']]$$, + TRUE, $$//*[local-name()='namePart']$$ ); -- /* to fool vim */; + +-- 'author' field +UPDATE config.metabib_field SET display_xpath = + $$//*[local-name()='namePart']$$ -- /* to fool vim */ + WHERE id = 8; + +INSERT INTO config.display_field_map (name, field, multi) VALUES + ('title', 6, FALSE), + ('author', 8, FALSE), + ('creators', 37, TRUE), + ('subject', 16, TRUE), + ('isbn', 18, TRUE) +; +END IF; END $INSERT$; + + +-- SELECT evergreen.upgrade_deps_block_check('1075', :eg_version); + +CREATE OR REPLACE FUNCTION evergreen.vandelay_import_item_imported_as_inh_fkey() RETURNS TRIGGER AS $f$ +BEGIN + IF NEW.imported_as IS NULL THEN + RETURN NEW; + END IF; + PERFORM 1 FROM asset.copy WHERE id = NEW.imported_as; + IF NOT FOUND THEN + RAISE foreign_key_violation USING MESSAGE = FORMAT( + $$Referenced asset.copy id not found, imported_as:%s$$, NEW.imported_as + ); + END IF; + RETURN NEW; +END; +$f$ LANGUAGE PLPGSQL VOLATILE COST 50; + +-- SELECT evergreen.upgrade_deps_block_check('1077', :eg_version); -- csharp/gmcharlt + +-- if the "public" version of this function exists, drop it to prevent confusion/trouble + +-- drop triggers that depend on this first +DROP TRIGGER IF EXISTS c_maintain_control_numbers ON biblio.record_entry; +DROP TRIGGER IF EXISTS c_maintain_control_numbers ON serial.record_entry; +DROP TRIGGER IF EXISTS c_maintain_control_numbers ON authority.record_entry; + +DROP FUNCTION IF EXISTS public.maintain_control_numbers(); + +-- create the function within the "evergreen" schema + +CREATE OR REPLACE FUNCTION evergreen.maintain_control_numbers() RETURNS TRIGGER AS $func$ +use strict; +use MARC::Record; +use MARC::File::XML (BinaryEncoding => 'UTF-8'); +use MARC::Charset; +use Encode; +use Unicode::Normalize; + +MARC::Charset->assume_unicode(1); + +my $record = MARC::Record->new_from_xml($_TD->{new}{marc}); +my $schema = $_TD->{table_schema}; +my $rec_id = $_TD->{new}{id}; + +# Short-circuit if maintaining control numbers per MARC21 spec is not enabled +my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'"); +if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') { + return; +} + +# Get the control number identifier from an OU setting based on $_TD->{new}{owner} +my $ou_cni = 'EVRGRN'; + +my $owner; +if ($schema eq 'serial') { + $owner = $_TD->{new}{owning_lib}; +} else { + # are.owner and bre.owner can be null, so fall back to the consortial setting + $owner = $_TD->{new}{owner} || 1; +} + +my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)"); +if ($ous_rv->{processed}) { + $ou_cni = $ous_rv->{rows}[0]->{value}; + $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting" +} else { + # Fall back to the shortname of the OU if there was no OU setting + $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner"); + if ($ous_rv->{processed}) { + $ou_cni = $ous_rv->{rows}[0]->{shortname}; + } +} + +my ($create, $munge) = (0, 0); + +my @scns = $record->field('035'); + +foreach my $id_field ('001', '003') { + my $spec_value; + my @controls = $record->field($id_field); + + if ($id_field eq '001') { + $spec_value = $rec_id; + } else { + $spec_value = $ou_cni; + } + + # Create the 001/003 if none exist + if (scalar(@controls) == 1) { + # Only one field; check to see if we need to munge it + unless (grep $_->data() eq $spec_value, @controls) { + $munge = 1; + } + } else { + # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match + foreach my $control (@controls) { + $record->delete_field($control); + } + $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value)); + $create = 1; + } +} + +my $cn = $record->field('001')->data(); +# Special handling of OCLC numbers, often found in records that lack 003 +if ($cn =~ /^o(c[nm]|n)\d/) { + $cn =~ s/^o(c[nm]|n)0*(\d+)/$2/; + $record->field('003')->data('OCoLC'); + $create = 0; +} + +# Now, if we need to munge the 001, we will first push the existing 001/003 +# into the 035; but if the record did not have one (and one only) 001 and 003 +# to begin with, skip this process +if ($munge and not $create) { + + my $scn = "(" . $record->field('003')->data() . ")" . $cn; + + # Do not create duplicate 035 fields + unless (grep $_->subfield('a') eq $scn, @scns) { + $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn)); + } +} + +# Set the 001/003 and update the MARC +if ($create or $munge) { + $record->field('001')->data($rec_id); + $record->field('003')->data($ou_cni); + + my $xml = $record->as_xml_record(); + $xml =~ s/\n//sgo; + $xml =~ s/^<\?xml.+\?\s*>//go; + $xml =~ s/>\s+entityize() + # to avoid having to set PERL5LIB for PostgreSQL as well + + $xml = NFC($xml); + + # Convert raw ampersands to entities + $xml =~ s/&(?!\S+;)/&/gso; + + # Convert Unicode characters to entities + $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe; + + $xml =~ s/[\x00-\x1f]//go; + $_TD->{new}{marc} = $xml; + + return "MODIFY"; +} + +return; +$func$ LANGUAGE PLPERLU; + +-- re-create the triggers +CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_control_numbers(); +CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_control_numbers(); +CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_control_numbers(); + +-- INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.1', :eg_version); +-- PERFORM evergreen.upgrade_deps_block_check('1078', :eg_version); -- csharp/bshum/gmcharlt + +/* KCLS ALREADY HAS +DO $INSERT$ +BEGIN + IF evergreen.insert_on_deploy() THEN +INSERT INTO config.billing_type (id, name, owner) + SELECT 7, 'Damaged Item', 1 + WHERE NOT EXISTS (SELECT 1 FROM config.billing_type WHERE name = 'Damaged Item'); + +INSERT INTO config.billing_type (id, name, owner) + SELECT 8, 'Damaged Item Processing Fee', 1 + WHERE NOT EXISTS (SELECT 1 FROM config.billing_type WHERE name = 'Damaged Item Processing Fee'); + +INSERT INTO config.billing_type (id, name, owner) + SELECT 9, 'Notification Fee', 1 + WHERE NOT EXISTS (SELECT 1 FROM config.billing_type WHERE name = 'Notification Fee'); + END IF; +END $INSERT$; +*/ + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.2', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1079', :eg_version); -- rhamby/cesardv/gmcharlt +-- KCLS ALREADY HAS - FUNC UPDATE WON'T HURT, MIGHT HELP + +CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$ +DECLARE + moved_objects INT := 0; + source_cn asset.call_number%ROWTYPE; + target_cn asset.call_number%ROWTYPE; + metarec metabib.metarecord%ROWTYPE; + hold action.hold_request%ROWTYPE; + ser_rec serial.record_entry%ROWTYPE; + ser_sub serial.subscription%ROWTYPE; + acq_lineitem acq.lineitem%ROWTYPE; + acq_request acq.user_request%ROWTYPE; + booking booking.resource_type%ROWTYPE; + source_part biblio.monograph_part%ROWTYPE; + target_part biblio.monograph_part%ROWTYPE; + multi_home biblio.peer_bib_copy_map%ROWTYPE; + uri_count INT := 0; + counter INT := 0; + uri_datafield TEXT; + uri_text TEXT := ''; +BEGIN + + -- move any 856 entries on records that have at least one MARC-mapped URI entry + SELECT INTO uri_count COUNT(*) + FROM asset.uri_call_number_map m + JOIN asset.call_number cn ON (m.call_number = cn.id) + WHERE cn.record = source_record; + + IF uri_count > 0 THEN + + -- This returns more nodes than you might expect: + -- 7 instead of 1 for an 856 with $u $y $9 + SELECT COUNT(*) INTO counter + FROM oils_xpath_table( + 'id', + 'marc', + 'biblio.record_entry', + '//*[@tag="856"]', + 'id=' || source_record + ) as t(i int,c text); + + FOR i IN 1 .. counter LOOP + SELECT '' || + STRING_AGG( + '' || + regexp_replace( + regexp_replace( + regexp_replace(data,'&','&','g'), + '>', '>', 'g' + ), + '<', '<', 'g' + ) || '', '' + ) || '' INTO uri_datafield + FROM oils_xpath_table( + 'id', + 'marc', + 'biblio.record_entry', + '//*[@tag="856"][position()=' || i || ']/@ind1|' || + '//*[@tag="856"][position()=' || i || ']/@ind2|' || + '//*[@tag="856"][position()=' || i || ']/*/@code|' || + '//*[@tag="856"][position()=' || i || ']/*[@code]', + 'id=' || source_record + ) as t(id int,ind1 text, ind2 text,subfield text,data text); + + -- As most of the results will be NULL, protect against NULLifying + -- the valid content that we do generate + uri_text := uri_text || COALESCE(uri_datafield, ''); + END LOOP; + + IF uri_text <> '' THEN + UPDATE biblio.record_entry + SET marc = regexp_replace(marc,'(]*record>)', uri_text || E'\\1') + WHERE id = target_record; + END IF; + + END IF; + + -- Find and move metarecords to the target record + SELECT INTO metarec * + FROM metabib.metarecord + WHERE master_record = source_record; + + IF FOUND THEN + UPDATE metabib.metarecord + SET master_record = target_record, + mods = NULL + WHERE id = metarec.id; + + moved_objects := moved_objects + 1; + END IF; + + -- Find call numbers attached to the source ... + FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP + + SELECT INTO target_cn * + FROM asset.call_number + WHERE label = source_cn.label + AND prefix = source_cn.prefix + AND suffix = source_cn.suffix + AND owning_lib = source_cn.owning_lib + AND record = target_record + AND NOT deleted; + + -- ... and if there's a conflicting one on the target ... + IF FOUND THEN + + -- ... move the copies to that, and ... + UPDATE asset.copy + SET call_number = target_cn.id + WHERE call_number = source_cn.id; + + -- ... move V holds to the move-target call number + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP + + UPDATE action.hold_request + SET target = target_cn.id + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + UPDATE asset.call_number SET deleted = TRUE WHERE id = source_cn.id; + + -- ... if not ... + ELSE + -- ... just move the call number to the target record + UPDATE asset.call_number + SET record = target_record + WHERE id = source_cn.id; + END IF; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find T holds targeting the source record ... + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP + + -- ... and move them to the target record + UPDATE action.hold_request + SET target = target_record + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find serial records targeting the source record ... + FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP + -- ... and move them to the target record + UPDATE serial.record_entry + SET record = target_record + WHERE id = ser_rec.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find serial subscriptions targeting the source record ... + FOR ser_sub IN SELECT * FROM serial.subscription WHERE record_entry = source_record LOOP + -- ... and move them to the target record + UPDATE serial.subscription + SET record_entry = target_record + WHERE id = ser_sub.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find booking resource types targeting the source record ... + FOR booking IN SELECT * FROM booking.resource_type WHERE record = source_record LOOP + -- ... and move them to the target record + UPDATE booking.resource_type + SET record = target_record + WHERE id = booking.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find acq lineitems targeting the source record ... + FOR acq_lineitem IN SELECT * FROM acq.lineitem WHERE eg_bib_id = source_record LOOP + -- ... and move them to the target record + UPDATE acq.lineitem + SET eg_bib_id = target_record + WHERE id = acq_lineitem.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find acq user purchase requests targeting the source record ... + FOR acq_request IN SELECT * FROM acq.user_request WHERE eg_bib = source_record LOOP + -- ... and move them to the target record + UPDATE acq.user_request + SET eg_bib = target_record + WHERE id = acq_request.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find parts attached to the source ... + FOR source_part IN SELECT * FROM biblio.monograph_part WHERE record = source_record LOOP + + SELECT INTO target_part * + FROM biblio.monograph_part + WHERE label = source_part.label + AND record = target_record; + + -- ... and if there's a conflicting one on the target ... + IF FOUND THEN + + -- ... move the copy-part maps to that, and ... + UPDATE asset.copy_part_map + SET part = target_part.id + WHERE part = source_part.id; + + -- ... move P holds to the move-target part + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_part.id AND hold_type = 'P' LOOP + + UPDATE action.hold_request + SET target = target_part.id + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- ... if not ... + ELSE + -- ... just move the part to the target record + UPDATE biblio.monograph_part + SET record = target_record + WHERE id = source_part.id; + END IF; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find multi_home items attached to the source ... + FOR multi_home IN SELECT * FROM biblio.peer_bib_copy_map WHERE peer_record = source_record LOOP + -- ... and move them to the target record + UPDATE biblio.peer_bib_copy_map + SET peer_record = target_record + WHERE id = multi_home.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- And delete mappings where the item's home bib was merged with the peer bib + DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = ( + SELECT (SELECT record FROM asset.call_number WHERE id = call_number) + FROM asset.copy WHERE id = target_copy + ); + + -- Finally, "delete" the source record + DELETE FROM biblio.record_entry WHERE id = source_record; + + -- That's all, folks! + RETURN moved_objects; +END; +$func$ LANGUAGE plpgsql; + + +--SELECT evergreen.upgrade_deps_block_check('1080', :eg_version); -- miker/jboyer/gmcharlt + +CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$ +DECLARE + ocn asset.call_number%ROWTYPE; + ncn asset.call_number%ROWTYPE; + cid BIGINT; +BEGIN + + IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN -- Only needs ON INSERT OR DELETE, so handle separately + IF TG_OP = 'INSERT' THEN + INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES ( + NEW.peer_record, + NEW.target_copy, + asset.calculate_copy_visibility_attribute_set(NEW.target_copy) + ); + + RETURN NEW; + ELSIF TG_OP = 'DELETE' THEN + DELETE FROM asset.copy_vis_attr_cache + WHERE record = OLD.peer_record AND target_copy = OLD.target_copy; + + RETURN OLD; + END IF; + END IF; + + IF TG_OP = 'INSERT' THEN -- Handles ON INSERT. ON UPDATE is below. + IF TG_TABLE_NAME IN ('copy', 'unit') THEN + SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number; + INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES ( + ncn.record, + NEW.id, + asset.calculate_copy_visibility_attribute_set(NEW.id) + ); + ELSIF TG_TABLE_NAME = 'record_entry' THEN + NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id); + END IF; + + RETURN NEW; + END IF; + + -- handle items first, since with circulation activity + -- their statuses change frequently + IF TG_TABLE_NAME IN ('copy', 'unit') THEN -- This handles ON UPDATE OR DELETE. ON INSERT above + + IF TG_OP = 'DELETE' THEN -- Shouldn't get here, normally + DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id; + RETURN OLD; + END IF; + + SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number; + + IF OLD.deleted <> NEW.deleted THEN + IF NEW.deleted THEN + DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id; + ELSE + INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES ( + ncn.record, + NEW.id, + asset.calculate_copy_visibility_attribute_set(NEW.id) + ); + END IF; + + RETURN NEW; + ELSIF OLD.call_number <> NEW.call_number THEN + SELECT * INTO ocn FROM asset.call_number cn WHERE id = OLD.call_number; + + IF ncn.record <> ocn.record THEN + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(ncn.record) + WHERE id = ocn.record; + + -- We have to use a record-specific WHERE clause + -- to avoid modifying the entries for peer-bib copies. + UPDATE asset.copy_vis_attr_cache + SET target_copy = NEW.id, + record = ncn.record + WHERE target_copy = OLD.id + AND record = ocn.record; + END IF; + END IF; + + IF OLD.location <> NEW.location OR + OLD.status <> NEW.status OR + OLD.opac_visible <> NEW.opac_visible OR + OLD.circ_lib <> NEW.circ_lib + THEN + -- Any of these could change visibility, but + -- we'll save some queries and not try to calculate + -- the change directly. We want to update peer-bib + -- entries in this case, unlike above. + UPDATE asset.copy_vis_attr_cache + SET target_copy = NEW.id, + vis_attr_vector = asset.calculate_copy_visibility_attribute_set(NEW.id) + WHERE target_copy = OLD.id; + + END IF; + + ELSIF TG_TABLE_NAME = 'call_number' THEN -- Only ON UPDATE. Copy handler will deal with ON INSERT OR DELETE. + + IF OLD.record <> NEW.record THEN + IF NEW.label = '##URI##' THEN + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record) + WHERE id = OLD.record; + + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(NEW.record) + WHERE id = NEW.record; + END IF; + + UPDATE asset.copy_vis_attr_cache + SET record = NEW.record, + vis_attr_vector = asset.calculate_copy_visibility_attribute_set(target_copy) + WHERE target_copy IN (SELECT id FROM asset.copy WHERE call_number = NEW.id) + AND record = OLD.record; + + ELSIF OLD.owning_lib <> NEW.owning_lib THEN + UPDATE asset.copy_vis_attr_cache + SET vis_attr_vector = asset.calculate_copy_visibility_attribute_set(target_copy) + WHERE target_copy IN (SELECT id FROM asset.copy WHERE call_number = NEW.id) + AND record = NEW.record; + + IF NEW.label = '##URI##' THEN + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record) + WHERE id = OLD.record; + END IF; + END IF; + + ELSIF TG_TABLE_NAME = 'record_entry' THEN -- Only handles ON UPDATE OR DELETE + + IF TG_OP = 'DELETE' THEN -- Shouldn't get here, normally + DELETE FROM asset.copy_vis_attr_cache WHERE record = OLD.id; + RETURN OLD; + ELSIF OLD.source <> NEW.source THEN + NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id); + END IF; + + END IF; + + RETURN NEW; +END; +$func$ LANGUAGE PLPGSQL; + + +--SELECT evergreen.upgrade_deps_block_check('1081', :eg_version); -- jboyer/gmcharlt + +DROP TRIGGER IF EXISTS inherit_copy_bucket_item_target_copy_fkey ON container.copy_bucket_item; +DROP TRIGGER IF EXISTS inherit_import_item_imported_as_fkey ON vandelay.import_item; +DROP TRIGGER IF EXISTS inherit_asset_copy_note_copy_fkey ON asset.copy_note; +DROP TRIGGER IF EXISTS inherit_asset_copy_tag_copy_map_copy_fkey ON asset.copy_tag_copy_map; + +CREATE CONSTRAINT TRIGGER inherit_copy_bucket_item_target_copy_fkey + AFTER UPDATE OR INSERT ON container.copy_bucket_item + DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.container_copy_bucket_item_target_copy_inh_fkey(); +CREATE CONSTRAINT TRIGGER inherit_import_item_imported_as_fkey + AFTER UPDATE OR INSERT ON vandelay.import_item + DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.vandelay_import_item_imported_as_inh_fkey(); +CREATE CONSTRAINT TRIGGER inherit_asset_copy_note_copy_fkey + AFTER UPDATE OR INSERT ON asset.copy_note + DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_copy_note_owning_copy_inh_fkey(); +CREATE CONSTRAINT TRIGGER inherit_asset_copy_tag_copy_map_copy_fkey + AFTER UPDATE OR INSERT ON asset.copy_tag_copy_map + DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_copy_tag_copy_map_copy_inh_fkey(); + + + +--SELECT evergreen.upgrade_deps_block_check('1082', :eg_version); -- jboyer/gmcharlt + +DELETE FROM asset.copy_vis_attr_cache WHERE target_copy IN (SELECT id FROM asset.copy WHERE deleted); + +-- Evergreen DB patch XXXX.schema.qualify_unaccent_refs.sql +-- +-- LP#1671150 Fix unaccent() function call in evergreen.unaccent_and_squash() +-- + + +-- check whether patch can be applied +--SELECT evergreen.upgrade_deps_block_check('1083', :eg_version); + +/* KCLS ALREADY HAS +CREATE OR REPLACE FUNCTION evergreen.unaccent_and_squash ( IN arg text) RETURNS text + IMMUTABLE STRICT AS $$ + BEGIN + RETURN evergreen.lowercase(public.unaccent('public.unaccent', regexp_replace(arg, '[\s[:punct:]]','','g'))); + END; +$$ LANGUAGE PLPGSQL; + +-- Drop indexes if present, so that we can re-create them +DROP INDEX IF EXISTS actor.actor_usr_first_given_name_unaccent_idx; +DROP INDEX IF EXISTS actor.actor_usr_second_given_name_unaccent_idx; +DROP INDEX IF EXISTS actor.actor_usr_family_name_unaccent_idx; +DROP INDEX IF EXISTS actor.actor_usr_usrname_unaccent_idx; + +-- Create (or re-create) indexes -- they may be missing if pg_restore failed to create +-- them due to the previously unqualified call to unaccent() +CREATE INDEX actor_usr_first_given_name_unaccent_idx ON actor.usr (evergreen.unaccent_and_squash(first_given_name)); +CREATE INDEX actor_usr_second_given_name_unaccent_idx ON actor.usr (evergreen.unaccent_and_squash(second_given_name)); +CREATE INDEX actor_usr_family_name_unaccent_idx ON actor.usr (evergreen.unaccent_and_squash(family_name)); +CREATE INDEX actor_usr_usrname_unaccent_idx ON actor.usr (evergreen.unaccent_and_squash(usrname)); +*/ + +--SELECT evergreen.upgrade_deps_block_check('1084', :eg_version); +DO $INSERT$ +BEGIN + IF evergreen.insert_on_deploy() THEN +INSERT INTO config.usr_setting_type (name, label, description, datatype) + VALUES ('webstaff.cat.copy.templates', 'Web Client Copy Editor Templates', + 'Web Client Copy Editor Templates', 'object'); + END IF; +END $INSERT$; + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.3', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1085', :eg_version); + +CREATE OR REPLACE FUNCTION asset.calculate_copy_visibility_attribute_set ( copy_id BIGINT ) RETURNS INT[] AS $f$ +DECLARE + copy_row asset.copy%ROWTYPE; + lgroup_map asset.copy_location_group_map%ROWTYPE; + attr_set INT[] := '{}'::INT[]; +BEGIN + SELECT * INTO copy_row FROM asset.copy WHERE id = copy_id; + + attr_set := attr_set || search.calculate_visibility_attribute(copy_row.opac_visible::INT, 'copy_flags'); + attr_set := attr_set || search.calculate_visibility_attribute(copy_row.circ_lib, 'circ_lib'); + attr_set := attr_set || search.calculate_visibility_attribute(copy_row.status, 'status'); + attr_set := attr_set || search.calculate_visibility_attribute(copy_row.location, 'location'); + + SELECT ARRAY_APPEND( + attr_set, + search.calculate_visibility_attribute(owning_lib, 'owning_lib') + ) INTO attr_set + FROM asset.call_number + WHERE id = copy_row.call_number; + + FOR lgroup_map IN SELECT * FROM asset.copy_location_group_map WHERE location = copy_row.location LOOP + attr_set := attr_set || search.calculate_visibility_attribute(lgroup_map.lgroup, 'location_group'); + END LOOP; + + RETURN attr_set; +END; +$f$ LANGUAGE PLPGSQL; + +DROP FUNCTION IF EXISTS biblio.calculate_bib_visibility_attribute_set ( BIGINT ); +CREATE OR REPLACE FUNCTION biblio.calculate_bib_visibility_attribute_set ( bib_id BIGINT, new_source INT DEFAULT NULL, force_source BOOL DEFAULT FALSE ) RETURNS INT[] AS $f$ +DECLARE + bib_row biblio.record_entry%ROWTYPE; + cn_row asset.call_number%ROWTYPE; + attr_set INT[] := '{}'::INT[]; +BEGIN + SELECT * INTO bib_row FROM biblio.record_entry WHERE id = bib_id; + + IF force_source THEN + IF new_source IS NOT NULL THEN + attr_set := attr_set || search.calculate_visibility_attribute(new_source, 'bib_source'); + END IF; + ELSIF bib_row.source IS NOT NULL THEN + attr_set := attr_set || search.calculate_visibility_attribute(bib_row.source, 'bib_source'); + END IF; + + FOR cn_row IN + SELECT * + FROM asset.call_number + WHERE record = bib_id + AND label = '##URI##' + AND NOT deleted + LOOP + attr_set := attr_set || search.calculate_visibility_attribute(cn_row.owning_lib, 'luri_org'); + END LOOP; + + RETURN attr_set; +END; +$f$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$ +DECLARE + ocn asset.call_number%ROWTYPE; + ncn asset.call_number%ROWTYPE; + cid BIGINT; + dobib BOOL; +BEGIN + + SELECT enabled = FALSE INTO dobib FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc'; + + IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN -- Only needs ON INSERT OR DELETE, so handle separately + IF TG_OP = 'INSERT' THEN + INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES ( + NEW.peer_record, + NEW.target_copy, + asset.calculate_copy_visibility_attribute_set(NEW.target_copy) + ); + + RETURN NEW; + ELSIF TG_OP = 'DELETE' THEN + DELETE FROM asset.copy_vis_attr_cache + WHERE record = OLD.peer_record AND target_copy = OLD.target_copy; + + RETURN OLD; + END IF; + END IF; + + IF TG_OP = 'INSERT' THEN -- Handles ON INSERT. ON UPDATE is below. + IF TG_TABLE_NAME IN ('copy', 'unit') THEN + SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number; + INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES ( + ncn.record, + NEW.id, + asset.calculate_copy_visibility_attribute_set(NEW.id) + ); + ELSIF TG_TABLE_NAME = 'record_entry' THEN + NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id, NEW.source, TRUE); + ELSIF TG_TABLE_NAME = 'call_number' AND NEW.label = '##URI##' AND dobib THEN -- New located URI + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(NEW.record) + WHERE id = NEW.record; + + END IF; + + RETURN NEW; + END IF; + + -- handle items first, since with circulation activity + -- their statuses change frequently + IF TG_TABLE_NAME IN ('copy', 'unit') THEN -- This handles ON UPDATE OR DELETE. ON INSERT above + + IF TG_OP = 'DELETE' THEN -- Shouldn't get here, normally + DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id; + RETURN OLD; + END IF; + + SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number; + + IF OLD.deleted <> NEW.deleted THEN + IF NEW.deleted THEN + DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id; + ELSE + INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES ( + ncn.record, + NEW.id, + asset.calculate_copy_visibility_attribute_set(NEW.id) + ); + END IF; + + RETURN NEW; + ELSIF OLD.location <> NEW.location OR + OLD.status <> NEW.status OR + OLD.opac_visible <> NEW.opac_visible OR + OLD.circ_lib <> NEW.circ_lib OR + OLD.call_number <> NEW.call_number + THEN + IF OLD.call_number <> NEW.call_number THEN -- Special check since it's more expensive than the next branch + SELECT * INTO ocn FROM asset.call_number cn WHERE id = OLD.call_number; + + IF ncn.record <> ocn.record THEN + -- We have to use a record-specific WHERE clause + -- to avoid modifying the entries for peer-bib copies. + UPDATE asset.copy_vis_attr_cache + SET target_copy = NEW.id, + record = ncn.record + WHERE target_copy = OLD.id + AND record = ocn.record; + + END IF; + ELSE + -- Any of these could change visibility, but + -- we'll save some queries and not try to calculate + -- the change directly. We want to update peer-bib + -- entries in this case, unlike above. + UPDATE asset.copy_vis_attr_cache + SET target_copy = NEW.id, + vis_attr_vector = asset.calculate_copy_visibility_attribute_set(NEW.id) + WHERE target_copy = OLD.id; + END IF; + END IF; + + ELSIF TG_TABLE_NAME = 'call_number' THEN + + IF TG_OP = 'DELETE' AND OLD.label = '##URI##' AND dobib THEN -- really deleted located URI, if the delete protection rule is disabled... + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record) + WHERE id = OLD.record; + RETURN OLD; + END IF; + + IF OLD.label = '##URI##' AND dobib THEN -- Located URI + IF OLD.deleted <> NEW.deleted OR OLD.record <> NEW.record OR OLD.owning_lib <> NEW.owning_lib THEN + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(NEW.record) + WHERE id = NEW.record; + + IF OLD.record <> NEW.record THEN -- maybe on merge? + UPDATE biblio.record_entry + SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record) + WHERE id = OLD.record; + END IF; + END IF; + + ELSIF OLD.record <> NEW.record OR OLD.owning_lib <> NEW.owning_lib THEN + UPDATE asset.copy_vis_attr_cache + SET record = NEW.record, + vis_attr_vector = asset.calculate_copy_visibility_attribute_set(target_copy) + WHERE target_copy IN (SELECT id FROM asset.copy WHERE call_number = NEW.id) + AND record = OLD.record; + + END IF; + + ELSIF TG_TABLE_NAME = 'record_entry' AND OLD.source IS DISTINCT FROM NEW.source THEN -- Only handles ON UPDATE, INSERT above + NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id, NEW.source, TRUE); + END IF; + + RETURN NEW; +END; +$func$ LANGUAGE PLPGSQL; + +/* created again later in the data file +DROP TRIGGER IF EXISTS z_opac_vis_mat_view_tgr ON asset.call_number; +DROP TRIGGER IF EXISTS z_opac_vis_mat_view_tgr ON biblio.record_entry; +CREATE TRIGGER z_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE OR DELETE ON asset.call_number FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); +CREATE TRIGGER z_opac_vis_mat_view_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility(); +*/ + +--SELECT evergreen.upgrade_deps_block_check('1086', :eg_version); + +CREATE OR REPLACE FUNCTION asset.location_group_default () RETURNS TEXT AS $f$ + SELECT '!()'::TEXT; -- For now, as there's no way to cause a location group to hide all copies. +/* + SELECT '!(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, 'location_group')),'|') || ')' + FROM asset.copy_location_group + WHERE NOT opac_visible; +*/ +$f$ LANGUAGE SQL IMMUTABLE; + + + +--SELECT evergreen.upgrade_deps_block_check('1087', :eg_version); + +CREATE OR REPLACE FUNCTION asset.patron_default_visibility_mask () RETURNS TABLE (b_attrs TEXT, c_attrs TEXT) AS $f$ +DECLARE + copy_flags TEXT; -- "c" attr + + owning_lib TEXT; -- "c" attr + circ_lib TEXT; -- "c" attr + status TEXT; -- "c" attr + location TEXT; -- "c" attr + location_group TEXT; -- "c" attr + + luri_org TEXT; -- "b" attr + bib_sources TEXT; -- "b" attr + + bib_tests TEXT := ''; +BEGIN + copy_flags := asset.all_visible_flags(); -- Will always have at least one + + owning_lib := NULLIF(asset.owning_lib_default(),'!()'); + + circ_lib := NULLIF(asset.circ_lib_default(),'!()'); + status := NULLIF(asset.status_default(),'!()'); + location := NULLIF(asset.location_default(),'!()'); + location_group := NULLIF(asset.location_group_default(),'!()'); + + -- LURIs will be handled at the perl layer directly + -- luri_org := NULLIF(asset.luri_org_default(),'!()'); + bib_sources := NULLIF(asset.bib_source_default(),'()'); + + + IF luri_org IS NOT NULL AND bib_sources IS NOT NULL THEN + bib_tests := '('||ARRAY_TO_STRING( ARRAY[luri_org,bib_sources], '|')||')&('||luri_org||')&'; + ELSIF luri_org IS NOT NULL THEN + bib_tests := luri_org || '&'; + ELSIF bib_sources IS NOT NULL THEN + bib_tests := bib_sources || '|'; + END IF; + + RETURN QUERY SELECT bib_tests, + '('||ARRAY_TO_STRING( + ARRAY[copy_flags,owning_lib,circ_lib,status,location,location_group]::TEXT[], + '&' + )||')'; +END; +$f$ LANGUAGE PLPGSQL STABLE ROWS 1; + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.4', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1088', :eg_version); + +CREATE OR REPLACE FUNCTION metabib.staged_browse(query text, fields integer[], context_org integer, context_locations integer[], staff boolean, browse_superpage_size integer, count_up_from_zero boolean, result_limit integer, next_pivot_pos integer) + RETURNS SETOF metabib.flat_browse_entry_appearance +AS $f$ +DECLARE + curs REFCURSOR; + rec RECORD; + qpfts_query TEXT; + aqpfts_query TEXT; + afields INT[]; + bfields INT[]; + result_row metabib.flat_browse_entry_appearance%ROWTYPE; + results_skipped INT := 0; + row_counter INT := 0; + row_number INT; + slice_start INT; + slice_end INT; + full_end INT; + all_records BIGINT[]; + all_brecords BIGINT[]; + all_arecords BIGINT[]; + superpage_of_records BIGINT[]; + superpage_size INT; + c_tests TEXT := ''; + b_tests TEXT := ''; + c_orgs INT[]; + unauthorized_entry RECORD; +BEGIN + IF count_up_from_zero THEN + row_number := 0; + ELSE + row_number := -1; + END IF; + + IF NOT staff THEN + SELECT x.c_attrs, x.b_attrs INTO c_tests, b_tests FROM asset.patron_default_visibility_mask() x; + END IF; + + -- b_tests supplies its own query_int operator, c_tests does not + IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF; + + SELECT ARRAY_AGG(id) INTO c_orgs FROM actor.org_unit_descendants(context_org); + + c_tests := c_tests || search.calculate_visibility_attribute_test('circ_lib',c_orgs) + || '&' || search.calculate_visibility_attribute_test('owning_lib',c_orgs); + + PERFORM 1 FROM config.internal_flag WHERE enabled AND name = 'opac.located_uri.act_as_copy'; + IF FOUND THEN + b_tests := b_tests || search.calculate_visibility_attribute_test( + 'luri_org', + (SELECT ARRAY_AGG(id) FROM actor.org_unit_full_path(context_org) x) + ); + ELSE + b_tests := b_tests || search.calculate_visibility_attribute_test( + 'luri_org', + (SELECT ARRAY_AGG(id) FROM actor.org_unit_ancestors(context_org) x) + ); + END IF; + + IF context_locations THEN + IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF; + c_tests := c_tests || search.calculate_visibility_attribute_test('location',context_locations); + END IF; + + OPEN curs NO SCROLL FOR EXECUTE query; + + LOOP + FETCH curs INTO rec; + IF NOT FOUND THEN + IF result_row.pivot_point IS NOT NULL THEN + RETURN NEXT result_row; + END IF; + RETURN; + END IF; + + --Is unauthorized? + SELECT INTO unauthorized_entry * + FROM metabib.browse_entry_simple_heading_map mbeshm + INNER JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id ) + INNER JOIN authority.control_set_authority_field acsaf ON ( acsaf.id = ash.atag ) + JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field) + WHERE mbeshm.entry = rec.id + AND ahf.heading_purpose = 'variant'; + + -- Gather aggregate data based on the MBE row we're looking at now, authority axis + IF (unauthorized_entry.record IS NOT NULL) THEN + --unauthorized term belongs to an auth linked to a bib? + SELECT INTO all_arecords, result_row.sees, afields + ARRAY_AGG(DISTINCT abl.bib), + STRING_AGG(DISTINCT abl.authority::TEXT, $$,$$), + ARRAY_AGG(DISTINCT map.metabib_field) + FROM authority.bib_linking abl + INNER JOIN authority.control_set_auth_field_metabib_field_map_refs map ON ( + map.authority_field = unauthorized_entry.atag + AND map.metabib_field = ANY(fields) + ) + WHERE abl.authority = unauthorized_entry.record; + ELSE + --do usual procedure + SELECT INTO all_arecords, result_row.sees, afields + ARRAY_AGG(DISTINCT abl.bib), -- bibs to check for visibility + STRING_AGG(DISTINCT aal.source::TEXT, $$,$$), -- authority record ids + ARRAY_AGG(DISTINCT map.metabib_field) -- authority-tag-linked CMF rows + + FROM metabib.browse_entry_simple_heading_map mbeshm + JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id ) + JOIN authority.authority_linking aal ON ( ash.record = aal.source ) + JOIN authority.bib_linking abl ON ( aal.target = abl.authority ) + JOIN authority.control_set_auth_field_metabib_field_map_refs map ON ( + ash.atag = map.authority_field + AND map.metabib_field = ANY(fields) + ) + JOIN authority.control_set_authority_field acsaf ON ( + map.authority_field = acsaf.id + ) + JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field) + WHERE mbeshm.entry = rec.id + AND ahf.heading_purpose = 'variant'; + + END IF; + + -- Gather aggregate data based on the MBE row we're looking at now, bib axis + SELECT INTO all_brecords, result_row.authorities, bfields + ARRAY_AGG(DISTINCT source), + STRING_AGG(DISTINCT authority::TEXT, $$,$$), + ARRAY_AGG(DISTINCT def) + FROM metabib.browse_entry_def_map + WHERE entry = rec.id + AND def = ANY(fields); + + SELECT INTO result_row.fields STRING_AGG(DISTINCT x::TEXT, $$,$$) FROM UNNEST(afields || bfields) x; + + result_row.sources := 0; + result_row.asources := 0; + + -- Bib-linked vis checking + IF ARRAY_UPPER(all_brecords,1) IS NOT NULL THEN + + SELECT INTO result_row.sources COUNT(DISTINCT b.id) + FROM biblio.record_entry b + JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id) + WHERE b.id = ANY(all_brecords[1:browse_superpage_size]) + AND ( + acvac.vis_attr_vector @@ c_tests::query_int + OR b.vis_attr_vector @@ b_tests::query_int + ); + + result_row.accurate := TRUE; + + END IF; + + -- Authority-linked vis checking + IF ARRAY_UPPER(all_arecords,1) IS NOT NULL THEN + + SELECT INTO result_row.asources COUNT(DISTINCT b.id) + FROM biblio.record_entry b + JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id) + WHERE b.id = ANY(all_arecords[1:browse_superpage_size]) + AND ( + acvac.vis_attr_vector @@ c_tests::query_int + OR b.vis_attr_vector @@ b_tests::query_int + ); + + result_row.aaccurate := TRUE; + + END IF; + + IF result_row.sources > 0 OR result_row.asources > 0 THEN + + -- The function that calls this function needs row_number in order + -- to correctly order results from two different runs of this + -- functions. + result_row.row_number := row_number; + + -- Now, if row_counter is still less than limit, return a row. If + -- not, but it is less than next_pivot_pos, continue on without + -- returning actual result rows until we find + -- that next pivot, and return it. + + IF row_counter < result_limit THEN + result_row.browse_entry := rec.id; + result_row.value := rec.value; + + RETURN NEXT result_row; + ELSE + result_row.browse_entry := NULL; + result_row.authorities := NULL; + result_row.fields := NULL; + result_row.value := NULL; + result_row.sources := NULL; + result_row.sees := NULL; + result_row.accurate := NULL; + result_row.aaccurate := NULL; + result_row.pivot_point := rec.id; + + IF row_counter >= next_pivot_pos THEN + RETURN NEXT result_row; + RETURN; + END IF; + END IF; + + IF count_up_from_zero THEN + row_number := row_number + 1; + ELSE + row_number := row_number - 1; + END IF; + + -- row_counter is different from row_number. + -- It simply counts up from zero so that we know when + -- we've reached our limit. + row_counter := row_counter + 1; + END IF; + END LOOP; +END; +$f$ LANGUAGE plpgsql ROWS 10; + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.6', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1107', :eg_version); + +CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$ +DECLARE + moved_objects INT := 0; + source_cn asset.call_number%ROWTYPE; + target_cn asset.call_number%ROWTYPE; + metarec metabib.metarecord%ROWTYPE; + hold action.hold_request%ROWTYPE; + ser_rec serial.record_entry%ROWTYPE; + ser_sub serial.subscription%ROWTYPE; + acq_lineitem acq.lineitem%ROWTYPE; + acq_request acq.user_request%ROWTYPE; + booking booking.resource_type%ROWTYPE; + source_part biblio.monograph_part%ROWTYPE; + target_part biblio.monograph_part%ROWTYPE; + multi_home biblio.peer_bib_copy_map%ROWTYPE; + uri_count INT := 0; + counter INT := 0; + uri_datafield TEXT; + uri_text TEXT := ''; +BEGIN + + -- move any 856 entries on records that have at least one MARC-mapped URI entry + SELECT INTO uri_count COUNT(*) + FROM asset.uri_call_number_map m + JOIN asset.call_number cn ON (m.call_number = cn.id) + WHERE cn.record = source_record; + + IF uri_count > 0 THEN + + -- This returns more nodes than you might expect: + -- 7 instead of 1 for an 856 with $u $y $9 + SELECT COUNT(*) INTO counter + FROM oils_xpath_table( + 'id', + 'marc', + 'biblio.record_entry', + '//*[@tag="856"]', + 'id=' || source_record + ) as t(i int,c text); + + FOR i IN 1 .. counter LOOP + SELECT '' || + STRING_AGG( + '' || + regexp_replace( + regexp_replace( + regexp_replace(data,'&','&','g'), + '>', '>', 'g' + ), + '<', '<', 'g' + ) || '', '' + ) || '' INTO uri_datafield + FROM oils_xpath_table( + 'id', + 'marc', + 'biblio.record_entry', + '//*[@tag="856"][position()=' || i || ']/@ind1|' || + '//*[@tag="856"][position()=' || i || ']/@ind2|' || + '//*[@tag="856"][position()=' || i || ']/*/@code|' || + '//*[@tag="856"][position()=' || i || ']/*[@code]', + 'id=' || source_record + ) as t(id int,ind1 text, ind2 text,subfield text,data text); + + -- As most of the results will be NULL, protect against NULLifying + -- the valid content that we do generate + uri_text := uri_text || COALESCE(uri_datafield, ''); + END LOOP; + + IF uri_text <> '' THEN + UPDATE biblio.record_entry + SET marc = regexp_replace(marc,'(]*record>)', uri_text || E'\\1') + WHERE id = target_record; + END IF; + + END IF; + + -- Find and move metarecords to the target record + SELECT INTO metarec * + FROM metabib.metarecord + WHERE master_record = source_record; + + IF FOUND THEN + UPDATE metabib.metarecord + SET master_record = target_record, + mods = NULL + WHERE id = metarec.id; + + moved_objects := moved_objects + 1; + END IF; + + -- Find call numbers attached to the source ... + FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP + + SELECT INTO target_cn * + FROM asset.call_number + WHERE label = source_cn.label + AND prefix = source_cn.prefix + AND suffix = source_cn.suffix + AND owning_lib = source_cn.owning_lib + AND record = target_record + AND NOT deleted; + + -- ... and if there's a conflicting one on the target ... + IF FOUND THEN + + -- ... move the copies to that, and ... + UPDATE asset.copy + SET call_number = target_cn.id + WHERE call_number = source_cn.id; + + -- ... move V holds to the move-target call number + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP + + UPDATE action.hold_request + SET target = target_cn.id + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + UPDATE asset.call_number SET deleted = TRUE WHERE id = source_cn.id; + + -- ... if not ... + ELSE + -- ... just move the call number to the target record + UPDATE asset.call_number + SET record = target_record + WHERE id = source_cn.id; + END IF; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find T holds targeting the source record ... + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP + + -- ... and move them to the target record + UPDATE action.hold_request + SET target = target_record + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find serial records targeting the source record ... + FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP + -- ... and move them to the target record + UPDATE serial.record_entry + SET record = target_record + WHERE id = ser_rec.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find serial subscriptions targeting the source record ... + FOR ser_sub IN SELECT * FROM serial.subscription WHERE record_entry = source_record LOOP + -- ... and move them to the target record + UPDATE serial.subscription + SET record_entry = target_record + WHERE id = ser_sub.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find booking resource types targeting the source record ... + FOR booking IN SELECT * FROM booking.resource_type WHERE record = source_record LOOP + -- ... and move them to the target record + UPDATE booking.resource_type + SET record = target_record + WHERE id = booking.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find acq lineitems targeting the source record ... + FOR acq_lineitem IN SELECT * FROM acq.lineitem WHERE eg_bib_id = source_record LOOP + -- ... and move them to the target record + UPDATE acq.lineitem + SET eg_bib_id = target_record + WHERE id = acq_lineitem.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find acq user purchase requests targeting the source record ... + FOR acq_request IN SELECT * FROM acq.user_request WHERE eg_bib = source_record LOOP + -- ... and move them to the target record + UPDATE acq.user_request + SET eg_bib = target_record + WHERE id = acq_request.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find parts attached to the source ... + FOR source_part IN SELECT * FROM biblio.monograph_part WHERE record = source_record LOOP + + SELECT INTO target_part * + FROM biblio.monograph_part + WHERE label = source_part.label + AND record = target_record; + + -- ... and if there's a conflicting one on the target ... + IF FOUND THEN + + -- ... move the copy-part maps to that, and ... + UPDATE asset.copy_part_map + SET part = target_part.id + WHERE part = source_part.id; + + -- ... move P holds to the move-target part + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_part.id AND hold_type = 'P' LOOP + + UPDATE action.hold_request + SET target = target_part.id + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- ... if not ... + ELSE + -- ... just move the part to the target record + UPDATE biblio.monograph_part + SET record = target_record + WHERE id = source_part.id; + END IF; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find multi_home items attached to the source ... + FOR multi_home IN SELECT * FROM biblio.peer_bib_copy_map WHERE peer_record = source_record LOOP + -- ... and move them to the target record + UPDATE biblio.peer_bib_copy_map + SET peer_record = target_record + WHERE id = multi_home.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- And delete mappings where the item's home bib was merged with the peer bib + DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = ( + SELECT (SELECT record FROM asset.call_number WHERE id = call_number) + FROM asset.copy WHERE id = target_copy + ); + + -- replace book bag entries of source_record with target_record + UPDATE container.biblio_record_entry_bucket_item + SET target_biblio_record_entry = target_record + WHERE bucket IN (SELECT id FROM container.biblio_record_entry_bucket WHERE btype = 'bookbag') + AND target_biblio_record_entry = source_record; + + -- Finally, "delete" the source record + DELETE FROM biblio.record_entry WHERE id = source_record; + + -- That's all, folks! + RETURN moved_objects; +END; +$func$ LANGUAGE plpgsql; + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.0', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1089', :eg_version); + +-- Add the circ.holds.max_duplicate_holds org. unit setting type. +DO $INSERT$ +BEGIN + IF evergreen.insert_on_deploy() THEN + +INSERT into config.org_unit_setting_type +( name, grp, label, description, datatype, fm_class ) +VALUES +( 'circ.holds.max_duplicate_holds', 'holds', + oils_i18n_gettext( + 'circ.holds.max_duplicate_holds', + 'Maximum number of duplicate holds allowed.', + 'coust', 'label'), + oils_i18n_gettext( + 'circ.holds.max_duplicate_holds', + 'Maximum number of duplicate title or metarecord holds allowed per patron.', + 'coust', 'description'), + 'integer', null ); + END IF; +END $INSERT$; + + +--SELECT evergreen.upgrade_deps_block_check('1090', :eg_version); + +/* KCLS ALREAD HAS THIS +ALTER TABLE biblio.record_entry + ADD COLUMN merge_date TIMESTAMP WITH TIME ZONE, + ADD COLUMN merged_to BIGINT REFERENCES biblio.record_entry(id); +*/ + +CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$ +DECLARE + moved_objects INT := 0; + source_cn asset.call_number%ROWTYPE; + target_cn asset.call_number%ROWTYPE; + metarec metabib.metarecord%ROWTYPE; + hold action.hold_request%ROWTYPE; + ser_rec serial.record_entry%ROWTYPE; + ser_sub serial.subscription%ROWTYPE; + acq_lineitem acq.lineitem%ROWTYPE; + acq_request acq.user_request%ROWTYPE; + booking booking.resource_type%ROWTYPE; + source_part biblio.monograph_part%ROWTYPE; + target_part biblio.monograph_part%ROWTYPE; + multi_home biblio.peer_bib_copy_map%ROWTYPE; + uri_count INT := 0; + counter INT := 0; + uri_datafield TEXT; + uri_text TEXT := ''; +BEGIN + + -- move any 856 entries on records that have at least one MARC-mapped URI entry + SELECT INTO uri_count COUNT(*) + FROM asset.uri_call_number_map m + JOIN asset.call_number cn ON (m.call_number = cn.id) + WHERE cn.record = source_record; + + IF uri_count > 0 THEN + + -- This returns more nodes than you might expect: + -- 7 instead of 1 for an 856 with $u $y $9 + SELECT COUNT(*) INTO counter + FROM oils_xpath_table( + 'id', + 'marc', + 'biblio.record_entry', + '//*[@tag="856"]', + 'id=' || source_record + ) as t(i int,c text); + + FOR i IN 1 .. counter LOOP + SELECT '' || + STRING_AGG( + '' || + regexp_replace( + regexp_replace( + regexp_replace(data,'&','&','g'), + '>', '>', 'g' + ), + '<', '<', 'g' + ) || '', '' + ) || '' INTO uri_datafield + FROM oils_xpath_table( + 'id', + 'marc', + 'biblio.record_entry', + '//*[@tag="856"][position()=' || i || ']/@ind1|' || + '//*[@tag="856"][position()=' || i || ']/@ind2|' || + '//*[@tag="856"][position()=' || i || ']/*/@code|' || + '//*[@tag="856"][position()=' || i || ']/*[@code]', + 'id=' || source_record + ) as t(id int,ind1 text, ind2 text,subfield text,data text); + + -- As most of the results will be NULL, protect against NULLifying + -- the valid content that we do generate + uri_text := uri_text || COALESCE(uri_datafield, ''); + END LOOP; + + IF uri_text <> '' THEN + UPDATE biblio.record_entry + SET marc = regexp_replace(marc,'(]*record>)', uri_text || E'\\1') + WHERE id = target_record; + END IF; + + END IF; + + -- Find and move metarecords to the target record + SELECT INTO metarec * + FROM metabib.metarecord + WHERE master_record = source_record; + + IF FOUND THEN + UPDATE metabib.metarecord + SET master_record = target_record, + mods = NULL + WHERE id = metarec.id; + + moved_objects := moved_objects + 1; + END IF; + + -- Find call numbers attached to the source ... + FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP + + SELECT INTO target_cn * + FROM asset.call_number + WHERE label = source_cn.label + AND prefix = source_cn.prefix + AND suffix = source_cn.suffix + AND owning_lib = source_cn.owning_lib + AND record = target_record + AND NOT deleted; + + -- ... and if there's a conflicting one on the target ... + IF FOUND THEN + + -- ... move the copies to that, and ... + UPDATE asset.copy + SET call_number = target_cn.id + WHERE call_number = source_cn.id; + + -- ... move V holds to the move-target call number + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP + + UPDATE action.hold_request + SET target = target_cn.id + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + UPDATE asset.call_number SET deleted = TRUE WHERE id = source_cn.id; + + -- ... if not ... + ELSE + -- ... just move the call number to the target record + UPDATE asset.call_number + SET record = target_record + WHERE id = source_cn.id; + END IF; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find T holds targeting the source record ... + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP + + -- ... and move them to the target record + UPDATE action.hold_request + SET target = target_record + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find serial records targeting the source record ... + FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP + -- ... and move them to the target record + UPDATE serial.record_entry + SET record = target_record + WHERE id = ser_rec.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find serial subscriptions targeting the source record ... + FOR ser_sub IN SELECT * FROM serial.subscription WHERE record_entry = source_record LOOP + -- ... and move them to the target record + UPDATE serial.subscription + SET record_entry = target_record + WHERE id = ser_sub.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find booking resource types targeting the source record ... + FOR booking IN SELECT * FROM booking.resource_type WHERE record = source_record LOOP + -- ... and move them to the target record + UPDATE booking.resource_type + SET record = target_record + WHERE id = booking.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find acq lineitems targeting the source record ... + FOR acq_lineitem IN SELECT * FROM acq.lineitem WHERE eg_bib_id = source_record LOOP + -- ... and move them to the target record + UPDATE acq.lineitem + SET eg_bib_id = target_record + WHERE id = acq_lineitem.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find acq user purchase requests targeting the source record ... + FOR acq_request IN SELECT * FROM acq.user_request WHERE eg_bib = source_record LOOP + -- ... and move them to the target record + UPDATE acq.user_request + SET eg_bib = target_record + WHERE id = acq_request.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find parts attached to the source ... + FOR source_part IN SELECT * FROM biblio.monograph_part WHERE record = source_record LOOP + + SELECT INTO target_part * + FROM biblio.monograph_part + WHERE label = source_part.label + AND record = target_record; + + -- ... and if there's a conflicting one on the target ... + IF FOUND THEN + + -- ... move the copy-part maps to that, and ... + UPDATE asset.copy_part_map + SET part = target_part.id + WHERE part = source_part.id; + + -- ... move P holds to the move-target part + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_part.id AND hold_type = 'P' LOOP + + UPDATE action.hold_request + SET target = target_part.id + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- ... if not ... + ELSE + -- ... just move the part to the target record + UPDATE biblio.monograph_part + SET record = target_record + WHERE id = source_part.id; + END IF; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find multi_home items attached to the source ... + FOR multi_home IN SELECT * FROM biblio.peer_bib_copy_map WHERE peer_record = source_record LOOP + -- ... and move them to the target record + UPDATE biblio.peer_bib_copy_map + SET peer_record = target_record + WHERE id = multi_home.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- And delete mappings where the item's home bib was merged with the peer bib + DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = ( + SELECT (SELECT record FROM asset.call_number WHERE id = call_number) + FROM asset.copy WHERE id = target_copy + ); + + -- Apply merge tracking + UPDATE biblio.record_entry + SET merge_date = NOW() WHERE id = target_record; + + UPDATE biblio.record_entry + SET merge_date = NOW(), merged_to = target_record + WHERE id = source_record; + + -- Finally, "delete" the source record + DELETE FROM biblio.record_entry WHERE id = source_record; + + -- That's all, folks! + RETURN moved_objects; +END; +$func$ LANGUAGE plpgsql; + + + +--SELECT evergreen.upgrade_deps_block_check('1091', :eg_version); + +ALTER TABLE acq.funding_source DROP CONSTRAINT funding_source_code_key; +ALTER TABLE acq.funding_source ALTER COLUMN code SET NOT NULL; +ALTER TABLE acq.funding_source ADD CONSTRAINT funding_source_code_once_per_owner UNIQUE (code,owner); + + +--SELECT evergreen.upgrade_deps_block_check('1092', :eg_version); + +CREATE OR REPLACE FUNCTION metabib.reingest_record_attributes (rid BIGINT, pattr_list TEXT[] DEFAULT NULL, prmarc TEXT DEFAULT NULL, rdeleted BOOL DEFAULT TRUE) RETURNS VOID AS $func$ +DECLARE + transformed_xml TEXT; + rmarc TEXT := prmarc; + tmp_val TEXT; + prev_xfrm TEXT; + normalizer RECORD; + xfrm config.xml_transform%ROWTYPE; + attr_vector INT[] := '{}'::INT[]; + attr_vector_tmp INT[]; + attr_list TEXT[] := pattr_list; + attr_value TEXT[]; + norm_attr_value TEXT[]; + tmp_xml TEXT; + tmp_array TEXT[]; + attr_def config.record_attr_definition%ROWTYPE; + ccvm_row config.coded_value_map%ROWTYPE; + jump_past BOOL; +BEGIN + + IF attr_list IS NULL OR rdeleted THEN -- need to do the full dance on INSERT or undelete + SELECT ARRAY_AGG(name) INTO attr_list FROM config.record_attr_definition + WHERE ( + tag IS NOT NULL OR + fixed_field IS NOT NULL OR + xpath IS NOT NULL OR + phys_char_sf IS NOT NULL OR + composite + ) AND ( + filter OR sorter + ); + END IF; + + IF rmarc IS NULL THEN + SELECT marc INTO rmarc FROM biblio.record_entry WHERE id = rid; + END IF; + + FOR attr_def IN SELECT * FROM config.record_attr_definition WHERE NOT composite AND name = ANY( attr_list ) ORDER BY format LOOP + + jump_past := FALSE; -- This gets set when we are non-multi and have found something + attr_value := '{}'::TEXT[]; + norm_attr_value := '{}'::TEXT[]; + attr_vector_tmp := '{}'::INT[]; + + SELECT * INTO ccvm_row FROM config.coded_value_map c WHERE c.ctype = attr_def.name LIMIT 1; + + IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection + SELECT ARRAY_AGG(value) INTO attr_value + FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x + WHERE record = rid + AND tag LIKE attr_def.tag + AND CASE + WHEN attr_def.sf_list IS NOT NULL + THEN POSITION(subfield IN attr_def.sf_list) > 0 + ELSE TRUE + END + GROUP BY tag + ORDER BY tag; + + IF NOT attr_def.multi THEN + attr_value := ARRAY[ARRAY_TO_STRING(attr_value, COALESCE(attr_def.joiner,' '))]; + jump_past := TRUE; + END IF; + END IF; + + IF NOT jump_past AND attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field + attr_value := attr_value || vandelay.marc21_extract_fixed_field_list(rmarc, attr_def.fixed_field); + + IF NOT attr_def.multi THEN + attr_value := ARRAY[attr_value[1]]; + jump_past := TRUE; + END IF; + END IF; + + IF NOT jump_past AND attr_def.xpath IS NOT NULL THEN -- and xpath expression + + SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format; + + -- See if we can skip the XSLT ... it's expensive + IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN + -- Can't skip the transform + IF xfrm.xslt <> '---' THEN + transformed_xml := oils_xslt_process(rmarc,xfrm.xslt); + ELSE + transformed_xml := rmarc; + END IF; + + prev_xfrm := xfrm.name; + END IF; + + IF xfrm.name IS NULL THEN + -- just grab the marcxml (empty) transform + SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1; + prev_xfrm := xfrm.name; + END IF; + + FOR tmp_xml IN SELECT UNNEST(oils_xpath(attr_def.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]])) LOOP + tmp_val := oils_xpath_string( + '//*', + tmp_xml, + COALESCE(attr_def.joiner,' '), + ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] + ); + IF tmp_val IS NOT NULL AND BTRIM(tmp_val) <> '' THEN + attr_value := attr_value || tmp_val; + EXIT WHEN NOT attr_def.multi; + END IF; + END LOOP; + END IF; + + IF NOT jump_past AND attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map + SELECT ARRAY_AGG(m.value) INTO tmp_array + FROM vandelay.marc21_physical_characteristics(rmarc) v + LEFT JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value) + WHERE v.subfield = attr_def.phys_char_sf AND (m.value IS NOT NULL AND BTRIM(m.value) <> '') + AND ( ccvm_row.id IS NULL OR ( ccvm_row.id IS NOT NULL AND v.id IS NOT NULL) ); + + attr_value := attr_value || tmp_array; + + IF NOT attr_def.multi THEN + attr_value := ARRAY[attr_value[1]]; + END IF; + + END IF; + + -- apply index normalizers to attr_value + FOR tmp_val IN SELECT value FROM UNNEST(attr_value) x(value) LOOP + FOR normalizer IN + SELECT n.func AS func, + n.param_count AS param_count, + m.params AS params + FROM config.index_normalizer n + JOIN config.record_attr_index_norm_map m ON (m.norm = n.id) + WHERE attr = attr_def.name + ORDER BY m.pos LOOP + EXECUTE 'SELECT ' || normalizer.func || '(' || + COALESCE( quote_literal( tmp_val ), 'NULL' ) || + CASE + WHEN normalizer.param_count > 0 + THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'') + ELSE '' + END || + ')' INTO tmp_val; + + END LOOP; + IF tmp_val IS NOT NULL AND tmp_val <> '' THEN + -- note that a string that contains only blanks + -- is a valid value for some attributes + norm_attr_value := norm_attr_value || tmp_val; + END IF; + END LOOP; + + IF attr_def.filter THEN + -- Create unknown uncontrolled values and find the IDs of the values + IF ccvm_row.id IS NULL THEN + FOR tmp_val IN SELECT value FROM UNNEST(norm_attr_value) x(value) LOOP + IF tmp_val IS NOT NULL AND BTRIM(tmp_val) <> '' THEN + BEGIN -- use subtransaction to isolate unique constraint violations + INSERT INTO metabib.uncontrolled_record_attr_value ( attr, value ) VALUES ( attr_def.name, tmp_val ); + EXCEPTION WHEN unique_violation THEN END; + END IF; + END LOOP; + + SELECT ARRAY_AGG(id) INTO attr_vector_tmp FROM metabib.uncontrolled_record_attr_value WHERE attr = attr_def.name AND value = ANY( norm_attr_value ); + ELSE + SELECT ARRAY_AGG(id) INTO attr_vector_tmp FROM config.coded_value_map WHERE ctype = attr_def.name AND code = ANY( norm_attr_value ); + END IF; + + -- Add the new value to the vector + attr_vector := attr_vector || attr_vector_tmp; + END IF; + + IF attr_def.sorter THEN + DELETE FROM metabib.record_sorter WHERE source = rid AND attr = attr_def.name; + IF norm_attr_value[1] IS NOT NULL THEN + INSERT INTO metabib.record_sorter (source, attr, value) VALUES (rid, attr_def.name, norm_attr_value[1]); + END IF; + END IF; + + END LOOP; + +/* We may need to rewrite the vlist to contain + the intersection of new values for requested + attrs and old values for ignored attrs. To + do this, we take the old attr vlist and + subtract any values that are valid for the + requested attrs, and then add back the new + set of attr values. */ + + IF ARRAY_LENGTH(pattr_list, 1) > 0 THEN + SELECT vlist INTO attr_vector_tmp FROM metabib.record_attr_vector_list WHERE source = rid; + SELECT attr_vector_tmp - ARRAY_AGG(id::INT) INTO attr_vector_tmp FROM metabib.full_attr_id_map WHERE attr = ANY (pattr_list); + attr_vector := attr_vector || attr_vector_tmp; + END IF; + + -- On to composite attributes, now that the record attrs have been pulled. Processed in name order, so later composite + -- attributes can depend on earlier ones. + PERFORM metabib.compile_composite_attr_cache_init(); + FOR attr_def IN SELECT * FROM config.record_attr_definition WHERE composite AND name = ANY( attr_list ) ORDER BY name LOOP + + FOR ccvm_row IN SELECT * FROM config.coded_value_map c WHERE c.ctype = attr_def.name ORDER BY value LOOP + + tmp_val := metabib.compile_composite_attr( ccvm_row.id ); + CONTINUE WHEN tmp_val IS NULL OR tmp_val = ''; -- nothing to do + + IF attr_def.filter THEN + IF attr_vector @@ tmp_val::query_int THEN + attr_vector = attr_vector + intset(ccvm_row.id); + EXIT WHEN NOT attr_def.multi; + END IF; + END IF; + + IF attr_def.sorter THEN + IF attr_vector @@ tmp_val THEN + DELETE FROM metabib.record_sorter WHERE source = rid AND attr = attr_def.name; + INSERT INTO metabib.record_sorter (source, attr, value) VALUES (rid, attr_def.name, ccvm_row.code); + END IF; + END IF; + + END LOOP; + + END LOOP; + + IF ARRAY_LENGTH(attr_vector, 1) > 0 THEN + IF rdeleted THEN -- initial insert OR revivication + DELETE FROM metabib.record_attr_vector_list WHERE source = rid; + INSERT INTO metabib.record_attr_vector_list (source, vlist) VALUES (rid, attr_vector); + ELSE + UPDATE metabib.record_attr_vector_list SET vlist = attr_vector WHERE source = rid; + END IF; + END IF; + +END; + +$func$ LANGUAGE PLPGSQL; + +--SELECT evergreen.upgrade_deps_block_check('1095', :eg_version); + +CREATE OR REPLACE FUNCTION asset.copy_state (cid BIGINT) RETURNS TEXT AS $$ +DECLARE + last_circ_stop TEXT; + the_copy asset.copy%ROWTYPE; +BEGIN + + SELECT * INTO the_copy FROM asset.copy WHERE id = cid; + IF NOT FOUND THEN RETURN NULL; END IF; + + IF the_copy.status = 3 THEN -- Lost + RETURN 'LOST'; + ELSIF the_copy.status = 4 THEN -- Missing + RETURN 'MISSING'; + ELSIF the_copy.status = 14 THEN -- Damaged + RETURN 'DAMAGED'; + ELSIF the_copy.status = 17 THEN -- Lost and paid + RETURN 'LOST_AND_PAID'; + END IF; + + SELECT stop_fines INTO last_circ_stop + FROM action.circulation + WHERE target_copy = cid + ORDER BY xact_start DESC LIMIT 1; + + IF FOUND THEN + IF last_circ_stop IN ( + 'CLAIMSNEVERCHECKEDOUT', + 'CLAIMSRETURNED', + 'LONGOVERDUE' + ) THEN + RETURN last_circ_stop; + END IF; + END IF; + + RETURN 'NORMAL'; +END; +$$ LANGUAGE PLPGSQL; + +CREATE TYPE config.copy_alert_type_state AS ENUM ( + 'NORMAL', + 'LOST', + 'LOST_AND_PAID', + 'MISSING', + 'DAMAGED', + 'CLAIMSRETURNED', + 'LONGOVERDUE', + 'CLAIMSNEVERCHECKEDOUT' +); + +CREATE TYPE config.copy_alert_type_event AS ENUM ( + 'CHECKIN', + 'CHECKOUT' +); + +CREATE TABLE config.copy_alert_type ( + id serial primary key, -- reserve 1-100 for system + scope_org int not null references actor.org_unit (id) on delete cascade, + active bool not null default true, + name text not null unique, + state config.copy_alert_type_state, + event config.copy_alert_type_event, + in_renew bool, + invert_location bool not null default false, + at_circ bool, + at_owning bool, + next_status int[] +); +SELECT SETVAL('config.copy_alert_type_id_seq'::TEXT, 100); + +CREATE OR REPLACE FUNCTION evergreen.asset_copy_alert_copy_inh_fkey() RETURNS TRIGGER AS $f$ +BEGIN + PERFORM 1 FROM asset.copy WHERE id = NEW.copy; + IF NOT FOUND THEN + RAISE foreign_key_violation USING MESSAGE = FORMAT( + $$Referenced asset.copy id not found, copy:%s$$, NEW.copy + ); + END IF; + RETURN NEW; +END; +$f$ LANGUAGE PLPGSQL VOLATILE COST 50; + +CREATE TABLE actor.copy_alert_suppress ( + id serial primary key, + org int not null references actor.org_unit (id) on delete cascade, + alert_type int not null references config.copy_alert_type (id) on delete cascade +); + +CREATE TABLE asset.copy_alert ( + id bigserial primary key, + alert_type int not null references config.copy_alert_type (id) on delete cascade, + copy bigint not null, + temp bool not null default false, + create_time timestamptz not null default now(), + create_staff bigint not null references actor.usr (id) on delete set null, + note text, + ack_time timestamptz, + ack_staff bigint references actor.usr (id) on delete set null +); + +CREATE CONSTRAINT TRIGGER inherit_asset_copy_alert_copy_fkey + AFTER UPDATE OR INSERT ON asset.copy_alert + DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_copy_alert_copy_inh_fkey(); + +CREATE VIEW asset.active_copy_alert AS + SELECT * + FROM asset.copy_alert + WHERE ack_time IS NULL; + + + +--SELECT evergreen.upgrade_deps_block_check('1096', :eg_version); + +DO $INSERT$ +BEGIN + IF evergreen.insert_on_deploy() THEN + +-- staff-usable alert types with no location awareness +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event, in_renew) +VALUES (1, 1, TRUE, 'Normal checkout', 'NORMAL', 'CHECKOUT', FALSE); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event, in_renew) +VALUES (2, 1, TRUE, 'Normal checkin', 'NORMAL', 'CHECKIN', FALSE); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event, in_renew) +VALUES (3, 1, FALSE, 'Normal renewal', 'NORMAL', 'CHECKIN', TRUE); + +-- copy alerts upon checkin or renewal of exceptional copy statuses are not active by +-- default; they're meant to be turned once a site is ready to fully +-- commit to using the webstaff client for circulation +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (4, 1, FALSE, 'Checkin of lost copy', 'LOST', 'CHECKIN'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (5, 1, FALSE, 'Checkin of missing copy', 'MISSING', 'CHECKIN'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (6, 1, FALSE, 'Checkin of lost-and-paid copy', 'LOST_AND_PAID', 'CHECKIN'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (7, 1, FALSE, 'Checkin of damaged copy', 'DAMAGED', 'CHECKIN'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (8, 1, FALSE, 'Checkin of claims-returned copy', 'CLAIMSRETURNED', 'CHECKIN'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (9, 1, FALSE, 'Checkin of long overdue copy', 'LONGOVERDUE', 'CHECKIN'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (10, 1, FALSE, 'Checkin of claims-never-checked-out copy', 'CLAIMSNEVERCHECKEDOUT', 'CHECKIN'); + +-- copy alerts upon checkout of exceptional copy statuses are not active by +-- default; they're meant to be turned once a site is ready to fully +-- commit to using the webstaff client for circulation +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (11, 1, FALSE, 'Checkout of lost copy', 'LOST', 'CHECKOUT'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (12, 1, FALSE, 'Checkout of missing copy', 'MISSING', 'CHECKOUT'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (13, 1, FALSE, 'Checkout of lost-and-paid copy', 'LOST_AND_PAID', 'CHECKOUT'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (14, 1, FALSE, 'Checkout of damaged copy', 'DAMAGED', 'CHECKOUT'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (15, 1, FALSE, 'Checkout of claims-returned copy', 'CLAIMSRETURNED', 'CHECKOUT'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (16, 1, FALSE, 'Checkout of long overdue copy', 'LONGOVERDUE', 'CHECKOUT'); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event) +VALUES (17, 1, FALSE, 'Checkout of claims-never-checked-out copy', 'CLAIMSNEVERCHECKEDOUT', 'CHECKOUT'); + +-- staff-usable alert types based on location +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event, in_renew, at_circ) +VALUES (18, 1, FALSE, 'Normal checkout at circ lib', 'NORMAL', 'CHECKOUT', FALSE, TRUE); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event, in_renew, at_circ) +VALUES (19, 1, FALSE, 'Normal checkin at circ lib', 'NORMAL', 'CHECKIN', FALSE, TRUE); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event, in_renew, at_circ) +VALUES (20, 1, FALSE, 'Normal renewal at circ lib', 'NORMAL', 'CHECKIN', TRUE, TRUE); + +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event, in_renew, at_owning) +VALUES (21, 1, FALSE, 'Normal checkout at owning lib', 'NORMAL', 'CHECKOUT', FALSE, TRUE); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event, in_renew, at_owning) +VALUES (22, 1, FALSE, 'Normal checkin at owning lib', 'NORMAL', 'CHECKIN', FALSE, TRUE); +INSERT INTO config.copy_alert_type (id, scope_org, active, name, state, event, in_renew, at_owning) +VALUES (23, 1, FALSE, 'Normal renewal at owning lib', 'NORMAL', 'CHECKIN', TRUE, TRUE); + + +--SELECT evergreen.upgrade_deps_block_check('1097', :eg_version); + +INSERT INTO config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES + ('circ.copy_alerts.forgive_fines_on_lost_checkin', + 'circ', + oils_i18n_gettext('circ.copy_alerts.forgive_fines_on_lost_checkin', + 'Forgive fines when checking out a lost item and copy alert is suppressed?', + 'coust', 'label'), + oils_i18n_gettext('circ.copy_alerts.forgive_fines_on_lost_checkin', + 'Controls whether fines are automatically forgiven when checking out an '|| + 'item that has been marked as lost, and the corresponding copy alert has been '|| + 'suppressed.', + 'coust', 'description'), + 'bool'); + +INSERT INTO config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES + ('circ.copy_alerts.forgive_fines_on_long_overdue_checkin', + 'circ', + oils_i18n_gettext('circ.copy_alerts.forgive_fines_on_long_overdue_checkin', + 'Forgive fines when checking out a long-overdue item and copy alert is suppressed?', + 'coust', 'label'), + oils_i18n_gettext('circ.copy_alerts.forgive_fines_on_lost_checkin', + 'Controls whether fines are automatically forgiven when checking out an '|| + 'item that has been marked as lost, and the corresponding copy alert has been '|| + 'suppressed.', + 'coust', 'description'), + 'bool'); + + END IF; +END $INSERT$; + +--SELECT evergreen.upgrade_deps_block_check('1098', :eg_version); + +\qecho Copying copy alert messages to normal checkout copy alerts... +INSERT INTO asset.copy_alert (alert_type, copy, note, create_staff) +SELECT 1, id, alert_message, 1 +FROM asset.copy +WHERE alert_message IS NOT NULL +AND alert_message <> ''; + +\qecho Copying copy alert messages to normal checkin copy alerts... +INSERT INTO asset.copy_alert (alert_type, copy, note, create_staff) +SELECT 2, id, alert_message, 1 +FROM asset.copy +WHERE alert_message IS NOT NULL +AND alert_message <> ''; + +\qecho Clearing legacy copy alert field; this may take a while +UPDATE asset.copy SET alert_message = NULL +WHERE alert_message IS NOT NULL; + + +--SELECT evergreen.upgrade_deps_block_check('1099', :eg_version); +-- moved update to data file + +--SELECT evergreen.upgrade_deps_block_check('1100', :eg_version); + +-- NEW config.metabib_field entries + +UPDATE config.metabib_field SET display_xpath = facet_xpath, display_field = TRUE WHERE id = 33; + + +DO $INSERT$ +BEGIN + IF evergreen.insert_on_deploy() THEN +INSERT INTO config.metabib_field (id, field_class, name, + label, xpath, display_field, search_field, browse_field) +VALUES ( + 38, 'identifier', 'edition', + oils_i18n_gettext(38, 'Edition', 'cmf', 'label'), + $$//mods33:mods/mods33:originInfo//mods33:edition[1]$$, + TRUE, TRUE, FALSE +); + +INSERT INTO config.metabib_field (id, field_class, name, + label, xpath, display_field, search_field, browse_field) +VALUES ( + 39, 'keyword', 'physical_description', + oils_i18n_gettext(39, 'Physical Descrption', 'cmf', 'label'), + $$(//mods33:mods/mods33:physicalDescription/mods33:form|//mods33:mods/mods33:physicalDescription/mods33:extent|//mods33:mods/mods33:physicalDescription/mods33:reformattingQuality|//mods33:mods/mods33:physicalDescription/mods33:internetMediaType|//mods33:mods/mods33:physicalDescription/mods33:digitalOrigin)$$, + TRUE, TRUE, FALSE +); + +INSERT INTO config.metabib_field (id, field_class, name, + label, xpath, display_field, search_field, browse_field) +VALUES ( + 40, 'identifier', 'publisher', + oils_i18n_gettext(40, 'Publisher', 'cmf', 'label'), + $$//mods33:mods/mods33:originInfo//mods33:publisher[1]$$, + TRUE, TRUE, FALSE +); + +INSERT INTO config.metabib_field (id, field_class, name, + label, xpath, display_field, search_field, browse_field) +VALUES ( + 41, 'keyword', 'abstract', + oils_i18n_gettext(41, 'Abstract', 'cmf', 'label'), + $$//mods33:mods/mods33:abstract$$, + TRUE, TRUE, FALSE +); + +INSERT INTO config.metabib_field (id, field_class, name, + label, xpath, display_field, search_field, browse_field) +VALUES ( + 42, 'keyword', 'toc', + oils_i18n_gettext(42, 'Table of Contents', 'cmf', 'label'), + $$//mods33:tableOfContents$$, + TRUE, TRUE, FALSE +); + +INSERT INTO config.metabib_field (id, field_class, name, + label, xpath, display_field, search_field, browse_field) +VALUES ( + 43, 'identifier', 'type_of_resource', + oils_i18n_gettext(43, 'Type of Resource', 'cmf', 'label'), + $$//mods33:mods/mods33:typeOfResource$$, + TRUE, FALSE, FALSE +); + +INSERT INTO config.metabib_field (id, field_class, name, + label, xpath, display_field, search_field, browse_field) +VALUES ( + 44, 'identifier', 'pubdate', + oils_i18n_gettext(44, 'Publication Date', 'cmf', 'label'), + $$//mods33:mods/mods33:originInfo//mods33:dateIssued[@encoding="marc"]|//mods33:mods/mods33:originInfo//mods33:dateIssued[1]$$, + TRUE, FALSE, FALSE +); + +INSERT INTO config.metabib_field (id, field_class, name, + label, xpath, display_field, search_field, browse_field) +VALUES ( + 46, 'keyword', 'bibliography', + oils_i18n_gettext(46, 'Bibliography', 'cmf', 'label'), + $$//mods33:note[@type='bibliography']$$, + TRUE, TRUE, FALSE +),( + 47, 'keyword', 'thesis', + oils_i18n_gettext(47, 'Thesis', 'cmf', 'label'), + $$//mods33:note[@type='thesis']$$, + TRUE, TRUE, FALSE +),( + 48, 'keyword', 'production_credits', + oils_i18n_gettext(48, 'Creation/Production Credits', 'cmf', 'label'), + $$//mods33:note[@type='creation/production credits']$$, + TRUE, TRUE, FALSE +),( + 49, 'keyword', 'performers', + oils_i18n_gettext(49, 'Performers', 'cmf', 'label'), + $$//mods33:note[@type='performers']$$, + TRUE, TRUE, FALSE +),( + 50, 'keyword', 'general_note', + oils_i18n_gettext(50, 'General Note', 'cmf', 'label'), + $$//mods33:note[not(@type)]$$, + TRUE, TRUE, FALSE +) +; + +INSERT INTO config.metabib_field (id, field_class, name, format, + label, xpath, display_xpath, display_field, search_field, browse_field) +VALUES ( + 51, 'author', 'first_author', 'mods32', + oils_i18n_gettext(51, 'Author', 'cmf', 'label'), + $$//mods32:mods/mods32:name[mods32:role/mods32:roleTerm[text()='creator']][1]$$, + $$//*[local-name()='namePart']$$, + TRUE, TRUE, FALSE +); + +INSERT INTO config.metabib_field (id, field_class, name, format, + label, xpath, display_xpath, display_field, search_field, browse_field) +VALUES ( + 52, 'identifier', 'origin_info', 'marcxml', + oils_i18n_gettext(52, 'Origin Info', 'cmf', 'label'), + $$//*[@tag='260']$$, + $$//*[local-name()='subfield' and contains('abc',@code)]$$, + TRUE, FALSE, FALSE +); + + +-- Modify existing config.metabib_field entries + +UPDATE config.metabib_field SET display_field = TRUE WHERE id IN ( + 1, -- seriestitle + 11, -- subject_geographic + 12, -- subject_name + 13, -- subject_temporal + 14, -- subject_topic + 19, -- ISSN + 20, -- UPC + 26 -- TCN +); + +-- Map display field names to config.metabib_field entries + +INSERT INTO config.display_field_map (name, field, multi) VALUES + ('series_title', 1, TRUE), + ('subject_geographic', 11, TRUE), + ('subject_name', 12, TRUE), + ('subject_temporal', 13, TRUE), + ('subject_topic', 14, TRUE), + ('issn', 19, TRUE), + ('upc', 20, TRUE), + ('tcn', 26, FALSE), + ('edition', 38, FALSE), + ('physical_description',39, TRUE), + ('genre', 33, TRUE), + ('bibliography', 46, TRUE), + ('thesis', 47, TRUE), + ('performers', 49, TRUE), + ('production_credits', 48, TRUE), + ('general_note', 50, TRUE), + ('publisher', 52, FALSE), + ('abstract', 41, FALSE), + ('toc', 42, FALSE), + ('type_of_resource', 43, FALSE), + ('pubdate', 44, FALSE) +; + +UPDATE config.display_field_map SET field = 51 WHERE name = 'author'; + + END IF; +END $INSERT$; + +-- Add a column to wide-display-entry per well-known field + +DROP VIEW IF EXISTS metabib.wide_display_entry; +CREATE VIEW metabib.wide_display_entry AS + SELECT + bre.id AS source, + COALESCE(mcde_title.value, 'null')::TEXT AS title, + COALESCE(mcde_author.value, 'null')::TEXT AS author, + COALESCE(mcde_subject_geographic.value, 'null')::TEXT AS subject_geographic, + COALESCE(mcde_subject_name.value, 'null')::TEXT AS subject_name, + COALESCE(mcde_subject_temporal.value, 'null')::TEXT AS subject_temporal, + COALESCE(mcde_subject_topic.value, 'null')::TEXT AS subject_topic, + COALESCE(mcde_creators.value, 'null')::TEXT AS creators, + COALESCE(mcde_isbn.value, 'null')::TEXT AS isbn, + COALESCE(mcde_issn.value, 'null')::TEXT AS issn, + COALESCE(mcde_upc.value, 'null')::TEXT AS upc, + COALESCE(mcde_tcn.value, 'null')::TEXT AS tcn, + COALESCE(mcde_edition.value, 'null')::TEXT AS edition, + COALESCE(mcde_physical_description.value, 'null')::TEXT AS physical_description, + COALESCE(mcde_publisher.value, 'null')::TEXT AS publisher, + COALESCE(mcde_series_title.value, 'null')::TEXT AS series_title, + COALESCE(mcde_abstract.value, 'null')::TEXT AS abstract, + COALESCE(mcde_toc.value, 'null')::TEXT AS toc, + COALESCE(mcde_pubdate.value, 'null')::TEXT AS pubdate, + COALESCE(mcde_type_of_resource.value, 'null')::TEXT AS type_of_resource + FROM biblio.record_entry bre + LEFT JOIN metabib.compressed_display_entry mcde_title + ON (bre.id = mcde_title.source AND mcde_title.name = 'title') + LEFT JOIN metabib.compressed_display_entry mcde_author + ON (bre.id = mcde_author.source AND mcde_author.name = 'author') + LEFT JOIN metabib.compressed_display_entry mcde_subject + ON (bre.id = mcde_subject.source AND mcde_subject.name = 'subject') + LEFT JOIN metabib.compressed_display_entry mcde_subject_geographic + ON (bre.id = mcde_subject_geographic.source + AND mcde_subject_geographic.name = 'subject_geographic') + LEFT JOIN metabib.compressed_display_entry mcde_subject_name + ON (bre.id = mcde_subject_name.source + AND mcde_subject_name.name = 'subject_name') + LEFT JOIN metabib.compressed_display_entry mcde_subject_temporal + ON (bre.id = mcde_subject_temporal.source + AND mcde_subject_temporal.name = 'subject_temporal') + LEFT JOIN metabib.compressed_display_entry mcde_subject_topic + ON (bre.id = mcde_subject_topic.source + AND mcde_subject_topic.name = 'subject_topic') + LEFT JOIN metabib.compressed_display_entry mcde_creators + ON (bre.id = mcde_creators.source AND mcde_creators.name = 'creators') + LEFT JOIN metabib.compressed_display_entry mcde_isbn + ON (bre.id = mcde_isbn.source AND mcde_isbn.name = 'isbn') + LEFT JOIN metabib.compressed_display_entry mcde_issn + ON (bre.id = mcde_issn.source AND mcde_issn.name = 'issn') + LEFT JOIN metabib.compressed_display_entry mcde_upc + ON (bre.id = mcde_upc.source AND mcde_upc.name = 'upc') + LEFT JOIN metabib.compressed_display_entry mcde_tcn + ON (bre.id = mcde_tcn.source AND mcde_tcn.name = 'tcn') + LEFT JOIN metabib.compressed_display_entry mcde_edition + ON (bre.id = mcde_edition.source AND mcde_edition.name = 'edition') + LEFT JOIN metabib.compressed_display_entry mcde_physical_description + ON (bre.id = mcde_physical_description.source + AND mcde_physical_description.name = 'physical_description') + LEFT JOIN metabib.compressed_display_entry mcde_publisher + ON (bre.id = mcde_publisher.source AND mcde_publisher.name = 'publisher') + LEFT JOIN metabib.compressed_display_entry mcde_series_title + ON (bre.id = mcde_series_title.source AND mcde_series_title.name = 'series_title') + LEFT JOIN metabib.compressed_display_entry mcde_abstract + ON (bre.id = mcde_abstract.source AND mcde_abstract.name = 'abstract') + LEFT JOIN metabib.compressed_display_entry mcde_toc + ON (bre.id = mcde_toc.source AND mcde_toc.name = 'toc') + LEFT JOIN metabib.compressed_display_entry mcde_pubdate + ON (bre.id = mcde_pubdate.source AND mcde_pubdate.name = 'pubdate') + LEFT JOIN metabib.compressed_display_entry mcde_type_of_resource + ON (bre.id = mcde_type_of_resource.source + AND mcde_type_of_resource.name = 'type_of_resource') +; + +CREATE OR REPLACE VIEW reporter.old_super_simple_record AS +SELECT r.id, + r.fingerprint, + r.quality, + r.tcn_source, + r.tcn_value, + evergreen.oils_json_to_text(d.title) AS title, + evergreen.oils_json_to_text(d.author) AS author, + evergreen.oils_json_to_text(d.publisher) AS publisher, + evergreen.oils_json_to_text(d.pubdate) AS pubdate, + CASE WHEN d.isbn = 'null' + THEN NULL + ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.isbn::JSON))) + END AS isbn, + CASE WHEN d.issn = 'null' + THEN NULL + ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.issn::JSON))) + END AS issn + FROM biblio.record_entry r + JOIN metabib.wide_display_entry d ON (r.id = d.source); + + + +--SELECT evergreen.upgrade_deps_block_check('1101', :eg_version); + +ALTER TABLE config.metabib_field ALTER COLUMN xpath DROP NOT NULL; + +CREATE TABLE config.metabib_field_virtual_map ( + id SERIAL PRIMARY KEY, + real INT NOT NULL REFERENCES config.metabib_field (id), + virtual INT NOT NULL REFERENCES config.metabib_field (id), + weight INT NOT NULL DEFAULT 1 +); +COMMENT ON TABLE config.metabib_field_virtual_map IS $$ +Maps between real (physically extracted) index definitions +and virtual (target sync, no required extraction of its own) +index definitions. + +The virtual side may not extract any data of its own, but +will collect data from all of the real fields. This reduces +extraction (ingest) overhead by eliminating duplcated extraction, +and allows for searching across novel combinations of fields, such +as names used as either subjects or authors. By preserving this +mapping rather than defining duplicate extractions, information +about the originating, "real" index definitions can be used +in interesting ways, such as highlighting in search results. +$$; + +CREATE OR REPLACE VIEW metabib.combined_all_field_entry AS + SELECT * FROM metabib.combined_title_field_entry + UNION ALL + SELECT * FROM metabib.combined_author_field_entry + UNION ALL + SELECT * FROM metabib.combined_subject_field_entry + UNION ALL + SELECT * FROM metabib.combined_keyword_field_entry + UNION ALL + SELECT * FROM metabib.combined_identifier_field_entry + UNION ALL + SELECT * FROM metabib.combined_series_field_entry; + + +CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( + rid BIGINT, + default_joiner TEXT, + field_types TEXT[], + only_fields INT[] +) RETURNS SETOF metabib.field_entry_template AS $func$ +DECLARE + bib biblio.record_entry%ROWTYPE; + idx config.metabib_field%ROWTYPE; + xfrm config.xml_transform%ROWTYPE; + prev_xfrm TEXT; + transformed_xml TEXT; + xml_node TEXT; + xml_node_list TEXT[]; + facet_text TEXT; + display_text TEXT; + browse_text TEXT; + sort_value TEXT; + raw_text TEXT; + curr_text TEXT; + joiner TEXT := default_joiner; -- XXX will index defs supply a joiner? + authority_text TEXT; + authority_link BIGINT; + output_row metabib.field_entry_template%ROWTYPE; + process_idx BOOL; +BEGIN + + -- Start out with no field-use bools set + output_row.browse_field = FALSE; + output_row.facet_field = FALSE; + output_row.display_field = FALSE; + output_row.search_field = FALSE; + + -- Get the record + SELECT INTO bib * FROM biblio.record_entry WHERE id = rid; + + -- Loop over the indexing entries + FOR idx IN SELECT * FROM config.metabib_field WHERE id = ANY (only_fields) ORDER BY format LOOP + CONTINUE WHEN idx.xpath IS NULL OR idx.xpath = ''; -- pure virtual field + + process_idx := FALSE; + IF idx.display_field AND 'display' = ANY (field_types) THEN process_idx = TRUE; END IF; + IF idx.browse_field AND 'browse' = ANY (field_types) THEN process_idx = TRUE; END IF; + IF idx.search_field AND 'search' = ANY (field_types) THEN process_idx = TRUE; END IF; + IF idx.facet_field AND 'facet' = ANY (field_types) THEN process_idx = TRUE; END IF; + CONTINUE WHEN process_idx = FALSE; -- disabled for all types + + joiner := COALESCE(idx.joiner, default_joiner); + + SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format; + + -- See if we can skip the XSLT ... it's expensive + IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN + -- Can't skip the transform + IF xfrm.xslt <> '---' THEN + transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt); + ELSE + transformed_xml := bib.marc; + END IF; + + prev_xfrm := xfrm.name; + END IF; + + xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + + raw_text := NULL; + FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP + CONTINUE WHEN xml_node !~ E'^\\s*<'; + + -- XXX much of this should be moved into oils_xpath_string... + curr_text := ARRAY_TO_STRING(evergreen.array_remove_item_by_value(evergreen.array_remove_item_by_value( + oils_xpath( '//text()', -- get the content of all the nodes within the main selected node + REGEXP_REPLACE( xml_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space + ), ' '), ''), -- throw away morally empty (bankrupt?) strings + joiner + ); + + CONTINUE WHEN curr_text IS NULL OR curr_text = ''; + + IF raw_text IS NOT NULL THEN + raw_text := raw_text || joiner; + END IF; + + raw_text := COALESCE(raw_text,'') || curr_text; + + -- autosuggest/metabib.browse_entry + IF idx.browse_field THEN + + IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN + browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + ELSE + browse_text := curr_text; + END IF; + + IF idx.browse_sort_xpath IS NOT NULL AND + idx.browse_sort_xpath <> '' THEN + + sort_value := oils_xpath_string( + idx.browse_sort_xpath, xml_node, joiner, + ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] + ); + ELSE + sort_value := browse_text; + END IF; + + output_row.field_class = idx.field_class; + output_row.field = idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g')); + output_row.sort_value := + public.naco_normalize(sort_value); + + output_row.authority := NULL; + + IF idx.authority_xpath IS NOT NULL AND idx.authority_xpath <> '' THEN + authority_text := oils_xpath_string( + idx.authority_xpath, xml_node, joiner, + ARRAY[ + ARRAY[xfrm.prefix, xfrm.namespace_uri], + ARRAY['xlink','http://www.w3.org/1999/xlink'] + ] + ); + + IF authority_text ~ '^\d+$' THEN + authority_link := authority_text::BIGINT; + PERFORM * FROM authority.record_entry WHERE id = authority_link; + IF FOUND THEN + output_row.authority := authority_link; + END IF; + END IF; + + END IF; + + output_row.browse_field = TRUE; + -- Returning browse rows with search_field = true for search+browse + -- configs allows us to retain granularity of being able to search + -- browse fields with "starts with" type operators (for example, for + -- titles of songs in music albums) + IF idx.search_field THEN + output_row.search_field = TRUE; + END IF; + RETURN NEXT output_row; + output_row.browse_field = FALSE; + output_row.search_field = FALSE; + output_row.sort_value := NULL; + END IF; + + -- insert raw node text for faceting + IF idx.facet_field THEN + + IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN + facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + ELSE + facet_text := curr_text; + END IF; + + output_row.field_class = idx.field_class; + output_row.field = -1 * idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g')); + + output_row.facet_field = TRUE; + RETURN NEXT output_row; + output_row.facet_field = FALSE; + END IF; + + -- insert raw node text for display + IF idx.display_field THEN + + IF idx.display_xpath IS NOT NULL AND idx.display_xpath <> '' THEN + display_text := oils_xpath_string( idx.display_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + ELSE + display_text := curr_text; + END IF; + + output_row.field_class = idx.field_class; + output_row.field = -1 * idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(display_text, E'\\s+', ' ', 'g')); + + output_row.display_field = TRUE; + RETURN NEXT output_row; + output_row.display_field = FALSE; + END IF; + + END LOOP; + + CONTINUE WHEN raw_text IS NULL OR raw_text = ''; + + -- insert combined node text for searching + IF idx.search_field THEN + output_row.field_class = idx.field_class; + output_row.field = idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g')); + + output_row.search_field = TRUE; + RETURN NEXT output_row; + output_row.search_field = FALSE; + END IF; + + END LOOP; + +END; +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION metabib.update_combined_index_vectors(bib_id BIGINT) RETURNS VOID AS $func$ +DECLARE + rdata TSVECTOR; + vclass TEXT; + vfield INT; + rfields INT[]; +BEGIN + DELETE FROM metabib.combined_keyword_field_entry WHERE record = bib_id; + INSERT INTO metabib.combined_keyword_field_entry(record, metabib_field, index_vector) + SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.keyword_field_entry WHERE source = bib_id GROUP BY field; + INSERT INTO metabib.combined_keyword_field_entry(record, metabib_field, index_vector) + SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.keyword_field_entry WHERE source = bib_id; + + DELETE FROM metabib.combined_title_field_entry WHERE record = bib_id; + INSERT INTO metabib.combined_title_field_entry(record, metabib_field, index_vector) + SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.title_field_entry WHERE source = bib_id GROUP BY field; + INSERT INTO metabib.combined_title_field_entry(record, metabib_field, index_vector) + SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.title_field_entry WHERE source = bib_id; + + DELETE FROM metabib.combined_author_field_entry WHERE record = bib_id; + INSERT INTO metabib.combined_author_field_entry(record, metabib_field, index_vector) + SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.author_field_entry WHERE source = bib_id GROUP BY field; + INSERT INTO metabib.combined_author_field_entry(record, metabib_field, index_vector) + SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.author_field_entry WHERE source = bib_id; + + DELETE FROM metabib.combined_subject_field_entry WHERE record = bib_id; + INSERT INTO metabib.combined_subject_field_entry(record, metabib_field, index_vector) + SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.subject_field_entry WHERE source = bib_id GROUP BY field; + INSERT INTO metabib.combined_subject_field_entry(record, metabib_field, index_vector) + SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.subject_field_entry WHERE source = bib_id; + + DELETE FROM metabib.combined_series_field_entry WHERE record = bib_id; + INSERT INTO metabib.combined_series_field_entry(record, metabib_field, index_vector) + SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.series_field_entry WHERE source = bib_id GROUP BY field; + INSERT INTO metabib.combined_series_field_entry(record, metabib_field, index_vector) + SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.series_field_entry WHERE source = bib_id; + + DELETE FROM metabib.combined_identifier_field_entry WHERE record = bib_id; + INSERT INTO metabib.combined_identifier_field_entry(record, metabib_field, index_vector) + SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.identifier_field_entry WHERE source = bib_id GROUP BY field; + INSERT INTO metabib.combined_identifier_field_entry(record, metabib_field, index_vector) + SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector) + FROM metabib.identifier_field_entry WHERE source = bib_id; + + -- For each virtual def, gather the data from the combined real field + -- entries and append it to the virtual combined entry. + FOR vfield, rfields IN SELECT virtual, ARRAY_AGG(real) FROM config.metabib_field_virtual_map GROUP BY virtual LOOP + SELECT field_class INTO vclass + FROM config.metabib_field + WHERE id = vfield; + + SELECT string_agg(index_vector::TEXT,' ')::tsvector INTO rdata + FROM metabib.combined_all_field_entry + WHERE record = bib_id + AND metabib_field = ANY (rfields); + + BEGIN -- I cannot wait for INSERT ON CONFLICT ... 9.5, though + EXECUTE $$ + INSERT INTO metabib.combined_$$ || vclass || $$_field_entry + (record, metabib_field, index_vector) VALUES ($1, $2, $3) + $$ USING bib_id, vfield, rdata; + EXCEPTION WHEN unique_violation THEN + EXECUTE $$ + UPDATE metabib.combined_$$ || vclass || $$_field_entry + SET index_vector = index_vector || $3 + WHERE record = $1 + AND metabib_field = $2 + $$ USING bib_id, vfield, rdata; + WHEN OTHERS THEN + -- ignore and move on + END; + END LOOP; +END; +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE VIEW search.best_tsconfig AS + SELECT m.id AS id, + COALESCE(f.ts_config, c.ts_config, 'simple') AS ts_config + FROM config.metabib_field m + LEFT JOIN config.metabib_class_ts_map c ON (c.field_class = m.field_class AND c.index_weight = 'C') + LEFT JOIN config.metabib_field_ts_map f ON (f.metabib_field = m.id AND f.index_weight = 'C'); + +CREATE TYPE search.highlight_result AS ( id BIGINT, source BIGINT, field INT, value TEXT, highlight TEXT ); + +CREATE OR REPLACE FUNCTION search.highlight_display_fields_impl( + rid BIGINT, + tsq TEXT, + field_list INT[] DEFAULT '{}'::INT[], + css_class TEXT DEFAULT 'oils_SH', + hl_all BOOL DEFAULT TRUE, + minwords INT DEFAULT 5, + maxwords INT DEFAULT 25, + shortwords INT DEFAULT 0, + maxfrags INT DEFAULT 0, + delimiter TEXT DEFAULT ' ... ' +) RETURNS SETOF search.highlight_result AS $f$ +DECLARE + opts TEXT := ''; + v_css_class TEXT := css_class; + v_delimiter TEXT := delimiter; + v_field_list INT[] := field_list; + hl_query TEXT; +BEGIN + IF v_delimiter LIKE $$%'%$$ OR v_delimiter LIKE '%"%' THEN --" + v_delimiter := ' ... '; + END IF; + + IF NOT hl_all THEN + opts := opts || 'MinWords=' || minwords; + opts := opts || ', MaxWords=' || maxwords; + opts := opts || ', ShortWords=' || shortwords; + opts := opts || ', MaxFragments=' || maxfrags; + opts := opts || ', FragmentDelimiter="' || delimiter || '"'; + ELSE + opts := opts || 'HighlightAll=TRUE'; + END IF; + + IF v_css_class LIKE $$%'%$$ OR v_css_class LIKE '%"%' THEN -- " + v_css_class := 'oils_SH'; + END IF; + + opts := opts || $$, StopSel=, StartSel=""$xx$ -- "' + ) AS highlight + FROM metabib.display_entry de + JOIN config.metabib_field mf ON (mf.id = de.field) + JOIN search.best_tsconfig t ON (t.id = de.field) + WHERE de.source = $2 + AND field = ANY ($3) + ORDER BY de.id;$$; + + RETURN QUERY EXECUTE hl_query USING opts, rid, v_field_list; +END; +$f$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION evergreen.escape_for_html (TEXT) RETURNS TEXT AS $$ + SELECT regexp_replace( + regexp_replace( + regexp_replace( + $1, + '&', + '&', + 'g' + ), + '<', + '<', + 'g' + ), + '>', + '>', + 'g' + ); +$$ LANGUAGE SQL IMMUTABLE LEAKPROOF STRICT COST 10; + +CREATE OR REPLACE FUNCTION search.highlight_display_fields( + rid BIGINT, + tsq_map TEXT, -- { '(a | b) & c' => '1,2,3,4', ...} + css_class TEXT DEFAULT 'oils_SH', + hl_all BOOL DEFAULT TRUE, + minwords INT DEFAULT 5, + maxwords INT DEFAULT 25, + shortwords INT DEFAULT 0, + maxfrags INT DEFAULT 0, + delimiter TEXT DEFAULT ' ... ' +) RETURNS SETOF search.highlight_result AS $f$ +DECLARE + tsq_hstore HSTORE; + tsq TEXT; + fields TEXT; + afields INT[]; + seen INT[]; +BEGIN + + IF (tsq_map ILIKE 'hstore%') THEN + EXECUTE 'SELECT ' || tsq_map INTO tsq_hstore; + ELSE + tsq_hstore := tsq_map::HSTORE; + END IF; + + FOR tsq, fields IN SELECT key, value FROM each(tsq_hstore::HSTORE) LOOP + SELECT ARRAY_AGG(unnest::INT) INTO afields + FROM unnest(regexp_split_to_array(fields,',')); + seen := seen || afields; + + RETURN QUERY + SELECT * FROM search.highlight_display_fields_impl( + rid, tsq, afields, css_class, hl_all,minwords, + maxwords, shortwords, maxfrags, delimiter + ); + END LOOP; + + RETURN QUERY + SELECT id, + source, + field, + value, + value AS highlight + FROM metabib.display_entry + WHERE source = rid + AND NOT (field = ANY (seen)); +END; +$f$ LANGUAGE PLPGSQL ROWS 10; + +CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( + bib_id bigint, + fp text, + bib_is_deleted boolean DEFAULT false, + retain_deleted boolean DEFAULT false +) RETURNS bigint AS $function$ +DECLARE + new_mapping BOOL := TRUE; + source_count INT; + old_mr BIGINT; + tmp_mr metabib.metarecord%ROWTYPE; + deleted_mrs BIGINT[]; +BEGIN + + -- We need to make sure we're not a deleted master record of an MR + IF bib_is_deleted THEN + IF NOT retain_deleted THEN -- Go away for any MR that we're master of, unless retained + DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; + END IF; + + FOR old_mr IN SELECT id FROM metabib.metarecord WHERE master_record = bib_id LOOP + + -- Now, are there any more sources on this MR? + SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = old_mr; + + IF source_count = 0 AND NOT retain_deleted THEN -- No other records + deleted_mrs := ARRAY_APPEND(deleted_mrs, old_mr); -- Just in case... + DELETE FROM metabib.metarecord WHERE id = old_mr; + + ELSE -- indeed there are. Update it with a null cache and recalcualated master record + UPDATE metabib.metarecord + SET mods = NULL, + master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC LIMIT 1) + WHERE id = old_mr; + END IF; + END LOOP; + + ELSE -- insert or update + + FOR tmp_mr IN SELECT m.* FROM metabib.metarecord m JOIN metabib.metarecord_source_map s ON (s.metarecord = m.id) WHERE s.source = bib_id LOOP + + -- Find the first fingerprint-matching + IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN + old_mr := tmp_mr.id; + new_mapping := FALSE; + + ELSE -- Our fingerprint changed ... maybe remove the old MR + DELETE FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id AND source = bib_id; -- remove the old source mapping + SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id; + IF source_count = 0 THEN -- No other records + deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id); + DELETE FROM metabib.metarecord WHERE id = tmp_mr.id; + END IF; + END IF; + + END LOOP; + + -- we found no suitable, preexisting MR based on old source maps + IF old_mr IS NULL THEN + SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint? + + IF old_mr IS NULL THEN -- nope, create one and grab its id + INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id ); + SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; + + ELSE -- indeed there is. update it with a null cache and recalcualated master record + UPDATE metabib.metarecord + SET mods = NULL, + master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC LIMIT 1) + WHERE id = old_mr; + END IF; + + ELSE -- there was one we already attached to, update its mods cache and master_record + UPDATE metabib.metarecord + SET mods = NULL, + master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC LIMIT 1) + WHERE id = old_mr; + END IF; + + IF new_mapping THEN + INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping + END IF; + + END IF; + + IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN + UPDATE action.hold_request SET target = old_mr WHERE target IN ( SELECT unnest(deleted_mrs) ) AND hold_type = 'M'; -- if we had to delete any MRs above, make sure their holds are moved + END IF; + + RETURN old_mr; + +END; +$function$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION evergreen.marc_to (marc text, xfrm text) RETURNS TEXT AS $$ + SELECT evergreen.xml_pretty_print(xslt_process($1,xslt)::XML)::TEXT FROM config.xml_transform WHERE name = $2; +$$ LANGUAGE SQL; + + + +--SELECT evergreen.upgrade_deps_block_check('1102', :eg_version); + +update config.xml_transform set xslt = $XXXX$ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BK + SE + + + BK + MM + CF + MP + VM + MU + + + + + + + + + b + afgk + + + + + abfgk + + + + + + + + + + + ,;/ + + + + + + + + + + <xsl:value-of select="substring($titleChop,@ind2+1)"/> + + + + + <xsl:value-of select="$titleChop"/> + + + + + + + + + b + b + afgk + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">abfgk</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + abfgk + + + + + + + + + + + <xsl:value-of select="substring($titleBrowseChop,@ind2+1)"/> + + + + + <xsl:value-of select="$titleBrowseChop"/> + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">a</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + a + + + + + + + + + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + + + + + + + + <xsl:value-of select="substring($titleChop,@ind2+1)"/> + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <!-- 1/04 removed $h, $b --> + <xsl:with-param name="codes">af</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:value-of select="$titleChop"/> + + + + + + + + + + + <xsl:value-of select="substring($titleChop,$nfi+1)"/> + + + + + <xsl:value-of select="$titleChop"/> + + + + + + + + + + + + ah + + + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + <xsl:value-of select="substring($titleChop,@ind1+1)"/> + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + + + creator + + + + + + + + + + creator + + + + + + + + + + creator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + personal + + + + + + + + + + + yes + + + yes + + + text + cartographic + notated music + sound recording-nonmusical + sound recording-musical + still image + moving image + three dimensional object + software, multimedia + mixed material + + + + globe + + + remote sensing image + + + + + + map + + + atlas + + + + + + + + database + + + loose-leaf + + + series + + + newspaper + + + periodical + + + web site + + + + + + + + abstract or summary + + + bibliography + + + catalog + + + dictionary + + + encyclopedia + + + handbook + + + legal article + + + index + + + discography + + + legislation + + + theses + + + survey of literature + + + review + + + programmed text + + + filmography + + + directory + + + statistics + + + technical report + + + legal case and case notes + + + law report or digest + + + treaty + + + + + + conference publication + + + + + + + + numeric data + + + database + + + font + + + game + + + + + + patent + + + festschrift + + + + biography + + + + + essay + + + drama + + + comic strip + + + fiction + + + humor, satire + + + letter + + + novel + + + short story + + + speech + + + + + + + biography + + + conference publication + + + drama + + + essay + + + fiction + + + folktale + + + history + + + humor, satire + + + memoir + + + poetry + + + rehearsal + + + reporting + + + sound + + + speech + + + + + + + art original + + + kit + + + art reproduction + + + diorama + + + filmstrip + + + legal article + + + picture + + + graphic + + + technical drawing + + + motion picture + + + chart + + + flash card + + + microscope slide + + + model + + + realia + + + slide + + + transparency + + + videorecording + + + toy + + + + + + + + + + abvxyz + - + + + + + + + + + code + marccountry + + + + + + + + code + iso3166 + + + + + + + + text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + :,;/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + monographic + continuing + + + + + + + ab + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + reformatted digital + + + digitized microfilm + + + digitized other analog + + + + + + + + + + + + + + + +
braille
+
+ +
print
+
+ +
electronic
+
+ +
microfiche
+
+ +
microfilm
+
+
+ + +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ +
+
+ + + + access + + + preservation + + + replacement + + + + + +
chip cartridge
+
+ +
computer optical disc cartridge
+
+ +
magnetic disc
+
+ +
magneto-optical disc
+
+ +
optical disc
+
+ +
remote
+
+ +
tape cartridge
+
+ +
tape cassette
+
+ +
tape reel
+
+ + +
celestial globe
+
+ +
earth moon globe
+
+ +
planetary or lunar globe
+
+ +
terrestrial globe
+
+ + +
kit
+
+ + +
atlas
+
+ +
diagram
+
+ +
map
+
+ +
model
+
+ +
profile
+
+ +
remote-sensing image
+
+ +
section
+
+ +
view
+
+ + +
aperture card
+
+ +
microfiche
+
+ +
microfiche cassette
+
+ +
microfilm cartridge
+
+ +
microfilm cassette
+
+ +
microfilm reel
+
+ +
microopaque
+
+ + +
film cartridge
+
+ +
film cassette
+
+ +
film reel
+
+ + +
chart
+
+ +
collage
+
+ +
drawing
+
+ +
flash card
+
+ +
painting
+
+ +
photomechanical print
+
+ +
photonegative
+
+ +
photoprint
+
+ +
picture
+
+ +
print
+
+ +
technical drawing
+
+ + +
notated music
+
+ + +
filmslip
+
+ +
filmstrip cartridge
+
+ +
filmstrip roll
+
+ +
other filmstrip type
+
+ +
slide
+
+ +
transparency
+
+ +
remote-sensing image
+
+ +
cylinder
+
+ +
roll
+
+ +
sound cartridge
+
+ +
sound cassette
+
+ +
sound disc
+
+ +
sound-tape reel
+
+ +
sound-track film
+
+ +
wire recording
+
+ + +
braille
+
+ +
combination
+
+ +
moon
+
+ +
tactile, with no writing system
+
+ + +
braille
+
+ +
large print
+
+ +
regular print
+
+ +
text in looseleaf binder
+
+ + +
videocartridge
+
+ +
videocassette
+
+ +
videodisc
+
+ +
videoreel
+
+ + + + + + + + + + abce + + + +
+ + + + + + + + + + ab + + + + + + + + agrt + + + + + + + ab + + + + + + + + + adolescent + + + adult + + + general + + + juvenile + + + preschool + + + specialized + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + defg + + + + + + + + + + + + marcgac + + + + + + iso3166 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ab + + + + + + + abx + + + + + + + ab + + + + + + + + + + + + + + + + + + + + + + + + + + + + ab + + + + + + + + + + av + + + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + <xsl:value-of select="substring($titleChop,@ind2+1)"/> + + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">av</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + abcx3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">g</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + aq + t + g + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">dg</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + + c + t + dgn + + + + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">g</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + aqdc + t + gn + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">adfgklmorsv</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + <xsl:value-of select="substring($titleChop,@ind1+1)"/> + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">g</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + aq + t + g + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">dg</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + + c + t + dgn + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">g</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + aqdc + t + gn + + + + + + + + + + + + + + adfgklmorsv + + + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + <xsl:value-of select="substring($titleChop,@ind2+1)"/> + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + + + + + + + + isbn + + + + + + + + + + isrc + + + + + + + + + + ismn + + + + + + + + + + sici + + + + ab + + + + + + issn + + + + + + + + lccn + + + + + + + + + + issue number + matrix number + music plate + music publisher + videorecording identifier + + + + + + + ba + ab + + + + + + + + + + ab + + + + + + + + doi + hdl + uri + + + + + + + + + + + + + + + + + y3z + + + + + + + + + + + + + + + + + + + + + y3 + + + + + + + z + + + + + + + + + + + + + + + + + + abje + + + + + + + + abcd35 + + + + + + + abcde35 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + n + n + fgkdlmor + + + + + p + p + fgkdlmor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + g + g + pst + + + + + p + p + fgkdlmor + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cdn + + + + + + + + + + aq + + + + :,;/ + + + + + + + + + + acdeq + + + + + + constituent + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="."></xsl:value-of> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="."></xsl:value-of> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="."></xsl:value-of> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="."></xsl:value-of> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + code + marcgac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + lcsh + lcshac + mesh + + nal + csh + rvm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + aq + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cdnp + + + + + + + + + + + + + + + + abcdeqnp + + + + + + + + + + + + + + + + + + + + + adfhklor + + + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + <xsl:value-of select="substring($titleChop,@ind1+1)"/> + + + + + + <xsl:value-of select="$titleChop" /> + + + + + + + + + + + + + + + + + abcd + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bc + + + + + + + + + + + + + + + + + + + + + + + + + + + yes + + + + + + + + + + + + + + + + + + + + + + + + + + + Arabic + Latin + Chinese, Japanese, Korean + Cyrillic + Hebrew + Greek + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + summary or subtitle + sung or spoken text + libretto + table of contents + accompanying material + translation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + summary or subtitle + sung or spoken text + libretto + table of contents + accompanying material + translation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .:,;/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
$XXXX$ where name = $$mods32$$; + +update config.xml_transform set xslt = $XXXX$ + + + + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + + + + ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ + + + + !'()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ + + + 0123456789ABCDEF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BK + SE + + + BK + MM + CF + MP + VM + MU + + + + + + + + + b + afgk + + + + + abfgk + + + + + + + + + + + ,;/ + + + + + + + + + + <xsl:value-of select="substring($titleChop,@ind2+1)"/> + + + + + <xsl:value-of select="$titleChop"/> + + + + + + + + + b + b + afgk + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">a</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <!-- 1/04 removed $h, b --> + <xsl:with-param name="codes">a</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <!-- 1/04 removed $h, $b --> + <xsl:with-param name="codes">af</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + <xsl:call-template name="uri"/> + + <xsl:variable name="str"> + <xsl:for-each select="marc:subfield"> + <xsl:if + test="(contains('adfklmors',@code) and (not(../marc:subfield[@code='n' or @code='p']) or (following-sibling::marc:subfield[@code='n' or @code='p'])))"> + <xsl:value-of select="text()"/> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:for-each> + </xsl:variable> + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="substring($str,1,string-length($str)-1)"/> + </xsl:with-param> + </xsl:call-template> + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">ah</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + creator + + + + + + + + + + + + creator + + + + + + + + + + + + creator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + personal + + + + + + + + + + + yes + + + yes + + + text + cartographic + notated music + sound recording-nonmusical + sound recording-musical + still image + moving image + three dimensional object + software, multimedia + mixed material + + + + globe + + + remote-sensing image + + + + + + map + + + atlas + + + + + + + + database + + + loose-leaf + + + series + + + newspaper + + + periodical + + + web site + + + + + + + + abstract or summary + + + bibliography + + + catalog + + + dictionary + + + encyclopedia + + + handbook + + + legal article + + + index + + + discography + + + legislation + + + theses + + + survey of literature + + + review + + + programmed text + + + filmography + + + directory + + + statistics + + + technical report + + + legal case and case notes + + + law report or digest + + + treaty + + + + + + conference publication + + + + + + + + numeric data + + + database + + + font + + + game + + + + + + patent + + + offprint + + + festschrift + + + + biography + + + + + essay + + + drama + + + comic strip + + + fiction + + + humor, satire + + + letter + + + novel + + + short story + + + speech + + + + + + + biography + + + conference publication + + + drama + + + essay + + + fiction + + + folktale + + + history + + + humor, satire + + + memoir + + + poetry + + + rehearsal + + + reporting + + + sound + + + speech + + + + + + + art original + + + kit + + + art reproduction + + + diorama + + + filmstrip + + + legal article + + + picture + + + graphic + + + technical drawing + + + motion picture + + + chart + + + flash card + + + microscope slide + + + model + + + realia + + + slide + + + transparency + + + videorecording + + + toy + + + + + + + + + + + + + abcdef + - + + + + + + + + + + abvxyz + - + + + + + + + + + code + marccountry + + + + + + + + code + iso3166 + + + + + + + + text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + :,;/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + monographic + continuing + + + + + + + ab + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + reformatted digital + + + digitized microfilm + + + digitized other analog + + + + + + + + + + + + + + + +
braille
+
+ +
print
+
+ +
electronic
+
+ +
microfiche
+
+ +
microfilm
+
+
+ + +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + +
+ + + + access + + + preservation + + + replacement + + + + + +
chip cartridge
+
+ +
computer optical disc cartridge
+
+ +
magnetic disc
+
+ +
magneto-optical disc
+
+ +
optical disc
+
+ +
remote
+
+ +
tape cartridge
+
+ +
tape cassette
+
+ +
tape reel
+
+ + +
celestial globe
+
+ +
earth moon globe
+
+ +
planetary or lunar globe
+
+ +
terrestrial globe
+
+ + +
kit
+
+ + +
atlas
+
+ +
diagram
+
+ +
map
+
+ +
model
+
+ +
profile
+
+ +
remote-sensing image
+
+ +
section
+
+ +
view
+
+ + +
aperture card
+
+ +
microfiche
+
+ +
microfiche cassette
+
+ +
microfilm cartridge
+
+ +
microfilm cassette
+
+ +
microfilm reel
+
+ +
microopaque
+
+ + +
film cartridge
+
+ +
film cassette
+
+ +
film reel
+
+ + +
chart
+
+ +
collage
+
+ +
drawing
+
+ +
flash card
+
+ +
painting
+
+ +
photomechanical print
+
+ +
photonegative
+
+ +
photoprint
+
+ +
picture
+
+ +
print
+
+ +
technical drawing
+
+ + +
notated music
+
+ + +
filmslip
+
+ +
filmstrip cartridge
+
+ +
filmstrip roll
+
+ +
other filmstrip type
+
+ +
slide
+
+ +
transparency
+
+ +
remote-sensing image
+
+ +
cylinder
+
+ +
roll
+
+ +
sound cartridge
+
+ +
sound cassette
+
+ +
sound disc
+
+ +
sound-tape reel
+
+ +
sound-track film
+
+ +
wire recording
+
+ + +
braille
+
+ +
combination
+
+ +
moon
+
+ +
tactile, with no writing system
+
+ + +
braille
+
+ +
large print
+
+ +
regular print
+
+ +
text in looseleaf binder
+
+ + +
videocartridge
+
+ +
videocassette
+
+ +
videodisc
+
+ +
videoreel
+
+ + + + + + + + + + abce + + + +
+ + + + + + + + + + ab + + + + + + + + agrt + + + + + + + ab + + + + + + + + + adolescent + + + adult + + + general + + + juvenile + + + preschool + + + specialized + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + defg + + + + + + + + + + + + marcgac + + + + + + iso3166 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ab + + + + + + + abx + + + + + + + ab + + + + + + + + + + + + + + + + + + + + + + + + + + + + ab + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">av</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">av</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + abcx3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">g</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + aq + t + g + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">dg</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + + c + t + dgn + + + + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">g</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + aqdc + t + gn + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">adfgklmorsv</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="marc:subfield[@code='a']"/> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">g</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + aq + t + g + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">dg</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + + c + t + dgn + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="specialSubfieldSelect"> + <xsl:with-param name="anyCodes">tfklsv</xsl:with-param> + <xsl:with-param name="axis">t</xsl:with-param> + <xsl:with-param name="afterCodes">g</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + aqdc + t + gn + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">adfgklmorsv</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + isbn + + + + + + + + + + isrc + + + + + + + + + + ismn + + + + + + + + + + sici + + + + ab + + + + + + + issn + + + + + + + + issn-l + + + + + + + + + + + + lccn + + + + + + + + + + issue number + matrix number + music plate + music publisher + videorecording identifier + + + + + + + + ba + ab + + + + + + + + + + + ab + + + + + + + + doi + hdl + uri + + + + + + + + + + + + + + + + + y3z + + + + + + + + + + + + + + + + + + + + + + + + + y3 + + + + + + + z + + + + + + + + + + + + + + + + y3 + + + + + + + z + + + + + + + + + + + + + + + + + + abe + + + + + + + + + u + + + + + + + + hijklmt + + + + + + + + + + abcd35 + + + + + + + abcde35 + + + + + + + + + + aacr2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + n + n + fgkdlmor + + + + + p + p + fgkdlmor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + g + g + pst + + + + + p + p + fgkdlmor + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cdn + + + + + + + + + + aq + + + + :,;/ + + + + + + + + + + acdeq + + + + + + constituent + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="."/> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="."/> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="."/> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:value-of select="."/> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + code + marcgac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + lcsh + lcshac + mesh + + nal + csh + rvm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + aq + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cdnp + + + + + + + + + + + + + + + abcdeqnp + + + + + + + + + + + + + + + + + + + <xsl:call-template name="chopPunctuation"> + <xsl:with-param name="chopString"> + <xsl:call-template name="subfieldSelect"> + <xsl:with-param name="codes">adfhklor</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + + + + + + + + + + + + + + + + + + + + + + abcd + + + + + + + + + + + + + + + + abcd + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bc + + + + + + + + + + + + + + + + + + + + + + + + + + + yes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Arabic + Latin + Chinese, Japanese, Korean + Cyrillic + Hebrew + Greek + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + summary or subtitle + sung or spoken text + libretto + table of contents + accompanying material + translation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + summary or subtitle + sung or spoken text + libretto + table of contents + accompanying material + translation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + abcdefghijklmnopqrstuvwxyz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .:,;/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .:,;/] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warning: string contains a character + that is out of range! Substituting "?". + 63 + + + + + + + + + + + + + + + + +
$XXXX$ where name = $$mods33$$; + + + +--SELECT evergreen.upgrade_deps_block_check('1103', :eg_version); + +DO $INSERT$ +BEGIN + IF evergreen.insert_on_deploy() THEN + +INSERT INTO config.metabib_field (id, field_class, name, label, browse_field) + VALUES (45, 'keyword', 'blob', 'All searchable fields', FALSE); + +INSERT INTO config.metabib_field (id, field_class, name, format, weight, + label, xpath, display_field, search_field, browse_field, facet_field) +VALUES ( + 53, 'title', 'maintitle', 'marcxml', 10, + oils_i18n_gettext(53, 'Main Title', 'cmf', 'label'), + $$//*[@tag='245']/*[@code='a']$$, + FALSE, TRUE, FALSE, FALSE +); + +INSERT INTO config.metabib_field_virtual_map (real, virtual) + SELECT id, + 45 + FROM config.metabib_field + WHERE search_field + AND id NOT IN (15, 45, 38, 40) -- keyword|keyword, self, edition, publisher + AND id NOT IN (SELECT real FROM config.metabib_field_virtual_map); + +UPDATE config.metabib_field SET xpath=$$//mods32:mods/mods32:subject[not(descendant::mods32:geographicCode)]$$ WHERE id = 16; + +UPDATE config.metabib_field_virtual_map SET weight = -1 WHERE real = 39; +UPDATE config.metabib_field_virtual_map SET weight = 0 WHERE real = 41; +UPDATE config.metabib_field_virtual_map SET weight = 0 WHERE real = 42; +UPDATE config.metabib_field_virtual_map SET weight = 0 WHERE real = 46; +UPDATE config.metabib_field_virtual_map SET weight = 0 WHERE real = 47; +UPDATE config.metabib_field_virtual_map SET weight = 0 WHERE real = 48; +UPDATE config.metabib_field_virtual_map SET weight = 0 WHERE real = 50; +UPDATE config.metabib_field_virtual_map SET weight = 8 WHERE real = 6; +UPDATE config.metabib_field_virtual_map SET weight = 8 WHERE real = 8; +UPDATE config.metabib_field_virtual_map SET weight = 8 WHERE real = 16; +UPDATE config.metabib_field_virtual_map SET weight = 12 WHERE real = 53; + +-- Stemming for genre +INSERT INTO config.metabib_field_ts_map (metabib_field, ts_config) + SELECT 33, 'english_nostop' WHERE NOT EXISTS ( + SELECT 1 FROM config.metabib_field_ts_map WHERE metabib_field = 33 AND ts_config = 'english_nostop' + ) +; + + +--SELECT evergreen.upgrade_deps_block_check('1104', :eg_version); + +INSERT INTO config.org_unit_setting_type +( name, grp, label, description, datatype ) +VALUES + ('circ.clear_hold_on_checkout', + 'circ', + oils_i18n_gettext('circ.clear_hold_on_checkout', + 'Clear hold when other patron checks out item', + 'coust', 'label'), + oils_i18n_gettext('circ.clear_hold_on_checkout', + 'Default to cancel the hold when patron A checks out item on hold for patron B.', + 'coust', 'description'), + 'bool'); + + + +--SELECT evergreen.upgrade_deps_block_check('1105', :eg_version); + +INSERT into config.org_unit_setting_type (name, label, grp, description, datatype) +values ('webstaff.circ.itemsout_notice_count_excludes_courtesies','Exclude Courtesy Notices from Patrons Itemsout Notices Count', + 'circ', 'Exclude Courtesy Notices from Patron Itemsout Notices Count', 'bool'); + + END IF; +END $INSERT$; + + +--SELECT evergreen.upgrade_deps_block_check('1106', :eg_version); + +ALTER TABLE money.billing + ADD COLUMN create_date TIMESTAMP WITH TIME ZONE, + ADD COLUMN period_start TIMESTAMP WITH TIME ZONE, + ADD COLUMN period_end TIMESTAMP WITH TIME ZONE; + +--Disable materialized update trigger +--It takes forever, and doesn't matter yet for what we are doing, as the +--view definition is unchanged (still using billing_ts) +ALTER TABLE money.billing DISABLE TRIGGER mat_summary_upd_tgr; + +--Limit to btype=1 / 'Overdue Materials' +--Update day-granular fines first (i.e. 24 hour, 1 day, 2 day, etc., all of which are multiples of 86400 seconds), and simply remove the time portion of timestamp +UPDATE money.billing mb + SET period_start = date_trunc('day', billing_ts), period_end = date_trunc('day', billing_ts) + (ac.fine_interval - '1 second') + FROM action.circulation ac +WHERE mb.xact = ac.id + AND mb.btype = 1 + AND (EXTRACT(EPOCH FROM ac.fine_interval))::integer % 86400 = 0; + +--Update fines for non-day intervals +UPDATE money.billing mb + SET period_start = billing_ts - ac.fine_interval + interval '1 sec', period_end = billing_ts + FROM action.circulation ac +WHERE mb.xact = ac.id + AND mb.btype = 1 + AND (EXTRACT(EPOCH FROM ac.fine_interval))::integer % 86400 > 0; + +SET CONSTRAINTS ALL IMMEDIATE; +UPDATE money.billing SET create_date = COALESCE(period_start, billing_ts); + +--Re-enable update trigger +ALTER TABLE money.billing ENABLE TRIGGER mat_summary_upd_tgr; + +ALTER TABLE money.billing ALTER COLUMN create_date SET DEFAULT NOW(); +ALTER TABLE money.billing ALTER COLUMN create_date SET NOT NULL; + +CREATE INDEX m_b_create_date_idx ON money.billing (create_date); +CREATE INDEX m_b_period_start_idx ON money.billing (period_start); +CREATE INDEX m_b_period_end_idx ON money.billing (period_end); + +CREATE OR REPLACE FUNCTION money.maintain_billing_ts () RETURNS TRIGGER AS $$ +BEGIN + NEW.billing_ts := COALESCE(NEW.period_end, NEW.create_date); + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; +CREATE TRIGGER maintain_billing_ts_tgr BEFORE INSERT OR UPDATE ON money.billing FOR EACH ROW EXECUTE PROCEDURE money.maintain_billing_ts(); + + +--SELECT evergreen.upgrade_deps_block_check('1108', :eg_version); + +CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$ +DECLARE + moved_objects INT := 0; + source_cn asset.call_number%ROWTYPE; + target_cn asset.call_number%ROWTYPE; + metarec metabib.metarecord%ROWTYPE; + hold action.hold_request%ROWTYPE; + ser_rec serial.record_entry%ROWTYPE; + ser_sub serial.subscription%ROWTYPE; + acq_lineitem acq.lineitem%ROWTYPE; + acq_request acq.user_request%ROWTYPE; + booking booking.resource_type%ROWTYPE; + source_part biblio.monograph_part%ROWTYPE; + target_part biblio.monograph_part%ROWTYPE; + multi_home biblio.peer_bib_copy_map%ROWTYPE; + uri_count INT := 0; + counter INT := 0; + uri_datafield TEXT; + uri_text TEXT := ''; +BEGIN + + -- move any 856 entries on records that have at least one MARC-mapped URI entry + SELECT INTO uri_count COUNT(*) + FROM asset.uri_call_number_map m + JOIN asset.call_number cn ON (m.call_number = cn.id) + WHERE cn.record = source_record; + + IF uri_count > 0 THEN + + -- This returns more nodes than you might expect: + -- 7 instead of 1 for an 856 with $u $y $9 + SELECT COUNT(*) INTO counter + FROM oils_xpath_table( + 'id', + 'marc', + 'biblio.record_entry', + '//*[@tag="856"]', + 'id=' || source_record + ) as t(i int,c text); + + FOR i IN 1 .. counter LOOP + SELECT '' || + STRING_AGG( + '' || + regexp_replace( + regexp_replace( + regexp_replace(data,'&','&','g'), + '>', '>', 'g' + ), + '<', '<', 'g' + ) || '', '' + ) || '' INTO uri_datafield + FROM oils_xpath_table( + 'id', + 'marc', + 'biblio.record_entry', + '//*[@tag="856"][position()=' || i || ']/@ind1|' || + '//*[@tag="856"][position()=' || i || ']/@ind2|' || + '//*[@tag="856"][position()=' || i || ']/*/@code|' || + '//*[@tag="856"][position()=' || i || ']/*[@code]', + 'id=' || source_record + ) as t(id int,ind1 text, ind2 text,subfield text,data text); + + -- As most of the results will be NULL, protect against NULLifying + -- the valid content that we do generate + uri_text := uri_text || COALESCE(uri_datafield, ''); + END LOOP; + + IF uri_text <> '' THEN + UPDATE biblio.record_entry + SET marc = regexp_replace(marc,'(]*record>)', uri_text || E'\\1') + WHERE id = target_record; + END IF; + + END IF; + + -- Find and move metarecords to the target record + SELECT INTO metarec * + FROM metabib.metarecord + WHERE master_record = source_record; + + IF FOUND THEN + UPDATE metabib.metarecord + SET master_record = target_record, + mods = NULL + WHERE id = metarec.id; + + moved_objects := moved_objects + 1; + END IF; + + -- Find call numbers attached to the source ... + FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP + + SELECT INTO target_cn * + FROM asset.call_number + WHERE label = source_cn.label + AND prefix = source_cn.prefix + AND suffix = source_cn.suffix + AND owning_lib = source_cn.owning_lib + AND record = target_record + AND NOT deleted; + + -- ... and if there's a conflicting one on the target ... + IF FOUND THEN + + -- ... move the copies to that, and ... + UPDATE asset.copy + SET call_number = target_cn.id + WHERE call_number = source_cn.id; + + -- ... move V holds to the move-target call number + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP + + UPDATE action.hold_request + SET target = target_cn.id + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + UPDATE asset.call_number SET deleted = TRUE WHERE id = source_cn.id; + + -- ... if not ... + ELSE + -- ... just move the call number to the target record + UPDATE asset.call_number + SET record = target_record + WHERE id = source_cn.id; + END IF; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find T holds targeting the source record ... + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP + + -- ... and move them to the target record + UPDATE action.hold_request + SET target = target_record + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find serial records targeting the source record ... + FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP + -- ... and move them to the target record + UPDATE serial.record_entry + SET record = target_record + WHERE id = ser_rec.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find serial subscriptions targeting the source record ... + FOR ser_sub IN SELECT * FROM serial.subscription WHERE record_entry = source_record LOOP + -- ... and move them to the target record + UPDATE serial.subscription + SET record_entry = target_record + WHERE id = ser_sub.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find booking resource types targeting the source record ... + FOR booking IN SELECT * FROM booking.resource_type WHERE record = source_record LOOP + -- ... and move them to the target record + UPDATE booking.resource_type + SET record = target_record + WHERE id = booking.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find acq lineitems targeting the source record ... + FOR acq_lineitem IN SELECT * FROM acq.lineitem WHERE eg_bib_id = source_record LOOP + -- ... and move them to the target record + UPDATE acq.lineitem + SET eg_bib_id = target_record + WHERE id = acq_lineitem.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find acq user purchase requests targeting the source record ... + FOR acq_request IN SELECT * FROM acq.user_request WHERE eg_bib = source_record LOOP + -- ... and move them to the target record + UPDATE acq.user_request + SET eg_bib = target_record + WHERE id = acq_request.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find parts attached to the source ... + FOR source_part IN SELECT * FROM biblio.monograph_part WHERE record = source_record LOOP + + SELECT INTO target_part * + FROM biblio.monograph_part + WHERE label = source_part.label + AND record = target_record; + + -- ... and if there's a conflicting one on the target ... + IF FOUND THEN + + -- ... move the copy-part maps to that, and ... + UPDATE asset.copy_part_map + SET part = target_part.id + WHERE part = source_part.id; + + -- ... move P holds to the move-target part + FOR hold IN SELECT * FROM action.hold_request WHERE target = source_part.id AND hold_type = 'P' LOOP + + UPDATE action.hold_request + SET target = target_part.id + WHERE id = hold.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- ... if not ... + ELSE + -- ... just move the part to the target record + UPDATE biblio.monograph_part + SET record = target_record + WHERE id = source_part.id; + END IF; + + moved_objects := moved_objects + 1; + END LOOP; + + -- Find multi_home items attached to the source ... + FOR multi_home IN SELECT * FROM biblio.peer_bib_copy_map WHERE peer_record = source_record LOOP + -- ... and move them to the target record + UPDATE biblio.peer_bib_copy_map + SET peer_record = target_record + WHERE id = multi_home.id; + + moved_objects := moved_objects + 1; + END LOOP; + + -- And delete mappings where the item's home bib was merged with the peer bib + DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = ( + SELECT (SELECT record FROM asset.call_number WHERE id = call_number) + FROM asset.copy WHERE id = target_copy + ); + + -- Apply merge tracking + UPDATE biblio.record_entry + SET merge_date = NOW() WHERE id = target_record; + + UPDATE biblio.record_entry + SET merge_date = NOW(), merged_to = target_record + WHERE id = source_record; + + -- replace book bag entries of source_record with target_record + UPDATE container.biblio_record_entry_bucket_item + SET target_biblio_record_entry = target_record + WHERE bucket IN (SELECT id FROM container.biblio_record_entry_bucket WHERE btype = 'bookbag') + AND target_biblio_record_entry = source_record; + + -- Finally, "delete" the source record + DELETE FROM biblio.record_entry WHERE id = source_record; + + -- That's all, folks! + RETURN moved_objects; +END; +$func$ LANGUAGE plpgsql; + + +--SELECT evergreen.upgrade_deps_block_check('1109', :eg_version); + +DO $INSERT$ +BEGIN + IF evergreen.insert_on_deploy() THEN +INSERT into config.org_unit_setting_type (name, label, grp, description, datatype) +values ('circ.staff_placed_holds_fallback_to_ws_ou','Workstation OU fallback for staff-placed holds', + 'circ', 'For staff-placed holds, in the absence of a patron preferred pickup location, fall back to using the staff workstation OU (rather than patron home OU)', 'bool'); + END IF; +END $INSERT$; + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.2', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1110', :eg_version); + +DROP FUNCTION IF EXISTS unapi.biblio_record_entry_feed (BIGINT[], TEXT, TEXT[], TEXT, INT, HSTORE, HSTORE, + BOOL, TEXT, TEXT, TEXT, TEXT, TEXT, XML); +DROP FUNCTION IF EXISTS unapi.metabib_virtual_record_feed (BIGINT[], TEXT, TEXT[], TEXT, INT, HSTORE, HSTORE, + BOOL, TEXT, TEXT, TEXT, TEXT, TEXT, XML); + +CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL, pref_lib INT DEFAULT NULL ) RETURNS XML AS $F$ +DECLARE + layout unapi.bre_output_layout%ROWTYPE; + transform config.xml_transform%ROWTYPE; + item_format TEXT; + tmp_xml TEXT; + xmlns_uri TEXT := 'http://open-ils.org/spec/feed-xml/v1'; + ouid INT; + element_list TEXT[]; +BEGIN + + IF org = '-' OR org IS NULL THEN + SELECT shortname INTO org FROM evergreen.org_top(); + END IF; + + SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org; + SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format; + + IF layout.name IS NULL THEN + RETURN NULL::XML; + END IF; + + SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform; + xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri); + + -- Gather the bib xml + SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns, pref_lib)) INTO tmp_xml FROM UNNEST( id_list ) i; + + IF layout.title_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title; + END IF; + + IF layout.description_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description; + END IF; + + IF layout.creator_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator; + END IF; + + IF layout.update_ts_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts; + END IF; + + IF unapi_url IS NOT NULL THEN + EXECUTE $$SELECT XMLCONCAT( XMLELEMENT( name link, XMLATTRIBUTES( 'http://www.w3.org/1999/xhtml' AS xmlns, 'unapi-server' AS rel, $1 AS href, 'unapi' AS title)), $2)$$ INTO tmp_xml USING unapi_url, tmp_xml::XML; + END IF; + + IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF; + + element_list := regexp_split_to_array(layout.feed_top,E'\\.'); + FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP + EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML; + END LOOP; + + RETURN tmp_xml::XML; +END; +$F$ LANGUAGE PLPGSQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.metabib_virtual_record_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL, pref_lib INT DEFAULT NULL ) RETURNS XML AS $F$ +DECLARE + layout unapi.bre_output_layout%ROWTYPE; + transform config.xml_transform%ROWTYPE; + item_format TEXT; + tmp_xml TEXT; + xmlns_uri TEXT := 'http://open-ils.org/spec/feed-xml/v1'; + ouid INT; + element_list TEXT[]; +BEGIN + + IF org = '-' OR org IS NULL THEN + SELECT shortname INTO org FROM evergreen.org_top(); + END IF; + + SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org; + SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format; + + IF layout.name IS NULL THEN + RETURN NULL::XML; + END IF; + + SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform; + xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri); + + -- Gather the bib xml + SELECT XMLAGG( unapi.mmr(i, format, '', includes, org, depth, slimit, soffset, include_xmlns, pref_lib)) INTO tmp_xml FROM UNNEST( id_list ) i; + + IF layout.title_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title; + END IF; + + IF layout.description_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description; + END IF; + + IF layout.creator_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator; + END IF; + + IF layout.update_ts_element IS NOT NULL THEN + EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts; + END IF; + + IF unapi_url IS NOT NULL THEN + EXECUTE $$SELECT XMLCONCAT( XMLELEMENT( name link, XMLATTRIBUTES( 'http://www.w3.org/1999/xhtml' AS xmlns, 'unapi-server' AS rel, $1 AS href, 'unapi' AS title)), $2)$$ INTO tmp_xml USING unapi_url, tmp_xml::XML; + END IF; + + IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF; + + element_list := regexp_split_to_array(layout.feed_top,E'\\.'); + FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP + EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML; + END LOOP; + + RETURN tmp_xml::XML; +END; +$F$ LANGUAGE PLPGSQL STABLE; + + +--SELECT evergreen.upgrade_deps_block_check('1111', :eg_version); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT INTO permission.perm_list ( id, code, description ) VALUES + ( 594, 'ADMIN_COPY_ALERT_TYPE', oils_i18n_gettext( 594, + 'Administer copy alert types', 'ppl', 'description' )), + ( 595, 'CREATE_COPY_ALERT_TYPE', oils_i18n_gettext( 595, + 'Create copy alert types', 'ppl', 'description' )), + ( 596, 'UPDATE_COPY_ALERT_TYPE', oils_i18n_gettext( 596, + 'Update copy alert types', 'ppl', 'description' )), + ( 597, 'DELETE_COPY_ALERT_TYPE', oils_i18n_gettext( 597, + 'Delete copy alert types', 'ppl', 'description' )), + ( 598, 'ADMIN_COPY_ALERT_SUPPRESS', oils_i18n_gettext( 598, + 'Administer copy alert suppression', 'ppl', 'description' )), + ( 599, 'CREATE_COPY_ALERT_SUPPRESS', oils_i18n_gettext( 599, + 'Create copy alert suppression', 'ppl', 'description' )), + ( 600, 'UPDATE_COPY_ALERT_SUPPRESS', oils_i18n_gettext( 600, + 'Update copy alert suppression', 'ppl', 'description' )), + ( 601, 'DELETE_COPY_ALERT_SUPPRESS', oils_i18n_gettext( 601, + 'Delete copy alert suppression', 'ppl', 'description' )), + ( 602, 'ADMIN_COPY_ALERT', oils_i18n_gettext( 602, + 'Administer copy alerts', 'ppl', 'description' )), + ( 603, 'CREATE_COPY_ALERT', oils_i18n_gettext( 603, + 'Create copy alerts', 'ppl', 'description' )), + ( 604, 'VIEW_COPY_ALERT', oils_i18n_gettext( 604, + 'View copy alerts', 'ppl', 'description' )), + ( 605, 'UPDATE_COPY_ALERT', oils_i18n_gettext( 605, + 'Update copy alerts', 'ppl', 'description' )), + ( 606, 'DELETE_COPY_ALERT', oils_i18n_gettext( 606, + 'Delete copy alerts', 'ppl', 'description' )) +; +END IF; END $INSERT$; + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.3', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1112', :eg_version); + +-- Add an index to action.usr_circ_history (source_circ) to speed up aging circs and purging accounts + +/* KCLS already has this one. +CREATE INDEX action_usr_circ_history_source_circ_idx + ON action.usr_circ_history + USING btree + (source_circ); + */ + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.4', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1113', :eg_version); + +CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree( + old_year INTEGER, + user_id INTEGER, + org_unit_id INTEGER, + encumb_only BOOL DEFAULT FALSE, + include_desc BOOL DEFAULT TRUE +) RETURNS VOID AS $$ +DECLARE +-- +new_fund INT; +new_year INT := old_year + 1; +org_found BOOL; +perm_ous BOOL; +xfer_amount NUMERIC := 0; +roll_fund RECORD; +deb RECORD; +detail RECORD; +roll_distrib_forms BOOL; +-- +BEGIN + -- + -- Sanity checks + -- + IF old_year IS NULL THEN + RAISE EXCEPTION 'Input year argument is NULL'; + ELSIF old_year NOT BETWEEN 2008 and 2200 THEN + RAISE EXCEPTION 'Input year is out of range'; + END IF; + -- + IF user_id IS NULL THEN + RAISE EXCEPTION 'Input user id argument is NULL'; + END IF; + -- + IF org_unit_id IS NULL THEN + RAISE EXCEPTION 'Org unit id argument is NULL'; + ELSE + -- + -- Validate the org unit + -- + SELECT TRUE + INTO org_found + FROM actor.org_unit + WHERE id = org_unit_id; + -- + IF org_found IS NULL THEN + RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id; + ELSIF encumb_only THEN + SELECT INTO perm_ous value::BOOL FROM + actor.org_unit_ancestor_setting( + 'acq.fund.allow_rollover_without_money', org_unit_id + ); + IF NOT FOUND OR NOT perm_ous THEN + RAISE EXCEPTION 'Encumbrance-only rollover not permitted at org %', org_unit_id; + END IF; + END IF; + END IF; + -- + -- Loop over the propagable funds to identify the details + -- from the old fund plus the id of the new one, if it exists. + -- + FOR roll_fund in + SELECT + oldf.id AS old_fund, + oldf.org, + oldf.name, + oldf.currency_type, + oldf.code, + oldf.rollover, + newf.id AS new_fund_id + FROM + acq.fund AS oldf + LEFT JOIN acq.fund AS newf + ON ( oldf.code = newf.code AND oldf.org = newf.org ) + WHERE + oldf.year = old_year + AND oldf.propagate + AND newf.year = new_year + AND ( ( include_desc AND oldf.org IN ( SELECT id FROM actor.org_unit_descendants( org_unit_id ) ) ) + OR (NOT include_desc AND oldf.org = org_unit_id ) ) + LOOP + --RAISE NOTICE 'Processing fund %', roll_fund.old_fund; + -- + IF roll_fund.new_fund_id IS NULL THEN + -- + -- The old fund hasn't been propagated yet. Propagate it now. + -- + INSERT INTO acq.fund ( + org, + name, + year, + currency_type, + code, + rollover, + propagate, + balance_warning_percent, + balance_stop_percent + ) VALUES ( + roll_fund.org, + roll_fund.name, + new_year, + roll_fund.currency_type, + roll_fund.code, + true, + true, + roll_fund.balance_warning_percent, + roll_fund.balance_stop_percent + ) + RETURNING id INTO new_fund; + + PERFORM acq.copy_fund_tags(roll_fund.id,new_fund); + + ELSE + new_fund = roll_fund.new_fund_id; + END IF; + -- + -- Determine the amount to transfer + -- + SELECT amount + INTO xfer_amount + FROM acq.fund_spent_balance + WHERE fund = roll_fund.old_fund; + -- + IF xfer_amount <> 0 THEN + IF NOT encumb_only AND roll_fund.rollover THEN + -- + -- Transfer balance from old fund to new + -- + --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund; + -- + PERFORM acq.transfer_fund( + roll_fund.old_fund, + xfer_amount, + new_fund, + xfer_amount, + user_id, + 'Rollover' + ); + ELSE + -- + -- Transfer balance from old fund to the void + -- + -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund; + -- + PERFORM acq.transfer_fund( + roll_fund.old_fund, + xfer_amount, + NULL, + NULL, + user_id, + 'Rollover into the void' + ); + END IF; + END IF; + -- + IF roll_fund.rollover THEN + -- + -- Move any lineitems from the old fund to the new one + -- where the associated debit is an encumbrance. + -- + -- Any other tables tying expenditure details to funds should + -- receive similar treatment. At this writing there are none. + -- + UPDATE acq.lineitem_detail + SET fund = new_fund + WHERE + fund = roll_fund.old_fund -- this condition may be redundant + AND fund_debit in + ( + SELECT id + FROM acq.fund_debit + WHERE + fund = roll_fund.old_fund + AND encumbrance + ); + -- + -- Move encumbrance debits from the old fund to the new fund + -- + UPDATE acq.fund_debit + SET fund = new_fund + wHERE + fund = roll_fund.old_fund + AND encumbrance; + END IF; + + -- Rollover distribution formulae funds + SELECT INTO roll_distrib_forms value::BOOL FROM + actor.org_unit_ancestor_setting( + 'acq.fund.rollover_distrib_forms', org_unit_id + ); + + IF roll_distrib_forms THEN + UPDATE acq.distribution_formula_entry + SET fund = roll_fund.new_fund_id + WHERE fund = roll_fund.old_fund; + END IF; + + -- + -- Mark old fund as inactive, now that we've closed it + -- + UPDATE acq.fund + SET active = FALSE + WHERE id = roll_fund.old_fund; + END LOOP; +END; +$$ LANGUAGE plpgsql; + + + +--SELECT evergreen.upgrade_deps_block_check('1114', :eg_version); + +CREATE OR REPLACE FUNCTION asset.copy_state (cid BIGINT) RETURNS TEXT AS $$ +DECLARE + last_circ_stop TEXT; + the_copy asset.copy%ROWTYPE; +BEGIN + + SELECT * INTO the_copy FROM asset.copy WHERE id = cid; + IF NOT FOUND THEN RETURN NULL; END IF; + + IF the_copy.status = 3 THEN -- Lost + RETURN 'LOST'; + ELSIF the_copy.status = 4 THEN -- Missing + RETURN 'MISSING'; + ELSIF the_copy.status = 14 THEN -- Damaged + RETURN 'DAMAGED'; + ELSIF the_copy.status = 17 THEN -- Lost and paid + RETURN 'LOST_AND_PAID'; + END IF; + + SELECT stop_fines INTO last_circ_stop + FROM action.circulation + WHERE target_copy = cid AND checkin_time IS NULL + ORDER BY xact_start DESC LIMIT 1; + + IF FOUND THEN + IF last_circ_stop IN ( + 'CLAIMSNEVERCHECKEDOUT', + 'CLAIMSRETURNED', + 'LONGOVERDUE' + ) THEN + RETURN last_circ_stop; + END IF; + END IF; + + RETURN 'NORMAL'; +END; +$$ LANGUAGE PLPGSQL; + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.5', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1119', :eg_version); + +CREATE OR REPLACE FUNCTION asset.staff_ou_record_copy_count(org integer, rid bigint) + RETURNS TABLE(depth integer, org_unit integer, visible bigint, available bigint, unshadow bigint, transcendant integer) + LANGUAGE plpgsql +AS $function$ +DECLARE + ans RECORD; + trans INT; +BEGIN + SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid; + + FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP + RETURN QUERY + WITH available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available), + cp AS( + SELECT cp.id, + (cp.status = ANY (available_statuses.ids))::INT as available, + (cl.opac_visible AND cp.opac_visible)::INT as opac_visible + FROM + available_statuses, + actor.org_unit_descendants(ans.id) d + JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted) + JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted) + JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted) + ), + peer AS ( + SELECT cp.id, + (cp.status = ANY (available_statuses.ids))::INT as available, + (cl.opac_visible AND cp.opac_visible)::INT as opac_visible + FROM + available_statuses, + actor.org_unit_descendants(ans.id) d + JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted) + JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted) + JOIN biblio.peer_bib_copy_map bp ON (bp.peer_record = rid AND bp.target_copy = cp.id) + ) + SELECT ans.depth, ans.id, count(id), sum(x.available::int), sum(x.opac_visible::int), trans + FROM ((select * from cp) union (select * from peer)) x + GROUP BY 1,2,6; + + IF NOT FOUND THEN + RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans; + END IF; + + END LOOP; + RETURN; +END; +$function$; + +CREATE OR REPLACE FUNCTION asset.opac_ou_record_copy_count(org integer, rid bigint) + RETURNS TABLE(depth integer, org_unit integer, visible bigint, available bigint, unshadow bigint, transcendant integer) + LANGUAGE plpgsql +AS $function$ +DECLARE + ans RECORD; + trans INT; +BEGIN + SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid; + + FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP + RETURN QUERY + WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x), + available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available), + mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x) + SELECT ans.depth, + ans.id, + COUNT( av.id ), + SUM( (cp.status = ANY (available_statuses.ids))::INT ), + COUNT( av.id ), + trans + FROM mask, + available_statuses, + org_list, + asset.copy_vis_attr_cache av + JOIN asset.copy cp ON (cp.id = av.target_copy AND av.record = rid) + JOIN asset.call_number cn ON (cp.call_number = cn.id AND not cn.deleted) + WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int + GROUP BY 1,2,6; + + IF NOT FOUND THEN + RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans; + END IF; + + END LOOP; + + RETURN; +END; +$function$; + + +--INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.2-beta1', :eg_version); +--SELECT evergreen.upgrade_deps_block_check('1115', :eg_version); + + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT INTO permission.perm_list (id,code,description) VALUES ( 607, 'EMERGENCY_CLOSING', 'Create and manage Emergency Closings'); + +INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('checkout.due.emergency_closing','aecc','Circulation due date was adjusted by the Emergency Closing handler'); +INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('hold.shelf_expire.emergency_closing','aech','Hold shelf expire time was adjusted by the Emergency Closing handler'); +INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('booking.due.emergency_closing','aecr','Booking reservation return date was adjusted by the Emergency Closing handler'); +END IF; END $INSERT$; + +CREATE TABLE action.emergency_closing ( + id SERIAL PRIMARY KEY, + creator INT NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + create_time TIMESTAMPTZ NOT NULL DEFAULT NOW(), + process_start_time TIMESTAMPTZ, + process_end_time TIMESTAMPTZ, + last_update_time TIMESTAMPTZ +); + +ALTER TABLE actor.org_unit_closed + ADD COLUMN emergency_closing INT + REFERENCES action.emergency_closing (id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED; + +CREATE TABLE action.emergency_closing_circulation ( + id BIGSERIAL PRIMARY KEY, + emergency_closing INT NOT NULL REFERENCES action.emergency_closing (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + circulation INT NOT NULL REFERENCES action.circulation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + original_due_date TIMESTAMPTZ, + process_time TIMESTAMPTZ +); +CREATE INDEX emergency_closing_circulation_emergency_closing_idx ON action.emergency_closing_circulation (emergency_closing); +CREATE INDEX emergency_closing_circulation_circulation_idx ON action.emergency_closing_circulation (circulation); + +CREATE TABLE action.emergency_closing_reservation ( + id BIGSERIAL PRIMARY KEY, + emergency_closing INT NOT NULL REFERENCES action.emergency_closing (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + reservation INT NOT NULL REFERENCES booking.reservation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + original_end_time TIMESTAMPTZ, + process_time TIMESTAMPTZ +); +CREATE INDEX emergency_closing_reservation_emergency_closing_idx ON action.emergency_closing_reservation (emergency_closing); +CREATE INDEX emergency_closing_reservation_reservation_idx ON action.emergency_closing_reservation (reservation); + +CREATE TABLE action.emergency_closing_hold ( + id BIGSERIAL PRIMARY KEY, + emergency_closing INT NOT NULL REFERENCES action.emergency_closing (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + hold INT NOT NULL REFERENCES action.hold_request (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + original_shelf_expire_time TIMESTAMPTZ, + process_time TIMESTAMPTZ +); +CREATE INDEX emergency_closing_hold_emergency_closing_idx ON action.emergency_closing_hold (emergency_closing); +CREATE INDEX emergency_closing_hold_hold_idx ON action.emergency_closing_hold (hold); + +CREATE OR REPLACE VIEW action.emergency_closing_status AS + SELECT e.*, + COALESCE(c.count, 0) AS circulations, + COALESCE(c.completed, 0) AS circulations_complete, + COALESCE(b.count, 0) AS reservations, + COALESCE(b.completed, 0) AS reservations_complete, + COALESCE(h.count, 0) AS holds, + COALESCE(h.completed, 0) AS holds_complete + FROM action.emergency_closing e + LEFT JOIN (SELECT emergency_closing, count(*) count, SUM((process_time IS NOT NULL)::INT) completed FROM action.emergency_closing_circulation GROUP BY 1) c ON (c.emergency_closing = e.id) + LEFT JOIN (SELECT emergency_closing, count(*) count, SUM((process_time IS NOT NULL)::INT) completed FROM action.emergency_closing_reservation GROUP BY 1) b ON (b.emergency_closing = e.id) + LEFT JOIN (SELECT emergency_closing, count(*) count, SUM((process_time IS NOT NULL)::INT) completed FROM action.emergency_closing_hold GROUP BY 1) h ON (h.emergency_closing = e.id) +; + +CREATE OR REPLACE FUNCTION evergreen.find_next_open_time ( circ_lib INT, initial TIMESTAMPTZ, hourly BOOL DEFAULT FALSE, initial_time TIME DEFAULT NULL, dow_count INT DEFAULT 0 ) + RETURNS TIMESTAMPTZ AS $$ +DECLARE + day_number INT; + plus_days INT; + final_time TEXT; + time_adjusted BOOL; + hoo_open TIME WITHOUT TIME ZONE; + hoo_close TIME WITHOUT TIME ZONE; + adjacent actor.org_unit_closed%ROWTYPE; + breakout INT := 0; +BEGIN + + IF dow_count > 6 THEN + RETURN initial; + END IF; + + IF initial_time IS NULL THEN + initial_time := initial::TIME; + END IF; + + final_time := (initial + '1 second'::INTERVAL)::TEXT; + LOOP + breakout := breakout + 1; + + time_adjusted := FALSE; + + IF dow_count > 0 THEN -- we're recursing, so check for HOO closing + day_number := EXTRACT(ISODOW FROM final_time::TIMESTAMPTZ) - 1; + plus_days := 0; + FOR i IN 1..7 LOOP + EXECUTE 'SELECT dow_' || day_number || '_open, dow_' || day_number || '_close FROM actor.hours_of_operation WHERE id = $1' + INTO hoo_open, hoo_close + USING circ_lib; + + -- RAISE NOTICE 'initial time: %; dow: %; close: %',initial_time,day_number,hoo_close; + + IF hoo_close = '00:00:00' THEN -- bah ... I guess we'll check the next day + day_number := (day_number + 1) % 7; + plus_days := plus_days + 1; + time_adjusted := TRUE; + CONTINUE; + END IF; + + IF hoo_close IS NULL THEN -- no hours of operation ... assume no closing? + hoo_close := '23:59:59'; + END IF; + + EXIT; + END LOOP; + + final_time := DATE(final_time::TIMESTAMPTZ + (plus_days || ' days')::INTERVAL)::TEXT; + IF hoo_close <> '00:00:00' AND hourly THEN -- Not a day-granular circ + final_time := final_time||' '|| hoo_close; + ELSE + final_time := final_time||' 23:59:59'; + END IF; + END IF; + + -- Loop through other closings + LOOP + SELECT * INTO adjacent FROM actor.org_unit_closed WHERE org_unit = circ_lib AND final_time::TIMESTAMPTZ between close_start AND close_end; + EXIT WHEN adjacent.id IS NULL; + time_adjusted := TRUE; + -- RAISE NOTICE 'recursing for closings with final_time: %',final_time; + final_time := evergreen.find_next_open_time(circ_lib, adjacent.close_end::TIMESTAMPTZ, hourly, initial_time, dow_count + 1)::TEXT; + END LOOP; + + EXIT WHEN breakout > 100; + EXIT WHEN NOT time_adjusted; + + END LOOP; + + RETURN final_time; +END; +$$ LANGUAGE PLPGSQL; + +CREATE TYPE action.emergency_closing_stage_1_count AS (circulations INT, reservations INT, holds INT); +CREATE OR REPLACE FUNCTION action.emergency_closing_stage_1 ( e_closing INT ) + RETURNS SETOF action.emergency_closing_stage_1_count AS $$ +DECLARE + tmp INT; + touched action.emergency_closing_stage_1_count%ROWTYPE; +BEGIN + -- First, gather circs + INSERT INTO action.emergency_closing_circulation (emergency_closing, circulation) + SELECT e_closing, + circ.id + FROM actor.org_unit_closed closing + JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing) + JOIN action.circulation circ ON ( + circ.circ_lib = closing.org_unit + AND circ.due_date BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL) + AND circ.xact_finish IS NULL + ) + WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_circulation t WHERE t.emergency_closing = e_closing AND t.circulation = circ.id); + + GET DIAGNOSTICS tmp = ROW_COUNT; + touched.circulations := tmp; + + INSERT INTO action.emergency_closing_reservation (emergency_closing, reservation) + SELECT e_closing, + res.id + FROM actor.org_unit_closed closing + JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing) + JOIN booking.reservation res ON ( + res.pickup_lib = closing.org_unit + AND res.end_time BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL) + ) + WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_reservation t WHERE t.emergency_closing = e_closing AND t.reservation = res.id); + + GET DIAGNOSTICS tmp = ROW_COUNT; + touched.reservations := tmp; + + INSERT INTO action.emergency_closing_hold (emergency_closing, hold) + SELECT e_closing, + hold.id + FROM actor.org_unit_closed closing + JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing) + JOIN action.hold_request hold ON ( + pickup_lib = closing.org_unit + AND hold.shelf_expire_time BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL) + AND hold.fulfillment_time IS NULL + AND hold.cancel_time IS NULL + ) + WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_hold t WHERE t.emergency_closing = e_closing AND t.hold = hold.id); + + GET DIAGNOSTICS tmp = ROW_COUNT; + touched.holds := tmp; + + UPDATE action.emergency_closing + SET process_start_time = NOW(), + last_update_time = NOW() + WHERE id = e_closing; + + RETURN NEXT touched; +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION action.emergency_closing_stage_2_hold ( hold_closing_entry INT ) + RETURNS BOOL AS $$ +DECLARE + hold action.hold_request%ROWTYPE; + e_closing action.emergency_closing%ROWTYPE; + e_c_hold action.emergency_closing_hold%ROWTYPE; + closing actor.org_unit_closed%ROWTYPE; + day_number INT; + hoo_close TIME WITHOUT TIME ZONE; + plus_days INT; +BEGIN + -- Gather objects involved + SELECT * INTO e_c_hold + FROM action.emergency_closing_hold + WHERE id = hold_closing_entry; + + IF e_c_hold.process_time IS NOT NULL THEN + -- Already processed ... moving on + RETURN FALSE; + END IF; + + SELECT * INTO e_closing + FROM action.emergency_closing + WHERE id = e_c_hold.emergency_closing; + + IF e_closing.process_start_time IS NULL THEN + -- Huh... that's odd. And wrong. + RETURN FALSE; + END IF; + + SELECT * INTO closing + FROM actor.org_unit_closed + WHERE emergency_closing = e_closing.id; + + SELECT * INTO hold + FROM action.hold_request h + WHERE id = e_c_hold.hold; + + -- Record the processing + UPDATE action.emergency_closing_hold + SET original_shelf_expire_time = hold.shelf_expire_time, + process_time = NOW() + WHERE id = hold_closing_entry; + + UPDATE action.emergency_closing + SET last_update_time = NOW() + WHERE id = e_closing.id; + + UPDATE action.hold_request + SET shelf_expire_time = evergreen.find_next_open_time(closing.org_unit, hold.shelf_expire_time, TRUE) + WHERE id = hold.id; + + RETURN TRUE; +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION action.emergency_closing_stage_2_circ ( circ_closing_entry INT ) + RETURNS BOOL AS $$ +DECLARE + circ action.circulation%ROWTYPE; + e_closing action.emergency_closing%ROWTYPE; + e_c_circ action.emergency_closing_circulation%ROWTYPE; + closing actor.org_unit_closed%ROWTYPE; + adjacent actor.org_unit_closed%ROWTYPE; + bill money.billing%ROWTYPE; + last_bill money.billing%ROWTYPE; + day_number INT; + hoo_close TIME WITHOUT TIME ZONE; + plus_days INT; + avoid_negative BOOL; + extend_grace BOOL; + new_due_date TEXT; +BEGIN + -- Gather objects involved + SELECT * INTO e_c_circ + FROM action.emergency_closing_circulation + WHERE id = circ_closing_entry; + + IF e_c_circ.process_time IS NOT NULL THEN + -- Already processed ... moving on + RETURN FALSE; + END IF; + + SELECT * INTO e_closing + FROM action.emergency_closing + WHERE id = e_c_circ.emergency_closing; + + IF e_closing.process_start_time IS NULL THEN + -- Huh... that's odd. And wrong. + RETURN FALSE; + END IF; + + SELECT * INTO closing + FROM actor.org_unit_closed + WHERE emergency_closing = e_closing.id; + + SELECT * INTO circ + FROM action.circulation + WHERE id = e_c_circ.circulation; + + -- Record the processing + UPDATE action.emergency_closing_circulation + SET original_due_date = circ.due_date, + process_time = NOW() + WHERE id = circ_closing_entry; + + UPDATE action.emergency_closing + SET last_update_time = NOW() + WHERE id = e_closing.id; + + SELECT value::BOOL INTO avoid_negative FROM actor.org_unit_ancestor_setting('bill.prohibit_negative_balance_on_overdues', circ.circ_lib); + SELECT value::BOOL INTO extend_grace FROM actor.org_unit_ancestor_setting('circ.grace.extend', circ.circ_lib); + + new_due_date := evergreen.find_next_open_time( closing.org_unit, circ.due_date, EXTRACT(EPOCH FROM circ.duration)::INT % 86400 > 0 )::TEXT; + UPDATE action.circulation SET due_date = new_due_date::TIMESTAMPTZ WHERE id = circ.id; + + -- Now, see if we need to get rid of some fines + SELECT * INTO last_bill + FROM money.billing b + WHERE b.xact = circ.id + AND NOT b.voided + AND b.btype = 1 + ORDER BY billing_ts DESC + LIMIT 1; + + FOR bill IN + SELECT * + FROM money.billing b + WHERE b.xact = circ.id + AND b.btype = 1 + AND NOT b.voided + AND ( + b.billing_ts BETWEEN closing.close_start AND new_due_date::TIMESTAMPTZ + OR (extend_grace AND last_bill.billing_ts <= new_due_date::TIMESTAMPTZ + circ.grace_period) + ) + AND NOT EXISTS (SELECT 1 FROM money.account_adjustment a WHERE a.billing = b.id) + ORDER BY billing_ts + LOOP + IF avoid_negative THEN + PERFORM FROM money.materialized_billable_xact_summary WHERE id = circ.id AND balanced_owd < bill.amount; + EXIT WHEN FOUND; -- We can't go negative, and voiding this bill would do that... + END IF; + + UPDATE money.billing + SET voided = TRUE, + void_time = NOW(), + note = COALESCE(note,'') || ' :: Voided by emergency closing handler' + WHERE id = bill.id; + END LOOP; + + RETURN TRUE; +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION action.emergency_closing_stage_2_reservation ( res_closing_entry INT ) + RETURNS BOOL AS $$ +DECLARE + res booking.reservation%ROWTYPE; + e_closing action.emergency_closing%ROWTYPE; + e_c_res action.emergency_closing_reservation%ROWTYPE; + closing actor.org_unit_closed%ROWTYPE; + adjacent actor.org_unit_closed%ROWTYPE; + bill money.billing%ROWTYPE; + day_number INT; + hoo_close TIME WITHOUT TIME ZONE; + plus_days INT; + avoid_negative BOOL; + new_due_date TEXT; +BEGIN + -- Gather objects involved + SELECT * INTO e_c_res + FROM action.emergency_closing_reservation + WHERE id = res_closing_entry; + + IF e_c_res.process_time IS NOT NULL THEN + -- Already processed ... moving on + RETURN FALSE; + END IF; + + SELECT * INTO e_closing + FROM action.emergency_closing + WHERE id = e_c_res.emergency_closing; + + IF e_closing.process_start_time IS NULL THEN + -- Huh... that's odd. And wrong. + RETURN FALSE; + END IF; + + SELECT * INTO closing + FROM actor.org_unit_closed + WHERE emergency_closing = e_closing.id; + + SELECT * INTO res + FROM booking.reservation + WHERE id = e_c_res.reservation; + + IF res.pickup_lib IS NULL THEN -- Need to be far enough along to have a pickup lib + RETURN FALSE; + END IF; + + -- Record the processing + UPDATE action.emergency_closing_reservation + SET original_end_time = res.end_time, + process_time = NOW() + WHERE id = res_closing_entry; + + UPDATE action.emergency_closing + SET last_update_time = NOW() + WHERE id = e_closing.id; + + SELECT value::BOOL INTO avoid_negative FROM actor.org_unit_ancestor_setting('bill.prohibit_negative_balance_on_overdues', res.pickup_lib); + + new_due_date := evergreen.find_next_open_time( closing.org_unit, res.end_time, EXTRACT(EPOCH FROM res.booking_interval)::INT % 86400 > 0 )::TEXT; + UPDATE booking.reservation SET end_time = new_due_date::TIMESTAMPTZ WHERE id = res.id; + + -- Now, see if we need to get rid of some fines + FOR bill IN + SELECT * + FROM money.billing b + WHERE b.xact = res.id + AND b.btype = 1 + AND NOT b.voided + AND b.billing_ts BETWEEN closing.close_start AND new_due_date::TIMESTAMPTZ + AND NOT EXISTS (SELECT 1 FROM money.account_adjustment a WHERE a.billing = b.id) + LOOP + IF avoid_negative THEN + PERFORM FROM money.materialized_billable_xact_summary WHERE id = res.id AND balanced_owd < bill.amount; + EXIT WHEN FOUND; -- We can't go negative, and voiding this bill would do that... + END IF; + + UPDATE money.billing + SET voided = TRUE, + void_time = NOW(), + note = COALESCE(note,'') || ' :: Voided by emergency closing handler' + WHERE id = bill.id; + END LOOP; + + RETURN TRUE; +END; +$$ LANGUAGE PLPGSQL; + + + +CREATE TYPE actor.cascade_setting_summary AS ( + name TEXT, + value JSON, + has_org_setting BOOLEAN, + has_user_setting BOOLEAN, + has_workstation_setting BOOLEAN +); + +--SELECT evergreen.upgrade_deps_block_check('1116', :eg_version); + +CREATE TABLE config.workstation_setting_type ( + name TEXT PRIMARY KEY, + label TEXT UNIQUE NOT NULL, + grp TEXT REFERENCES config.settings_group (name), + description TEXT, + datatype TEXT NOT NULL DEFAULT 'string', + fm_class TEXT, + -- + -- define valid datatypes + -- + CONSTRAINT cwst_valid_datatype CHECK ( datatype IN + ( 'bool', 'integer', 'float', 'currency', 'interval', + 'date', 'string', 'object', 'array', 'link' ) ), + -- + -- fm_class is meaningful only for 'link' datatype + -- + CONSTRAINT cwst_no_empty_link CHECK + ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR + ( datatype <> 'link' AND fm_class IS NULL ) ) +); + +CREATE TABLE actor.workstation_setting ( + id SERIAL PRIMARY KEY, + workstation INT NOT NULL REFERENCES actor.workstation (id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + name TEXT NOT NULL REFERENCES config.workstation_setting_type (name) + ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + value JSON NOT NULL +); + + +CREATE INDEX actor_workstation_setting_workstation_idx + ON actor.workstation_setting (workstation); + +CREATE OR REPLACE FUNCTION config.setting_is_user_or_ws() +RETURNS TRIGGER AS $FUNC$ +BEGIN + + IF TG_TABLE_NAME = 'usr_setting_type' THEN + PERFORM TRUE FROM config.workstation_setting_type cwst + WHERE cwst.name = NEW.name; + IF NOT FOUND THEN + RETURN NULL; + END IF; + END IF; + + IF TG_TABLE_NAME = 'workstation_setting_type' THEN + PERFORM TRUE FROM config.usr_setting_type cust + WHERE cust.name = NEW.name; + IF NOT FOUND THEN + RETURN NULL; + END IF; + END IF; + + RAISE EXCEPTION + '% Cannot be used as both a user setting and a workstation setting.', + NEW.name; +END; +$FUNC$ LANGUAGE PLPGSQL STABLE; + +CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws + AFTER INSERT OR UPDATE ON config.usr_setting_type + FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws(); + +CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws + AFTER INSERT OR UPDATE ON config.workstation_setting_type + FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws(); + +CREATE OR REPLACE FUNCTION actor.get_cascade_setting( + setting_name TEXT, org_id INT, user_id INT, workstation_id INT) + RETURNS actor.cascade_setting_summary AS +$FUNC$ +DECLARE + setting_value JSON; + summary actor.cascade_setting_summary; + org_setting_type config.org_unit_setting_type%ROWTYPE; +BEGIN + + summary.name := setting_name; + + -- Collect the org setting type status first in case we exit early. + -- The existance of an org setting type is not considered + -- privileged information. + SELECT INTO org_setting_type * + FROM config.org_unit_setting_type WHERE name = setting_name; + IF FOUND THEN + summary.has_org_setting := TRUE; + ELSE + summary.has_org_setting := FALSE; + END IF; + + -- User and workstation settings have the same priority. + -- Start with user settings since that's the simplest code path. + -- The workstation_id is ignored if no user_id is provided. + IF user_id IS NOT NULL THEN + + SELECT INTO summary.value value FROM actor.usr_setting + WHERE usr = user_id AND name = setting_name; + + IF FOUND THEN + -- if we have a value, we have a setting type + summary.has_user_setting := TRUE; + + IF workstation_id IS NOT NULL THEN + -- Only inform the caller about the workstation + -- setting type disposition when a workstation id is + -- provided. Otherwise, it's NULL to indicate UNKNOWN. + summary.has_workstation_setting := FALSE; + END IF; + + RETURN summary; + END IF; + + -- no user setting value, but a setting type may exist + SELECT INTO summary.has_user_setting EXISTS ( + SELECT TRUE FROM config.usr_setting_type + WHERE name = setting_name + ); + + IF workstation_id IS NOT NULL THEN + + IF NOT summary.has_user_setting THEN + -- A workstation setting type may only exist when a user + -- setting type does not. + + SELECT INTO summary.value value + FROM actor.workstation_setting + WHERE workstation = workstation_id AND name = setting_name; + + IF FOUND THEN + -- if we have a value, we have a setting type + summary.has_workstation_setting := TRUE; + RETURN summary; + END IF; + + -- no value, but a setting type may exist + SELECT INTO summary.has_workstation_setting EXISTS ( + SELECT TRUE FROM config.workstation_setting_type + WHERE name = setting_name + ); + END IF; + + -- Finally make use of the workstation to determine the org + -- unit if none is provided. + IF org_id IS NULL AND summary.has_org_setting THEN + SELECT INTO org_id owning_lib + FROM actor.workstation WHERE id = workstation_id; + END IF; + END IF; + END IF; + + -- Some org unit settings are protected by a view permission. + -- First see if we have any data that needs protecting, then + -- check the permission if needed. + + IF NOT summary.has_org_setting THEN + RETURN summary; + END IF; + + -- avoid putting the value into the summary until we confirm + -- the value should be visible to the caller. + SELECT INTO setting_value value + FROM actor.org_unit_ancestor_setting(setting_name, org_id); + + IF NOT FOUND THEN + -- No value found -- perm check is irrelevant. + RETURN summary; + END IF; + + IF org_setting_type.view_perm IS NOT NULL THEN + + IF user_id IS NULL THEN + RAISE NOTICE 'Perm check required but no user_id provided'; + RETURN summary; + END IF; + + IF NOT permission.usr_has_perm( + user_id, (SELECT code FROM permission.perm_list + WHERE id = org_setting_type.view_perm), org_id) + THEN + RAISE NOTICE 'Perm check failed for user % on %', + user_id, org_setting_type.view_perm; + RETURN summary; + END IF; + END IF; + + -- Perm check succeeded or was not necessary. + summary.value := setting_value; + RETURN summary; +END; +$FUNC$ LANGUAGE PLPGSQL; + + +CREATE OR REPLACE FUNCTION actor.get_cascade_setting_batch( + setting_names TEXT[], org_id INT, user_id INT, workstation_id INT) + RETURNS SETOF actor.cascade_setting_summary AS +$FUNC$ +-- Returns a row per setting matching the setting name order. If no +-- value is applied, NULL is returned to retain name-response ordering. +DECLARE + setting_name TEXT; + summary actor.cascade_setting_summary; +BEGIN + FOREACH setting_name IN ARRAY setting_names LOOP + SELECT INTO summary * FROM actor.get_cascade_setting( + setting_Name, org_id, user_id, workstation_id); + RETURN NEXT summary; + END LOOP; +END; +$FUNC$ LANGUAGE PLPGSQL; + + + + + +--SELECT evergreen.upgrade_deps_block_check('1117', :eg_version); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO permission.perm_list (id, code, description) VALUES + (608, 'APPLY_WORKSTATION_SETTING', + oils_i18n_gettext(608, 'APPLY_WORKSTATION_SETTING', 'ppl', 'description')); + +INSERT INTO config.workstation_setting_type (name, grp, datatype, label) +VALUES ( + 'eg.circ.checkin.no_precat_alert', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.no_precat_alert', + 'Checkin: Ignore Precataloged Items', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.noop', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.noop', + 'Checkin: Suppress Holds and Transits', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.void_overdues', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.void_overdues', + 'Checkin: Amnesty Mode', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.auto_print_holds_transits', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.auto_print_holds_transits', + 'Checkin: Auto-Print Holds and Transits', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.clear_expired', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.clear_expired', + 'Checkin: Clear Holds Shelf', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.retarget_holds', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.retarget_holds', + 'Checkin: Retarget Local Holds', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.retarget_holds_all', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.retarget_holds_all', + 'Checkin: Retarget All Statuses', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.hold_as_transit', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.hold_as_transit', + 'Checkin: Capture Local Holds as Transits', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.manual_float', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.manual_float', + 'Checkin: Manual Floating Active', + 'cwst', 'label' + ) +), ( + 'eg.circ.patron.summary.collapse', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.patron.summary.collapse', + 'Collaps Patron Summary Display', + 'cwst', 'label' + ) +), ( + 'circ.bills.receiptonpay', 'circ', 'bool', + oils_i18n_gettext( + 'circ.bills.receiptonpay', + 'Print Receipt On Payment', + 'cwst', 'label' + ) +), ( + 'circ.renew.strict_barcode', 'circ', 'bool', + oils_i18n_gettext( + 'circ.renew.strict_barcode', + 'Renew: Strict Barcode', + 'cwst', 'label' + ) +), ( + 'circ.checkin.strict_barcode', 'circ', 'bool', + oils_i18n_gettext( + 'circ.checkin.strict_barcode', + 'Checkin: Strict Barcode', + 'cwst', 'label' + ) +), ( + 'circ.checkout.strict_barcode', 'circ', 'bool', + oils_i18n_gettext( + 'circ.checkout.strict_barcode', + 'Checkout: Strict Barcode', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_copies', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_copies', + 'Holdings View Show Copies', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_empty', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_empty', + 'Holdings View Show Empty Volumes', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_empty_org', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_empty_org', + 'Holdings View Show Empty Orgs', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_vols', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_vols', + 'Holdings View Show Volumes', + 'cwst', 'label' + ) +), ( + 'cat.copy.defaults', 'cat', 'object', + oils_i18n_gettext( + 'cat.copy.defaults', + 'Copy Edit Default Values', + 'cwst', 'label' + ) +), ( + 'cat.printlabels.default_template', 'cat', 'string', + oils_i18n_gettext( + 'cat.printlabels.default_template', + 'Print Label Default Template', + 'cwst', 'label' + ) +), ( + 'cat.printlabels.templates', 'cat', 'object', + oils_i18n_gettext( + 'cat.printlabels.templates', + 'Print Label Templates', + 'cwst', 'label' + ) +), ( + 'eg.circ.patron.search.include_inactive', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.patron.search.include_inactive', + 'Patron Search Include Inactive', + 'cwst', 'label' + ) +), ( + 'eg.circ.patron.search.show_extras', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.patron.search.show_extras', + 'Patron Search Show Extra Search Options', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.checkin.checkin', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.checkin.checkin', + 'Grid Config: circ.checkin.checkin', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.checkin.capture', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.checkin.capture', + 'Grid Config: circ.checkin.capture', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.copy_tag_type', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.copy_tag_type', + 'Grid Config: admin.server.config.copy_tag_type', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.metabib_field_virtual_map.grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.metabib_field_virtual_map.grid', + 'Grid Config: admin.server.config.metabib_field_virtual_map.grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.metabib_field.grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.metabib_field.grid', + 'Grid Config: admin.server.config.metabib_field.grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.marc_field', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.marc_field', + 'Grid Config: admin.server.config.marc_field', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.asset.copy_tag', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.asset.copy_tag', + 'Grid Config: admin.server.asset.copy_tag', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.local.circ.neg_balance_users', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.local.circ.neg_balance_users', + 'Grid Config: admin.local.circ.neg_balance_users', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.local.rating.badge', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.local.rating.badge', + 'Grid Config: admin.local.rating.badge', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.workstation.work_log', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.workstation.work_log', + 'Grid Config: admin.workstation.work_log', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.workstation.patron_log', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.workstation.patron_log', + 'Grid Config: admin.workstation.patron_log', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.serials.pattern_template', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.serials.pattern_template', + 'Grid Config: admin.serials.pattern_template', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.copy_templates', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.copy_templates', + 'Grid Config: serials.copy_templates', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.record_overlay.holdings', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.record_overlay.holdings', + 'Grid Config: cat.record_overlay.holdings', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.record.search', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.record.search', + 'Grid Config: cat.bucket.record.search', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.record.view', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.record.view', + 'Grid Config: cat.bucket.record.view', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.record.pending', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.record.pending', + 'Grid Config: cat.bucket.record.pending', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.copy.view', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.copy.view', + 'Grid Config: cat.bucket.copy.view', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.copy.pending', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.copy.pending', + 'Grid Config: cat.bucket.copy.pending', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.items', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.items', + 'Grid Config: cat.items', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.volcopy.copies', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.volcopy.copies', + 'Grid Config: cat.volcopy.copies', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.volcopy.copies.complete', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.volcopy.copies.complete', + 'Grid Config: cat.volcopy.copies.complete', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.peer_bibs', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.peer_bibs', + 'Grid Config: cat.peer_bibs', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.catalog.holds', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.catalog.holds', + 'Grid Config: cat.catalog.holds', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.holdings', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.holdings', + 'Grid Config: cat.holdings', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.z3950_results', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.z3950_results', + 'Grid Config: cat.z3950_results', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.holds.shelf', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.holds.shelf', + 'Grid Config: circ.holds.shelf', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.holds.pull', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.holds.pull', + 'Grid Config: circ.holds.pull', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.in_house_use', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.in_house_use', + 'Grid Config: circ.in_house_use', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.renew', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.renew', + 'Grid Config: circ.renew', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.transits.list', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.transits.list', + 'Grid Config: circ.transits.list', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.holds', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.holds', + 'Grid Config: circ.patron.holds', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.pending_patrons.list', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.pending_patrons.list', + 'Grid Config: circ.pending_patrons.list', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.items_out.noncat', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.items_out.noncat', + 'Grid Config: circ.patron.items_out.noncat', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.items_out', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.items_out', + 'Grid Config: circ.patron.items_out', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.billhistory_payments', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.billhistory_payments', + 'Grid Config: circ.patron.billhistory_payments', + 'cwst', 'label' + ) +), ( + 'eg.grid.user.bucket.view', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.user.bucket.view', + 'Grid Config: user.bucket.view', + 'cwst', 'label' + ) +), ( + 'eg.grid.user.bucket.pending', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.user.bucket.pending', + 'Grid Config: user.bucket.pending', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.staff_messages', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.staff_messages', + 'Grid Config: circ.patron.staff_messages', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.archived_messages', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.archived_messages', + 'Grid Config: circ.patron.archived_messages', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.bills', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.bills', + 'Grid Config: circ.patron.bills', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.checkout', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.checkout', + 'Grid Config: circ.patron.checkout', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.mfhd_grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.mfhd_grid', + 'Grid Config: serials.mfhd_grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.view_item_grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.view_item_grid', + 'Grid Config: serials.view_item_grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.dist_stream_grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.dist_stream_grid', + 'Grid Config: serials.dist_stream_grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.search', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.search', + 'Grid Config: circ.patron.search', + 'cwst', 'label' + ) +), ( + 'eg.cat.record.summary.collapse', 'gui', 'bool', + oils_i18n_gettext( + 'eg.cat.record.summary.collapse', + 'Collapse Bib Record Summary', + 'cwst', 'label' + ) +), ( + 'cat.marcedit.flateditor', 'gui', 'bool', + oils_i18n_gettext( + 'cat.marcedit.flateditor', + 'Use Flat MARC Editor', + 'cwst', 'label' + ) +), ( + 'cat.marcedit.stack_subfields', 'gui', 'bool', + oils_i18n_gettext( + 'cat.marcedit.stack_subfields', + 'MARC Editor Stack Subfields', + 'cwst', 'label' + ) +), ( + 'eg.offline.print_receipt', 'gui', 'bool', + oils_i18n_gettext( + 'eg.offline.print_receipt', + 'Offline Print Receipt', + 'cwst', 'label' + ) +), ( + 'eg.offline.strict_barcode', 'gui', 'bool', + oils_i18n_gettext( + 'eg.offline.strict_barcode', + 'Offline Use Strict Barcode', + 'cwst', 'label' + ) +), ( + 'cat.default_bib_marc_template', 'gui', 'string', + oils_i18n_gettext( + 'cat.default_bib_marc_template', + 'Default MARC Template', + 'cwst', 'label' + ) +), ( + 'eg.audio.disable', 'gui', 'bool', + oils_i18n_gettext( + 'eg.audio.disable', + 'Disable Staff Client Notification Audio', + 'cwst', 'label' + ) +), ( + 'eg.search.adv_pane', 'gui', 'string', + oils_i18n_gettext( + 'eg.search.adv_pane', + 'Catalog Advanced Search Default Pane', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.bills_current', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.bills_current', + 'Print Template Context: bills_current', + 'cwst', 'label' + ) +), ( + 'eg.print.template.bills_current', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.bills_current', + 'Print Template: bills_current', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.bills_historical', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.bills_historical', + 'Print Template Context: bills_historical', + 'cwst', 'label' + ) +), ( + 'eg.print.template.bills_historical', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.bills_historical', + 'Print Template: bills_historical', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.bill_payment', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.bill_payment', + 'Print Template Context: bill_payment', + 'cwst', 'label' + ) +), ( + 'eg.print.template.bill_payment', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.bill_payment', + 'Print Template: bill_payment', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.checkin', + 'Print Template Context: checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template.checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.checkin', + 'Print Template: checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.checkout', + 'Print Template Context: checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template.checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.checkout', + 'Print Template: checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_transit_slip', + 'Print Template Context: hold_transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_transit_slip', + 'Print Template: hold_transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_shelf_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_shelf_slip', + 'Print Template Context: hold_shelf_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_shelf_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_shelf_slip', + 'Print Template: hold_shelf_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.holds_for_bib', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.holds_for_bib', + 'Print Template Context: holds_for_bib', + 'cwst', 'label' + ) +), ( + 'eg.print.template.holds_for_bib', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.holds_for_bib', + 'Print Template: holds_for_bib', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.holds_for_patron', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.holds_for_patron', + 'Print Template Context: holds_for_patron', + 'cwst', 'label' + ) +), ( + 'eg.print.template.holds_for_patron', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.holds_for_patron', + 'Print Template: holds_for_patron', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_pull_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_pull_list', + 'Print Template Context: hold_pull_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_pull_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_pull_list', + 'Print Template: hold_pull_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_shelf_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_shelf_list', + 'Print Template Context: hold_shelf_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_shelf_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_shelf_list', + 'Print Template: hold_shelf_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.in_house_use_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.in_house_use_list', + 'Print Template Context: in_house_use_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.in_house_use_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.in_house_use_list', + 'Print Template: in_house_use_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.item_status', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.item_status', + 'Print Template Context: item_status', + 'cwst', 'label' + ) +), ( + 'eg.print.template.item_status', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.item_status', + 'Print Template: item_status', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.items_out', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.items_out', + 'Print Template Context: items_out', + 'cwst', 'label' + ) +), ( + 'eg.print.template.items_out', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.items_out', + 'Print Template: items_out', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.patron_address', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.patron_address', + 'Print Template Context: patron_address', + 'cwst', 'label' + ) +), ( + 'eg.print.template.patron_address', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.patron_address', + 'Print Template: patron_address', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.patron_data', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.patron_data', + 'Print Template Context: patron_data', + 'cwst', 'label' + ) +), ( + 'eg.print.template.patron_data', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.patron_data', + 'Print Template: patron_data', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.patron_note', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.patron_note', + 'Print Template Context: patron_note', + 'cwst', 'label' + ) +), ( + 'eg.print.template.patron_note', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.patron_note', + 'Print Template: patron_note', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.renew', + 'Print Template Context: renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template.renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.renew', + 'Print Template: renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.transit_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.transit_list', + 'Print Template Context: transit_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.transit_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.transit_list', + 'Print Template: transit_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.transit_slip', + 'Print Template Context: transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template.transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.transit_slip', + 'Print Template: transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_checkout', + 'Print Template Context: offline_checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_checkout', + 'Print Template: offline_checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_renew', + 'Print Template Context: offline_renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_renew', + 'Print Template: offline_renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_checkin', + 'Print Template Context: offline_checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_checkin', + 'Print Template: offline_checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_in_house_use', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_in_house_use', + 'Print Template Context: offline_in_house_use', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_in_house_use', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_in_house_use', + 'Print Template: offline_in_house_use', + 'cwst', 'label' + ) +), ( + 'eg.serials.stream_names', 'gui', 'array', + oils_i18n_gettext( + 'eg.serials.stream_names', + 'Serials Local Stream Names', + 'cwst', 'label' + ) +), ( + 'eg.serials.items.do_print_routing_lists', 'gui', 'bool', + oils_i18n_gettext( + 'eg.serials.items.do_print_routing_lists', + 'Serials Print Routing Lists', + 'cwst', 'label' + ) +), ( + 'eg.serials.items.receive_and_barcode', 'gui', 'bool', + oils_i18n_gettext( + 'eg.serials.items.receive_and_barcode', + 'Serials Barcode On Receive', + 'cwst', 'label' + ) +); + + +-- More values with fm_class'es +INSERT INTO config.workstation_setting_type (name, grp, datatype, fm_class, label) +VALUES ( + 'eg.search.search_lib', 'gui', 'link', 'aou', + oils_i18n_gettext( + 'eg.search.search_lib', + 'Staff Catalog Default Search Library', + 'cwst', 'label' + ) +), ( + 'eg.search.pref_lib', 'gui', 'link', 'aou', + oils_i18n_gettext( + 'eg.search.pref_lib', + 'Staff Catalog Preferred Library', + 'cwst', 'label' + ) +); + + +END IF; END $INSERT$; + + +--SELECT evergreen.upgrade_deps_block_check('1118', :eg_version); + +UPDATE action_trigger.event_definition +SET template = +$$ +[%- USE date -%] +[%- SET user = target.0.owner -%] +To: [%- params.recipient_email || user.email %] +From: [%- params.sender_email || default_sender %] +Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %] +Subject: Bibliographic Records +Auto-Submitted: auto-generated + +[% FOR cbreb IN target %] +[% FOR item IN cbreb.items; + bre_id = item.target_biblio_record_entry; + + bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'}); + title = ''; + FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]'); + title = title _ part.textContent; + END; + + author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent; + item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value'); + publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent; + pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent; + isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent; + issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent; + upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent; +%] + +[% loop.count %]/[% loop.size %]. Bib ID# [% bre_id %] +[% IF isbn %]ISBN: [% isbn _ "\n" %][% END -%] +[% IF issn %]ISSN: [% issn _ "\n" %][% END -%] +[% IF upc %]UPC: [% upc _ "\n" %] [% END -%] +Title: [% title %] +Author: [% author %] +Publication Info: [% publisher %] [% pubdate %] +Item Type: [% item_type %] + +[% END %] +[% END %] +$$ +WHERE hook = 'biblio.format.record_entry.email' +-- from previous stock definition +AND MD5(template) = 'ee4e6c1b3049086c570c7a77413d46c1'; + +UPDATE action_trigger.event_definition +SET template = +$$ +
+ +
    + [% FOR cbreb IN target %] + [% FOR item IN cbreb.items; + bre_id = item.target_biblio_record_entry; + + bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'}); + title = ''; + FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]'); + title = title _ part.textContent; + END; + + author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent; + item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value'); + publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent; + pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent; + isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent; + %] + +
  1. + Bib ID# [% bre_id %] ISBN: [% isbn %]
    + Title: [% title %]
    + Author: [% author %]
    + Publication Info: [% publisher %] [% pubdate %]
    + Item Type: [% item_type %] +
  2. + [% END %] + [% END %] +
+
+$$ +WHERE hook = 'biblio.format.record_entry.print' +-- from previous stock definition +AND MD5(template) = '9ada7ea8417cb23f89d0dc8f15ec68d0'; + + +--SELECT evergreen.upgrade_deps_block_check('1120', :eg_version); + +--Only insert if the attributes are not already present + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO config.z3950_attr (source, name, label, code, format, truncation) +SELECT 'oclc','upc','UPC','1007','6','0' +WHERE NOT EXISTS (SELECT name FROM config.z3950_attr WHERE source = 'oclc' AND name = 'upc'); + +INSERT INTO config.z3950_attr (source, name, label, code, format, truncation) +SELECT 'loc','upc','UPC','1007','1','1' +WHERE NOT EXISTS (SELECT name FROM config.z3950_attr WHERE source = 'loc' AND name = 'upc'); + +END IF; END $INSERT$; + +--SELECT evergreen.upgrade_deps_block_check('1121', :eg_version); + +CREATE TABLE permission.grp_tree_display_entry ( + id SERIAL PRIMARY KEY, + position INTEGER NOT NULL, + org INTEGER NOT NULL REFERENCES actor.org_unit (id) + DEFERRABLE INITIALLY DEFERRED, + grp INTEGER NOT NULL REFERENCES permission.grp_tree (id) + DEFERRABLE INITIALLY DEFERRED, + CONSTRAINT pgtde_once_per_org UNIQUE (org, grp) +); + +ALTER TABLE permission.grp_tree_display_entry + ADD COLUMN parent integer REFERENCES permission.grp_tree_display_entry (id) + DEFERRABLE INITIALLY DEFERRED; + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT INTO permission.perm_list (id, code, description) +VALUES (609, 'MANAGE_CUSTOM_PERM_GRP_TREE', oils_i18n_gettext( 609, + 'Allows a user to manage custom permission group lists.', 'ppl', 'description' )); +END IF; END $INSERT$; + +--SELECT evergreen.upgrade_deps_block_check('1122', :eg_version); + +ALTER TABLE actor.usr + ADD COLUMN pref_prefix TEXT, + ADD COLUMN pref_first_given_name TEXT, + ADD COLUMN pref_second_given_name TEXT, + ADD COLUMN pref_family_name TEXT, + ADD COLUMN pref_suffix TEXT, + ADD COLUMN name_keywords TEXT, + ADD COLUMN name_kw_tsvector TSVECTOR; + +ALTER TABLE staging.user_stage + ADD COLUMN pref_first_given_name TEXT, + ADD COLUMN pref_second_given_name TEXT, + ADD COLUMN pref_family_name TEXT; + +CREATE INDEX actor_usr_pref_first_given_name_idx + ON actor.usr (evergreen.lowercase(pref_first_given_name)); +CREATE INDEX actor_usr_pref_second_given_name_idx + ON actor.usr (evergreen.lowercase(pref_second_given_name)); +CREATE INDEX actor_usr_pref_family_name_idx + ON actor.usr (evergreen.lowercase(pref_family_name)); +CREATE INDEX actor_usr_pref_first_given_name_unaccent_idx + ON actor.usr (evergreen.unaccent_and_squash(pref_first_given_name)); +CREATE INDEX actor_usr_pref_second_given_name_unaccent_idx + ON actor.usr (evergreen.unaccent_and_squash(pref_second_given_name)); +CREATE INDEX actor_usr_pref_family_name_unaccent_idx + ON actor.usr (evergreen.unaccent_and_squash(pref_family_name)); + +CREATE OR REPLACE FUNCTION actor.user_ingest_name_keywords() + RETURNS TRIGGER AS $func$ +BEGIN + NEW.name_kw_tsvector := TO_TSVECTOR( + COALESCE(NEW.prefix, '') || ' ' || + COALESCE(NEW.first_given_name, '') || ' ' || + COALESCE(evergreen.unaccent_and_squash(NEW.first_given_name), '') || ' ' || + COALESCE(NEW.second_given_name, '') || ' ' || + COALESCE(evergreen.unaccent_and_squash(NEW.second_given_name), '') || ' ' || + COALESCE(NEW.family_name, '') || ' ' || + COALESCE(evergreen.unaccent_and_squash(NEW.family_name), '') || ' ' || + COALESCE(NEW.suffix, '') || ' ' || + COALESCE(NEW.pref_prefix, '') || ' ' || + COALESCE(NEW.pref_first_given_name, '') || ' ' || + COALESCE(evergreen.unaccent_and_squash(NEW.pref_first_given_name), '') || ' ' || + COALESCE(NEW.pref_second_given_name, '') || ' ' || + COALESCE(evergreen.unaccent_and_squash(NEW.pref_second_given_name), '') || ' ' || + COALESCE(NEW.pref_family_name, '') || ' ' || + COALESCE(evergreen.unaccent_and_squash(NEW.pref_family_name), '') || ' ' || + COALESCE(NEW.pref_suffix, '') || ' ' || + COALESCE(NEW.name_keywords, '') + ); + RETURN NEW; +END; +$func$ LANGUAGE PLPGSQL; + +-- Add after the batch upate above to avoid duplicate updates. +CREATE TRIGGER user_ingest_name_keywords_tgr + BEFORE INSERT OR UPDATE ON actor.usr + FOR EACH ROW EXECUTE PROCEDURE actor.user_ingest_name_keywords(); + +-- merge pref names from source user to target user, except when +-- clobbering existing pref names. +CREATE OR REPLACE FUNCTION actor.usr_merge(src_usr INT, dest_usr INT, + del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) + RETURNS VOID AS $$ +DECLARE + suffix TEXT; + bucket_row RECORD; + picklist_row RECORD; + queue_row RECORD; + folder_row RECORD; +BEGIN + + -- do some initial cleanup + UPDATE actor.usr SET card = NULL WHERE id = src_usr; + UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr; + UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr; + + -- actor.* + IF del_cards THEN + DELETE FROM actor.card where usr = src_usr; + ELSE + IF deactivate_cards THEN + UPDATE actor.card SET active = 'f' WHERE usr = src_usr; + END IF; + UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr; + END IF; + + + IF del_addrs THEN + DELETE FROM actor.usr_address WHERE usr = src_usr; + ELSE + UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr; + END IF; + + UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr; + -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them... + UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr; + PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr); + + -- permission.* + PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr); + + + -- container.* + + -- For each *_bucket table: transfer every bucket belonging to src_usr + -- into the custody of dest_usr. + -- + -- In order to avoid colliding with an existing bucket owned by + -- the destination user, append the source user's id (in parenthesese) + -- to the name. If you still get a collision, add successive + -- spaces to the name and keep trying until you succeed. + -- + FOR bucket_row in + SELECT id, name + FROM container.biblio_record_entry_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.biblio_record_entry_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR bucket_row in + SELECT id, name + FROM container.call_number_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.call_number_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR bucket_row in + SELECT id, name + FROM container.copy_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.copy_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR bucket_row in + SELECT id, name + FROM container.user_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.user_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr; + + -- vandelay.* + -- transfer queues the same way we transfer buckets (see above) + FOR queue_row in + SELECT id, name + FROM vandelay.queue + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE vandelay.queue + SET owner = dest_usr, name = name || suffix + WHERE id = queue_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + -- money.* + PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr); + UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr; + UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr; + UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr; + + -- action.* + UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr; + UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr; + UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr; + UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr; + + UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr; + UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr; + UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr; + UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr; + + UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr; + UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr; + + -- acq.* + UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr; + UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr; + + -- transfer picklists the same way we transfer buckets (see above) + FOR picklist_row in + SELECT id, name + FROM acq.picklist + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE acq.picklist + SET owner = dest_usr, name = name || suffix + WHERE id = picklist_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr; + UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr; + + -- asset.* + UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr; + + -- serial.* + UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr; + UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr; + + -- reporter.* + -- It's not uncommon to define the reporter schema in a replica + -- DB only, so don't assume these tables exist in the write DB. + BEGIN + UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + -- transfer folders the same way we transfer buckets (see above) + FOR folder_row in + SELECT id, name + FROM reporter.template_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.template_folder + SET owner = dest_usr, name = name || suffix + WHERE id = folder_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + -- transfer folders the same way we transfer buckets (see above) + FOR folder_row in + SELECT id, name + FROM reporter.report_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.report_folder + SET owner = dest_usr, name = name || suffix + WHERE id = folder_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + -- transfer folders the same way we transfer buckets (see above) + FOR folder_row in + SELECT id, name + FROM reporter.output_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.output_folder + SET owner = dest_usr, name = name || suffix + WHERE id = folder_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + -- propagate preferred name values from the source user to the + -- destination user, but only when values are not being replaced. + WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr) + UPDATE actor.usr SET + pref_prefix = + COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)), + pref_first_given_name = + COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)), + pref_second_given_name = + COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)), + pref_family_name = + COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)), + pref_suffix = + COALESCE(pref_suffix, (SELECT pref_suffix FROM susr)) + WHERE id = dest_usr; + + -- Copy and deduplicate name keywords + -- String -> array -> rows -> DISTINCT -> array -> string + WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr), + dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr) + UPDATE actor.usr SET name_keywords = ( + WITH keywords AS ( + SELECT DISTINCT UNNEST( + REGEXP_SPLIT_TO_ARRAY( + COALESCE((SELECT name_keywords FROM susr), '') || ' ' || + COALESCE((SELECT name_keywords FROM dusr), ''), E'\\s+' + ) + ) AS parts + ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw + ) WHERE id = dest_usr; + + -- Finally, delete the source user + DELETE FROM actor.usr WHERE id = src_usr; + +END; +$$ LANGUAGE plpgsql; + + +--SELECT evergreen.upgrade_deps_block_check('1123', :eg_version); + + ALTER TABLE config.rule_circ_duration + ADD column max_auto_renewals INTEGER; + + ALTER TABLE action.circulation + ADD column auto_renewal BOOLEAN; + + ALTER TABLE action.circulation + ADD column auto_renewal_remaining INTEGER; + + ALTER TABLE action.aged_circulation + ADD column auto_renewal BOOLEAN; + + ALTER TABLE action.aged_circulation + ADD column auto_renewal_remaining INTEGER; + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + + INSERT INTO action_trigger.validator values('CircIsAutoRenewable', 'Checks whether the circulation is able to be autorenewed.'); + INSERT INTO action_trigger.reactor values('Circ::AutoRenew', 'Auto-Renews a circulation.'); + INSERT INTO action_trigger.hook(key, core_type, description) values('autorenewal', 'circ', 'Item was auto-renewed to patron.'); + + -- AutoRenewer A/T Def: + INSERT INTO action_trigger.event_definition(active, owner, name, hook, validator, reactor, delay, max_delay, delay_field, group_field) + values (false, 1, 'Autorenew', 'checkout.due', 'CircIsOpen', 'Circ::AutoRenew', '-23 hours'::interval,'-1 minute'::interval, 'due_date', 'usr'); + + -- AutoRenewal outcome Email notifier A/T Def: + INSERT INTO action_trigger.event_definition(active, owner, name, hook, validator, reactor, group_field, template) + values (false, 1, 'AutorenewNotify', 'autorenewal', 'NOOP_True', 'SendEmail', 'usr', +$$ +[%- USE date -%] +[%- user = target.0.usr -%] +To: [%- params.recipient_email || user.email %] +From: [%- params.sender_email || default_sender %] +Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %] +Subject: Items Out Auto-Renewal Notification +Auto-Submitted: auto-generated + +Dear [% user.family_name %], [% user.first_given_name %] +An automatic renewal attempt was made for the following items: + +[% FOR circ IN target %] + [%- SET idx = loop.count - 1; SET udata = user_data.$idx -%] + [%- SET cid = circ.target_copy || udata.copy -%] + [%- SET copy_details = helpers.get_copy_bib_basics(cid) -%] + Item# [% loop.count %] + Title: [% copy_details.title %] + Author: [% copy_details.author %] + [%- IF udata.is_renewed %] + Status: Loan Renewed + New Due Date: [% date.format(helpers.format_date(udata.new_due_date), '%Y-%m-%d') %] + [%- ELSE %] + Status: Not Renewed + Reason: [% udata.reason %] + Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %] + [% END %] +[% END %] +$$ + ); + + INSERT INTO action_trigger.environment (event_def, path ) VALUES + ( currval('action_trigger.event_definition_id_seq'), 'usr' ), + ( currval('action_trigger.event_definition_id_seq'), 'circ_lib' ); + +END IF; END $INSERT$; + + +DROP VIEW action.all_circulation; +CREATE OR REPLACE VIEW action.all_circulation AS + SELECT id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location, + copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy, + circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date, + stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine, + max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule, + max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ, + auto_renewal, auto_renewal_remaining, NULL AS usr + FROM action.aged_circulation + UNION ALL + SELECT DISTINCT circ.id,COALESCE(a.post_code,b.post_code) AS usr_post_code, p.home_ou AS usr_home_ou, p.profile AS usr_profile, EXTRACT(YEAR FROM p.dob)::INT AS usr_birth_year, + cp.call_number AS copy_call_number, circ.copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib, + cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff, + circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration, + circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule, + circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time, + circ.parent_circ, circ.auto_renewal, circ.auto_renewal_remaining, circ.usr + FROM action.circulation circ + JOIN asset.copy cp ON (circ.target_copy = cp.id) + JOIN asset.call_number cn ON (cp.call_number = cn.id) + JOIN actor.usr p ON (circ.usr = p.id) + LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id) + LEFT JOIN actor.usr_address b ON (p.billing_address = b.id); + + +DROP FUNCTION action.summarize_all_circ_chain (INTEGER); +DROP FUNCTION action.all_circ_chain (INTEGER); + +-- rebuild slim circ view +DROP VIEW action.all_circulation_slim; +CREATE OR REPLACE VIEW action.all_circulation_slim AS + SELECT + id, + usr, + xact_start, + xact_finish, + unrecovered, + target_copy, + circ_lib, + circ_staff, + checkin_staff, + checkin_lib, + renewal_remaining, + grace_period, + due_date, + stop_fines_time, + checkin_time, + create_time, + duration, + fine_interval, + recurring_fine, + max_fine, + phone_renewal, + desk_renewal, + opac_renewal, + duration_rule, + recurring_fine_rule, + max_fine_rule, + stop_fines, + workstation, + checkin_workstation, + copy_location, + checkin_scan_time, + auto_renewal, + auto_renewal_remaining, + parent_circ + FROM action.circulation +UNION ALL + SELECT + id, + NULL AS usr, + xact_start, + xact_finish, + unrecovered, + target_copy, + circ_lib, + circ_staff, + checkin_staff, + checkin_lib, + renewal_remaining, + grace_period, + due_date, + stop_fines_time, + checkin_time, + create_time, + duration, + fine_interval, + recurring_fine, + max_fine, + phone_renewal, + desk_renewal, + opac_renewal, + duration_rule, + recurring_fine_rule, + max_fine_rule, + stop_fines, + workstation, + checkin_workstation, + copy_location, + checkin_scan_time, + auto_renewal, + auto_renewal_remaining, + parent_circ + FROM action.aged_circulation +; + +CREATE OR REPLACE FUNCTION action.all_circ_chain (ctx_circ_id INTEGER) + RETURNS SETOF action.all_circulation_slim AS $$ +DECLARE + tmp_circ action.all_circulation_slim%ROWTYPE; + circ_0 action.all_circulation_slim%ROWTYPE; +BEGIN + + SELECT INTO tmp_circ * FROM action.all_circulation_slim WHERE id = ctx_circ_id; + + IF tmp_circ IS NULL THEN + RETURN NEXT tmp_circ; + END IF; + circ_0 := tmp_circ; + + -- find the front of the chain + WHILE TRUE LOOP + SELECT INTO tmp_circ * FROM action.all_circulation_slim + WHERE id = tmp_circ.parent_circ; + IF tmp_circ IS NULL THEN + EXIT; + END IF; + circ_0 := tmp_circ; + END LOOP; + + -- now send the circs to the caller, oldest to newest + tmp_circ := circ_0; + WHILE TRUE LOOP + IF tmp_circ IS NULL THEN + EXIT; + END IF; + RETURN NEXT tmp_circ; + SELECT INTO tmp_circ * FROM action.all_circulation_slim + WHERE parent_circ = tmp_circ.id; + END LOOP; + +END; +$$ LANGUAGE 'plpgsql'; + +-- same as action.summarize_circ_chain, but returns data collected +-- from action.all_circulation, which may include aged circulations. +CREATE OR REPLACE FUNCTION action.summarize_all_circ_chain + (ctx_circ_id INTEGER) RETURNS action.circ_chain_summary AS $$ + +DECLARE + + -- first circ in the chain + circ_0 action.all_circulation_slim%ROWTYPE; + + -- last circ in the chain + circ_n action.all_circulation_slim%ROWTYPE; + + -- circ chain under construction + chain action.circ_chain_summary; + tmp_circ action.all_circulation_slim%ROWTYPE; + +BEGIN + + chain.num_circs := 0; + FOR tmp_circ IN SELECT * FROM action.all_circ_chain(ctx_circ_id) LOOP + + IF chain.num_circs = 0 THEN + circ_0 := tmp_circ; + END IF; + + chain.num_circs := chain.num_circs + 1; + circ_n := tmp_circ; + END LOOP; + + chain.start_time := circ_0.xact_start; + chain.last_stop_fines := circ_n.stop_fines; + chain.last_stop_fines_time := circ_n.stop_fines_time; + chain.last_checkin_time := circ_n.checkin_time; + chain.last_checkin_scan_time := circ_n.checkin_scan_time; + SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation; + SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation; + + IF chain.num_circs > 1 THEN + chain.last_renewal_time := circ_n.xact_start; + SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation; + END IF; + + RETURN chain; + +END; +$$ LANGUAGE 'plpgsql'; + +--SELECT evergreen.upgrade_deps_block_check('1124', :eg_version); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT into config.workstation_setting_type (name, grp, datatype, label) +VALUES ( + 'eg.grid.circ.wide_holds.shelf', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.circ.wide_holds.shelf', + 'Grid Config: circ.wide_holds.shelf', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.catalog.wide_holds', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.catalog.wide_holds', + 'Grid Config: cat.catalog.wide_holds', + 'cwst', 'label' + ) +); + +DELETE from config.workstation_setting_type +WHERE name = 'eg.grid.cat.catalog.holds' OR name = 'eg.grid.circ.holds.shelf'; +END IF; END $INSERT$; + +--SELECT evergreen.upgrade_deps_block_check('1125', :eg_version); + +CREATE TABLE asset.latest_inventory ( + id SERIAL PRIMARY KEY, + inventory_workstation INTEGER REFERENCES actor.workstation (id) DEFERRABLE INITIALLY DEFERRED, + inventory_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + copy BIGINT NOT NULL +); +CREATE INDEX latest_inventory_copy_idx ON asset.latest_inventory (copy); + +CREATE OR REPLACE FUNCTION evergreen.asset_latest_inventory_copy_inh_fkey() RETURNS TRIGGER AS $f$ +BEGIN + PERFORM 1 FROM asset.copy WHERE id = NEW.copy; + IF NOT FOUND THEN + RAISE foreign_key_violation USING MESSAGE = FORMAT( + $$Referenced asset.copy id not found, copy:%s$$, NEW.copy + ); + END IF; + RETURN NEW; +END; +$f$ LANGUAGE PLPGSQL VOLATILE COST 50; + +CREATE CONSTRAINT TRIGGER inherit_asset_latest_inventory_copy_fkey + AFTER UPDATE OR INSERT ON asset.latest_inventory + DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_latest_inventory_copy_inh_fkey(); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT into config.workstation_setting_type (name, grp, datatype, label) +VALUES ( + 'eg.circ.checkin.do_inventory_update', 'circ', 'bool', + oils_i18n_gettext ( + 'eg.circ.checkin.do_inventory_update', + 'Checkin: Update Inventory', + 'cwst', 'label' + ) +); +END IF; END $INSERT$; + +--SELECT evergreen.upgrade_deps_block_check('1126', :eg_version); + +CREATE TABLE vandelay.session_tracker ( + id BIGSERIAL PRIMARY KEY, + + -- string of characters (e.g. md5) used for linking trackers + -- of different actions into a series. There can be multiple + -- session_keys of each action type, creating the opportunity + -- to link multiple action trackers into a single session. + session_key TEXT NOT NULL, + + -- optional user-supplied name + name TEXT NOT NULL, + + usr INTEGER NOT NULL REFERENCES actor.usr(id) + DEFERRABLE INITIALLY DEFERRED, + + -- org unit can be derived from WS + workstation INTEGER NOT NULL REFERENCES actor.workstation(id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + + -- bib/auth + record_type vandelay.bib_queue_queue_type NOT NULL DEFAULT 'bib', + + -- Queue defines the source of the data, it does not necessarily + -- mean that an action is being performed against an entire queue. + -- E.g. some imports are misc. lists of record IDs, but they always + -- come from one queue. + -- No foreign key -- could be auth or bib queue. + queue BIGINT NOT NULL, + + create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + update_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + + state TEXT NOT NULL DEFAULT 'active', + + action_type TEXT NOT NULL DEFAULT 'enqueue', -- import + + -- total number of tasks to perform / loosely defined + -- could be # of recs to import or # of recs + # of copies + -- depending on the import context + total_actions INTEGER NOT NULL DEFAULT 0, + + -- total number of tasked performed so far + actions_performed INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT vand_tracker_valid_state + CHECK (state IN ('active','error','complete')), + + CONSTRAINT vand_tracker_valid_action_type + CHECK (action_type IN ('upload', 'enqueue', 'import')) +); + + +CREATE OR REPLACE FUNCTION actor.usr_merge( src_usr INT, dest_usr INT, del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) RETURNS VOID AS $$ +DECLARE + suffix TEXT; + bucket_row RECORD; + picklist_row RECORD; + queue_row RECORD; + folder_row RECORD; +BEGIN + + -- do some initial cleanup + UPDATE actor.usr SET card = NULL WHERE id = src_usr; + UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr; + UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr; + + -- actor.* + IF del_cards THEN + DELETE FROM actor.card where usr = src_usr; + ELSE + IF deactivate_cards THEN + UPDATE actor.card SET active = 'f' WHERE usr = src_usr; + END IF; + UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr; + END IF; + + + IF del_addrs THEN + DELETE FROM actor.usr_address WHERE usr = src_usr; + ELSE + UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr; + END IF; + + UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr; + -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them... + UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr; + PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr); + + -- permission.* + PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr); + + + -- container.* + + -- For each *_bucket table: transfer every bucket belonging to src_usr + -- into the custody of dest_usr. + -- + -- In order to avoid colliding with an existing bucket owned by + -- the destination user, append the source user's id (in parenthesese) + -- to the name. If you still get a collision, add successive + -- spaces to the name and keep trying until you succeed. + -- + FOR bucket_row in + SELECT id, name + FROM container.biblio_record_entry_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.biblio_record_entry_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR bucket_row in + SELECT id, name + FROM container.call_number_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.call_number_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR bucket_row in + SELECT id, name + FROM container.copy_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.copy_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR bucket_row in + SELECT id, name + FROM container.user_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.user_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr; + + -- vandelay.* + -- transfer queues the same way we transfer buckets (see above) + FOR queue_row in + SELECT id, name + FROM vandelay.queue + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE vandelay.queue + SET owner = dest_usr, name = name || suffix + WHERE id = queue_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr; + + -- money.* + PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr); + UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr; + UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr; + UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr; + + -- action.* + UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr; + UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr; + UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr; + UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr; + + UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr; + UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr; + UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr; + UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr; + + UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr; + UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr; + + -- acq.* + UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr; + UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr; + + -- transfer picklists the same way we transfer buckets (see above) + FOR picklist_row in + SELECT id, name + FROM acq.picklist + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE acq.picklist + SET owner = dest_usr, name = name || suffix + WHERE id = picklist_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr; + UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr; + + -- asset.* + UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr; + + -- serial.* + UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr; + UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr; + + -- reporter.* + -- It's not uncommon to define the reporter schema in a replica + -- DB only, so don't assume these tables exist in the write DB. + BEGIN + UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + -- transfer folders the same way we transfer buckets (see above) + FOR folder_row in + SELECT id, name + FROM reporter.template_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.template_folder + SET owner = dest_usr, name = name || suffix + WHERE id = folder_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + -- transfer folders the same way we transfer buckets (see above) + FOR folder_row in + SELECT id, name + FROM reporter.report_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.report_folder + SET owner = dest_usr, name = name || suffix + WHERE id = folder_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + -- transfer folders the same way we transfer buckets (see above) + FOR folder_row in + SELECT id, name + FROM reporter.output_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.output_folder + SET owner = dest_usr, name = name || suffix + WHERE id = folder_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + -- propagate preferred name values from the source user to the + -- destination user, but only when values are not being replaced. + WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr) + UPDATE actor.usr SET + pref_prefix = + COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)), + pref_first_given_name = + COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)), + pref_second_given_name = + COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)), + pref_family_name = + COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)), + pref_suffix = + COALESCE(pref_suffix, (SELECT pref_suffix FROM susr)) + WHERE id = dest_usr; + + -- Copy and deduplicate name keywords + -- String -> array -> rows -> DISTINCT -> array -> string + WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr), + dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr) + UPDATE actor.usr SET name_keywords = ( + WITH keywords AS ( + SELECT DISTINCT UNNEST( + REGEXP_SPLIT_TO_ARRAY( + COALESCE((SELECT name_keywords FROM susr), '') || ' ' || + COALESCE((SELECT name_keywords FROM dusr), ''), E'\\s+' + ) + ) AS parts + ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw + ) WHERE id = dest_usr; + + -- Finally, delete the source user + DELETE FROM actor.usr WHERE id = src_usr; + +END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION actor.usr_purge_data( + src_usr IN INTEGER, + specified_dest_usr IN INTEGER +) RETURNS VOID AS $$ +DECLARE + suffix TEXT; + renamable_row RECORD; + dest_usr INTEGER; +BEGIN + + IF specified_dest_usr IS NULL THEN + dest_usr := 1; -- Admin user on stock installs + ELSE + dest_usr := specified_dest_usr; + END IF; + + -- acq.* + UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr; + UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr; + UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr; + DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr; + + -- Update with a rename to avoid collisions + FOR renamable_row in + SELECT id, name + FROM acq.picklist + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE acq.picklist + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr; + UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr; + + -- action.* + DELETE FROM action.circulation WHERE usr = src_usr; + UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr; + UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr; + UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr; + UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr; + UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr; + DELETE FROM action.hold_request WHERE usr = src_usr; + UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr; + DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr; + UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr; + DELETE FROM action.survey_response WHERE usr = src_usr; + UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr; + DELETE FROM action.usr_circ_history WHERE usr = src_usr; + + -- actor.* + DELETE FROM actor.card WHERE usr = src_usr; + DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr; + + -- The following update is intended to avoid transient violations of a foreign + -- key constraint, whereby actor.usr_address references itself. It may not be + -- necessary, but it does no harm. + UPDATE actor.usr_address SET replaces = NULL + WHERE usr = src_usr AND replaces IS NOT NULL; + DELETE FROM actor.usr_address WHERE usr = src_usr; + DELETE FROM actor.usr_note WHERE usr = src_usr; + UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr; + DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr; + UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr; + DELETE FROM actor.usr_setting WHERE usr = src_usr; + DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr; + UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr; + + -- asset.* + UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr; + + -- auditor.* + DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr; + DELETE FROM auditor.actor_usr_history WHERE id = src_usr; + UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr; + UPDATE auditor.asset_call_number_history SET editor = dest_usr WHERE editor = src_usr; + UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr; + UPDATE auditor.asset_copy_history SET editor = dest_usr WHERE editor = src_usr; + UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr; + UPDATE auditor.biblio_record_entry_history SET editor = dest_usr WHERE editor = src_usr; + + -- biblio.* + UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr; + UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr; + UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr; + + -- container.* + -- Update buckets with a rename to avoid collisions + FOR renamable_row in + SELECT id, name + FROM container.biblio_record_entry_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.biblio_record_entry_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR renamable_row in + SELECT id, name + FROM container.call_number_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.call_number_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR renamable_row in + SELECT id, name + FROM container.copy_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.copy_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR renamable_row in + SELECT id, name + FROM container.user_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.user_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + DELETE FROM container.user_bucket_item WHERE target_user = src_usr; + + -- money.* + DELETE FROM money.billable_xact WHERE usr = src_usr; + DELETE FROM money.collections_tracker WHERE usr = src_usr; + UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr; + + -- permission.* + DELETE FROM permission.usr_grp_map WHERE usr = src_usr; + DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr; + DELETE FROM permission.usr_perm_map WHERE usr = src_usr; + DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr; + + -- reporter.* + -- Update with a rename to avoid collisions + BEGIN + FOR renamable_row in + SELECT id, name + FROM reporter.output_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.output_folder + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + BEGIN + UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + -- Update with a rename to avoid collisions + BEGIN + FOR renamable_row in + SELECT id, name + FROM reporter.report_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.report_folder + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + BEGIN + UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + BEGIN + UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + -- Update with a rename to avoid collisions + BEGIN + FOR renamable_row in + SELECT id, name + FROM reporter.template_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.template_folder + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + -- vandelay.* + -- Update with a rename to avoid collisions + FOR renamable_row in + SELECT id, name + FROM vandelay.queue + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE vandelay.queue + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr; + + -- NULL-ify addresses last so other cleanup (e.g. circ anonymization) + -- can access the information before deletion. + UPDATE actor.usr SET + active = FALSE, + card = NULL, + mailing_address = NULL, + billing_address = NULL + WHERE id = src_usr; + +END; +$$ LANGUAGE plpgsql; + + +--SELECT evergreen.upgrade_deps_block_check('1127', :eg_version); + +ALTER TABLE acq.user_request ADD COLUMN cancel_time TIMESTAMPTZ; +ALTER TABLE acq.user_request ADD COLUMN upc TEXT; +ALTER TABLE action.hold_request ADD COLUMN acq_request INT REFERENCES acq.user_request (id); + +UPDATE + config.org_unit_setting_type +SET + label = oils_i18n_gettext( + 'circ.holds.canceled.display_age', + 'Canceled holds/requests display age', + 'coust', 'label'), + description = oils_i18n_gettext( + 'circ.holds.canceled.display_age', + 'Show all canceled entries in patron holds and patron acquisition requests interfaces that were canceled within this amount of time', + 'coust', 'description') +WHERE + name = 'circ.holds.canceled.display_age' +; + +UPDATE + config.org_unit_setting_type +SET + label = oils_i18n_gettext( + 'circ.holds.canceled.display_count', + 'Canceled holds/requests display count', + 'coust', 'label'), + description = oils_i18n_gettext( + 'circ.holds.canceled.display_count', + 'How many canceled entries to show in patron holds and patron acquisition requests interfaces', + 'coust', 'description') +WHERE + name = 'circ.holds.canceled.display_count' +; + + +-- replace existing, unused cancel reason with the new one. +UPDATE acq.cancel_reason SET org_unit = 1, keep_debits = FALSE, + label = 'Canceled: Fulfilled', + description = 'This acquisition request has been fulfilled.' + WHERE id = 1015; + +/* +INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) + VALUES ( + 1, 'f', 1015, + oils_i18n_gettext(1015, 'Canceled: Fulfilled', 'acqcr', 'label'), + oils_i18n_gettext(1015, 'This acquisition request has been fulfilled.', 'acqcr', 'description') + ) +; +*/ + +UPDATE + acq.user_request_type +SET + label = oils_i18n_gettext('2', 'Articles', 'aurt', 'label') +WHERE + id = 2 +; + + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT INTO acq.user_request_type (id,label) + SELECT 6, oils_i18n_gettext('6', 'Other', 'aurt', 'label'); + +INSERT INTO permission.perm_list ( id, code, description ) VALUES + ( 610, 'CLEAR_PURCHASE_REQUEST', oils_i18n_gettext(610, + 'Clear Completed User Purchase Requests', 'ppl', 'description')) +; + +END IF; END $INSERT$; + +SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, (SELECT MAX(id)+1 FROM acq.user_request_type)); + +CREATE TABLE acq.user_request_status_type ( + id SERIAL PRIMARY KEY + ,label TEXT +); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN +INSERT INTO acq.user_request_status_type (id,label) VALUES + (0,oils_i18n_gettext(0,'Error','aurst','label')) + ,(1,oils_i18n_gettext(1,'New','aurst','label')) + ,(2,oils_i18n_gettext(2,'Pending','aurst','label')) + ,(3,oils_i18n_gettext(3,'Ordered, Hold Not Placed','aurst','label')) + ,(4,oils_i18n_gettext(4,'Ordered, Hold Placed','aurst','label')) + ,(5,oils_i18n_gettext(5,'Received','aurst','label')) + ,(6,oils_i18n_gettext(6,'Fulfilled','aurst','label')) + ,(7,oils_i18n_gettext(7,'Canceled','aurst','label')) +; + +END IF; END $INSERT$; + +SELECT SETVAL('acq.user_request_status_type_id_seq'::TEXT, 100); + +-- not used +DELETE FROM actor.org_unit_setting WHERE name = 'acq.holds.allow_holds_from_purchase_request'; +DELETE FROM config.org_unit_setting_type_log WHERE field_name = 'acq.holds.allow_holds_from_purchase_request'; +DELETE FROM config.org_unit_setting_type WHERE name = 'acq.holds.allow_holds_from_purchase_request'; + + +--SELECT evergreen.upgrade_deps_block_check('1128', :eg_version); + +-- KCLS already had this +/* +DROP VIEW auditor.acq_invoice_lifecycle; + +ALTER TABLE acq.invoice + ADD COLUMN close_date TIMESTAMPTZ, + ADD COLUMN closed_by INTEGER + REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED; + +-- duplicate steps for auditor table +ALTER TABLE auditor.acq_invoice_history + ADD COLUMN close_date TIMESTAMPTZ, + ADD COLUMN closed_by INTEGER; + +UPDATE acq.invoice SET close_date = NOW() WHERE complete; +UPDATE auditor.acq_invoice_history SET close_date = NOW() WHERE complete; + +ALTER TABLE acq.invoice DROP COLUMN complete; +ALTER TABLE auditor.acq_invoice_history DROP COLUMN complete; + +-- this recreates auditor.acq_invoice_lifecycle; +SELECT auditor.update_auditors(); +*/ + +CREATE OR REPLACE FUNCTION actor.usr_merge( src_usr INT, dest_usr INT, del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) RETURNS VOID AS $$ +DECLARE + suffix TEXT; + bucket_row RECORD; + picklist_row RECORD; + queue_row RECORD; + folder_row RECORD; +BEGIN + + -- do some initial cleanup + UPDATE actor.usr SET card = NULL WHERE id = src_usr; + UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr; + UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr; + + -- actor.* + IF del_cards THEN + DELETE FROM actor.card where usr = src_usr; + ELSE + IF deactivate_cards THEN + UPDATE actor.card SET active = 'f' WHERE usr = src_usr; + END IF; + UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr; + END IF; + + + IF del_addrs THEN + DELETE FROM actor.usr_address WHERE usr = src_usr; + ELSE + UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr; + END IF; + + UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr; + -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them... + UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr; + PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr); + + -- permission.* + PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr); + + + -- container.* + + -- For each *_bucket table: transfer every bucket belonging to src_usr + -- into the custody of dest_usr. + -- + -- In order to avoid colliding with an existing bucket owned by + -- the destination user, append the source user's id (in parenthesese) + -- to the name. If you still get a collision, add successive + -- spaces to the name and keep trying until you succeed. + -- + FOR bucket_row in + SELECT id, name + FROM container.biblio_record_entry_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.biblio_record_entry_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR bucket_row in + SELECT id, name + FROM container.call_number_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.call_number_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR bucket_row in + SELECT id, name + FROM container.copy_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.copy_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR bucket_row in + SELECT id, name + FROM container.user_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.user_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = bucket_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr; + + -- vandelay.* + -- transfer queues the same way we transfer buckets (see above) + FOR queue_row in + SELECT id, name + FROM vandelay.queue + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE vandelay.queue + SET owner = dest_usr, name = name || suffix + WHERE id = queue_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + -- money.* + PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr); + PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr); + UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr; + UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr; + UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr; + + -- action.* + UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr; + UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr; + UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr; + UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr; + + UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr; + UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr; + UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr; + UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr; + + UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr; + UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr; + + -- acq.* + UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr; + UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr; + UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr; + + -- transfer picklists the same way we transfer buckets (see above) + FOR picklist_row in + SELECT id, name + FROM acq.picklist + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE acq.picklist + SET owner = dest_usr, name = name || suffix + WHERE id = picklist_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr; + UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr; + + -- asset.* + UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr; + + -- serial.* + UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr; + UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr; + + -- reporter.* + -- It's not uncommon to define the reporter schema in a replica + -- DB only, so don't assume these tables exist in the write DB. + BEGIN + UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + -- transfer folders the same way we transfer buckets (see above) + FOR folder_row in + SELECT id, name + FROM reporter.template_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.template_folder + SET owner = dest_usr, name = name || suffix + WHERE id = folder_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + -- transfer folders the same way we transfer buckets (see above) + FOR folder_row in + SELECT id, name + FROM reporter.report_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.report_folder + SET owner = dest_usr, name = name || suffix + WHERE id = folder_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + BEGIN + -- transfer folders the same way we transfer buckets (see above) + FOR folder_row in + SELECT id, name + FROM reporter.output_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.output_folder + SET owner = dest_usr, name = name || suffix + WHERE id = folder_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + -- Finally, delete the source user + DELETE FROM actor.usr WHERE id = src_usr; + +END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION actor.usr_purge_data( + src_usr IN INTEGER, + specified_dest_usr IN INTEGER +) RETURNS VOID AS $$ +DECLARE + suffix TEXT; + renamable_row RECORD; + dest_usr INTEGER; +BEGIN + + IF specified_dest_usr IS NULL THEN + dest_usr := 1; -- Admin user on stock installs + ELSE + dest_usr := specified_dest_usr; + END IF; + + -- acq.* + UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr; + UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr; + UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr; + DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr; + + -- Update with a rename to avoid collisions + FOR renamable_row in + SELECT id, name + FROM acq.picklist + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE acq.picklist + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr; + UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr; + UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr; + UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr; + + -- action.* + DELETE FROM action.circulation WHERE usr = src_usr; + UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr; + UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr; + UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr; + UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr; + UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr; + DELETE FROM action.hold_request WHERE usr = src_usr; + UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr; + UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr; + DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr; + UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr; + DELETE FROM action.survey_response WHERE usr = src_usr; + UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr; + DELETE FROM action.usr_circ_history WHERE usr = src_usr; + + -- actor.* + DELETE FROM actor.card WHERE usr = src_usr; + DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr; + + -- The following update is intended to avoid transient violations of a foreign + -- key constraint, whereby actor.usr_address references itself. It may not be + -- necessary, but it does no harm. + UPDATE actor.usr_address SET replaces = NULL + WHERE usr = src_usr AND replaces IS NOT NULL; + DELETE FROM actor.usr_address WHERE usr = src_usr; + DELETE FROM actor.usr_note WHERE usr = src_usr; + UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr; + DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr; + UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr; + DELETE FROM actor.usr_setting WHERE usr = src_usr; + DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr; + UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr; + + -- asset.* + UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr; + UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr; + UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr; + + -- auditor.* + DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr; + DELETE FROM auditor.actor_usr_history WHERE id = src_usr; + UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr; + UPDATE auditor.asset_call_number_history SET editor = dest_usr WHERE editor = src_usr; + UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr; + UPDATE auditor.asset_copy_history SET editor = dest_usr WHERE editor = src_usr; + UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr; + UPDATE auditor.biblio_record_entry_history SET editor = dest_usr WHERE editor = src_usr; + + -- biblio.* + UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr; + UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr; + UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr; + UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr; + + -- container.* + -- Update buckets with a rename to avoid collisions + FOR renamable_row in + SELECT id, name + FROM container.biblio_record_entry_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.biblio_record_entry_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR renamable_row in + SELECT id, name + FROM container.call_number_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.call_number_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR renamable_row in + SELECT id, name + FROM container.copy_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.copy_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + FOR renamable_row in + SELECT id, name + FROM container.user_bucket + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE container.user_bucket + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + DELETE FROM container.user_bucket_item WHERE target_user = src_usr; + + -- money.* + DELETE FROM money.billable_xact WHERE usr = src_usr; + DELETE FROM money.collections_tracker WHERE usr = src_usr; + UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr; + + -- permission.* + DELETE FROM permission.usr_grp_map WHERE usr = src_usr; + DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr; + DELETE FROM permission.usr_perm_map WHERE usr = src_usr; + DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr; + + -- reporter.* + -- Update with a rename to avoid collisions + BEGIN + FOR renamable_row in + SELECT id, name + FROM reporter.output_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.output_folder + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + BEGIN + UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + -- Update with a rename to avoid collisions + BEGIN + FOR renamable_row in + SELECT id, name + FROM reporter.report_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.report_folder + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + BEGIN + UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + BEGIN + UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + -- Update with a rename to avoid collisions + BEGIN + FOR renamable_row in + SELECT id, name + FROM reporter.template_folder + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE reporter.template_folder + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + EXCEPTION WHEN undefined_table THEN + -- do nothing + END; + + -- vandelay.* + -- Update with a rename to avoid collisions + FOR renamable_row in + SELECT id, name + FROM vandelay.queue + WHERE owner = src_usr + LOOP + suffix := ' (' || src_usr || ')'; + LOOP + BEGIN + UPDATE vandelay.queue + SET owner = dest_usr, name = name || suffix + WHERE id = renamable_row.id; + EXCEPTION WHEN unique_violation THEN + suffix := suffix || ' '; + CONTINUE; + END; + EXIT; + END LOOP; + END LOOP; + + -- NULL-ify addresses last so other cleanup (e.g. circ anonymization) + -- can access the information before deletion. + UPDATE actor.usr SET + active = FALSE, + card = NULL, + mailing_address = NULL, + billing_address = NULL + WHERE id = src_usr; + +END; +$$ LANGUAGE plpgsql; + + + + + +-- UNDO (minus user purge/merge changes) +/* + +DROP VIEW auditor.acq_invoice_lifecycle; +ALTER TABLE acq.invoice ADD COLUMN complete BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE auditor.acq_invoice_history + ADD COLUMN complete BOOLEAN NOT NULL DEFAULT FALSE; +UPDATE acq.invoice SET complete = TRUE where close_date IS NOT NULL; +UPDATE auditor.acq_invoice_history + SET complete = TRUE where close_date IS NOT NULL; +SET CONSTRAINTS ALL IMMEDIATE; -- or get pending triggers error. +ALTER TABLE acq.invoice DROP COLUMN close_date, DROP COLUMN closed_by; +ALTER TABLE auditor.acq_invoice_history + DROP COLUMN close_date, DROP COLUMN closed_by; +SELECT auditor.update_auditors(); + +*/ + + +--SELECT evergreen.upgrade_deps_block_check('1129', :eg_version); + +DO $INSERT$ BEGIN IF evergreen.insert_on_deploy() THEN + +INSERT into config.workstation_setting_type (name, grp, datatype, label) +VALUES ( + 'eg.grid.admin.acq.cancel_reason', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.cancel_reason', + 'Grid Config: admin.acq.cancel_reason', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.claim_event_type', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.claim_event_type', + 'Grid Config: admin.acq.claim_event_type', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.claim_policy', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.claim_policy', + 'Grid Config: admin.acq.claim_policy', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.claim_policy_action', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.claim_policy_action', + 'Grid Config: admin.acq.claim_policy_action', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.claim_type', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.claim_type', + 'Grid Config: admin.acq.claim_type', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.currency_type', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.currency_type', + 'Grid Config: admin.acq.currency_type', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.edi_account', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.edi_account', + 'Grid Config: admin.acq.edi_account', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.edi_message', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.edi_message', + 'Grid Config: admin.acq.edi_message', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.exchange_rate', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.exchange_rate', + 'Grid Config: admin.acq.exchange_rate', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.fund_tag', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.fund_tag', + 'Grid Config: admin.acq.fund_tag', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.invoice_item_type', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.invoice_item_type', + 'Grid Config: admin.acq.invoice_item_type', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.invoice_payment_method', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.invoice_payment_method', + 'Grid Config: admin.acq.invoice_payment_method', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.lineitem_alert_text', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.lineitem_alert_text', + 'Grid Config: admin.acq.lineitem_alert_text', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.acq.lineitem_marc_attr_definition', 'gui', 'object', + oils_i18n_gettext ( + 'eg.grid.admin.acq.lineitem_marc_attr_definition', + 'Grid Config: admin.acq.lineitem_marc_attr_definition', + 'cwst', 'label' + ) +); + +END IF; END $INSERT$; + +---------------------------------------------------------------------------- +-- VERSION INSERTS +---------------------------------------------------------------------------- + +DO $INSERT$ +BEGIN + IF evergreen.insert_on_deploy() THEN +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.0', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1032', '3.0.0'); -- Bmagic/csharp/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1034', '3.0.0'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1035', '3.0.0'); -- dyrcona/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1036', '3.0.0'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1041', '3.0.0'); -- stompro/csharp/gmcharlt +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1042', '3.0.0'); -- mmorgan/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1043', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1044', '3.0.0'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1045', '3.0.0'); -- csharp/berick/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1046', '3.0.0'); -- phasefx/berick/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1047', '3.0.0'); -- gmcharlt/stompro +PERFORM evergreen.upgrade_deps_block_check('1048', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1049', '3.0.0'); -- mmorgan/stompro/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1050', '3.0.0'); -- mmorgan/cesardv/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1051', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1052', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1053', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1054', '3.0.0'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1056', '3.0.0'); -- miker/gmcharlt +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1057', '3.0.0'); -- miker/gmcharlt/kmlussier +PERFORM evergreen.upgrade_deps_block_check('1076', '3.0.0'); -- miker/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1058', '3.0.0'); -- mccanna/csharp/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1059', '3.0.0'); --Stompro/DPearl/kmlussier +PERFORM evergreen.upgrade_deps_block_check('1060', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1061', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1062', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1063', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1064', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1065', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1066', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1067', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1068', '3.0.0'); --miker/gmcharlt/kmlussier +PERFORM evergreen.upgrade_deps_block_check('1069', '3.0.0'); --gmcharlt/kmlussier +PERFORM evergreen.upgrade_deps_block_check('1070', '3.0.0'); --miker/gmcharlt/kmlussier +PERFORM evergreen.upgrade_deps_block_check('1071', '3.0.0'); --gmcharlt/kmlussier +PERFORM evergreen.upgrade_deps_block_check('1072', '3.0.0'); --gmcharlt/kmlussier +PERFORM evergreen.upgrade_deps_block_check('1073', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1074', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1075', '3.0.0'); +PERFORM evergreen.upgrade_deps_block_check('1077', '3.0.0'); -- csharp/gmcharlt +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.1', '3.0.0'); +--PERFORM evergreen.upgrade_deps_block_check('1078', '3.0.1'); -- csharp/bshum/gmcharlt +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.2', '3.0.1'); +--PERFORM evergreen.upgrade_deps_block_check('1079', '3.0.2'); -- rhamby/cesardv/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1080', '3.0.2'); -- miker/jboyer/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1081', '3.0.2'); -- jboyer/gmcharlt +PERFORM evergreen.upgrade_deps_block_check('1082', '3.0.2'); -- jboyer/gmcharlt +--PERFORM evergreen.upgrade_deps_block_check('1083', '3.0.2'); +PERFORM evergreen.upgrade_deps_block_check('1084', '3.0.2'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.3', '3.0.2'); +PERFORM evergreen.upgrade_deps_block_check('1085', '3.0.3'); +PERFORM evergreen.upgrade_deps_block_check('1086', '3.0.3'); +PERFORM evergreen.upgrade_deps_block_check('1087', '3.0.3'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.4', '3.0.3'); +PERFORM evergreen.upgrade_deps_block_check('1088', '3.0.4'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.5', '3.0.4'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0.6', '3.0.5'); +PERFORM evergreen.upgrade_deps_block_check('1107', '3.0.6'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.0', '3.0.6'); +PERFORM evergreen.upgrade_deps_block_check('1089', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1090', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1091', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1092', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1093', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1094', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1095', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1096', '3.1.0'); + +PERFORM evergreen.upgrade_deps_block_check('1097', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1098', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1099', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1100', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1101', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1102', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1103', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1104', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1105', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1106', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1108', '3.1.0'); +PERFORM evergreen.upgrade_deps_block_check('1109', '3.1.0'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.1', '3.1.0'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.2', '3.1.1'); +PERFORM evergreen.upgrade_deps_block_check('1110', '3.1.2'); +PERFORM evergreen.upgrade_deps_block_check('1111', '3.1.2'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.3', '3.1.2'); +PERFORM evergreen.upgrade_deps_block_check('1112', '3.1.3'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.4', '3.1.3'); +PERFORM evergreen.upgrade_deps_block_check('1113', '3.1.4'); +PERFORM evergreen.upgrade_deps_block_check('1114', '3.1.4'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.1.5', '3.1.4'); +PERFORM evergreen.upgrade_deps_block_check('1119', '3.1.5'); +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.2.1', '3.1.5'); +PERFORM evergreen.upgrade_deps_block_check('1115', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1116', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1117', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1118', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1120', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1121', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1122', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1123', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1124', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1125', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1126', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1127', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1128', '3.2.1'); +PERFORM evergreen.upgrade_deps_block_check('1129', '3.2.1'); + END IF; +END $INSERT$; + +COMMIT; +--ROLLBACK; +