1 --Upgrade Script for 2.12.5 to 3.0-beta1
2 \set eg_version '''3.0-beta1'''
4 -- verify that we're running a recent enough version of Pg
11 SELECT current_setting('server_version_num') INTO ver;
13 RAISE EXCEPTION 'Not running a new enough version of PostgreSQL. Minimum required is 9.4; you have %', ver;
19 \set ON_ERROR_STOP off
22 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.0-beta1', :eg_version);
24 SELECT evergreen.upgrade_deps_block_check('1032', :eg_version); -- Bmagic/csharp/gmcharlt
26 CREATE OR REPLACE VIEW action.all_circulation_combined_types AS
27 SELECT acirc.id AS id,
32 ac_acirc.circ_modifier AS item_type,
33 'regular_circ'::text AS circ_type
34 FROM action.circulation acirc,
36 WHERE acirc.target_copy = ac_acirc.id
38 SELECT ancc.id::BIGINT AS id,
39 ancc.circ_time AS xact_start,
41 ancc.staff AS circ_staff,
42 ancc.circ_time AS create_time,
43 cnct_ancc.name AS item_type,
44 'non-cat_circ'::text AS circ_type
45 FROM action.non_cataloged_circulation ancc,
46 config.non_cataloged_type cnct_ancc
47 WHERE ancc.item_type = cnct_ancc.id
49 SELECT aihu.id::BIGINT AS id,
50 aihu.use_time AS xact_start,
51 aihu.org_unit AS circ_lib,
52 aihu.staff AS circ_staff,
53 aihu.use_time AS create_time,
54 ac_aihu.circ_modifier AS item_type,
55 'in-house_use'::text AS circ_type
56 FROM action.in_house_use aihu,
58 WHERE aihu.item = ac_aihu.id
60 SELECT ancihu.id::BIGINT AS id,
61 ancihu.use_time AS xact_start,
62 ancihu.org_unit AS circ_lib,
63 ancihu.staff AS circ_staff,
64 ancihu.use_time AS create_time,
65 cnct_ancihu.name AS item_type,
66 'non-cat_circ'::text AS circ_type
67 FROM action.non_cat_in_house_use ancihu,
68 config.non_cataloged_type cnct_ancihu
69 WHERE ancihu.item_type = cnct_ancihu.id
71 SELECT aacirc.id AS id,
76 ac_aacirc.circ_modifier AS item_type,
77 'aged_circ'::text AS circ_type
78 FROM action.aged_circulation aacirc,
80 WHERE aacirc.target_copy = ac_aacirc.id;
83 SELECT evergreen.upgrade_deps_block_check('1034', :eg_version);
85 ALTER TABLE config.hold_matrix_matchpoint
86 ADD COLUMN description TEXT;
88 ALTER TABLE config.circ_matrix_matchpoint
89 ADD COLUMN description TEXT;
92 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1035', :eg_version); -- dyrcona/gmcharlt
94 -- Per Lp bug 1684984, the config.internal_flag,
95 -- ingest.disable_metabib_field_entry, was made obsolete by the
96 -- addition of the ingest.skip_browse_indexing,
97 -- ingest.skip_search_indexing, and ingest.skip_facet_indexing flags.
98 -- Since it is not used in the database, we delete it.
99 DELETE FROM config.internal_flag
100 WHERE name = 'ingest.disable_metabib_field_entry';
103 SELECT evergreen.upgrade_deps_block_check('1036', :eg_version);
105 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
107 temp_value config.hard_due_date_values%ROWTYPE;
111 SELECT DISTINCT ON (hard_due_date) *
112 FROM config.hard_due_date_values
113 WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
114 ORDER BY hard_due_date, active_date DESC -- Latest (nearest to us) active time
116 UPDATE config.hard_due_date
117 SET ceiling_date = temp_value.ceiling_date
118 WHERE id = temp_value.hard_due_date
119 AND ceiling_date <> temp_value.ceiling_date -- Time is equal if we've already updated the chdd
120 AND temp_value.ceiling_date >= NOW(); -- Don't update ceiling dates to the past
123 updated := updated + 1;
129 $func$ LANGUAGE plpgsql;
132 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1041', :eg_version); -- stompro/csharp/gmcharlt
134 --delete all instances from permission.grp_perm_map first
135 DELETE FROM permission.grp_perm_map where perm in
136 (select id from permission.perm_list where code='SET_CIRC_MISSING');
138 --delete all instances from permission.usr_perm_map too
139 DELETE FROM permission.usr_perm_map where perm in
140 (select id from permission.perm_list where code='SET_CIRC_MISSING');
142 --delete from permission.perm_list
143 DELETE FROM permission.perm_list where code='SET_CIRC_MISSING';
146 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1042', :eg_version); -- mmorgan/gmcharlt
148 ALTER TABLE asset.copy_location
152 SELECT evergreen.upgrade_deps_block_check('1043', :eg_version);
154 ALTER TABLE action_trigger.event_definition
155 ADD COLUMN retention_interval INTERVAL;
157 CREATE OR REPLACE FUNCTION action_trigger.check_valid_retention_interval()
158 RETURNS TRIGGER AS $_$
162 * 1. Retention intervals are alwyas allowed on active hooks.
163 * 2. On passive hooks, retention intervals are only allowed
164 * when the event definition has a max_delay value and the
165 * retention_interval value is greater than the difference
166 * beteween the delay and max_delay values.
168 PERFORM TRUE FROM action_trigger.hook
169 WHERE key = NEW.hook AND NOT passive;
175 IF NEW.max_delay IS NOT NULL THEN
176 IF EXTRACT(EPOCH FROM NEW.retention_interval) >
177 ABS(EXTRACT(EPOCH FROM (NEW.max_delay - NEW.delay))) THEN
178 RETURN NEW; -- all good
180 RAISE EXCEPTION 'retention_interval is too short';
183 RAISE EXCEPTION 'retention_interval requires max_delay';
186 $_$ LANGUAGE PLPGSQL;
188 CREATE TRIGGER is_valid_retention_interval
189 BEFORE INSERT OR UPDATE ON action_trigger.event_definition
190 FOR EACH ROW WHEN (NEW.retention_interval IS NOT NULL)
191 EXECUTE PROCEDURE action_trigger.check_valid_retention_interval();
193 CREATE OR REPLACE FUNCTION action_trigger.purge_events() RETURNS VOID AS $_$
195 * Deleting expired events without simultaneously deleting their outputs
196 * creates orphaned outputs. Deleting their outputs and all of the events
197 * linking back to them, plus any outputs those events link to is messy and
198 * inefficient. It's simpler to handle them in 2 sweeping steps.
200 * 1. Delete expired events.
201 * 2. Delete orphaned event outputs.
203 * This has the added benefit of removing outputs that may have been
204 * orphaned by some other process. Such outputs are not usuable by
207 * This does not guarantee that all events within an event group are
208 * purged at the same time. In such cases, the remaining events will
209 * be purged with the next instance of the purge (or soon thereafter).
210 * This is another nod toward efficiency over completeness of old
211 * data that's circling the bit bucket anyway.
215 DELETE FROM action_trigger.event WHERE id IN (
217 FROM action_trigger.event evt
218 JOIN action_trigger.event_definition def ON (def.id = evt.event_def)
219 WHERE def.retention_interval IS NOT NULL
220 AND evt.state <> 'pending'
221 AND evt.update_time < (NOW() - def.retention_interval)
224 WITH linked_outputs AS (
225 SELECT templates.id AS id FROM (
226 SELECT DISTINCT(template_output) AS id
227 FROM action_trigger.event WHERE template_output IS NOT NULL
229 SELECT DISTINCT(error_output) AS id
230 FROM action_trigger.event WHERE error_output IS NOT NULL
232 SELECT DISTINCT(async_output) AS id
233 FROM action_trigger.event WHERE async_output IS NOT NULL
235 ) DELETE FROM action_trigger.event_output
236 WHERE id NOT IN (SELECT id FROM linked_outputs);
239 $_$ LANGUAGE PLPGSQL;
244 DROP FUNCTION IF EXISTS action_trigger.purge_events();
245 DROP TRIGGER IF EXISTS is_valid_retention_interval ON action_trigger.event_definition;
246 DROP FUNCTION IF EXISTS action_trigger.check_valid_retention_interval();
247 ALTER TABLE action_trigger.event_definition DROP COLUMN retention_interval;
253 SELECT evergreen.upgrade_deps_block_check('1044', :eg_version);
255 UPDATE action_trigger.hook SET passive = FALSE WHERE key IN (
258 'format.selfcheck.checkout',
259 'format.selfcheck.items_out',
260 'format.selfcheck.holds',
261 'format.selfcheck.fines',
262 'format.acqcle.html',
263 'format.acqinv.html',
273 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1045', :eg_version); -- csharp/berick/gmcharlt
275 ALTER TABLE action.transit_copy
276 ADD COLUMN cancel_time TIMESTAMPTZ;
278 -- change "abort" to "cancel" in stock perm descriptions
279 UPDATE permission.perm_list
280 SET description = 'Allow a user to cancel a copy transit if the user is at the transit destination or source'
281 WHERE code = 'ABORT_TRANSIT'
282 AND description = 'Allow a user to abort a copy transit if the user is at the transit destination or source';
283 UPDATE permission.perm_list
284 SET description = 'Allow a user to cancel a copy transit if the user is not at the transit source or dest'
285 WHERE code = 'ABORT_REMOTE_TRANSIT'
286 AND description = 'Allow a user to abort a copy transit if the user is not at the transit source or dest';
287 UPDATE permission.perm_list
288 SET description = 'Allows a user to cancel a transit on a copy with status of LOST'
289 WHERE code = 'ABORT_TRANSIT_ON_LOST'
290 AND description = 'Allows a user to abort a transit on a copy with status of LOST';
291 UPDATE permission.perm_list
292 SET description = 'Allows a user to cancel a transit on a copy with status of MISSING'
293 WHERE code = 'ABORT_TRANSIT_ON_MISSING'
294 AND description = 'Allows a user to abort a transit on a copy with status of MISSING';
296 SELECT evergreen.upgrade_deps_block_check('1046', :eg_version); -- phasefx/berick/gmcharlt
298 INSERT into config.org_unit_setting_type (
304 ) VALUES ( ----------------------------------------
305 'webstaff.format.dates'
308 'webstaff.format.dates'
309 ,'Format Dates with this pattern'
314 'webstaff.format.dates'
315 ,'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.'
320 ), ( ----------------------------------------
321 'webstaff.format.date_and_time'
324 'webstaff.format.date_and_time'
325 ,'Format Date+Time with this pattern'
330 'webstaff.format.date_and_time'
331 ,'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.'
339 config.org_unit_setting_type
341 label = 'Deprecated: ' || label -- FIXME: Is this okay?
343 name IN ('format.date','format.time')
347 SELECT evergreen.upgrade_deps_block_check('1047', :eg_version); -- gmcharlt/stompro
349 CREATE TABLE config.copy_tag_type (
350 code TEXT NOT NULL PRIMARY KEY,
352 owner INTEGER NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
355 CREATE INDEX config_copy_tag_type_owner_idx
356 ON config.copy_tag_type (owner);
358 CREATE TABLE asset.copy_tag (
359 id SERIAL PRIMARY KEY,
360 tag_type TEXT REFERENCES config.copy_tag_type (code)
361 ON UPDATE CASCADE ON DELETE CASCADE,
364 index_vector tsvector NOT NULL,
366 pub BOOLEAN DEFAULT TRUE,
367 owner INTEGER NOT NULL REFERENCES actor.org_unit (id)
370 CREATE INDEX asset_copy_tag_label_idx
371 ON asset.copy_tag (label);
372 CREATE INDEX asset_copy_tag_label_lower_idx
373 ON asset.copy_tag (evergreen.lowercase(label));
374 CREATE INDEX asset_copy_tag_index_vector_idx
376 USING GIN(index_vector);
377 CREATE INDEX asset_copy_tag_tag_type_idx
378 ON asset.copy_tag (tag_type);
379 CREATE INDEX asset_copy_tag_owner_idx
380 ON asset.copy_tag (owner);
382 CREATE OR REPLACE FUNCTION asset.set_copy_tag_value () RETURNS TRIGGER AS $$
384 IF NEW.value IS NULL THEN
385 NEW.value = NEW.label;
390 $$ LANGUAGE 'plpgsql';
392 -- name of following trigger chosen to ensure it runs first
393 CREATE TRIGGER asset_copy_tag_do_value
394 BEFORE INSERT OR UPDATE ON asset.copy_tag
395 FOR EACH ROW EXECUTE PROCEDURE asset.set_copy_tag_value();
396 CREATE TRIGGER asset_copy_tag_fti_trigger
397 BEFORE UPDATE OR INSERT ON asset.copy_tag
398 FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('default');
400 CREATE TABLE asset.copy_tag_copy_map (
401 id BIGSERIAL PRIMARY KEY,
402 copy BIGINT REFERENCES asset.copy (id)
403 ON UPDATE CASCADE ON DELETE CASCADE,
404 tag INTEGER REFERENCES asset.copy_tag (id)
405 ON UPDATE CASCADE ON DELETE CASCADE
408 CREATE INDEX asset_copy_tag_copy_map_copy_idx
409 ON asset.copy_tag_copy_map (copy);
410 CREATE INDEX asset_copy_tag_copy_map_tag_idx
411 ON asset.copy_tag_copy_map (tag);
413 INSERT INTO config.copy_tag_type (code, label, owner) VALUES ('bookplate', 'Digital Bookplate', 1);
415 INSERT INTO permission.perm_list ( id, code, description ) VALUES
416 ( 590, 'ADMIN_COPY_TAG_TYPES', oils_i18n_gettext( 590,
417 'Administer copy tag types', 'ppl', 'description' )),
418 ( 591, 'ADMIN_COPY_TAG', oils_i18n_gettext( 591,
419 'Administer copy tag', 'ppl', 'description' ))
422 INSERT INTO config.org_unit_setting_type
423 (name, label, description, grp, datatype)
425 'opac.search.enable_bookplate_search',
427 'opac.search.enable_bookplate_search',
428 'Enable Digital Bookplate Search',
433 'opac.search.enable_bookplate_search',
434 'If enabled, adds a "Digital Bookplate" option to the query type selectors in the public catalog for search on copy tags.',
443 SELECT evergreen.upgrade_deps_block_check('1048', :eg_version);
445 INSERT into config.org_unit_setting_type (
451 ) VALUES ( ----------------------------------------
452 'webstaff.cat.label.font.family'
455 'webstaff.cat.label.font.family'
456 ,'Item Print Label Font Family'
461 'webstaff.cat.label.font.family'
462 ,'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"'
467 ), ( ----------------------------------------
468 'webstaff.cat.label.font.size'
471 'webstaff.cat.label.font.size'
472 ,'Item Print Label Font Size'
477 'webstaff.cat.label.font.size'
478 ,'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"'
483 ), ( ----------------------------------------
484 'webstaff.cat.label.font.weight'
487 'webstaff.cat.label.font.weight'
488 ,'Item Print Label Font Weight'
493 'webstaff.cat.label.font.weight'
494 ,'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"'
499 ), ( ----------------------------------------
500 'webstaff.cat.label.left_label.left_margin'
503 'webstaff.cat.label.left_label.left_margin'
504 ,'Item Print Label - Left Margin for Left Label'
509 'webstaff.cat.label.left_label.left_margin'
510 ,'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"'
515 ), ( ----------------------------------------
516 'webstaff.cat.label.right_label.left_margin'
519 'webstaff.cat.label.right_label.left_margin'
520 ,'Item Print Label - Left Margin for Right Label'
525 'webstaff.cat.label.right_label.left_margin'
526 ,'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"'
531 ), ( ----------------------------------------
532 'webstaff.cat.label.left_label.height'
535 'webstaff.cat.label.left_label.height'
536 ,'Item Print Label - Height for Left Label'
541 'webstaff.cat.label.left_label.height'
542 ,'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"'
547 ), ( ----------------------------------------
548 'webstaff.cat.label.left_label.width'
551 'webstaff.cat.label.left_label.width'
552 ,'Item Print Label - Width for Left Label'
557 'webstaff.cat.label.left_label.width'
558 ,'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"'
563 ), ( ----------------------------------------
564 'webstaff.cat.label.right_label.height'
567 'webstaff.cat.label.right_label.height'
568 ,'Item Print Label - Height for Right Label'
573 'webstaff.cat.label.right_label.height'
574 ,'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"'
579 ), ( ----------------------------------------
580 'webstaff.cat.label.right_label.width'
583 'webstaff.cat.label.right_label.width'
584 ,'Item Print Label - Width for Right Label'
589 'webstaff.cat.label.right_label.width'
590 ,'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"'
596 'webstaff.cat.label.inline_css'
599 'webstaff.cat.label.inline_css'
600 ,'Item Print Label - Inline CSS'
605 'webstaff.cat.label.inline_css'
606 ,'This setting allows you to inject arbitrary CSS into the item print label template. For example, ".printlabel { text-transform: uppercase; }"'
612 'webstaff.cat.label.call_number_wrap_filter_height'
615 'webstaff.cat.label.call_number_wrap_filter_height'
616 ,'Item Print Label - Call Number Wrap Filter Height'
621 'webstaff.cat.label.call_number_wrap_filter_height'
622 ,'This setting is used to set the default height (in number of lines) to use for call number wrapping in the left print label.'
628 'webstaff.cat.label.call_number_wrap_filter_width'
631 'webstaff.cat.label.call_number_wrap_filter_width'
632 ,'Item Print Label - Call Number Wrap Filter Width'
637 'webstaff.cat.label.call_number_wrap_filter_width'
638 ,'This setting is used to set the default width (in number of characters) to use for call number wrapping in the left print label.'
647 -- for testing, setting removal:
648 --DELETE FROM actor.org_unit_setting WHERE name IN (
649 -- 'webstaff.cat.label.font.family'
650 -- ,'webstaff.cat.label.font.size'
651 -- ,'webstaff.cat.label.font.weight'
652 -- ,'webstaff.cat.label.left_label.height'
653 -- ,'webstaff.cat.label.left_label.width'
654 -- ,'webstaff.cat.label.left_label.left_margin'
655 -- ,'webstaff.cat.label.right_label.height'
656 -- ,'webstaff.cat.label.right_label.width'
657 -- ,'webstaff.cat.label.right_label.left_margin'
658 -- ,'webstaff.cat.label.inline_css'
659 -- ,'webstaff.cat.label.call_number_wrap_filter_height'
660 -- ,'webstaff.cat.label.call_number_wrap_filter_width'
662 --DELETE FROM config.org_unit_setting_type_log WHERE field_name IN (
663 -- 'webstaff.cat.label.font.family'
664 -- ,'webstaff.cat.label.font.size'
665 -- ,'webstaff.cat.label.font.weight'
666 -- ,'webstaff.cat.label.left_label.height'
667 -- ,'webstaff.cat.label.left_label.width'
668 -- ,'webstaff.cat.label.left_label.left_margin'
669 -- ,'webstaff.cat.label.right_label.height'
670 -- ,'webstaff.cat.label.right_label.width'
671 -- ,'webstaff.cat.label.right_label.left_margin'
672 -- ,'webstaff.cat.label.inline_css'
673 -- ,'webstaff.cat.label.call_number_wrap_filter_height'
674 -- ,'webstaff.cat.label.call_number_wrap_filter_width'
676 --DELETE FROM config.org_unit_setting_type WHERE name IN (
677 -- 'webstaff.cat.label.font.family'
678 -- ,'webstaff.cat.label.font.size'
679 -- ,'webstaff.cat.label.font.weight'
680 -- ,'webstaff.cat.label.left_label.height'
681 -- ,'webstaff.cat.label.left_label.width'
682 -- ,'webstaff.cat.label.left_label.left_margin'
683 -- ,'webstaff.cat.label.right_label.height'
684 -- ,'webstaff.cat.label.right_label.width'
685 -- ,'webstaff.cat.label.right_label.left_margin'
686 -- ,'webstaff.cat.label.inline_css'
687 -- ,'webstaff.cat.label.call_number_wrap_filter_height'
688 -- ,'webstaff.cat.label.call_number_wrap_filter_width'
693 SELECT evergreen.upgrade_deps_block_check('1049', :eg_version); -- mmorgan/stompro/gmcharlt
695 \echo -----------------------------------------------------------
696 \echo Setting invalid age_protect and circ_as_type entries to NULL,
697 \echo otherwise they will break the Serial Copy Templates editor.
698 \echo Please review any Serial Copy Templates listed below.
700 UPDATE asset.copy_template act
701 SET age_protect = NULL
702 FROM actor.org_unit aou
703 WHERE aou.id=act.owning_lib
704 AND act.age_protect NOT IN
706 SELECT id FROM config.rule_age_hold_protect
708 RETURNING act.id "Template ID", act.name "Template Name",
709 aou.shortname "Owning Lib",
710 'Age Protection value reset to null.' "Description";
712 UPDATE asset.copy_template act
713 SET circ_as_type = NULL
714 FROM actor.org_unit aou
715 WHERE aou.id=act.owning_lib
716 AND act.circ_as_type NOT IN
718 SELECT code FROM config.item_type_map
720 RETURNING act.id "Template ID", act.name "Template Name",
721 aou.shortname "Owning Lib",
722 'Circ as Type value reset to null.' as "Description";
724 \echo -----------End Serial Template Fix----------------
726 SELECT evergreen.upgrade_deps_block_check('1050', :eg_version); -- mmorgan/cesardv/gmcharlt
728 CREATE OR REPLACE FUNCTION permission.usr_perms ( INT ) RETURNS SETOF permission.usr_perm_map AS $$
729 SELECT DISTINCT ON (usr,perm) *
731 (SELECT * FROM permission.usr_perm_map WHERE usr = $1)
733 (SELECT -p.id, $1 AS usr, p.perm, p.depth, p.grantable
734 FROM permission.grp_perm_map p
736 SELECT (permission.grp_ancestors(
737 (SELECT profile FROM actor.usr WHERE id = $1)
742 (SELECT -p.id, $1 AS usr, p.perm, p.depth, p.grantable
743 FROM permission.grp_perm_map p
744 WHERE p.grp IN (SELECT (permission.grp_ancestors(m.grp)).id FROM permission.usr_grp_map m WHERE usr = $1))
746 ORDER BY 2, 3, 4 ASC, 5 DESC ;
747 $$ LANGUAGE SQL STABLE ROWS 10;
749 SELECT evergreen.upgrade_deps_block_check('1051', :eg_version);
751 CREATE OR REPLACE VIEW action.all_circulation_slim AS
785 FROM action.circulation
820 FROM action.aged_circulation
823 DROP FUNCTION action.summarize_all_circ_chain(INTEGER);
824 DROP FUNCTION action.all_circ_chain(INTEGER);
826 CREATE OR REPLACE FUNCTION action.all_circ_chain (ctx_circ_id INTEGER)
827 RETURNS SETOF action.all_circulation_slim AS $$
829 tmp_circ action.all_circulation_slim%ROWTYPE;
830 circ_0 action.all_circulation_slim%ROWTYPE;
833 SELECT INTO tmp_circ * FROM action.all_circulation_slim WHERE id = ctx_circ_id;
835 IF tmp_circ IS NULL THEN
836 RETURN NEXT tmp_circ;
840 -- find the front of the chain
842 SELECT INTO tmp_circ * FROM action.all_circulation_slim
843 WHERE id = tmp_circ.parent_circ;
844 IF tmp_circ IS NULL THEN
850 -- now send the circs to the caller, oldest to newest
853 IF tmp_circ IS NULL THEN
856 RETURN NEXT tmp_circ;
857 SELECT INTO tmp_circ * FROM action.all_circulation_slim
858 WHERE parent_circ = tmp_circ.id;
862 $$ LANGUAGE 'plpgsql';
864 CREATE OR REPLACE FUNCTION action.summarize_all_circ_chain
865 (ctx_circ_id INTEGER) RETURNS action.circ_chain_summary AS $$
869 -- first circ in the chain
870 circ_0 action.all_circulation_slim%ROWTYPE;
872 -- last circ in the chain
873 circ_n action.all_circulation_slim%ROWTYPE;
875 -- circ chain under construction
876 chain action.circ_chain_summary;
877 tmp_circ action.all_circulation_slim%ROWTYPE;
881 chain.num_circs := 0;
882 FOR tmp_circ IN SELECT * FROM action.all_circ_chain(ctx_circ_id) LOOP
884 IF chain.num_circs = 0 THEN
888 chain.num_circs := chain.num_circs + 1;
892 chain.start_time := circ_0.xact_start;
893 chain.last_stop_fines := circ_n.stop_fines;
894 chain.last_stop_fines_time := circ_n.stop_fines_time;
895 chain.last_checkin_time := circ_n.checkin_time;
896 chain.last_checkin_scan_time := circ_n.checkin_scan_time;
897 SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
898 SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
900 IF chain.num_circs > 1 THEN
901 chain.last_renewal_time := circ_n.xact_start;
902 SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
908 $$ LANGUAGE 'plpgsql';
910 CREATE OR REPLACE FUNCTION rating.percent_time_circulating(badge_id INT)
911 RETURNS TABLE (record BIGINT, value NUMERIC) AS $f$
913 badge rating.badge_with_orgs%ROWTYPE;
916 SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;
918 PERFORM rating.precalc_bibs_by_copy(badge_id);
920 DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
921 SELECT id FROM precalc_filter_bib_list
923 SELECT id FROM precalc_bibs_by_copy_list
926 ANALYZE precalc_copy_filter_bib_list;
930 SUM(COALESCE(circ_time,0))::NUMERIC / SUM(age)::NUMERIC
931 FROM (SELECT cn.record AS bib,
933 EXTRACT( EPOCH FROM AGE(cp.active_date) ) + 1 AS age,
934 SUM( -- time copy spent circulating
938 COALESCE(circ.checkin_time, circ.stop_fines_time, NOW()),
942 )::NUMERIC AS circ_time
944 JOIN precalc_copy_filter_bib_list c ON (cp.id = c.copy)
945 JOIN asset.call_number cn ON (cn.id = cp.call_number)
946 LEFT JOIN action.all_circulation_slim circ ON (
947 circ.target_copy = cp.id
948 AND stop_fines NOT IN (
955 checkin_time IS NULL AND
956 stop_fines = 'MAXFINES'
959 WHERE cn.owning_lib = ANY (badge.orgs)
960 AND cp.active_date IS NOT NULL
961 -- Next line requires that copies with no circs (circ.id IS NULL) also not be deleted
962 AND ((circ.id IS NULL AND NOT cp.deleted) OR circ.id IS NOT NULL)
967 $f$ LANGUAGE PLPGSQL STRICT;
973 SELECT evergreen.upgrade_deps_block_check('1052', :eg_version);
975 CREATE OR REPLACE FUNCTION rating.inhouse_over_time(badge_id INT)
976 RETURNS TABLE (record BIGINT, value NUMERIC) AS $f$
978 badge rating.badge_with_orgs%ROWTYPE;
981 iscale NUMERIC := NULL;
984 SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;
986 IF badge.horizon_age IS NULL THEN
987 RAISE EXCEPTION 'Badge "%" with id % requires a horizon age but has none.',
992 PERFORM rating.precalc_bibs_by_copy(badge_id);
994 DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
995 SELECT id FROM precalc_filter_bib_list
997 SELECT id FROM precalc_bibs_by_copy_list
1000 ANALYZE precalc_copy_filter_bib_list;
1002 iint := EXTRACT(EPOCH FROM badge.importance_interval);
1003 IF badge.importance_age IS NOT NULL THEN
1004 iage := (EXTRACT(EPOCH FROM badge.importance_age) / iint)::INT;
1007 -- if iscale is smaller than 1, scaling slope will be shallow ... BEWARE!
1008 iscale := COALESCE(badge.importance_scale, 1.0);
1012 SUM( uses * GREATEST( iscale * (iage - cage), 1.0 ))
1014 SELECT cn.record AS bib,
1015 (1 + EXTRACT(EPOCH FROM AGE(u.use_time)) / iint)::INT AS cage,
1016 COUNT(u.id)::INT AS uses
1017 FROM action.in_house_use u
1018 JOIN precalc_copy_filter_bib_list cf ON (u.item = cf.copy)
1019 JOIN asset.copy cp ON (cp.id = u.item)
1020 JOIN asset.call_number cn ON (cn.id = cp.call_number)
1021 WHERE u.use_time >= NOW() - badge.horizon_age
1022 AND cn.owning_lib = ANY (badge.orgs)
1027 $f$ LANGUAGE PLPGSQL STRICT;
1029 INSERT INTO rating.popularity_parameter (id, name, func, require_horizon,require_percentile) VALUES
1030 (18,'In-House Use Over Time', 'rating.inhouse_over_time', TRUE, TRUE);
1034 SELECT evergreen.upgrade_deps_block_check('1053', :eg_version);
1036 CREATE OR REPLACE FUNCTION rating.org_unit_count(badge_id INT)
1037 RETURNS TABLE (record INT, value NUMERIC) AS $f$
1039 badge rating.badge_with_orgs%ROWTYPE;
1042 SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;
1044 PERFORM rating.precalc_bibs_by_copy(badge_id);
1046 DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
1047 SELECT id FROM precalc_filter_bib_list
1049 SELECT id FROM precalc_bibs_by_copy_list
1051 ANALYZE precalc_copy_filter_bib_list;
1053 -- Use circ rather than owning lib here as that means "on the shelf at..."
1055 SELECT f.id::INT AS bib,
1056 COUNT(DISTINCT cp.circ_lib)::NUMERIC
1058 JOIN precalc_copy_filter_bib_list f ON (cp.id = f.copy)
1059 WHERE cp.circ_lib = ANY (badge.orgs) GROUP BY 1;
1062 $f$ LANGUAGE PLPGSQL STRICT;
1064 INSERT INTO rating.popularity_parameter (id, name, func, require_percentile) VALUES
1065 (17,'Circulation Library Count', 'rating.org_unit_count', TRUE);
1069 SELECT evergreen.upgrade_deps_block_check('1054', :eg_version);
1071 INSERT into config.org_unit_setting_type
1072 ( name, grp, label, description, datatype ) VALUES
1074 ( 'lib.timezone', 'lib',
1075 oils_i18n_gettext('lib.timezone',
1076 'Library time zone',
1078 oils_i18n_gettext('lib.timezone',
1079 'Define the time zone in which a library physically resides',
1080 'coust', 'description'),
1083 ALTER TABLE actor.org_unit_closed ADD COLUMN full_day BOOLEAN DEFAULT FALSE;
1084 ALTER TABLE actor.org_unit_closed ADD COLUMN multi_day BOOLEAN DEFAULT FALSE;
1086 UPDATE actor.org_unit_closed SET multi_day = TRUE
1087 WHERE close_start::DATE <> close_end::DATE;
1089 UPDATE actor.org_unit_closed SET full_day = TRUE
1090 WHERE close_start::DATE = close_end::DATE
1091 AND SUBSTRING(close_start::time::text FROM 1 FOR 8) = '00:00:00'
1092 AND SUBSTRING(close_end::time::text FROM 1 FOR 8) = '23:59:59';
1094 CREATE OR REPLACE FUNCTION action.push_circ_due_time () RETURNS TRIGGER AS $$
1096 proper_tz TEXT := COALESCE(
1099 FROM actor.org_unit_ancestor_setting('lib.timezone',NEW.circ_lib)
1102 CURRENT_SETTING('timezone')
1106 IF (EXTRACT(EPOCH FROM NEW.duration)::INT % EXTRACT(EPOCH FROM '1 day'::INTERVAL)::INT) = 0 -- day-granular duration
1107 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
1108 NEW.due_date = ((NEW.due_date AT TIME ZONE proper_tz)::DATE + '1 day'::INTERVAL - '1 second'::INTERVAL) || ' ' || proper_tz;
1113 $$ LANGUAGE PLPGSQL;
1116 \qecho The following query will adjust all historical, unaged circulations so
1117 \qecho that if their due date field is pushed to the end of the day, it is done
1118 \qecho in the circulating library''''s time zone, and not the server time zone.
1120 \qecho It is safe to run this after any change to library time zones.
1122 \qecho Running this is not required, as no code before this change has
1123 \qecho depended on the time string of '''23:59:59'''. It is also not necessary
1124 \qecho if all of your libraries are in the same time zone, and that time zone
1125 \qecho is the same as the database''''s configured time zone.
1129 \qecho ' new_tz text;'
1130 \qecho ' ou_id int;'
1132 \qecho ' for ou_id in select id from actor.org_unit loop'
1133 \qecho ' for new_tz in select oils_json_to_text(value) from actor.org_unit_ancestor_setting('''lib.timezone''',ou_id) loop'
1134 \qecho ' if new_tz is not null then'
1135 \qecho ' update action.circulation'
1136 \qecho ' set due_date = (due_date::timestamp || ''' ''' || new_tz)::timestamptz'
1137 \qecho ' where circ_lib = ou_id'
1138 \qecho ' and substring((due_date at time zone new_tz)::time::text from 1 for 8) <> '''23:59:59''';'
1146 SELECT evergreen.upgrade_deps_block_check('1055', :eg_version);
1148 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 $$
1152 picklist_row RECORD;
1157 -- do some initial cleanup
1158 UPDATE actor.usr SET card = NULL WHERE id = src_usr;
1159 UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
1160 UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
1164 DELETE FROM actor.card where usr = src_usr;
1166 IF deactivate_cards THEN
1167 UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
1169 UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
1174 DELETE FROM actor.usr_address WHERE usr = src_usr;
1176 UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
1179 UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
1180 -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
1181 UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
1182 PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
1183 PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
1186 PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
1187 PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
1188 PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
1189 PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
1194 -- For each *_bucket table: transfer every bucket belonging to src_usr
1195 -- into the custody of dest_usr.
1197 -- In order to avoid colliding with an existing bucket owned by
1198 -- the destination user, append the source user's id (in parenthesese)
1199 -- to the name. If you still get a collision, add successive
1200 -- spaces to the name and keep trying until you succeed.
1204 FROM container.biblio_record_entry_bucket
1205 WHERE owner = src_usr
1207 suffix := ' (' || src_usr || ')';
1210 UPDATE container.biblio_record_entry_bucket
1211 SET owner = dest_usr, name = name || suffix
1212 WHERE id = bucket_row.id;
1213 EXCEPTION WHEN unique_violation THEN
1214 suffix := suffix || ' ';
1223 FROM container.call_number_bucket
1224 WHERE owner = src_usr
1226 suffix := ' (' || src_usr || ')';
1229 UPDATE container.call_number_bucket
1230 SET owner = dest_usr, name = name || suffix
1231 WHERE id = bucket_row.id;
1232 EXCEPTION WHEN unique_violation THEN
1233 suffix := suffix || ' ';
1242 FROM container.copy_bucket
1243 WHERE owner = src_usr
1245 suffix := ' (' || src_usr || ')';
1248 UPDATE container.copy_bucket
1249 SET owner = dest_usr, name = name || suffix
1250 WHERE id = bucket_row.id;
1251 EXCEPTION WHEN unique_violation THEN
1252 suffix := suffix || ' ';
1261 FROM container.user_bucket
1262 WHERE owner = src_usr
1264 suffix := ' (' || src_usr || ')';
1267 UPDATE container.user_bucket
1268 SET owner = dest_usr, name = name || suffix
1269 WHERE id = bucket_row.id;
1270 EXCEPTION WHEN unique_violation THEN
1271 suffix := suffix || ' ';
1278 UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
1281 -- transfer queues the same way we transfer buckets (see above)
1285 WHERE owner = src_usr
1287 suffix := ' (' || src_usr || ')';
1290 UPDATE vandelay.queue
1291 SET owner = dest_usr, name = name || suffix
1292 WHERE id = queue_row.id;
1293 EXCEPTION WHEN unique_violation THEN
1294 suffix := suffix || ' ';
1302 PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
1303 PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
1304 UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
1305 UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
1306 UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
1309 UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
1310 UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
1311 UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
1312 UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
1314 UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
1315 UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
1316 UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
1317 UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
1319 UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
1320 UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
1321 UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
1322 UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
1323 UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
1326 UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
1327 UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
1329 -- transfer picklists the same way we transfer buckets (see above)
1333 WHERE owner = src_usr
1335 suffix := ' (' || src_usr || ')';
1339 SET owner = dest_usr, name = name || suffix
1340 WHERE id = picklist_row.id;
1341 EXCEPTION WHEN unique_violation THEN
1342 suffix := suffix || ' ';
1349 UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
1350 UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
1351 UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
1352 UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
1353 UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
1354 UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
1355 UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
1356 UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
1359 UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
1360 UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
1361 UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
1362 UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
1363 UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
1364 UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
1367 UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
1368 UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
1371 -- It's not uncommon to define the reporter schema in a replica
1372 -- DB only, so don't assume these tables exist in the write DB.
1374 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
1375 EXCEPTION WHEN undefined_table THEN
1379 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
1380 EXCEPTION WHEN undefined_table THEN
1384 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
1385 EXCEPTION WHEN undefined_table THEN
1389 -- transfer folders the same way we transfer buckets (see above)
1392 FROM reporter.template_folder
1393 WHERE owner = src_usr
1395 suffix := ' (' || src_usr || ')';
1398 UPDATE reporter.template_folder
1399 SET owner = dest_usr, name = name || suffix
1400 WHERE id = folder_row.id;
1401 EXCEPTION WHEN unique_violation THEN
1402 suffix := suffix || ' ';
1408 EXCEPTION WHEN undefined_table THEN
1412 -- transfer folders the same way we transfer buckets (see above)
1415 FROM reporter.report_folder
1416 WHERE owner = src_usr
1418 suffix := ' (' || src_usr || ')';
1421 UPDATE reporter.report_folder
1422 SET owner = dest_usr, name = name || suffix
1423 WHERE id = folder_row.id;
1424 EXCEPTION WHEN unique_violation THEN
1425 suffix := suffix || ' ';
1431 EXCEPTION WHEN undefined_table THEN
1435 -- transfer folders the same way we transfer buckets (see above)
1438 FROM reporter.output_folder
1439 WHERE owner = src_usr
1441 suffix := ' (' || src_usr || ')';
1444 UPDATE reporter.output_folder
1445 SET owner = dest_usr, name = name || suffix
1446 WHERE id = folder_row.id;
1447 EXCEPTION WHEN unique_violation THEN
1448 suffix := suffix || ' ';
1454 EXCEPTION WHEN undefined_table THEN
1458 -- Finally, delete the source user
1459 DELETE FROM actor.usr WHERE id = src_usr;
1462 $$ LANGUAGE plpgsql;
1468 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1056', :eg_version); -- miker/gmcharlt
1470 INSERT INTO permission.perm_list (id,code,description) VALUES (592,'CONTAINER_BATCH_UPDATE','Allow batch update via buckets');
1472 INSERT INTO container.user_bucket_type (code,label) SELECT code,label FROM container.copy_bucket_type where code = 'staff_client';
1474 CREATE TABLE action.fieldset_group (
1475 id SERIAL PRIMARY KEY,
1477 create_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1478 complete_time TIMESTAMPTZ,
1479 container INT, -- Points to a container of some type ...
1480 container_type TEXT, -- One of 'biblio_record_entry', 'user', 'call_number', 'copy'
1481 can_rollback BOOL DEFAULT TRUE,
1482 rollback_group INT REFERENCES action.fieldset_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1483 rollback_time TIMESTAMPTZ,
1484 creator INT NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1485 owning_lib INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
1488 ALTER TABLE action.fieldset ADD COLUMN fieldset_group INT REFERENCES action.fieldset_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
1489 ALTER TABLE action.fieldset ADD COLUMN error_msg TEXT;
1490 ALTER TABLE container.biblio_record_entry_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
1491 ALTER TABLE container.user_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
1492 ALTER TABLE container.call_number_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
1493 ALTER TABLE container.copy_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
1495 UPDATE query.stored_query SET id = id + 1000 WHERE id < 1000;
1496 UPDATE query.from_relation SET id = id + 1000 WHERE id < 1000;
1497 UPDATE query.expression SET id = id + 1000 WHERE id < 1000;
1499 SELECT SETVAL('query.stored_query_id_seq', 1, FALSE);
1500 SELECT SETVAL('query.from_relation_id_seq', 1, FALSE);
1501 SELECT SETVAL('query.expression_id_seq', 1, FALSE);
1503 INSERT INTO query.bind_variable (name,type,description,label)
1504 SELECT 'bucket','number','ID of the bucket to pull items from','Bucket ID'
1505 WHERE NOT EXISTS (SELECT 1 FROM query.bind_variable WHERE name = 'bucket');
1507 -- Assumes completely empty 'query' schema
1508 INSERT INTO query.stored_query (type, use_distinct) VALUES ('SELECT', TRUE); -- 1
1510 INSERT INTO query.from_relation (type, table_name, class_name, table_alias) VALUES ('RELATION', 'container.user_bucket_item', 'cubi', 'cubi'); -- 1
1511 UPDATE query.stored_query SET from_clause = 1;
1513 INSERT INTO query.expr_xcol (table_alias, column_name) VALUES ('cubi', 'target_user'); -- 1
1514 INSERT INTO query.select_item (stored_query,seq_no,expression) VALUES (1,1,1);
1516 INSERT INTO query.expr_xcol (table_alias, column_name) VALUES ('cubi', 'bucket'); -- 2
1517 INSERT INTO query.expr_xbind (bind_variable) VALUES ('bucket'); -- 3
1519 INSERT INTO query.expr_xop (left_operand, operator, right_operand) VALUES (2, '=', 3); -- 4
1520 UPDATE query.stored_query SET where_clause = 4;
1522 SELECT SETVAL('query.stored_query_id_seq', 1000, TRUE) FROM query.stored_query;
1523 SELECT SETVAL('query.from_relation_id_seq', 1000, TRUE) FROM query.from_relation;
1524 SELECT SETVAL('query.expression_id_seq', 10000, TRUE) FROM query.expression;
1526 CREATE OR REPLACE FUNCTION action.apply_fieldset(
1527 fieldset_id IN INT, -- id from action.fieldset
1528 table_name IN TEXT, -- table to be updated
1529 pkey_name IN TEXT, -- name of primary key column in that table
1530 query IN TEXT -- query constructed by qstore (for query-based
1531 -- fieldsets only; otherwise null
1547 fs_obj action.fieldset%ROWTYPE;
1548 fs_group action.fieldset_group%ROWTYPE;
1552 IF fieldset_id IS NULL THEN
1553 RETURN 'Fieldset ID parameter is NULL';
1555 IF table_name IS NULL THEN
1556 RETURN 'Table name parameter is NULL';
1558 IF pkey_name IS NULL THEN
1559 RETURN 'Primary key name parameter is NULL';
1564 quote_literal( pkey_value )
1574 -- Build the WHERE clause. This differs according to whether it's a
1575 -- single-row fieldset or a query-based fieldset.
1577 IF query IS NULL AND fs_pkey_value IS NULL THEN
1578 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
1579 ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
1580 fs_query := rtrim( query, ';' );
1581 where_clause := 'WHERE ' || pkey_name || ' IN ( '
1582 || fs_query || ' )';
1583 ELSIF query IS NULL AND fs_pkey_value IS NOT NULL THEN
1584 where_clause := 'WHERE ' || pkey_name || ' = ';
1585 IF pkey_name = 'id' THEN
1586 where_clause := where_clause || fs_pkey_value;
1587 ELSIF pkey_name = 'code' THEN
1588 where_clause := where_clause || quote_literal(fs_pkey_value);
1590 RETURN 'Only know how to handle "id" and "code" pkeys currently, received ' || pkey_name;
1592 ELSE -- both are not null
1593 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
1596 IF fs_status IS NULL THEN
1597 RETURN 'No fieldset found for id = ' || fieldset_id;
1598 ELSIF fs_status = 'APPLIED' THEN
1599 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
1602 SELECT * INTO fs_obj FROM action.fieldset WHERE id = fieldset_id;
1603 SELECT * INTO fs_group FROM action.fieldset_group WHERE id = fs_obj.fieldset_group;
1605 IF fs_group.can_rollback THEN
1606 -- This is part of a non-rollback group. We need to record the current values for future rollback.
1608 INSERT INTO action.fieldset_group (can_rollback, name, creator, owning_lib, container, container_type)
1609 VALUES (FALSE, 'ROLLBACK: '|| fs_group.name, fs_group.creator, fs_group.owning_lib, fs_group.container, fs_group.container_type);
1611 fsg_id := CURRVAL('action.fieldset_group_id_seq');
1613 FOR rb_row IN EXECUTE 'SELECT * FROM ' || table_name || ' ' || where_clause LOOP
1614 IF pkey_name = 'id' THEN
1615 fs_pkey_value := rb_row.id;
1616 ELSIF pkey_name = 'code' THEN
1617 fs_pkey_value := rb_row.code;
1619 RETURN 'Only know how to handle "id" and "code" pkeys currently, received ' || pkey_name;
1621 INSERT INTO action.fieldset (fieldset_group,owner,owning_lib,status,classname,name,pkey_value)
1622 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);
1624 fs_id := CURRVAL('action.fieldset_id_seq');
1628 FROM action.fieldset_col_val
1629 WHERE fieldset = fieldset_id
1631 EXECUTE 'INSERT INTO action.fieldset_col_val (fieldset, col, val) ' ||
1632 'SELECT '|| fs_id || ', '||quote_literal(cv.col)||', '||cv.col||' FROM '||table_name||' WHERE '||pkey_name||' = '||fs_pkey_value;
1637 statement := 'UPDATE ' || table_name || ' SET';
1643 FROM action.fieldset_col_val
1644 WHERE fieldset = fieldset_id
1646 statement := statement || sep || ' ' || cv.col
1647 || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
1652 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
1654 statement := statement || ' ' || where_clause;
1657 -- Execute the update
1661 GET DIAGNOSTICS update_count = ROW_COUNT;
1663 IF update_count = 0 THEN
1664 RAISE data_exception;
1667 IF fsg_id IS NOT NULL THEN
1668 UPDATE action.fieldset_group SET rollback_group = fsg_id WHERE id = fs_group.id;
1671 IF fs_group.id IS NOT NULL THEN
1672 UPDATE action.fieldset_group SET complete_time = now() WHERE id = fs_group.id;
1675 UPDATE action.fieldset SET status = 'APPLIED', applied_time = now() WHERE id = fieldset_id;
1677 EXCEPTION WHEN data_exception THEN
1678 msg := 'No eligible rows found for fieldset ' || fieldset_id;
1679 UPDATE action.fieldset SET status = 'ERROR', applied_time = now() WHERE id = fieldset_id;
1686 EXCEPTION WHEN OTHERS THEN
1687 msg := 'Unable to apply fieldset ' || fieldset_id || ': ' || sqlerrm;
1688 UPDATE action.fieldset SET status = 'ERROR', applied_time = now() WHERE id = fieldset_id;
1692 $$ LANGUAGE plpgsql;
1696 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1057', :eg_version); -- miker/gmcharlt/kmlussier
1698 -- Thist change drops a needless join and saves 10-15% in time cost
1699 CREATE OR REPLACE FUNCTION search.facets_for_record_set(ignore_facet_classes text[], hits bigint[]) RETURNS TABLE(id integer, value text, count bigint)
1701 SELECT id, value, count
1703 SELECT mfae.field AS id,
1705 COUNT(DISTINCT mfae.source),
1707 PARTITION BY mfae.field ORDER BY COUNT(DISTINCT mfae.source) DESC
1709 FROM metabib.facet_entry mfae
1710 JOIN config.metabib_field cmf ON (cmf.id = mfae.field)
1711 WHERE mfae.source = ANY ($2)
1713 AND cmf.field_class NOT IN (SELECT * FROM unnest($1))
1718 (SELECT value::INT FROM config.global_flag WHERE name = 'search.max_facets_per_field' AND enabled),
1724 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$
1726 layout unapi.bre_output_layout%ROWTYPE;
1727 transform config.xml_transform%ROWTYPE;
1730 xmlns_uri TEXT := 'http://open-ils.org/spec/feed-xml/v1';
1732 element_list TEXT[];
1735 IF org = '-' OR org IS NULL THEN
1736 SELECT shortname INTO org FROM evergreen.org_top();
1739 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
1740 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
1742 IF layout.name IS NULL THEN
1746 SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
1747 xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
1749 -- Gather the bib xml
1750 SELECT XMLAGG( unapi.mmr(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
1752 IF layout.title_element IS NOT NULL THEN
1753 EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
1756 IF layout.description_element IS NOT NULL THEN
1757 EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
1760 IF layout.creator_element IS NOT NULL THEN
1761 EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
1764 IF layout.update_ts_element IS NOT NULL THEN
1765 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;
1768 IF unapi_url IS NOT NULL THEN
1769 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;
1772 IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
1774 element_list := regexp_split_to_array(layout.feed_top,E'\\.');
1775 FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
1776 EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
1779 RETURN tmp_xml::XML;
1781 $F$ LANGUAGE PLPGSQL STABLE;
1783 CREATE TABLE asset.copy_vis_attr_cache (
1784 id BIGSERIAL PRIMARY KEY,
1785 record BIGINT NOT NULL, -- No FKEYs, managed by user triggers.
1786 target_copy BIGINT NOT NULL,
1787 vis_attr_vector INT[]
1789 CREATE INDEX copy_vis_attr_cache_record_idx ON asset.copy_vis_attr_cache (record);
1790 CREATE INDEX copy_vis_attr_cache_copy_idx ON asset.copy_vis_attr_cache (target_copy);
1792 ALTER TABLE biblio.record_entry ADD COLUMN vis_attr_vector INT[];
1794 CREATE OR REPLACE FUNCTION search.calculate_visibility_attribute ( value INT, attr TEXT ) RETURNS INT AS $f$
1797 WHEN 'luri_org' THEN 0 -- "b" attr
1798 WHEN 'bib_source' THEN 1 -- "b" attr
1800 WHEN 'copy_flags' THEN 0 -- "c" attr
1801 WHEN 'owning_lib' THEN 1 -- "c" attr
1802 WHEN 'circ_lib' THEN 2 -- "c" attr
1803 WHEN 'status' THEN 3 -- "c" attr
1804 WHEN 'location' THEN 4 -- "c" attr
1805 WHEN 'location_group' THEN 5 -- "c" attr
1809 /* copy_flags bit positions, LSB-first:
1811 0: asset.copy.opac_visible
1814 When adding flags, you must update asset.all_visible_flags()
1816 Because bib and copy values are stored separately, we can reuse
1817 shifts, saving us some space. We could probably take back a bit
1818 too, but I'm not sure its worth squeezing that last one out. We'd
1819 be left with just 2 slots for copy attrs, rather than 10.
1822 $f$ LANGUAGE SQL IMMUTABLE;
1824 CREATE OR REPLACE FUNCTION search.calculate_visibility_attribute_list ( attr TEXT, value INT[] ) RETURNS INT[] AS $f$
1825 SELECT ARRAY_AGG(search.calculate_visibility_attribute(x, $1)) FROM UNNEST($2) AS X;
1826 $f$ LANGUAGE SQL IMMUTABLE;
1828 CREATE OR REPLACE FUNCTION search.calculate_visibility_attribute_test ( attr TEXT, value INT[], negate BOOL DEFAULT FALSE ) RETURNS TEXT AS $f$
1829 SELECT CASE WHEN $3 THEN '!' ELSE '' END || '(' || ARRAY_TO_STRING(search.calculate_visibility_attribute_list($1,$2),'|') || ')';
1830 $f$ LANGUAGE SQL IMMUTABLE;
1832 CREATE OR REPLACE FUNCTION asset.calculate_copy_visibility_attribute_set ( copy_id BIGINT ) RETURNS INT[] AS $f$
1834 copy_row asset.copy%ROWTYPE;
1835 lgroup_map asset.copy_location_group_map%ROWTYPE;
1838 SELECT * INTO copy_row FROM asset.copy WHERE id = copy_id;
1840 attr_set := attr_set || search.calculate_visibility_attribute(copy_row.opac_visible::INT, 'copy_flags');
1841 attr_set := attr_set || search.calculate_visibility_attribute(copy_row.circ_lib, 'circ_lib');
1842 attr_set := attr_set || search.calculate_visibility_attribute(copy_row.status, 'status');
1843 attr_set := attr_set || search.calculate_visibility_attribute(copy_row.location, 'location');
1845 SELECT ARRAY_APPEND(
1847 search.calculate_visibility_attribute(owning_lib, 'owning_lib')
1849 FROM asset.call_number
1850 WHERE id = copy_row.call_number;
1852 FOR lgroup_map IN SELECT * FROM asset.copy_location_group_map WHERE location = copy_row.location LOOP
1853 attr_set := attr_set || search.calculate_visibility_attribute(lgroup_map.lgroup, 'location_group');
1858 $f$ LANGUAGE PLPGSQL;
1860 CREATE OR REPLACE FUNCTION biblio.calculate_bib_visibility_attribute_set ( bib_id BIGINT ) RETURNS INT[] AS $f$
1862 bib_row biblio.record_entry%ROWTYPE;
1863 cn_row asset.call_number%ROWTYPE;
1866 SELECT * INTO bib_row FROM biblio.record_entry WHERE id = bib_id;
1868 IF bib_row.source IS NOT NULL THEN
1869 attr_set := attr_set || search.calculate_visibility_attribute(bib_row.source, 'bib_source');
1874 FROM asset.call_number cn
1875 JOIN asset.uri_call_number_map m ON (cn.id = m.call_number)
1876 JOIN asset.uri u ON (u.id = m.uri)
1877 WHERE cn.record = bib_id
1878 AND cn.label = '##URI##'
1881 attr_set := attr_set || search.calculate_visibility_attribute(cn_row.owning_lib, 'luri_org');
1886 $f$ LANGUAGE PLPGSQL;
1889 SELECT evergreen.upgrade_deps_block_check('1076', :eg_version); -- miker/gmcharlt
1891 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
1893 ocn asset.call_number%ROWTYPE;
1894 ncn asset.call_number%ROWTYPE;
1898 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN -- Only needs ON INSERT OR DELETE, so handle separately
1899 IF TG_OP = 'INSERT' THEN
1900 INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES (
1903 asset.calculate_copy_visibility_attribute_set(NEW.target_copy)
1907 ELSIF TG_OP = 'DELETE' THEN
1908 DELETE FROM asset.copy_vis_attr_cache
1909 WHERE record = NEW.peer_record AND target_copy = NEW.target_copy;
1915 IF TG_OP = 'INSERT' THEN -- Handles ON INSERT. ON UPDATE is below.
1916 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
1917 SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number;
1918 INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES (
1921 asset.calculate_copy_visibility_attribute_set(NEW.id)
1923 ELSIF TG_TABLE_NAME = 'record_entry' THEN
1924 NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id);
1930 -- handle items first, since with circulation activity
1931 -- their statuses change frequently
1932 IF TG_TABLE_NAME IN ('copy', 'unit') THEN -- This handles ON UPDATE OR DELETE. ON INSERT above
1934 IF TG_OP = 'DELETE' THEN -- Shouldn't get here, normally
1935 DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id;
1939 SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number;
1941 IF OLD.deleted <> NEW.deleted THEN
1943 DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id;
1945 INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES (
1948 asset.calculate_copy_visibility_attribute_set(NEW.id)
1953 ELSIF OLD.call_number <> NEW.call_number THEN
1954 SELECT * INTO ocn FROM asset.call_number cn WHERE id = OLD.call_number;
1956 IF ncn.record <> ocn.record THEN
1957 UPDATE biblio.record_entry
1958 SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(ncn.record)
1959 WHERE id = ocn.record;
1961 -- We have to use a record-specific WHERE clause
1962 -- to avoid modifying the entries for peer-bib copies.
1963 UPDATE asset.copy_vis_attr_cache
1964 SET target_copy = NEW.id,
1966 WHERE target_copy = OLD.id
1967 AND record = ocn.record;
1971 IF OLD.location <> NEW.location OR
1972 OLD.status <> NEW.status OR
1973 OLD.opac_visible <> NEW.opac_visible OR
1974 OLD.circ_lib <> NEW.circ_lib
1976 -- Any of these could change visibility, but
1977 -- we'll save some queries and not try to calculate
1978 -- the change directly. We want to update peer-bib
1979 -- entries in this case, unlike above.
1980 UPDATE asset.copy_vis_attr_cache
1981 SET target_copy = NEW.id,
1982 vis_attr_vector = asset.calculate_copy_visibility_attribute_set(NEW.id)
1983 WHERE target_copy = OLD.id;
1987 ELSIF TG_TABLE_NAME = 'call_number' THEN -- Only ON UPDATE. Copy handler will deal with ON INSERT OR DELETE.
1989 IF OLD.record <> NEW.record THEN
1990 IF NEW.label = '##URI##' THEN
1991 UPDATE biblio.record_entry
1992 SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record)
1993 WHERE id = OLD.record;
1995 UPDATE biblio.record_entry
1996 SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(NEW.record)
1997 WHERE id = NEW.record;
2000 UPDATE asset.copy_vis_attr_cache
2001 SET record = NEW.record,
2002 vis_attr_vector = asset.calculate_copy_visibility_attribute_set(target_copy)
2003 WHERE target_copy IN (SELECT id FROM asset.copy WHERE call_number = NEW.id)
2004 AND record = OLD.record;
2006 ELSIF OLD.owning_lib <> NEW.owning_lib THEN
2007 UPDATE asset.copy_vis_attr_cache
2008 SET vis_attr_vector = asset.calculate_copy_visibility_attribute_set(target_copy)
2009 WHERE target_copy IN (SELECT id FROM asset.copy WHERE call_number = NEW.id)
2010 AND record = NEW.record;
2012 IF NEW.label = '##URI##' THEN
2013 UPDATE biblio.record_entry
2014 SET vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record)
2015 WHERE id = OLD.record;
2019 ELSIF TG_TABLE_NAME = 'record_entry' THEN -- Only handles ON UPDATE OR DELETE
2021 IF TG_OP = 'DELETE' THEN -- Shouldn't get here, normally
2022 DELETE FROM asset.copy_vis_attr_cache WHERE record = OLD.id;
2024 ELSIF OLD.source <> NEW.source THEN
2025 NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id);
2032 $func$ LANGUAGE PLPGSQL;
2035 -- Helper functions for use in constructing searches --
2037 CREATE OR REPLACE FUNCTION asset.all_visible_flags () RETURNS TEXT AS $f$
2038 SELECT '(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(1 << x, 'copy_flags')),'&') || ')'
2039 FROM GENERATE_SERIES(0,0) AS x; -- increment as new flags are added.
2040 $f$ LANGUAGE SQL STABLE;
2042 CREATE OR REPLACE FUNCTION asset.visible_orgs (otype TEXT) RETURNS TEXT AS $f$
2043 SELECT '(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, $1)),'|') || ')'
2046 $f$ LANGUAGE SQL STABLE;
2048 CREATE OR REPLACE FUNCTION asset.invisible_orgs (otype TEXT) RETURNS TEXT AS $f$
2049 SELECT '!(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, $1)),'|') || ')'
2051 WHERE NOT opac_visible;
2052 $f$ LANGUAGE SQL STABLE;
2054 -- Bib-oriented defaults for search
2055 CREATE OR REPLACE FUNCTION asset.bib_source_default () RETURNS TEXT AS $f$
2056 SELECT '(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, 'bib_source')),'|') || ')'
2057 FROM config.bib_source
2059 $f$ LANGUAGE SQL IMMUTABLE;
2061 CREATE OR REPLACE FUNCTION asset.luri_org_default () RETURNS TEXT AS $f$
2062 SELECT * FROM asset.invisible_orgs('luri_org');
2063 $f$ LANGUAGE SQL STABLE;
2065 -- Copy-oriented defaults for search
2066 CREATE OR REPLACE FUNCTION asset.location_group_default () RETURNS TEXT AS $f$
2067 SELECT '!(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, 'location_group')),'|') || ')'
2068 FROM asset.copy_location_group
2069 WHERE NOT opac_visible;
2070 $f$ LANGUAGE SQL STABLE;
2072 CREATE OR REPLACE FUNCTION asset.location_default () RETURNS TEXT AS $f$
2073 SELECT '!(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, 'location')),'|') || ')'
2074 FROM asset.copy_location
2075 WHERE NOT opac_visible;
2076 $f$ LANGUAGE SQL STABLE;
2078 CREATE OR REPLACE FUNCTION asset.status_default () RETURNS TEXT AS $f$
2079 SELECT '!(' || ARRAY_TO_STRING(ARRAY_AGG(search.calculate_visibility_attribute(id, 'status')),'|') || ')'
2080 FROM config.copy_status
2081 WHERE NOT opac_visible;
2082 $f$ LANGUAGE SQL STABLE;
2084 CREATE OR REPLACE FUNCTION asset.owning_lib_default () RETURNS TEXT AS $f$
2085 SELECT * FROM asset.invisible_orgs('owning_lib');
2086 $f$ LANGUAGE SQL STABLE;
2088 CREATE OR REPLACE FUNCTION asset.circ_lib_default () RETURNS TEXT AS $f$
2089 SELECT * FROM asset.invisible_orgs('circ_lib');
2090 $f$ LANGUAGE SQL STABLE;
2092 CREATE OR REPLACE FUNCTION asset.patron_default_visibility_mask () RETURNS TABLE (b_attrs TEXT, c_attrs TEXT) AS $f$
2094 copy_flags TEXT; -- "c" attr
2096 owning_lib TEXT; -- "c" attr
2097 circ_lib TEXT; -- "c" attr
2098 status TEXT; -- "c" attr
2099 location TEXT; -- "c" attr
2100 location_group TEXT; -- "c" attr
2102 luri_org TEXT; -- "b" attr
2103 bib_sources TEXT; -- "b" attr
2105 copy_flags := asset.all_visible_flags(); -- Will always have at least one
2107 owning_lib := NULLIF(asset.owning_lib_default(),'!()');
2109 circ_lib := NULLIF(asset.circ_lib_default(),'!()');
2110 status := NULLIF(asset.status_default(),'!()');
2111 location := NULLIF(asset.location_default(),'!()');
2112 location_group := NULLIF(asset.location_group_default(),'!()');
2114 luri_org := NULLIF(asset.luri_org_default(),'!()');
2115 bib_sources := NULLIF(asset.bib_source_default(),'()');
2118 '('||ARRAY_TO_STRING(
2119 ARRAY[luri_org,bib_sources],
2122 '('||ARRAY_TO_STRING(
2123 ARRAY[copy_flags,owning_lib,circ_lib,status,location,location_group]::TEXT[],
2127 $f$ LANGUAGE PLPGSQL STABLE ROWS 1;
2129 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)
2130 RETURNS TABLE(value text, field integer, buoyant_and_class_match boolean, field_match boolean, field_weight integer, rank real, buoyant boolean, match text)
2133 prepared_query_texts TEXT[];
2135 plain_query TSQUERY;
2136 opac_visibility_join TEXT;
2137 search_class_join TEXT;
2140 prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
2142 query := TO_TSQUERY('keyword', prepared_query_texts[1]);
2143 plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
2145 visibility_org := NULLIF(visibility_org,-1);
2146 IF visibility_org IS NOT NULL THEN
2147 PERFORM FROM actor.org_unit WHERE id = visibility_org AND parent_ou IS NULL;
2149 opac_visibility_join := '';
2151 opac_visibility_join := '
2152 JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = x.source)
2153 JOIN vm ON (acvac.vis_attr_vector @@
2154 (vm.c_attrs || $$&$$ ||
2155 search.calculate_visibility_attribute_test(
2157 (SELECT ARRAY_AGG(id) FROM actor.org_unit_descendants($4))
2164 opac_visibility_join := '';
2167 -- The following determines whether we only provide suggestsons matching
2168 -- the user's selected search_class, or whether we show other suggestions
2169 -- too. The reason for MIN() is that for search_classes like
2170 -- 'title|proper|uniform' you would otherwise get multiple rows. The
2171 -- implication is that if title as a class doesn't have restrict,
2172 -- nor does the proper field, but the uniform field does, you're going
2173 -- to get 'false' for your overall evaluation of 'should we restrict?'
2174 -- To invert that, change from MIN() to MAX().
2178 MIN(cmc.restrict::INT) AS restrict_class,
2179 MIN(cmf.restrict::INT) AS restrict_field
2180 FROM metabib.search_class_to_registered_components(search_class)
2181 AS _registered (field_class TEXT, field INT)
2183 config.metabib_class cmc ON (cmc.name = _registered.field_class)
2185 config.metabib_field cmf ON (cmf.id = _registered.field);
2187 -- evaluate 'should we restrict?'
2188 IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
2189 search_class_join := '
2191 metabib.search_class_to_registered_components($2)
2192 AS _registered (field_class TEXT, field INT) ON (
2193 (_registered.field IS NULL AND
2194 _registered.field_class = cmf.field_class) OR
2195 (_registered.field = cmf.id)
2199 search_class_join := '
2201 metabib.search_class_to_registered_components($2)
2202 AS _registered (field_class TEXT, field INT) ON (
2203 _registered.field_class = cmc.name
2208 RETURN QUERY EXECUTE '
2209 WITH vm AS ( SELECT * FROM asset.patron_default_visibility_mask() ),
2210 mbe AS (SELECT * FROM metabib.browse_entry WHERE index_vector @@ $1 LIMIT 10000)
2219 TS_HEADLINE(value, $7, $3)
2220 FROM (SELECT DISTINCT
2223 cmc.buoyant AND _registered.field_class IS NOT NULL AS push,
2224 _registered.field = cmf.id AS restrict,
2226 TS_RANK_CD(mbe.index_vector, $1, $6),
2229 FROM metabib.browse_entry_def_map mbedm
2230 JOIN mbe ON (mbe.id = mbedm.entry)
2231 JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
2232 JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
2233 ' || search_class_join || '
2234 ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
2236 ' || opac_visibility_join || '
2237 ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
2239 ' -- sic, repeat the order by clause in the outer select too
2241 query, search_class, headline_opts,
2242 visibility_org, query_limit, normalization, plain_query
2246 -- buoyant AND chosen class = match class
2247 -- chosen field = match field
2254 $f$ LANGUAGE plpgsql ROWS 10;
2256 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)
2257 RETURNS SETOF metabib.flat_browse_entry_appearance
2263 pivot_sort_value TEXT;
2264 pivot_sort_fallback TEXT;
2265 context_locations INT[];
2266 browse_superpage_size INT;
2267 results_skipped INT := 0;
2271 forward_to_pivot INT;
2273 -- First, find the pivot if we were given a browse term but not a pivot.
2274 IF pivot_id IS NULL THEN
2275 pivot_id := metabib.browse_pivot(search_field, browse_term);
2278 SELECT INTO pivot_sort_value, pivot_sort_fallback
2279 sort_value, value FROM metabib.browse_entry WHERE id = pivot_id;
2281 -- Bail if we couldn't find a pivot.
2282 IF pivot_sort_value IS NULL THEN
2286 -- Transform the context_loc_group argument (if any) (logc at the
2287 -- TPAC layer) into a form we'll be able to use.
2288 IF context_loc_group IS NOT NULL THEN
2289 SELECT INTO context_locations ARRAY_AGG(location)
2290 FROM asset.copy_location_group_map
2291 WHERE lgroup = context_loc_group;
2294 -- Get the configured size of browse superpages.
2295 SELECT INTO browse_superpage_size COALESCE(value::INT,100) -- NULL ok
2296 FROM config.global_flag
2297 WHERE enabled AND name = 'opac.browse.holdings_visibility_test_limit';
2299 -- First we're going to search backward from the pivot, then we're going
2300 -- to search forward. In each direction, we need two limits. At the
2301 -- lesser of the two limits, we delineate the edge of the result set
2302 -- we're going to return. At the greater of the two limits, we find the
2303 -- pivot value that would represent an offset from the current pivot
2304 -- at a distance of one "page" in either direction, where a "page" is a
2305 -- result set of the size specified in the "result_limit" argument.
2307 -- The two limits in each direction make four derived values in total,
2308 -- and we calculate them now.
2309 back_limit := CEIL(result_limit::FLOAT / 2);
2310 back_to_pivot := result_limit;
2311 forward_limit := result_limit / 2;
2312 forward_to_pivot := result_limit - 1;
2314 -- This is the meat of the SQL query that finds browse entries. We'll
2315 -- pass this to a function which uses it with a cursor, so that individual
2316 -- rows may be fetched in a loop until some condition is satisfied, without
2317 -- waiting for a result set of fixed size to be collected all at once.
2322 FROM metabib.browse_entry mbe
2324 EXISTS ( -- are there any bibs using this mbe via the requested fields?
2326 FROM metabib.browse_entry_def_map mbedm
2327 WHERE mbedm.entry = mbe.id AND mbedm.def = ANY(' || quote_literal(search_field) || ')
2328 ) OR EXISTS ( -- are there any authorities using this mbe via the requested fields?
2330 FROM metabib.browse_entry_simple_heading_map mbeshm
2331 JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
2332 JOIN authority.control_set_auth_field_metabib_field_map_refs map ON (
2333 ash.atag = map.authority_field
2334 AND map.metabib_field = ANY(' || quote_literal(search_field) || ')
2336 WHERE mbeshm.entry = mbe.id
2340 -- This is the variant of the query for browsing backward.
2341 back_query := core_query ||
2342 ' mbe.sort_value <= ' || quote_literal(pivot_sort_value) ||
2343 ' ORDER BY mbe.sort_value DESC, mbe.value DESC LIMIT 1000';
2345 -- This variant browses forward.
2346 forward_query := core_query ||
2347 ' mbe.sort_value > ' || quote_literal(pivot_sort_value) ||
2348 ' ORDER BY mbe.sort_value, mbe.value LIMIT 1000';
2350 -- We now call the function which applies a cursor to the provided
2351 -- queries, stopping at the appropriate limits and also giving us
2352 -- the next page's pivot.
2354 SELECT * FROM metabib.staged_browse(
2355 back_query, search_field, context_org, context_locations,
2356 staff, browse_superpage_size, TRUE, back_limit, back_to_pivot
2358 SELECT * FROM metabib.staged_browse(
2359 forward_query, search_field, context_org, context_locations,
2360 staff, browse_superpage_size, FALSE, forward_limit, forward_to_pivot
2361 ) ORDER BY row_number DESC;
2364 $f$ LANGUAGE plpgsql ROWS 10;
2366 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)
2367 RETURNS SETOF metabib.flat_browse_entry_appearance
2376 result_row metabib.flat_browse_entry_appearance%ROWTYPE;
2377 results_skipped INT := 0;
2378 row_counter INT := 0;
2383 all_records BIGINT[];
2384 all_brecords BIGINT[];
2385 all_arecords BIGINT[];
2386 superpage_of_records BIGINT[];
2392 IF count_up_from_zero THEN
2399 SELECT x.c_attrs, x.b_attrs INTO c_tests, b_tests FROM asset.patron_default_visibility_mask() x;
2402 IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF;
2403 IF b_tests <> '' THEN b_tests := b_tests || '&'; END IF;
2405 SELECT ARRAY_AGG(id) INTO c_orgs FROM actor.org_unit_descendants(context_org);
2407 c_tests := c_tests || search.calculate_visibility_attribute_test('circ_lib',c_orgs)
2408 || '&' || search.calculate_visibility_attribute_test('owning_lib',c_orgs);
2410 PERFORM 1 FROM config.internal_flag WHERE enabled AND name = 'opac.located_uri.act_as_copy';
2412 b_tests := b_tests || search.calculate_visibility_attribute_test(
2414 (SELECT ARRAY_AGG(id) FROM actor.org_unit_full_path(context_org) x)
2417 b_tests := b_tests || search.calculate_visibility_attribute_test(
2419 (SELECT ARRAY_AGG(id) FROM actor.org_unit_ancestors(context_org) x)
2423 IF context_locations THEN
2424 IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF;
2425 c_tests := c_tests || search.calculate_visibility_attribute_test('location',context_locations);
2428 OPEN curs NO SCROLL FOR EXECUTE query;
2431 FETCH curs INTO rec;
2433 IF result_row.pivot_point IS NOT NULL THEN
2434 RETURN NEXT result_row;
2439 -- Gather aggregate data based on the MBE row we're looking at now, authority axis
2440 SELECT INTO all_arecords, result_row.sees, afields
2441 ARRAY_AGG(DISTINCT abl.bib), -- bibs to check for visibility
2442 STRING_AGG(DISTINCT aal.source::TEXT, $$,$$), -- authority record ids
2443 ARRAY_AGG(DISTINCT map.metabib_field) -- authority-tag-linked CMF rows
2445 FROM metabib.browse_entry_simple_heading_map mbeshm
2446 JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
2447 JOIN authority.authority_linking aal ON ( ash.record = aal.source )
2448 JOIN authority.bib_linking abl ON ( aal.target = abl.authority )
2449 JOIN authority.control_set_auth_field_metabib_field_map_refs map ON (
2450 ash.atag = map.authority_field
2451 AND map.metabib_field = ANY(fields)
2453 WHERE mbeshm.entry = rec.id;
2455 -- Gather aggregate data based on the MBE row we're looking at now, bib axis
2456 SELECT INTO all_brecords, result_row.authorities, bfields
2457 ARRAY_AGG(DISTINCT source),
2458 STRING_AGG(DISTINCT authority::TEXT, $$,$$),
2459 ARRAY_AGG(DISTINCT def)
2460 FROM metabib.browse_entry_def_map
2461 WHERE entry = rec.id
2462 AND def = ANY(fields);
2464 SELECT INTO result_row.fields STRING_AGG(DISTINCT x::TEXT, $$,$$) FROM UNNEST(afields || bfields) x;
2466 result_row.sources := 0;
2467 result_row.asources := 0;
2469 -- Bib-linked vis checking
2470 IF ARRAY_UPPER(all_brecords,1) IS NOT NULL THEN
2472 SELECT INTO result_row.sources COUNT(DISTINCT b.id)
2473 FROM biblio.record_entry b
2474 JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id)
2475 WHERE b.id = ANY(all_brecords[1:browse_superpage_size])
2477 acvac.vis_attr_vector @@ c_tests::query_int
2478 OR b.vis_attr_vector @@ b_tests::query_int
2481 result_row.accurate := TRUE;
2485 -- Authority-linked vis checking
2486 IF ARRAY_UPPER(all_arecords,1) IS NOT NULL THEN
2488 SELECT INTO result_row.asources COUNT(DISTINCT b.id)
2489 FROM biblio.record_entry b
2490 JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id)
2491 WHERE b.id = ANY(all_arecords[1:browse_superpage_size])
2493 acvac.vis_attr_vector @@ c_tests::query_int
2494 OR b.vis_attr_vector @@ b_tests::query_int
2497 result_row.aaccurate := TRUE;
2501 IF result_row.sources > 0 OR result_row.asources > 0 THEN
2503 -- The function that calls this function needs row_number in order
2504 -- to correctly order results from two different runs of this
2506 result_row.row_number := row_number;
2508 -- Now, if row_counter is still less than limit, return a row. If
2509 -- not, but it is less than next_pivot_pos, continue on without
2510 -- returning actual result rows until we find
2511 -- that next pivot, and return it.
2513 IF row_counter < result_limit THEN
2514 result_row.browse_entry := rec.id;
2515 result_row.value := rec.value;
2517 RETURN NEXT result_row;
2519 result_row.browse_entry := NULL;
2520 result_row.authorities := NULL;
2521 result_row.fields := NULL;
2522 result_row.value := NULL;
2523 result_row.sources := NULL;
2524 result_row.sees := NULL;
2525 result_row.accurate := NULL;
2526 result_row.aaccurate := NULL;
2527 result_row.pivot_point := rec.id;
2529 IF row_counter >= next_pivot_pos THEN
2530 RETURN NEXT result_row;
2535 IF count_up_from_zero THEN
2536 row_number := row_number + 1;
2538 row_number := row_number - 1;
2541 -- row_counter is different from row_number.
2542 -- It simply counts up from zero so that we know when
2543 -- we've reached our limit.
2544 row_counter := row_counter + 1;
2548 $f$ LANGUAGE plpgsql ROWS 10;
2550 DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON biblio.peer_bib_copy_map;
2551 DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON biblio.record_entry;
2552 DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON asset.copy;
2553 DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON asset.call_number;
2554 DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON asset.copy_location;
2555 DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON serial.unit;
2556 DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON config.copy_status;
2557 DROP TRIGGER IF EXISTS a_opac_vis_mat_view_tgr ON actor.org_unit;
2559 -- Upgrade the data!
2560 INSERT INTO asset.copy_vis_attr_cache (target_copy, record, vis_attr_vector)
2563 asset.calculate_copy_visibility_attribute_set(cp.id)
2565 JOIN asset.call_number cn ON (cp.call_number = cn.id);
2567 -- updating vis cache for biblio.record_entry deferred to end
2569 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();
2570 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();
2571 CREATE TRIGGER z_opac_vis_mat_view_tgr AFTER UPDATE ON asset.call_number FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
2572 CREATE TRIGGER z_opac_vis_mat_view_del_tgr BEFORE DELETE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
2573 CREATE TRIGGER z_opac_vis_mat_view_del_tgr BEFORE DELETE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
2574 CREATE TRIGGER z_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
2575 CREATE TRIGGER z_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
2577 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$
2582 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;
2584 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
2586 WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
2587 available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
2588 mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
2592 SUM( (cp.status = ANY (available_statuses.ids))::INT ),
2598 asset.copy_vis_attr_cache av
2599 JOIN asset.copy cp ON (cp.id = av.target_copy AND av.record = rid)
2600 WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
2604 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
2611 $f$ LANGUAGE PLPGSQL;
2613 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$
2618 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;
2620 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
2622 WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
2623 available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
2624 mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
2628 SUM( (cp.status = ANY (available_statuses.ids))::INT ),
2633 asset.copy_vis_attr_cache av
2634 JOIN asset.copy cp ON (cp.id = av.target_copy AND av.record = rid)
2635 WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
2639 RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
2646 $f$ LANGUAGE PLPGSQL;
2648 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$
2653 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;
2655 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
2657 WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
2658 available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
2659 mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
2663 SUM( (cp.status = ANY (available_statuses.ids))::INT ),
2669 asset.copy_vis_attr_cache av
2670 JOIN asset.copy cp ON (cp.id = av.target_copy)
2671 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record)
2672 WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
2676 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
2683 $f$ LANGUAGE PLPGSQL;
2685 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$
2690 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;
2692 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
2694 WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
2695 available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
2696 mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
2700 SUM( (cp.status = ANY (available_statuses.ids))::INT ),
2706 asset.copy_vis_attr_cache av
2707 JOIN asset.copy cp ON (cp.id = av.target_copy)
2708 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record)
2709 WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
2713 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
2720 $f$ LANGUAGE PLPGSQL;
2722 CREATE OR REPLACE FUNCTION unapi.mmr_mra (
2728 depth INT DEFAULT NULL,
2729 slimit HSTORE DEFAULT NULL,
2730 soffset HSTORE DEFAULT NULL,
2731 include_xmlns BOOL DEFAULT TRUE,
2732 pref_lib INT DEFAULT NULL
2733 ) RETURNS XML AS $F$
2737 CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
2738 'tag:open-ils.org:U2@mmr/' || $1 AS metarecord
2740 (SELECT XMLAGG(foo.y)
2742 WITH sourcelist AS (
2743 WITH aou AS (SELECT COALESCE(id, (evergreen.org_top()).id) AS id FROM actor.org_unit WHERE shortname = $5 LIMIT 1),
2744 basevm AS (SELECT c_attrs FROM asset.patron_default_visibility_mask()),
2745 circvm AS (SELECT search.calculate_visibility_attribute_test('circ_lib', ARRAY_AGG(aoud.id)) AS mask
2746 FROM aou, LATERAL actor.org_unit_descendants(aou.id, $6) aoud)
2748 FROM aou, circvm, basevm, metabib.metarecord_source_map mmsm
2749 WHERE mmsm.metarecord = $1 AND (
2752 FROM circvm, basevm, asset.copy_vis_attr_cache acvac
2753 WHERE acvac.vis_attr_vector @@ (basevm.c_attrs || '&' || circvm.mask)::query_int
2754 AND acvac.record = mmsm.source
2756 OR EXISTS (SELECT 1 FROM evergreen.located_uris(source, aou.id, $10) LIMIT 1)
2757 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)
2765 cmra.value AS "coded-value",
2766 cmra.aid AS "cvmid",
2776 SELECT DISTINCT aid, attr, value, STRING_AGG(x.id::TEXT, ',') AS source_list
2778 SELECT v.source AS id,
2782 FROM metabib.record_attr_vector_list v
2783 JOIN config.coded_value_map c ON ( c.id = ANY( v.vlist ) )
2785 JOIN sourcelist ON (x.id = sourcelist.source)
2788 JOIN config.record_attr_definition rad ON (cmra.attr = rad.name)
2803 SELECT DISTINCT aid, attr, value
2805 SELECT v.source AS id,
2809 FROM metabib.record_attr_vector_list v
2810 JOIN metabib.uncontrolled_record_attr_value m ON ( m.id = ANY( v.vlist ) )
2812 JOIN sourcelist ON (x.id = sourcelist.source)
2814 JOIN config.record_attr_definition rad ON (umra.attr = rad.name)
2820 $F$ LANGUAGE SQL STABLE;
2822 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
2825 depth INT DEFAULT NULL,
2826 slimit HSTORE DEFAULT NULL,
2827 soffset HSTORE DEFAULT NULL,
2828 pref_lib INT DEFAULT NULL,
2829 includes TEXT[] DEFAULT NULL::TEXT[]
2830 ) RETURNS TABLE(id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
2831 WITH RECURSIVE ou_depth AS (
2836 FROM actor.org_unit_type aout
2837 INNER JOIN actor.org_unit ou ON ou_type = aout.id
2841 ), descendant_depth AS (
2845 FROM actor.org_unit ou
2846 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
2847 JOIN anscestor_depth ad ON (ad.id = ou.id),
2849 WHERE ad.depth = ou_depth.depth
2854 FROM actor.org_unit ou
2855 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
2856 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
2857 ), anscestor_depth AS (
2861 FROM actor.org_unit ou
2862 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
2868 FROM actor.org_unit ou
2869 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
2870 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
2872 SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id)
2875 SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
2876 SELECT acn.id, owning_lib.name, acn.label_sortkey,
2877 evergreen.rank_cp(acp),
2879 FROM asset.call_number acn
2880 JOIN asset.copy acp ON (acn.id = acp.call_number)
2881 JOIN descendants AS aou ON (acp.circ_lib = aou.id)
2882 JOIN actor.org_unit AS owning_lib ON (acn.owning_lib = owning_lib.id)
2883 WHERE acn.record = ANY ($1)
2884 AND acn.deleted IS FALSE
2885 AND acp.deleted IS FALSE
2886 AND CASE WHEN ('exclude_invisible_acn' = ANY($7)) THEN
2888 WITH basevm AS (SELECT c_attrs FROM asset.patron_default_visibility_mask()),
2889 circvm AS (SELECT search.calculate_visibility_attribute_test('circ_lib', ARRAY[acp.circ_lib]) AS mask)
2891 FROM basevm, circvm, asset.copy_vis_attr_cache acvac
2892 WHERE acvac.vis_attr_vector @@ (basevm.c_attrs || '&' || circvm.mask)::query_int
2893 AND acvac.target_copy = acp.id
2894 AND acvac.record = acn.record
2896 GROUP BY acn.id, evergreen.rank_cp(acp), owning_lib.name, acn.label_sortkey, aou.id
2900 CASE WHEN aou.id = $2 THEN -20000 END,
2901 CASE WHEN aou.id = $6 THEN -10000 END,
2902 (SELECT distance - 5000
2903 FROM actor.org_unit_descendants_distance($6) as x
2904 WHERE x.id = aou.id AND $6 IN (
2905 SELECT q.id FROM actor.org_unit_descendants($2) as q)),
2906 (SELECT e.distance FROM actor.org_unit_descendants_distance($2) as e WHERE e.id = aou.id),
2909 evergreen.rank_cp(acp)
2912 GROUP BY ua.id, ua.name, ua.label_sortkey
2913 ORDER BY rank, ua.name, ua.label_sortkey
2914 LIMIT ($4 -> 'acn')::INT
2915 OFFSET ($5 -> 'acn')::INT;
2916 $$ LANGUAGE SQL STABLE ROWS 10;