From: Dan Scott Date: Fri, 25 Nov 2011 19:46:37 +0000 (-0500) Subject: Start collecting version-upgrade scripts in one spot X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=ffe01e475e63623e945b066828a170a335ccb8ca;p=contrib%2FConifer.git Start collecting version-upgrade scripts in one spot Rename the upgrade scripts missing "-upgrade-db" in their name Add the 2.0.* upgrade scripts to version-upgrade Discard the really old upgrade scripts that don't necessarily match what was actually packaged and released. Signed-off-by: Dan Scott --- diff --git a/Open-ILS/src/sql/Pg/1.2.1-1.2.2-upgrade-db.sql b/Open-ILS/src/sql/Pg/1.2.1-1.2.2-upgrade-db.sql deleted file mode 100644 index 8c44a09f87..0000000000 --- a/Open-ILS/src/sql/Pg/1.2.1-1.2.2-upgrade-db.sql +++ /dev/null @@ -1,719 +0,0 @@ -/* - * Copyright (C) 2008 Equinox Software, Inc. - * Mike Rylander - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -BEGIN; - -------------------------------------------------------------------- -/* new materialized view for reporting schema -- ok if it fails */ -------------------------------------------------------------------- - -CREATE TABLE reporter.materialized_simple_record AS SELECT * FROM reporter.super_simple_record WHERE 1=0; - -INSERT INTO reporter.materialized_simple_record - (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn) - SELECT DISTINCT ON (id) * FROM reporter.super_simple_record; - -ALTER TABLE reporter.materialized_simple_record ADD PRIMARY KEY (id); - -CREATE OR REPLACE VIEW reporter.super_simple_record AS SELECT * FROM reporter.materialized_simple_record; - -CREATE OR REPLACE VIEW reporter.old_super_simple_record AS -SELECT r.id, - r.fingerprint, - r.quality, - r.tcn_source, - r.tcn_value, - title.value AS title, - FIRST(author.value) AS author, - publisher.value AS publisher, - SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate, - ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn, - ARRAY_ACCUM( SUBSTRING(issn.value FROM $$^\S+$$) ) 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 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' AND publisher.subfield = 'b') - LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' 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,6,8,9; - -CREATE OR REPLACE FUNCTION reporter.simple_rec_sync () RETURNS TRIGGER AS $$ -DECLARE - r_id BIGINT; - new_data RECORD; -BEGIN - IF TG_OP IN ('DELETE') THEN - r_id := OLD.record; - ELSE - r_id := NEW.record; - END IF; - - SELECT * INTO new_data FROM reporter.materialized_simple_record WHERE id = r_id FOR UPDATE; - DELETE FROM reporter.materialized_simple_record WHERE id = r_id; - - IF TG_OP IN ('DELETE') THEN - RETURN OLD; - ELSE - INSERT INTO reporter.materialized_simple_record SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record WHERE id = NEW.record; - RETURN NEW; - END IF; - -END; -$$ LANGUAGE PLPGSQL; - -CREATE TRIGGER zzz_update_materialized_simple_record_tgr - AFTER INSERT OR UPDATE OR DELETE ON metabib.full_rec - FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_sync(); - -COMMIT; - - -DROP VIEW reporter.overdue_reports; -DROP VIEW reporter.pending_reports; -DROP VIEW reporter.currently_running; - -BEGIN; - -------------------------------------------------------------------- -/* convenience views for report management */ -------------------------------------------------------------------- - -CREATE OR REPLACE VIEW reporter.overdue_reports AS - SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time - FROM reporter.schedule s - JOIN reporter.report r ON r.id = s.report - JOIN actor.usr u ON s.runner = u.id - JOIN actor.card c ON c.id = u.card - WHERE s.start_time IS NULL AND s.run_time < now(); - -CREATE OR REPLACE VIEW reporter.pending_reports AS - SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time - FROM reporter.schedule s - JOIN reporter.report r ON r.id = s.report - JOIN actor.usr u ON s.runner = u.id - JOIN actor.card c ON c.id = u.card - WHERE s.start_time IS NULL; - -CREATE OR REPLACE VIEW reporter.currently_running AS - SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time - FROM reporter.schedule s - JOIN reporter.report r ON r.id = s.report - JOIN actor.usr u ON s.runner = u.id - JOIN actor.card c ON c.id = u.card - WHERE s.start_time IS NOT NULL AND s.complete_time IS NULL; - - -------------------------------------------------------------------- -/* view for restricting circ counts by circ_mod */ -------------------------------------------------------------------- - -CREATE OR REPLACE VIEW action.open_circ_count_by_circ_mod AS - SELECT circ.usr, - cp.circ_modifier, - count(circ.id) - FROM action.circulation circ - JOIN asset.copy cp ON (circ.target_copy = cp.id) - WHERE circ.checkin_time IS NULL - AND ( circ.stop_fines IN ('LOST','LONGOVERDUE','CLAIMSRETURNED') OR circ.stop_fines IS NULL ) - GROUP BY 1,2; - - -------------------------------------------------------------------- -/* reporting functions for new (and fixed) transforms */ -------------------------------------------------------------------- - -CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$ - SELECT SUBSTRING( $1 FROM $_$^\S+$_$); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION public.first5 ( TEXT ) RETURNS TEXT AS $$ - SELECT SUBSTRING( $1, 1, 5); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION public.call_number_dewey( TEXT ) RETURNS TEXT AS $$ - my $txt = shift; - $txt =~ s/^\s+//o; - $txt =~ s/[\[\]\{\}\(\)`'"#<>\*\?\-\+\$\\]+//o; #' To help vim in SQL mode - $txt =~ s/\s+$//o; - if ($txt =~ /(\d{3}(?:\.\d+)?)/o) { - return $1; - } else { - return (split /\s+/, $txt)[0]; - } -$$ LANGUAGE 'plperlu' STRICT IMMUTABLE; - -COMMIT; - -DROP SCHEMA IF EXISTS search CASCADE; -BEGIN; - -------------------------------------------------------------------- -/* staged search -- also applied by 300.schema.staged_search.sql */ -------------------------------------------------------------------- - -CREATE SCHEMA search; - -CREATE TABLE search.relevance_adjustment ( - id SERIAL PRIMARY KEY, - active BOOL NOT NULL DEFAULT TRUE, - field INT NOT NULL REFERENCES config.metabib_field (id) DEFERRABLE INITIALLY DEFERRED, - bump_type TEXT NOT NULL CHECK (bump_type IN ('word_order','first_word','full_match')), - multiplier NUMERIC NOT NULL DEFAULT 1.0 -); -CREATE UNIQUE INDEX bump_once_per_field_idx ON search.relevance_adjustment ( field, bump_type ); - -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(1, 'first_word', 1.5); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(1, 'full_match', 20); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(2, 'first_word', 1.5); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(2, 'word_order', 10); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(2, 'full_match', 20); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(3, 'first_word', 1.5); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(3, 'word_order', 10); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(3, 'full_match', 20); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(4, 'first_word', 1.5); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(4, 'word_order', 10); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(4, 'full_match', 20); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(5, 'first_word', 1.5); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(5, 'word_order', 10); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(5, 'full_match', 20); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(6, 'first_word', 1.5); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(7, 'first_word', 1.5); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(8, 'first_word', 1.5); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(9, 'first_word', 1.5); -INSERT INTO search.relevance_adjustment (field, bump_type, multiplier) VALUES(14, 'word_order', 10); - -CREATE OR REPLACE FUNCTION search.pick_table (TEXT) RETURNS TEXT AS $$ - SELECT CASE - WHEN $1 = 'author' THEN 'metabib.author_field_entry' - WHEN $1 = 'title' THEN 'metabib.title_field_entry' - WHEN $1 = 'subject' THEN 'metabib.subject_field_entry' - WHEN $1 = 'keyword' THEN 'metabib.keyword_field_entry' - WHEN $1 = 'series' THEN 'metabib.series_field_entry' - END; -$$ LANGUAGE SQL; - -CREATE TYPE search.search_result AS ( id BIGINT, rel NUMERIC, record INT, total INT, checked INT, visible INT, deleted INT, excluded INT ); -CREATE TYPE search.search_args AS ( id INT, field_class TEXT, field_name TEXT, table_alias TEXT, term TEXT, term_type TEXT ); - -CREATE OR REPLACE FUNCTION search.staged_fts ( - - param_search_ou INT, - param_depth INT, - param_searches TEXT, -- JSON hash, to be turned into a resultset via search.parse_search_args - param_statuses INT[], - param_audience TEXT[], - param_language TEXT[], - param_lit_form TEXT[], - param_types TEXT[], - param_forms TEXT[], - param_vformats TEXT[], - param_pref_lang TEXT, - param_pref_lang_multiplier REAL, - param_sort TEXT, - param_sort_desc BOOL, - metarecord BOOL, - staff BOOL, - param_rel_limit INT, - param_chk_limit INT, - param_skip_chk INT - -) RETURNS SETOF search.search_result AS $func$ -DECLARE - - current_res search.search_result%ROWTYPE; - query_part search.search_args%ROWTYPE; - phrase_query_part search.search_args%ROWTYPE; - rank_adjust_id INT; - core_rel_limit INT; - core_chk_limit INT; - core_skip_chk INT; - rank_adjust search.relevance_adjustment%ROWTYPE; - query_table TEXT; - tmp_text TEXT; - tmp_int INT; - current_rank TEXT; - ranks TEXT[] := '{}'; - query_table_alias TEXT; - from_alias_array TEXT[] := '{}'; - used_ranks TEXT[] := '{}'; - mb_field INT; - mb_field_list INT[]; - search_org_list INT[]; - select_clause TEXT := 'SELECT'; - from_clause TEXT := ' FROM metabib.metarecord_source_map m JOIN metabib.rec_descriptor mrd ON (m.source = mrd.record) '; - where_clause TEXT := ' WHERE 1=1 '; - mrd_used BOOL := FALSE; - sort_desc BOOL := FALSE; - - core_result RECORD; - core_cursor REFCURSOR; - core_rel_query TEXT; - vis_limit_query TEXT; - inner_where_clause TEXT; - - total_count INT := 0; - check_count INT := 0; - deleted_count INT := 0; - visible_count INT := 0; - excluded_count INT := 0; - -BEGIN - - core_rel_limit := COALESCE( param_rel_limit, 25000 ); - core_chk_limit := COALESCE( param_chk_limit, 1000 ); - core_skip_chk := COALESCE( param_skip_chk, 1 ); - - IF metarecord THEN - select_clause := select_clause || ' m.metarecord as id, array_accum(distinct m.source) as records,'; - ELSE - select_clause := select_clause || ' m.source as id, array_accum(distinct m.source) as records,'; - END IF; - - -- first we need to construct the base query - FOR query_part IN SELECT * FROM search.parse_search_args(param_searches) WHERE term_type = 'fts_query' LOOP - - inner_where_clause := 'index_vector @@ ' || query_part.term; - - IF query_part.field_name IS NOT NULL THEN - - SELECT id INTO mb_field - FROM config.metabib_field - WHERE field_class = query_part.field_class - AND name = query_part.field_name; - - IF FOUND THEN - inner_where_clause := inner_where_clause || - ' AND ' || 'field = ' || mb_field; - END IF; - - END IF; - - -- moving on to the rank ... - SELECT * INTO query_part - FROM search.parse_search_args(param_searches) - WHERE term_type = 'fts_rank' - AND table_alias = query_part.table_alias; - - current_rank := query_part.term || ' * ' || query_part.table_alias || '_weight.weight'; - - IF query_part.field_name IS NOT NULL THEN - - SELECT array_accum(distinct id) INTO mb_field_list - FROM config.metabib_field - WHERE field_class = query_part.field_class - AND name = query_part.field_name; - - ELSE - - SELECT array_accum(distinct id) INTO mb_field_list - FROM config.metabib_field - WHERE field_class = query_part.field_class; - - END IF; - - FOR rank_adjust IN SELECT * FROM search.relevance_adjustment WHERE active AND field IN ( SELECT * FROM search.explode_array( mb_field_list ) ) LOOP - - IF NOT rank_adjust.bump_type = ANY (used_ranks) THEN - - IF rank_adjust.bump_type = 'first_word' THEN - SELECT term INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word' - ORDER BY id - LIMIT 1; - - tmp_text := query_part.table_alias || '.value ILIKE ' || quote_literal( tmp_text || '%' ); - - ELSIF rank_adjust.bump_type = 'word_order' THEN - SELECT array_to_string( array_accum( term ), '%' ) INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word'; - - tmp_text := query_part.table_alias || '.value ILIKE ' || quote_literal( '%' || tmp_text || '%' ); - - ELSIF rank_adjust.bump_type = 'full_match' THEN - SELECT array_to_string( array_accum( term ), E'\\s+' ) INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word'; - - tmp_text := query_part.table_alias || '.value ~ ' || quote_literal( '^' || tmp_text || E'\\W*$' ); - - END IF; - - current_rank := current_rank || ' * ( CASE WHEN ' || tmp_text || - ' THEN ' || rank_adjust.multiplier || '::REAL ELSE 1.0 END )'; - - used_ranks := array_append( used_ranks, rank_adjust.bump_type ); - - END IF; - - END LOOP; - - ranks := array_append( ranks, current_rank ); - used_ranks := '{}'; - - FOR phrase_query_part IN - SELECT * - FROM search.parse_search_args(param_searches) - WHERE term_type = 'phrase' - AND table_alias = query_part.table_alias LOOP - - tmp_text := replace( phrase_query_part.term, '*', E'\\*' ); - tmp_text := replace( tmp_text, '?', E'\\?' ); - tmp_text := replace( tmp_text, '+', E'\\+' ); - tmp_text := replace( tmp_text, '|', E'\\|' ); - tmp_text := replace( tmp_text, '(', E'\\(' ); - tmp_text := replace( tmp_text, ')', E'\\)' ); - tmp_text := replace( tmp_text, '[', E'\\[' ); - tmp_text := replace( tmp_text, ']', E'\\]' ); - - inner_where_clause := inner_where_clause || ' AND ' || 'value ~* ' || quote_literal( E'(^|\\W+)' || regexp_replace(tmp_text, E'\\s+',E'\\\\s+','g') || E'(\\W+|\$)' ); - - END LOOP; - - query_table := search.pick_table(query_part.field_class); - - from_clause := from_clause || - ' JOIN ( SELECT * FROM ' || query_table || ' WHERE ' || inner_where_clause || - CASE WHEN core_rel_limit > 0 THEN ' LIMIT ' || core_rel_limit::TEXT ELSE '' END || ' ) AS ' || query_part.table_alias || - ' ON ( m.source = ' || query_part.table_alias || '.source )' || - ' JOIN config.metabib_field AS ' || query_part.table_alias || '_weight' || - ' ON ( ' || query_part.table_alias || '.field = ' || query_part.table_alias || '_weight.id AND ' || query_part.table_alias || '_weight.search_field)'; - - from_alias_array := array_append(from_alias_array, query_part.table_alias); - - END LOOP; - - IF param_pref_lang IS NOT NULL AND param_pref_lang_multiplier IS NOT NULL THEN - current_rank := ' CASE WHEN mrd.item_lang = ' || quote_literal( param_pref_lang ) || - ' THEN ' || param_pref_lang_multiplier || '::REAL ELSE 1.0 END '; - - --ranks := array_append( ranks, current_rank ); - END IF; - - current_rank := ' AVG( ( (' || array_to_string( ranks, ') + (' ) || ') ) * ' || current_rank || ' ) '; - select_clause := select_clause || current_rank || ' AS rel,'; - - sort_desc = param_sort_desc; - - IF param_sort = 'pubdate' THEN - - tmp_text := '999999'; - IF param_sort_desc THEN tmp_text := '0'; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT SUBSTRING(frp.value FROM E'\\d{4}') - FROM metabib.full_rec frp - WHERE frp.record = m.source - AND frp.tag = '260' - AND frp.subfield = 'c' - LIMIT 1 - )), $$ || quote_literal(tmp_text) || $$ )::INT ) - $$; - - ELSIF param_sort = 'title' THEN - - tmp_text := 'zzzzzz'; - IF param_sort_desc THEN tmp_text := ' '; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\d+'),'0')::INT + 1 )) - FROM metabib.full_rec frt - WHERE frt.record = m.source - AND frt.tag = '245' - AND frt.subfield = 'a' - LIMIT 1 - )),$$ || quote_literal(tmp_text) || $$)) - $$; - - ELSIF param_sort = 'author' THEN - - tmp_text := 'zzzzzz'; - IF param_sort_desc THEN tmp_text := ' '; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT LTRIM(fra.value) - FROM metabib.full_rec fra - WHERE fra.record = m.source - AND fra.tag LIKE '1%' - AND fra.subfield = 'a' - ORDER BY fra.tag::text::int - LIMIT 1 - )),$$ || quote_literal(tmp_text) || $$)) - $$; - - ELSIF param_sort = 'create_date' THEN - current_rank := $$( FIRST (( SELECT create_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )$$; - ELSIF param_sort = 'edit_date' THEN - current_rank := $$( FIRST (( SELECT edit_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )$$; - ELSE - sort_desc := NOT COALESCE(param_sort_desc, FALSE); - END IF; - - select_clause := select_clause || current_rank || ' AS rank'; - - -- now add the other qualifiers - IF param_audience IS NOT NULL AND array_upper(param_audience, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.audience IN ('$$ || array_to_string(param_audience, $$','$$) || $$') $$; - END IF; - - IF param_language IS NOT NULL AND array_upper(param_language, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_lang IN ('$$ || array_to_string(param_language, $$','$$) || $$') $$; - END IF; - - IF param_lit_form IS NOT NULL AND array_upper(param_lit_form, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.lit_form IN ('$$ || array_to_string(param_lit_form, $$','$$) || $$') $$; - END IF; - - IF param_types IS NOT NULL AND array_upper(param_types, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_type IN ('$$ || array_to_string(param_types, $$','$$) || $$') $$; - END IF; - - IF param_forms IS NOT NULL AND array_upper(param_forms, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_form IN ('$$ || array_to_string(param_forms, $$','$$) || $$') $$; - END IF; - - IF param_vformats IS NOT NULL AND array_upper(param_vformats, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.vr_format IN ('$$ || array_to_string(param_vformats, $$','$$) || $$') $$; - END IF; - - core_rel_query := select_clause || from_clause || where_clause || - ' GROUP BY 1 ORDER BY 4' || CASE WHEN sort_desc THEN ' DESC' ELSE ' ASC' END || ';'; - --RAISE NOTICE 'Base Query: %', core_rel_query; - - IF param_depth IS NOT NULL THEN - SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth ); - ELSE - SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou ); - END IF; - - OPEN core_cursor FOR EXECUTE core_rel_query; - - LOOP - - FETCH core_cursor INTO core_result; - EXIT WHEN NOT FOUND; - - IF total_count % 1000 = 0 THEN - -- RAISE NOTICE ' % total, % checked so far ... ', total_count, check_count; - END IF; - - IF core_chk_limit > 0 AND total_count - core_skip_chk + 1 >= core_chk_limit THEN - total_count := total_count + 1; - CONTINUE; - END IF; - - total_count := total_count + 1; - - CONTINUE WHEN param_skip_chk IS NOT NULL and total_count < param_skip_chk; - - check_count := check_count + 1; - - PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all deleted ... ', core_result.records; - deleted_count := deleted_count + 1; - CONTINUE; - END IF; - - PERFORM 1 - FROM biblio.record_entry b - JOIN config.bib_source s ON (b.source = s.id) - WHERE s.transcendant - AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); - - IF FOUND THEN - -- RAISE NOTICE ' % were all transcendant ... ', core_result.records; - visible_count := visible_count + 1; - - current_res.id = core_result.id; - current_res.rel = core_result.rel; - - tmp_int := 1; - IF metarecord THEN - SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; - END IF; - - IF tmp_int = 1 THEN - current_res.record = core_result.records[1]; - ELSE - current_res.record = NULL; - END IF; - - RETURN NEXT current_res; - - CONTINUE; - END IF; - - IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - IF staff IS NULL OR NOT staff THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - JOIN actor.org_unit a ON (cp.circ_lib = a.id) - JOIN asset.copy_location cl ON (cp.location = cl.id) - JOIN config.copy_status cs ON (cp.status = cs.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cs.holdable - AND cl.opac_visible - AND cp.opac_visible - AND a.opac_visible - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - ELSE - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - JOIN actor.org_unit a ON (cp.circ_lib = a.id) - JOIN asset.copy_location cl ON (cp.location = cl.id) - JOIN config.copy_status cs ON (cp.status = cs.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF NOT FOUND THEN - - PERFORM 1 - FROM asset.call_number cn - WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - END IF; - - visible_count := visible_count + 1; - - current_res.id = core_result.id; - current_res.rel = core_result.rel; - - tmp_int := 1; - IF metarecord THEN - SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; - END IF; - - IF tmp_int = 1 THEN - current_res.record = core_result.records[1]; - ELSE - current_res.record = NULL; - END IF; - - RETURN NEXT current_res; - - IF visible_count % 1000 = 0 THEN - -- RAISE NOTICE ' % visible so far ... ', visible_count; - END IF; - - END LOOP; - - current_res.id = NULL; - current_res.rel = NULL; - current_res.record = NULL; - current_res.total = total_count; - current_res.checked = check_count; - current_res.deleted = deleted_count; - current_res.visible = visible_count; - current_res.excluded = excluded_count; - - CLOSE core_cursor; - - RETURN NEXT current_res; - -END; -$func$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION search.explode_array(anyarray) RETURNS SETOF anyelement AS $BODY$ - SELECT ($1)[s] FROM generate_series(1, array_upper($1, 1)) AS s; -$BODY$ -LANGUAGE 'sql' IMMUTABLE; - -CREATE OR REPLACE FUNCTION search.parse_search_args (TEXT) RETURNS SETOF search.search_args AS $perlcode$ - use JSON::XS; - my $json = shift; - - my $args = decode_json( $json ); - - my $id = 1; - - for my $k ( keys %$args ) { - (my $alias = $k) =~ s/\|/_/gso; - my ($class, $field) = split /\|/, $k; - my $part = $args->{$k}; - for my $p ( keys %$part ) { - my $data = $part->{$p}; - $data = [$data] if (!ref($data)); - for my $datum ( @$data ) { - return_next( - { field_class => $class, - field_name => $field, - term => $datum, - table_alias => $alias, - term_type => $p, - id => $id, - } - ); - $id++; - } - } - } - - return undef; - -$perlcode$ LANGUAGE PLPERLU; - -COMMIT; diff --git a/Open-ILS/src/sql/Pg/1.2.2.0-1.2.2.1-upgrade-db.sql b/Open-ILS/src/sql/Pg/1.2.2.0-1.2.2.1-upgrade-db.sql deleted file mode 100644 index 1f9f4c171c..0000000000 --- a/Open-ILS/src/sql/Pg/1.2.2.0-1.2.2.1-upgrade-db.sql +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2008 Equinox Software, Inc. - * Mike Rylander - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -BEGIN; - --- Handy management functions for the new materialized reporting view - -CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$ - DROP TRIGGER zzz_update_materialized_simple_record_tgr ON metabib.full_rec; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$ - - TRUNCATE TABLE reporter.materialized_simple_record; - - INSERT INTO reporter.materialized_simple_record - (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn) - SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record; - - CREATE TRIGGER zzz_update_materialized_simple_record_tgr - AFTER INSERT OR UPDATE OR DELETE ON metabib.full_rec - FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_sync(); - -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION reporter.refresh_materialized_simple_record () RETURNS VOID AS $$ - SELECT reporter.disable_materialized_simple_record_trigger(); - SELECT reporter.enable_materialized_simple_record_trigger(); -$$ LANGUAGE SQL; - -COMMIT; diff --git a/Open-ILS/src/sql/Pg/1.2.2.1-1.2.2.2-upgrade-db.sql b/Open-ILS/src/sql/Pg/1.2.2.1-1.2.2.2-upgrade-db.sql deleted file mode 100644 index 49ec817308..0000000000 --- a/Open-ILS/src/sql/Pg/1.2.2.1-1.2.2.2-upgrade-db.sql +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2008 Equinox Software, Inc. - * Mike Rylander - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -BEGIN; - -CREATE SCHEMA extend_reporter; - -CREATE TABLE extend_reporter.legacy_circ_count ( - id BIGSERIAL PRIMARY KEY REFERENCES asset.copy (id) DEFERRABLE INITIALLY DEFERRED, - circ_count INT NOT NULL DEFAULT 0 -); - -CREATE VIEW extend_reporter.full_circ_count AS - SELECT cp.id, COALESCE(sum(c.circ_count), 0::bigint) + COALESCE(count(circ.id), 0::bigint) AS circ_count - FROM asset."copy" cp - LEFT JOIN extend_reporter.legacy_circ_count c USING (id) - LEFT JOIN "action".circulation circ ON circ.target_copy = c.id - GROUP BY cp.id; - -UPDATE metabib.title_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.author_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.keyword_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.subject_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.series_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.full_rec - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -COMMIT; - diff --git a/Open-ILS/src/sql/Pg/1.2.2.2-1.2.2.3-upgrade-db.sql b/Open-ILS/src/sql/Pg/1.2.2.2-1.2.2.3-upgrade-db.sql deleted file mode 100644 index 92066c691f..0000000000 --- a/Open-ILS/src/sql/Pg/1.2.2.2-1.2.2.3-upgrade-db.sql +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2008 Equinox Software, Inc. - * Mike Rylander - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - - --- It's OK if any of the following, before the transaction, fails ... - -CREATE SCHEMA extend_reporter; - -CREATE TABLE extend_reporter.legacy_circ_count ( - id BIGSERIAL PRIMARY KEY REFERENCES asset.copy (id) DEFERRABLE INITIALLY DEFERRED, - circ_count INT NOT NULL DEFAULT 0 -); - -INSERT INTO permission.perm_list (code, description) VALUES ('DELETE_RECORD', 'Allow a staff member to directly remove a bibliographic record'); -SELECT SETVAL('permission.perm_list_id_seq'::TEXT, (SELECT MAX(id) FROM permission.perm_list)); - -BEGIN; - -CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS - SELECT cp.id, COALESCE(sum(c.circ_count), 0::bigint) + COALESCE(count(circ.id), 0::bigint) AS circ_count - FROM asset."copy" cp - LEFT JOIN extend_reporter.legacy_circ_count c USING (id) - LEFT JOIN "action".circulation circ ON circ.target_copy = c.id - GROUP BY cp.id; - -UPDATE metabib.title_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.author_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.keyword_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.subject_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.series_field_entry - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -UPDATE metabib.full_rec - SET value = REGEXP_REPLACE(value, E'(\\d{4})-(\\d{4})', E'\\1 \\2','g') - WHERE value ~ E'(\\d{4})-(\\d{4})'; - -COMMIT; - diff --git a/Open-ILS/src/sql/Pg/1.2.2.3-1.2.3.0-upgrade.sql b/Open-ILS/src/sql/Pg/1.2.2.3-1.2.3.0-upgrade.sql deleted file mode 100644 index 1d06a44352..0000000000 --- a/Open-ILS/src/sql/Pg/1.2.2.3-1.2.3.0-upgrade.sql +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright (C) 2007-2008 Equinox Software, Inc. - * Mike Rylander - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - - - -BEGIN; - -ALTER TABLE config.rule_max_fine ADD COLUMN is_percent BOOL NOT NULL DEFAULT FALSE; - -CREATE OR REPLACE FUNCTION biblio.next_autogen_tcn_value () RETURNS TEXT AS $$ - BEGIN RETURN 'AUTOGENERATED-' || nextval('biblio.autogen_tcn_value_seq'::TEXT); END; -$$ LANGUAGE PLPGSQL; - - -CREATE OR REPLACE FUNCTION search.staged_fts ( - - param_search_ou INT, - param_depth INT, - param_searches TEXT, -- JSON hash, to be turned into a resultset via search.parse_search_args - param_statuses INT[], - param_locations INT[], - param_audience TEXT[], - param_language TEXT[], - param_lit_form TEXT[], - param_types TEXT[], - param_forms TEXT[], - param_vformats TEXT[], - param_pref_lang TEXT, - param_pref_lang_multiplier REAL, - param_sort TEXT, - param_sort_desc BOOL, - metarecord BOOL, - staff BOOL, - param_rel_limit INT, - param_chk_limit INT, - param_skip_chk INT - -) RETURNS SETOF search.search_result AS $func$ -DECLARE - - current_res search.search_result%ROWTYPE; - query_part search.search_args%ROWTYPE; - phrase_query_part search.search_args%ROWTYPE; - rank_adjust_id INT; - core_rel_limit INT; - core_chk_limit INT; - core_skip_chk INT; - rank_adjust search.relevance_adjustment%ROWTYPE; - query_table TEXT; - tmp_text TEXT; - tmp_int INT; - current_rank TEXT; - ranks TEXT[] := '{}'; - query_table_alias TEXT; - from_alias_array TEXT[] := '{}'; - used_ranks TEXT[] := '{}'; - mb_field INT; - mb_field_list INT[]; - search_org_list INT[]; - select_clause TEXT := 'SELECT'; - from_clause TEXT := ' FROM metabib.metarecord_source_map m JOIN metabib.rec_descriptor mrd ON (m.source = mrd.record) '; - where_clause TEXT := ' WHERE 1=1 '; - mrd_used BOOL := FALSE; - sort_desc BOOL := FALSE; - - core_result RECORD; - core_cursor REFCURSOR; - core_rel_query TEXT; - vis_limit_query TEXT; - inner_where_clause TEXT; - - total_count INT := 0; - check_count INT := 0; - deleted_count INT := 0; - visible_count INT := 0; - excluded_count INT := 0; - -BEGIN - - core_rel_limit := COALESCE( param_rel_limit, 25000 ); - core_chk_limit := COALESCE( param_chk_limit, 1000 ); - core_skip_chk := COALESCE( param_skip_chk, 1 ); - - IF metarecord THEN - select_clause := select_clause || ' m.metarecord as id, array_accum(distinct m.source) as records,'; - ELSE - select_clause := select_clause || ' m.source as id, array_accum(distinct m.source) as records,'; - END IF; - - -- first we need to construct the base query - FOR query_part IN SELECT * FROM search.parse_search_args(param_searches) WHERE term_type = 'fts_query' LOOP - - inner_where_clause := 'index_vector @@ ' || query_part.term; - - IF query_part.field_name IS NOT NULL THEN - - SELECT id INTO mb_field - FROM config.metabib_field - WHERE field_class = query_part.field_class - AND name = query_part.field_name; - - IF FOUND THEN - inner_where_clause := inner_where_clause || - ' AND ' || 'field = ' || mb_field; - END IF; - - END IF; - - -- moving on to the rank ... - SELECT * INTO query_part - FROM search.parse_search_args(param_searches) - WHERE term_type = 'fts_rank' - AND table_alias = query_part.table_alias; - - current_rank := query_part.term || ' * ' || query_part.table_alias || '_weight.weight'; - - IF query_part.field_name IS NOT NULL THEN - - SELECT array_accum(distinct id) INTO mb_field_list - FROM config.metabib_field - WHERE field_class = query_part.field_class - AND name = query_part.field_name; - - ELSE - - SELECT array_accum(distinct id) INTO mb_field_list - FROM config.metabib_field - WHERE field_class = query_part.field_class; - - END IF; - - FOR rank_adjust IN SELECT * FROM search.relevance_adjustment WHERE active AND field IN ( SELECT * FROM search.explode_array( mb_field_list ) ) LOOP - - IF NOT rank_adjust.bump_type = ANY (used_ranks) THEN - - IF rank_adjust.bump_type = 'first_word' THEN - SELECT term INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word' - ORDER BY id - LIMIT 1; - - tmp_text := query_part.table_alias || '.value ILIKE ' || quote_literal( tmp_text || '%' ); - - ELSIF rank_adjust.bump_type = 'word_order' THEN - SELECT array_to_string( array_accum( term ), '%' ) INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word'; - - tmp_text := query_part.table_alias || '.value ILIKE ' || quote_literal( '%' || tmp_text || '%' ); - - ELSIF rank_adjust.bump_type = 'full_match' THEN - SELECT array_to_string( array_accum( term ), E'\\s+' ) INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word'; - - tmp_text := query_part.table_alias || '.value ~ ' || quote_literal( '^' || tmp_text || E'\\W*$' ); - - END IF; - - - IF tmp_text IS NOT NULL THEN - current_rank := current_rank || ' * ( CASE WHEN ' || tmp_text || - ' THEN ' || rank_adjust.multiplier || '::REAL ELSE 1.0 END )'; - END IF; - - used_ranks := array_append( used_ranks, rank_adjust.bump_type ); - - END IF; - - END LOOP; - - ranks := array_append( ranks, current_rank ); - used_ranks := '{}'; - - FOR phrase_query_part IN - SELECT * - FROM search.parse_search_args(param_searches) - WHERE term_type = 'phrase' - AND table_alias = query_part.table_alias LOOP - - tmp_text := replace( phrase_query_part.term, '*', E'\\*' ); - tmp_text := replace( tmp_text, '?', E'\\?' ); - tmp_text := replace( tmp_text, '+', E'\\+' ); - tmp_text := replace( tmp_text, '|', E'\\|' ); - tmp_text := replace( tmp_text, '(', E'\\(' ); - tmp_text := replace( tmp_text, ')', E'\\)' ); - tmp_text := replace( tmp_text, '[', E'\\[' ); - tmp_text := replace( tmp_text, ']', E'\\]' ); - - inner_where_clause := inner_where_clause || ' AND ' || 'value ~* ' || quote_literal( E'(^|\\W+)' || regexp_replace(tmp_text, E'\\s+',E'\\\\s+','g') || E'(\\W+|\$)' ); - - END LOOP; - - query_table := search.pick_table(query_part.field_class); - - from_clause := from_clause || - ' JOIN ( SELECT * FROM ' || query_table || ' WHERE ' || inner_where_clause || - CASE WHEN core_rel_limit > 0 THEN ' LIMIT ' || core_rel_limit::TEXT ELSE '' END || ' ) AS ' || query_part.table_alias || - ' ON ( m.source = ' || query_part.table_alias || '.source )' || - ' JOIN config.metabib_field AS ' || query_part.table_alias || '_weight' || - ' ON ( ' || query_part.table_alias || '.field = ' || query_part.table_alias || '_weight.id AND ' || query_part.table_alias || '_weight.search_field)'; - - from_alias_array := array_append(from_alias_array, query_part.table_alias); - - END LOOP; - - IF param_pref_lang IS NOT NULL AND param_pref_lang_multiplier IS NOT NULL THEN - current_rank := ' CASE WHEN mrd.item_lang = ' || quote_literal( param_pref_lang ) || - ' THEN ' || param_pref_lang_multiplier || '::REAL ELSE 1.0 END '; - - --ranks := array_append( ranks, current_rank ); - END IF; - - current_rank := ' AVG( ( (' || array_to_string( ranks, ') + (' ) || ') ) * ' || current_rank || ' ) '; - select_clause := select_clause || current_rank || ' AS rel,'; - - sort_desc = param_sort_desc; - - IF param_sort = 'pubdate' THEN - - tmp_text := '999999'; - IF param_sort_desc THEN tmp_text := '0'; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT SUBSTRING(frp.value FROM E'\\d{4}') - FROM metabib.full_rec frp - WHERE frp.record = m.source - AND frp.tag = '260' - AND frp.subfield = 'c' - LIMIT 1 - )), $$ || quote_literal(tmp_text) || $$ )::INT ) - $$; - - ELSIF param_sort = 'title' THEN - - tmp_text := 'zzzzzz'; - IF param_sort_desc THEN tmp_text := ' '; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\d+'),'0')::INT + 1 )) - FROM metabib.full_rec frt - WHERE frt.record = m.source - AND frt.tag = '245' - AND frt.subfield = 'a' - LIMIT 1 - )),$$ || quote_literal(tmp_text) || $$)) - $$; - - ELSIF param_sort = 'author' THEN - - tmp_text := 'zzzzzz'; - IF param_sort_desc THEN tmp_text := ' '; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT LTRIM(fra.value) - FROM metabib.full_rec fra - WHERE fra.record = m.source - AND fra.tag LIKE '1%' - AND fra.subfield = 'a' - ORDER BY fra.tag::text::int - LIMIT 1 - )),$$ || quote_literal(tmp_text) || $$)) - $$; - - ELSIF param_sort = 'create_date' THEN - current_rank := $$( FIRST (( SELECT create_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )$$; - ELSIF param_sort = 'edit_date' THEN - current_rank := $$( FIRST (( SELECT edit_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )$$; - ELSE - sort_desc := NOT COALESCE(param_sort_desc, FALSE); - END IF; - - select_clause := select_clause || current_rank || ' AS rank'; - - -- now add the other qualifiers - IF param_audience IS NOT NULL AND array_upper(param_audience, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.audience IN ('$$ || array_to_string(param_audience, $$','$$) || $$') $$; - END IF; - - IF param_language IS NOT NULL AND array_upper(param_language, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_lang IN ('$$ || array_to_string(param_language, $$','$$) || $$') $$; - END IF; - - IF param_lit_form IS NOT NULL AND array_upper(param_lit_form, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.lit_form IN ('$$ || array_to_string(param_lit_form, $$','$$) || $$') $$; - END IF; - - IF param_types IS NOT NULL AND array_upper(param_types, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_type IN ('$$ || array_to_string(param_types, $$','$$) || $$') $$; - END IF; - - IF param_forms IS NOT NULL AND array_upper(param_forms, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_form IN ('$$ || array_to_string(param_forms, $$','$$) || $$') $$; - END IF; - - IF param_vformats IS NOT NULL AND array_upper(param_vformats, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.vr_format IN ('$$ || array_to_string(param_vformats, $$','$$) || $$') $$; - END IF; - - core_rel_query := select_clause || from_clause || where_clause || - ' GROUP BY 1 ORDER BY 4' || CASE WHEN sort_desc THEN ' DESC' ELSE ' ASC' END || ';'; - --RAISE NOTICE 'Base Query: %', core_rel_query; - - IF param_depth IS NOT NULL THEN - SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth ); - ELSE - SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou ); - END IF; - - OPEN core_cursor FOR EXECUTE core_rel_query; - - LOOP - - FETCH core_cursor INTO core_result; - EXIT WHEN NOT FOUND; - - - IF total_count % 1000 = 0 THEN - -- RAISE NOTICE ' % total, % checked so far ... ', total_count, check_count; - END IF; - - IF core_chk_limit > 0 AND total_count - core_skip_chk + 1 >= core_chk_limit THEN - total_count := total_count + 1; - CONTINUE; - END IF; - - total_count := total_count + 1; - - CONTINUE WHEN param_skip_chk IS NOT NULL and total_count < param_skip_chk; - - check_count := check_count + 1; - - PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all deleted ... ', core_result.records; - deleted_count := deleted_count + 1; - CONTINUE; - END IF; - - PERFORM 1 - FROM biblio.record_entry b - JOIN config.bib_source s ON (b.source = s.id) - WHERE s.transcendant - AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); - - IF FOUND THEN - -- RAISE NOTICE ' % were all transcendant ... ', core_result.records; - visible_count := visible_count + 1; - - current_res.id = core_result.id; - current_res.rel = core_result.rel; - - tmp_int := 1; - IF metarecord THEN - SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; - END IF; - - IF tmp_int = 1 THEN - current_res.record = core_result.records[1]; - ELSE - current_res.record = NULL; - END IF; - - RETURN NEXT current_res; - - CONTINUE; - END IF; - - IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - IF staff IS NULL OR NOT staff THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - JOIN actor.org_unit a ON (cp.circ_lib = a.id) - JOIN asset.copy_location cl ON (cp.location = cl.id) - JOIN config.copy_status cs ON (cp.status = cs.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cs.holdable - AND cl.opac_visible - AND cp.opac_visible - AND a.opac_visible - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - ELSE - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - JOIN actor.org_unit a ON (cp.circ_lib = a.id) - JOIN asset.copy_location cl ON (cp.location = cl.id) - JOIN config.copy_status cs ON (cp.status = cs.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF NOT FOUND THEN - - PERFORM 1 - FROM asset.call_number cn - WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - END IF; - - visible_count := visible_count + 1; - - current_res.id = core_result.id; - current_res.rel = core_result.rel; - - tmp_int := 1; - IF metarecord THEN - SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; - END IF; - - IF tmp_int = 1 THEN - current_res.record = core_result.records[1]; - ELSE - current_res.record = NULL; - END IF; - - RETURN NEXT current_res; - - IF visible_count % 1000 = 0 THEN - -- RAISE NOTICE ' % visible so far ... ', visible_count; - END IF; - - END LOOP; - - current_res.id = NULL; - current_res.rel = NULL; - current_res.record = NULL; - current_res.total = total_count; - current_res.checked = check_count; - current_res.deleted = deleted_count; - current_res.visible = visible_count; - current_res.excluded = excluded_count; - - CLOSE core_cursor; - - RETURN NEXT current_res; - -END; -$func$ LANGUAGE PLPGSQL; - -COMMIT; - diff --git a/Open-ILS/src/sql/Pg/1.2.4-1.4-upgrade-db.sql b/Open-ILS/src/sql/Pg/1.2.4-1.4-upgrade-db.sql deleted file mode 100644 index 869dce85e0..0000000000 --- a/Open-ILS/src/sql/Pg/1.2.4-1.4-upgrade-db.sql +++ /dev/null @@ -1,5735 +0,0 @@ -/* - * Copyright (C) 2008 Equinox Software, Inc. - * Mike Rylander - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - - -ALTER TABLE auditor.asset_copy_history ALTER COLUMN price DROP NOT NULL; -- Price is nullable in 1.4+, auditor triggers complain when it's not informed of this - --- Get rid of embedded slashes from old ingest -UPDATE metabib.title_field_entry -SET value = REGEXP_REPLACE(value, E'(\\w+)\\/(\\w+)', E'\\1 \\2','g') -WHERE value ~ E'(\\w+)\\/(\\w+)'; - -UPDATE metabib.author_field_entry -SET value = REGEXP_REPLACE(value, E'(\\w+)\\/(\\w+)', E'\\1 \\2','g') -WHERE value ~ E'(\\w+)\\/(\\w+)'; - -UPDATE metabib.subject_field_entry -SET value = REGEXP_REPLACE(value, E'(\\w+)\\/(\\w+)', E'\\1 \\2','g') -WHERE value ~ E'(\\w+)\\/(\\w+)'; - -UPDATE metabib.series_field_entry -SET value = REGEXP_REPLACE(value, E'(\\w+)\\/(\\w+)', E'\\1 \\2','g') -WHERE value ~ E'(\\w+)\\/(\\w+)'; - -UPDATE metabib.keyword_field_entry -SET value = REGEXP_REPLACE(value, E'(\\w+)\\/(\\w+)', E'\\1 \\2','g') -WHERE value ~ E'(\\w+)\\/(\\w+)'; - -UPDATE metabib.full_rec -SET value = REGEXP_REPLACE(value, E'(\\w+)\\/(\\w+)', E'\\1 \\2','g') -WHERE value ~ E'(\\w+)\\/(\\w+)'; - -\set ON_ERROR_STOP 1 - -BEGIN; - --- To avoid any updates while we're doin' our thing... -SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; - -CREATE TABLE config.upgrade_log ( - version TEXT PRIMARY KEY, - install_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() -); -INSERT INTO config.upgrade_log (version) VALUES ('1.4.0.0'); - -SELECT set_curcfg('default'); - -CREATE OR REPLACE FUNCTION public.extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT regexp_replace(array_to_string( array_accum( output ),' ' ),$4,'','g') FROM xpath_table('id', 'marc', $1, $3, 'id='||$2)x(id INT, output TEXT); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION public.extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$ - SELECT public.extract_marc_field($1,$2,$3,''); -$$ LANGUAGE SQL; - -CREATE TABLE config.i18n_locale ( - code TEXT PRIMARY KEY, - marc_code TEXT NOT NULL REFERENCES config.language_map (code) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - name TEXT UNIQUE NOT NULL, - description TEXT -); -INSERT INTO config.i18n_locale (code,marc_code,name,description) VALUES ('en-US', 'eng', 'English (US)', 'American English'); -INSERT INTO config.i18n_locale (code,marc_code,name,description) VALUES ('en-CA', 'eng', 'English (Canada)', 'Canadian English'); -INSERT INTO config.i18n_locale (code,marc_code,name,description) VALUES ('fr-CA', 'fre', 'French (Canada)', 'Canadian French'); -INSERT INTO config.i18n_locale (code,marc_code,name,description) VALUES ('hy-AM', 'arm', 'Armenian', 'Armenian'); - - -CREATE TABLE config.i18n_core ( - id BIGSERIAL PRIMARY KEY, - fq_field TEXT NOT NULL, - identity_value TEXT NOT NULL, - translation TEXT NOT NULL REFERENCES config.i18n_locale (code) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - string TEXT NOT NULL -); -CREATE UNIQUE INDEX i18n_identity ON config.i18n_core (fq_field,identity_value,translation); - -CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$ -DECLARE - locale TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' ); - language TEXT := REGEXP_REPLACE( locale, E'-.+$', '' ); - result config.i18n_core%ROWTYPE; - fallback TEXT; - keyfield TEXT := keyclass || '.' || keycol; -BEGIN - - -- Try the full locale - SELECT * INTO result - FROM config.i18n_core - WHERE fq_field = keyfield - AND identity_value = keyvalue - AND translation = locale; - - -- Try just the language - IF NOT FOUND THEN - SELECT * INTO result - FROM config.i18n_core - WHERE fq_field = keyfield - AND identity_value = keyvalue - AND translation = language; - END IF; - - -- Fall back to the string we passed in in the first place - IF NOT FOUND THEN - EXECUTE - 'SELECT ' || - keycol || - ' FROM ' || keytable || - ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue) - INTO fallback; - RETURN fallback; - END IF; - - RETURN result.string; -END; -$func$ LANGUAGE PLPGSQL; - --- Functions for marking translatable strings in SQL statements --- Parameters are: primary key, string, class hint, property -CREATE OR REPLACE FUNCTION oils_i18n_gettext( INT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT $2; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION oils_i18n_gettext( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT $2; -$$ LANGUAGE SQL; - -ALTER TABLE config.xml_transform DROP CONSTRAINT xml_transform_namespace_uri_key; -INSERT INTO config.xml_transform VALUES ( 'mods32', 'http://www.loc.gov/mods/', 'mods', '' ); - - -/* Upgrade to MODS32 for transforms */ -ALTER TABLE config.metabib_field ALTER COLUMN format SET DEFAULT 'mods32'; -UPDATE config.metabib_field SET format = 'mods32'; - -/* Update index definitions to MODS32-compliant XPaths */ -UPDATE config.metabib_field - SET xpath = $$//mods:mods/mods:name[@type='corporate']/mods:namePart[../mods:role/mods:roleTerm[text()='creator']]$$ - WHERE field_class = 'author' AND name = 'corporate'; -UPDATE config.metabib_field - SET xpath = $$//mods:mods/mods:name[@type='personal']/mods:namePart[../mods:role/mods:roleTerm[text()='creator']]$$ - WHERE field_class = 'author' AND name = 'personal'; -UPDATE config.metabib_field - SET xpath = $$//mods:mods/mods:name[@type='conference']/mods:namePart[../mods:role/mods:roleTerm[text()='creator']]$$ - WHERE field_class = 'author' AND name = 'conference'; - -/* And they all want mods32: as their prefix */ -UPDATE config.metabib_field SET xpath = regexp_replace(xpath, 'mods:', 'mods32:', 'g'); - - -ALTER TABLE config.copy_status ADD COLUMN opac_visible BOOL NOT NULL DEFAULT FALSE; -UPDATE config.copy_status SET opac_visible = holdable; - -CREATE TABLE config.bib_level_map ( - code TEXT PRIMARY KEY, - value TEXT NOT NULL -); -INSERT INTO config.bib_level_map (code, value) VALUES ('a', oils_i18n_gettext('a', 'Monographic component part', 'cblvl', 'value')); -INSERT INTO config.bib_level_map (code, value) VALUES ('b', oils_i18n_gettext('b', 'Serial component part', 'cblvl', 'value')); -INSERT INTO config.bib_level_map (code, value) VALUES ('c', oils_i18n_gettext('c', 'Collection', 'cblvl', 'value')); -INSERT INTO config.bib_level_map (code, value) VALUES ('d', oils_i18n_gettext('d', 'Subunit', 'cblvl', 'value')); -INSERT INTO config.bib_level_map (code, value) VALUES ('i', oils_i18n_gettext('i', 'Integrating resource', 'cblvl', 'value')); -INSERT INTO config.bib_level_map (code, value) VALUES ('m', oils_i18n_gettext('m', 'Monograph/Item', 'cblvl', 'value')); -INSERT INTO config.bib_level_map (code, value) VALUES ('s', oils_i18n_gettext('s', 'Serial', 'cblvl', 'value')); - -CREATE TABLE config.z3950_source ( - name TEXT PRIMARY KEY, - label TEXT NOT NULL UNIQUE, - host TEXT NOT NULL, - port INT NOT NULL, - db TEXT NOT NULL, - record_format TEXT NOT NULL DEFAULT 'FI', - transmission_format TEXT NOT NULL DEFAULT 'usmarc', - auth BOOL NOT NULL DEFAULT TRUE -); -INSERT INTO config.z3950_source (name, label, host, port, db, auth) - VALUES ('loc', oils_i18n_gettext('loc', 'Library of Congress', 'czs', 'label'), 'z3950.loc.gov', 7090, 'Voyager', FALSE); -INSERT INTO config.z3950_source (name, label, host, port, db, auth) - VALUES ('oclc', oils_i18n_gettext('loc', 'OCLC', 'czs', 'label'), 'zcat.oclc.org', 210, 'OLUCWorldCat', TRUE); - - -CREATE TABLE config.z3950_attr ( - id SERIAL PRIMARY KEY, - source TEXT NOT NULL REFERENCES config.z3950_source (name) DEFERRABLE INITIALLY DEFERRED, - name TEXT NOT NULL, - label TEXT NOT NULL, - code INT NOT NULL, - format INT NOT NULL, - truncation INT NOT NULL DEFAULT 0, - CONSTRAINT z_code_format_once_per_source UNIQUE (code,format,source) -); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (1, 'loc','tcn', oils_i18n_gettext(1, 'Title Control Number', 'cza', 'label'), 12, 1); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (2, 'loc', 'isbn', oils_i18n_gettext(2, 'ISBN', 'cza', 'label'), 7, 6); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (3, 'loc', 'lccn', oils_i18n_gettext(3, 'LCCN', 'cza', 'label'), 9, 1); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (4, 'loc', 'author', oils_i18n_gettext(4, 'Author', 'cza', 'label'), 1003, 6); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (5, 'loc', 'title', oils_i18n_gettext(5, 'Title', 'cza', 'label'), 4, 6); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (6, 'loc', 'issn', oils_i18n_gettext(6, 'ISSN', 'cza', 'label'), 8, 1); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (7, 'loc', 'publisher', oils_i18n_gettext(7, 'Publisher', 'cza', 'label'), 1018, 6); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (8, 'loc', 'pubdate', oils_i18n_gettext(8, 'Publication Date', 'cza', 'label'), 31, 1); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (9, 'loc', 'item_type', oils_i18n_gettext(9, 'Item Type', 'cza', 'label'), 1001, 1); - -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (10, 'oclc', 'tcn', oils_i18n_gettext(10, 'Title Control Number', 'cza', 'label'), 12, 1); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (11, 'oclc', 'isbn', oils_i18n_gettext(11, 'ISBN', 'cza', 'label'), 7, 6); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (12, 'oclc', 'lccn', oils_i18n_gettext(12, 'LCCN', 'cza', 'label'), 9, 1); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (13, 'oclc', 'author', oils_i18n_gettext(13, 'Author', 'cza', 'label'), 1003, 6); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (14, 'oclc', 'title', oils_i18n_gettext(14, 'Title', 'cza', 'label'), 4, 6); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (15, 'oclc', 'issn', oils_i18n_gettext(15, 'ISSN', 'cza', 'label'), 8, 1); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (16, 'oclc', 'publisher', oils_i18n_gettext(16, 'Publisher', 'cza', 'label'), 1018, 6); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (17, 'oclc', 'pubdate', oils_i18n_gettext(17, 'Publication Date', 'cza', 'label'), 31, 1); -INSERT INTO config.z3950_attr (id, source, name, label, code, format) - VALUES (18, 'oclc', 'item_type', oils_i18n_gettext(18, 'Item Type', 'cza', 'label'), 1001, 1); -SELECT SETVAL('config.z3950_attr_id_seq'::TEXT, 100); - - - -CREATE TABLE actor.org_lasso ( - id SERIAL PRIMARY KEY, - name TEXT UNIQUE -); - -CREATE TABLE actor.org_lasso_map ( - id SERIAL PRIMARY KEY, - lasso INT NOT NULL REFERENCES actor.org_lasso (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - org_unit INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED -); -CREATE UNIQUE INDEX ou_lasso_lasso_ou_idx ON actor.org_lasso_map (lasso, org_unit); -CREATE INDEX ou_lasso_org_unit_idx ON actor.org_lasso_map (org_unit); - - -CREATE TABLE permission.usr_object_perm_map ( - id SERIAL PRIMARY KEY, - usr INT NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - perm INT NOT NULL REFERENCES permission.perm_list (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - object_type TEXT NOT NULL, - object_id TEXT NOT NULL, - grantable BOOL NOT NULL DEFAULT FALSE, - CONSTRAINT perm_usr_obj_once UNIQUE (usr,perm,object_type,object_id) -); -CREATE INDEX uopm_usr_idx ON permission.usr_object_perm_map (usr); - - -CREATE OR REPLACE FUNCTION permission.grp_ancestors ( INT ) RETURNS SETOF permission.grp_tree AS $$ - SELECT a.* - FROM connectby('permission.grp_tree'::text,'parent'::text,'id'::text,'name'::text,$1::text,100,'.'::text) - AS t(keyid text, parent_keyid text, level int, branch text,pos int) - JOIN permission.grp_tree a ON a.id::text = t.keyid::text - ORDER BY - CASE WHEN a.parent IS NULL - THEN 0 - ELSE 1 - END, a.name; -$$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION permission.usr_has_object_perm ( iuser INT, tperm TEXT, obj_type TEXT, obj_id TEXT, target_ou INT ) RETURNS BOOL AS $$ -DECLARE - r_usr actor.usr%ROWTYPE; - res BOOL; -BEGIN - - SELECT * INTO r_usr FROM actor.usr WHERE id = iuser; - - IF r_usr.active = FALSE THEN - RETURN FALSE; - END IF; - - IF r_usr.super_user = TRUE THEN - RETURN TRUE; - END IF; - - SELECT TRUE INTO res FROM permission.usr_object_perm_map WHERE usr = r_usr.id AND object_type = obj_type AND object_id = obj_id; - - IF FOUND THEN - RETURN TRUE; - END IF; - - IF target_ou > -1 THEN - RETURN permission.usr_has_perm( iuser, tperm, target_ou); - END IF; - - RETURN FALSE; - -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION permission.usr_has_object_perm ( INT, TEXT, TEXT, TEXT ) RETURNS BOOL AS $$ - SELECT permission.usr_has_object_perm( $1, $2, $3, $4, -1 ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION permission.grp_descendants ( INT ) RETURNS SETOF permission.grp_tree AS $$ - SELECT a.* - FROM connectby('permission.grp_tree'::text,'id'::text,'parent'::text,'name'::text,$1::text,100,'.'::text) - AS t(keyid text, parent_keyid text, level int, branch text,pos int) - JOIN permission.grp_tree a ON a.id::text = t.keyid::text - ORDER BY CASE WHEN a.parent IS NULL THEN 0 ELSE 1 END, a.name; -$$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION permission.grp_full_path ( INT ) RETURNS SETOF permission.grp_tree AS $$ - SELECT * - FROM permission.grp_ancestors($1) - UNION - SELECT * - FROM permission.grp_descendants($1); -$$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION permission.grp_combined_ancestors ( INT, INT ) RETURNS SETOF permission.grp_tree AS $$ - SELECT * - FROM permission.grp_ancestors($1) - UNION - SELECT * - FROM permission.grp_ancestors($2); -$$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION permission.grp_common_ancestors ( INT, INT ) RETURNS SETOF permission.grp_tree AS $$ - SELECT * - FROM permission.grp_ancestors($1) - INTERSECT - SELECT * - FROM permission.grp_ancestors($2); -$$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION permission.grp_proximity ( INT, INT ) RETURNS INT AS $$ - SELECT COUNT(id)::INT FROM ( - SELECT id FROM permission.grp_combined_ancestors($1, $2) - EXCEPT - SELECT id FROM permission.grp_common_ancestors($1, $2) - ) z; -$$ LANGUAGE SQL STABLE; - -INSERT INTO permission.usr_work_ou_map (usr, work_ou) - SELECT DISTINCT u.id, u.home_ou - FROM actor.usr u - JOIN permission.grp_tree g ON (u.profile = g.id) - LEFT JOIN permission.usr_work_ou_map m ON (u.id = m.usr AND u.home_ou = m.work_ou) - WHERE m.id IS NULL - AND g.id IN ( - SELECT DISTINCT (permission.grp_descendants(grp)).id - FROM permission.grp_perm_map gpm JOIN permission.perm_list pl ON (pl.id = gpm.perm) - WHERE pl.code = 'STAFF_LOGIN' - ); - -/* Enable LIKE to use an index for database clusters with locales other than C or POSIX */ -CREATE INDEX authority_full_rec_value_tpo_index ON authority.full_rec (value text_pattern_ops); - - -CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$ - use Unicode::Normalize; - - my $txt = lc(shift); - my $sf = shift; - - $txt = NFD($txt); - $txt =~ s/\pM+//go; # Remove diacritics - - $txt =~ s/\xE6/AE/go; # Convert ae digraph - $txt =~ s/\x{153}/OE/go;# Convert oe digraph - $txt =~ s/\xFE/TH/go; # Convert Icelandic thorn - - $txt =~ tr/\x{2070}\x{2071}\x{2072}\x{2073}\x{2074}\x{2075}\x{2076}\x{2077}\x{2078}\x{2079}\x{207A}\x{207B}/0123456789+-/;# Convert superscript numbers - $txt =~ tr/\x{2080}\x{2081}\x{2082}\x{2083}\x{2084}\x{2085}\x{2086}\x{2087}\x{2088}\x{2089}\x{208A}\x{208B}/0123456889+-/;# Convert subscript numbers - - $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/; # Convert Latin and Greek - $txt =~ tr/\x{2113}\xF0\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LD /; # Convert Misc - $txt =~ tr/\'\[\]\|//d; # Remove Misc - - if ($sf && $sf =~ /^a/o) { - my $commapos = index($txt,','); - if ($commapos > -1) { - if ($commapos != length($txt) - 1) { - my @list = split /,/, $txt; - my $first = shift @list; - $txt = $first . ',' . join(' ', @list); - } else { - $txt =~ s/,/ /go; - } - } - } else { - $txt =~ s/,/ /go; - } - - $txt =~ s/\s+/ /go; # Compress multiple spaces - $txt =~ s/^\s+//o; # Remove leading space - $txt =~ s/\s+$//o; # Remove trailing space - - return $txt; -$func$ LANGUAGE 'plperlu' STRICT IMMUTABLE; - -CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT ) RETURNS TEXT AS $func$ - SELECT public.naco_normalize($1,''); -$func$ LANGUAGE 'sql' STRICT IMMUTABLE; - -CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$ - SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g'); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$ - return lc(shift); -$$ LANGUAGE PLPERLU; - -CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$ - return uc(shift); -$$ LANGUAGE PLPERLU; - -CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$ - use Unicode::Normalize; - - my $x = NFD(shift); - $x =~ s/\pM+//go; - return $x; - -$$ LANGUAGE PLPERLU; - -CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$ - use Unicode::Normalize; - - my $x = NFC(shift); - $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe; - return $x; - -$$ LANGUAGE PLPERLU; - - -CREATE OR REPLACE FUNCTION public.call_number_dewey( TEXT ) RETURNS TEXT AS $$ - my $txt = shift; - $txt =~ s/^\s+//o; - $txt =~ s/[\[\]\{\}\(\)`'"#<>\*\?\-\+\$\\]+//og; - $txt =~ s/\s+$//o; - if ($txt =~ /(\d{3}(?:\.\d+)?)/o) { - return $1; - } else { - return (split /\s+/, $txt)[0]; - } -$$ LANGUAGE 'plperlu' STRICT IMMUTABLE; - - -CREATE OR REPLACE FUNCTION actor.org_unit_descendants ( INT ) RETURNS SETOF actor.org_unit AS $$ - SELECT a.* - FROM connectby('actor.org_unit'::text,'id'::text,'parent_ou'::text,'name'::text,$1::text,100,'.'::text) - AS t(keyid text, parent_keyid text, level int, branch text,pos int) - JOIN actor.org_unit a ON a.id::text = t.keyid::text - ORDER BY CASE WHEN a.parent_ou IS NULL THEN 0 ELSE 1 END, a.name; -$$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION actor.org_unit_ancestors ( INT ) RETURNS SETOF actor.org_unit AS $$ - SELECT a.* - FROM connectby('actor.org_unit'::text,'parent_ou'::text,'id'::text,'name'::text,$1::text,100,'.'::text) - AS t(keyid text, parent_keyid text, level int, branch text,pos int) - JOIN actor.org_unit a ON a.id::text = t.keyid::text - ORDER BY CASE WHEN a.parent_ou IS NULL THEN 0 ELSE 1 END, a.name; -$$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION actor.org_unit_descendants ( INT,INT ) RETURNS SETOF actor.org_unit AS $$ - SELECT a.* - FROM connectby('actor.org_unit'::text,'id'::text,'parent_ou'::text,'name'::text, - (SELECT x.id - FROM actor.org_unit_ancestors($1) x - JOIN actor.org_unit_type y ON x.ou_type = y.id - WHERE y.depth = $2)::text - ,100,'.'::text) - AS t(keyid text, parent_keyid text, level int, branch text,pos int) - JOIN actor.org_unit a ON a.id::text = t.keyid::text - ORDER BY CASE WHEN a.parent_ou IS NULL THEN 0 ELSE 1 END, a.name; -$$ LANGUAGE SQL STABLE; - -ALTER TABLE metabib.rec_descriptor ADD COLUMN date1 TEXT; -ALTER TABLE metabib.rec_descriptor ADD COLUMN date2 TEXT; - -UPDATE metabib.rec_descriptor - SET date1 = regexp_replace(substring(metabib.full_rec.value,8,4),E'\\D','0','g'), - date2 = regexp_replace(substring(metabib.full_rec.value,12,4),E'\\D','9','g') - FROM metabib.full_rec - WHERE metabib.full_rec.record = metabib.rec_descriptor.record AND metabib.full_rec.tag = '008'; - - -ALTER TABLE money.credit_card_payment ALTER cc_type DROP NOT NULL; -ALTER TABLE money.credit_card_payment ALTER cc_number DROP NOT NULL; -ALTER TABLE money.credit_card_payment ALTER expire_month DROP NOT NULL; -ALTER TABLE money.credit_card_payment ALTER expire_year DROP NOT NULL; -ALTER TABLE money.credit_card_payment ALTER approval_code DROP NOT NULL; - - -ALTER TABLE asset.copy_location ADD CONSTRAINT acl_name_once_per_lib UNIQUE (name, owning_lib); -ALTER TABLE asset.copy ALTER price DROP NOT NULL; -ALTER TABLE asset.copy ALTER price DROP DEFAULT; - -CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$ -DECLARE - moved_cns INT := 0; - source_cn asset.call_number%ROWTYPE; - target_cn asset.call_number%ROWTYPE; -BEGIN - 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 owning_lib = source_cn.owning_lib - AND record = target_record; - - IF FOUND THEN - UPDATE asset.copy - SET call_number = target_cn.id - WHERE call_number = source_cn.id; - DELETE FROM asset.call_number - WHERE id = target_cn.id; - ELSE - UPDATE asset.call_number - SET record = target_record - WHERE id = source_cn.id; - END IF; - - moved_cns := moved_cns + 1; - END LOOP; - - RETURN moved_cns; -END; -$func$ LANGUAGE plpgsql; - - -ALTER TABLE money.billable_xact ADD COLUMN unrecovered BOOL; - -CREATE OR REPLACE VIEW money.billable_xact_summary AS - SELECT xact.id, - xact.usr, - xact.xact_start, - xact.xact_finish, - credit.amount AS total_paid, - credit.payment_ts AS last_payment_ts, - credit.note AS last_payment_note, - credit.payment_type AS last_payment_type, - debit.amount AS total_owed, - debit.billing_ts AS last_billing_ts, - debit.note AS last_billing_note, - debit.billing_type AS last_billing_type, - COALESCE(debit.amount, 0::numeric) - COALESCE(credit.amount, 0::numeric) AS balance_owed, - p.relname AS xact_type - FROM money.billable_xact xact - JOIN pg_class p ON xact.tableoid = p.oid - LEFT JOIN ( - SELECT billing.xact, - sum(billing.amount) AS amount, - max(billing.billing_ts) AS billing_ts, - last(billing.note) AS note, - last(billing.billing_type) AS billing_type - FROM money.billing - WHERE billing.voided IS FALSE - GROUP BY billing.xact - ) debit ON xact.id = debit.xact - LEFT JOIN ( - SELECT payment_view.xact, - sum(payment_view.amount) AS amount, - max(payment_view.payment_ts) AS payment_ts, - last(payment_view.note) AS note, - last(payment_view.payment_type) AS payment_type - FROM money.payment_view - WHERE payment_view.voided IS FALSE - GROUP BY payment_view.xact - ) credit ON xact.id = credit.xact - ORDER BY debit.billing_ts, credit.payment_ts; - -ALTER TABLE action.circulation ADD COLUMN create_time TIMESTAMP WITH TIME ZONE DEFAULT NOW(); - - -CREATE TABLE action.aged_circulation ( - usr_post_code TEXT, - usr_home_ou INT NOT NULL, - usr_profile INT NOT NULL, - usr_birth_year INT, - copy_call_number INT NOT NULL, - copy_location INT NOT NULL, - copy_owning_lib INT NOT NULL, - copy_circ_lib INT NOT NULL, - copy_bib_record BIGINT NOT NULL, - LIKE action.circulation - -); -ALTER TABLE action.aged_circulation ADD PRIMARY KEY (id); -ALTER TABLE action.aged_circulation DROP COLUMN usr; -CREATE INDEX aged_circ_circ_lib_idx ON "action".aged_circulation (circ_lib); -CREATE INDEX aged_circ_start_idx ON "action".aged_circulation (xact_start); -CREATE INDEX aged_circ_copy_circ_lib_idx ON "action".aged_circulation (copy_circ_lib); -CREATE INDEX aged_circ_copy_owning_lib_idx ON "action".aged_circulation (copy_owning_lib); -CREATE INDEX aged_circ_copy_location_idx ON "action".aged_circulation (copy_location); - -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, due_date, - stop_fines_time, checkin_time, create_time, duration, fine_interval, recuring_fine, - max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recuring_fine_rule, - max_fine_rule, stop_fines - FROM action.aged_circulation - UNION ALL - SELECT 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, cp.location AS 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.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration, - circ.fine_interval, circ.recuring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule, - circ.recuring_fine_rule, circ.max_fine_rule, circ.stop_fines - 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 = a.id); - -CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$ -BEGIN - INSERT INTO action.aged_circulation - (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, due_date, - stop_fines_time, checkin_time, create_time, duration, fine_interval, recuring_fine, - max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recuring_fine_rule, - max_fine_rule, stop_fines) - 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, due_date, - stop_fines_time, checkin_time, create_time, duration, fine_interval, recuring_fine, - max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recuring_fine_rule, - max_fine_rule, stop_fines - FROM action.all_circulation WHERE id = OLD.id; - - RETURN OLD; -END; -$$ LANGUAGE 'plpgsql'; - -CREATE TRIGGER action_circulation_aging_tgr - BEFORE DELETE ON action.circulation - FOR EACH ROW - EXECUTE PROCEDURE action.age_circ_on_delete (); - -CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS - SELECT cp.id, COALESCE(sum(c.circ_count), 0::bigint) + COALESCE(count(circ.id), 0::bigint) AS circ_count - FROM asset."copy" cp - LEFT JOIN extend_reporter.legacy_circ_count c USING (id) - LEFT JOIN "action".all_circulation circ ON circ.target_copy = cp.id - GROUP BY cp.id; - - - -CREATE OR REPLACE FUNCTION search.staged_fts ( - - param_search_ou INT, - param_depth INT, - param_searches TEXT, -- JSON hash, to be turned into a resultset via search.parse_search_args - param_statuses INT[], - param_locations INT[], - param_audience TEXT[], - param_language TEXT[], - param_lit_form TEXT[], - param_types TEXT[], - param_forms TEXT[], - param_vformats TEXT[], - param_bib_level TEXT[], - param_before TEXT, - param_after TEXT, - param_during TEXT, - param_between TEXT[], - param_pref_lang TEXT, - param_pref_lang_multiplier REAL, - param_sort TEXT, - param_sort_desc BOOL, - metarecord BOOL, - staff BOOL, - param_rel_limit INT, - param_chk_limit INT, - param_skip_chk INT - -) RETURNS SETOF search.search_result AS $func$ -DECLARE - - current_res search.search_result%ROWTYPE; - query_part search.search_args%ROWTYPE; - phrase_query_part search.search_args%ROWTYPE; - rank_adjust_id INT; - core_rel_limit INT; - core_chk_limit INT; - core_skip_chk INT; - rank_adjust search.relevance_adjustment%ROWTYPE; - query_table TEXT; - tmp_text TEXT; - tmp_int INT; - current_rank TEXT; - ranks TEXT[] := '{}'; - query_table_alias TEXT; - from_alias_array TEXT[] := '{}'; - used_ranks TEXT[] := '{}'; - mb_field INT; - mb_field_list INT[]; - search_org_list INT[]; - select_clause TEXT := 'SELECT'; - from_clause TEXT := ' FROM metabib.metarecord_source_map m JOIN metabib.rec_descriptor mrd ON (m.source = mrd.record) '; - where_clause TEXT := ' WHERE 1=1 '; - mrd_used BOOL := FALSE; - sort_desc BOOL := FALSE; - - core_result RECORD; - core_cursor REFCURSOR; - core_rel_query TEXT; - vis_limit_query TEXT; - inner_where_clause TEXT; - - total_count INT := 0; - check_count INT := 0; - deleted_count INT := 0; - visible_count INT := 0; - excluded_count INT := 0; - -BEGIN - - core_rel_limit := COALESCE( param_rel_limit, 25000 ); - core_chk_limit := COALESCE( param_chk_limit, 1000 ); - core_skip_chk := COALESCE( param_skip_chk, 1 ); - - IF metarecord THEN - select_clause := select_clause || ' m.metarecord as id, array_accum(distinct m.source) as records,'; - ELSE - select_clause := select_clause || ' m.source as id, array_accum(distinct m.source) as records,'; - END IF; - - -- first we need to construct the base query - FOR query_part IN SELECT * FROM search.parse_search_args(param_searches) WHERE term_type = 'fts_query' LOOP - - inner_where_clause := 'index_vector @@ ' || query_part.term; - - IF query_part.field_name IS NOT NULL THEN - - SELECT id INTO mb_field - FROM config.metabib_field - WHERE field_class = query_part.field_class - AND name = query_part.field_name; - - IF FOUND THEN - inner_where_clause := inner_where_clause || - ' AND ' || 'field = ' || mb_field; - END IF; - - END IF; - - -- moving on to the rank ... - SELECT * INTO query_part - FROM search.parse_search_args(param_searches) - WHERE term_type = 'fts_rank' - AND table_alias = query_part.table_alias; - - current_rank := query_part.term || ' * ' || query_part.table_alias || '_weight.weight'; - - IF query_part.field_name IS NOT NULL THEN - - SELECT array_accum(distinct id) INTO mb_field_list - FROM config.metabib_field - WHERE field_class = query_part.field_class - AND name = query_part.field_name; - - ELSE - - SELECT array_accum(distinct id) INTO mb_field_list - FROM config.metabib_field - WHERE field_class = query_part.field_class; - - END IF; - - FOR rank_adjust IN SELECT * FROM search.relevance_adjustment WHERE active AND field IN ( SELECT * FROM search.explode_array( mb_field_list ) ) LOOP - - IF NOT rank_adjust.bump_type = ANY (used_ranks) THEN - - IF rank_adjust.bump_type = 'first_word' THEN - SELECT term INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word' - ORDER BY id - LIMIT 1; - - tmp_text := query_part.table_alias || '.value ILIKE ' || quote_literal( tmp_text || '%' ); - - ELSIF rank_adjust.bump_type = 'word_order' THEN - SELECT array_to_string( array_accum( term ), '%' ) INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word'; - - tmp_text := query_part.table_alias || '.value ILIKE ' || quote_literal( '%' || tmp_text || '%' ); - - ELSIF rank_adjust.bump_type = 'full_match' THEN - SELECT array_to_string( array_accum( term ), E'\\s+' ) INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word'; - - tmp_text := query_part.table_alias || '.value ~ ' || quote_literal( '^' || tmp_text || E'\\W*$' ); - - END IF; - - - IF tmp_text IS NOT NULL THEN - current_rank := current_rank || ' * ( CASE WHEN ' || tmp_text || - ' THEN ' || rank_adjust.multiplier || '::REAL ELSE 1.0 END )'; - END IF; - - used_ranks := array_append( used_ranks, rank_adjust.bump_type ); - - END IF; - - END LOOP; - - ranks := array_append( ranks, current_rank ); - used_ranks := '{}'; - - FOR phrase_query_part IN - SELECT * - FROM search.parse_search_args(param_searches) - WHERE term_type = 'phrase' - AND table_alias = query_part.table_alias LOOP - - tmp_text := replace( phrase_query_part.term, '*', E'\\*' ); - tmp_text := replace( tmp_text, '?', E'\\?' ); - tmp_text := replace( tmp_text, '+', E'\\+' ); - tmp_text := replace( tmp_text, '|', E'\\|' ); - tmp_text := replace( tmp_text, '(', E'\\(' ); - tmp_text := replace( tmp_text, ')', E'\\)' ); - tmp_text := replace( tmp_text, '[', E'\\[' ); - tmp_text := replace( tmp_text, ']', E'\\]' ); - - inner_where_clause := inner_where_clause || ' AND ' || 'value ~* ' || quote_literal( E'(^|\\W+)' || regexp_replace(tmp_text, E'\\s+',E'\\\\s+','g') || E'(\\W+|\$)' ); - - END LOOP; - - query_table := search.pick_table(query_part.field_class); - - from_clause := from_clause || - ' JOIN ( SELECT * FROM ' || query_table || ' WHERE ' || inner_where_clause || - CASE WHEN core_rel_limit > 0 THEN ' LIMIT ' || core_rel_limit::TEXT ELSE '' END || ' ) AS ' || query_part.table_alias || - ' ON ( m.source = ' || query_part.table_alias || '.source )' || - ' JOIN config.metabib_field AS ' || query_part.table_alias || '_weight' || - ' ON ( ' || query_part.table_alias || '.field = ' || query_part.table_alias || '_weight.id AND ' || query_part.table_alias || '_weight.search_field)'; - - from_alias_array := array_append(from_alias_array, query_part.table_alias); - - END LOOP; - - IF param_pref_lang IS NOT NULL AND param_pref_lang_multiplier IS NOT NULL THEN - current_rank := ' CASE WHEN mrd.item_lang = ' || quote_literal( param_pref_lang ) || - ' THEN ' || param_pref_lang_multiplier || '::REAL ELSE 1.0 END '; - - -- ranks := array_append( ranks, current_rank ); - END IF; - - current_rank := ' AVG( ( (' || array_to_string( ranks, ') + (' ) || ') ) * ' || current_rank || ' ) '; - select_clause := select_clause || current_rank || ' AS rel,'; - - sort_desc = param_sort_desc; - - IF param_sort = 'pubdate' THEN - - tmp_text := '999999'; - IF param_sort_desc THEN tmp_text := '0'; END IF; - - current_rank := $$ COALESCE( FIRST(NULLIF(REGEXP_REPLACE(mrd.date1, E'\\D+', '9', 'g'),'')), $$ || quote_literal(tmp_text) || $$ )::INT $$; - - ELSIF param_sort = 'title' THEN - - tmp_text := 'zzzzzz'; - IF param_sort_desc THEN tmp_text := ' '; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\d+'),'0')::INT + 1 )) - FROM metabib.full_rec frt - WHERE frt.record = m.source - AND frt.tag = '245' - AND frt.subfield = 'a' - LIMIT 1 - )),$$ || quote_literal(tmp_text) || $$)) - $$; - - ELSIF param_sort = 'author' THEN - - tmp_text := 'zzzzzz'; - IF param_sort_desc THEN tmp_text := ' '; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT LTRIM(fra.value) - FROM metabib.full_rec fra - WHERE fra.record = m.source - AND fra.tag LIKE '1%' - AND fra.subfield = 'a' - ORDER BY fra.tag::text::int - LIMIT 1 - )),$$ || quote_literal(tmp_text) || $$)) - $$; - - ELSIF param_sort = 'create_date' THEN - current_rank := $$( FIRST (( SELECT create_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )$$; - ELSIF param_sort = 'edit_date' THEN - current_rank := $$( FIRST (( SELECT edit_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )$$; - ELSE - sort_desc := NOT COALESCE(param_sort_desc, FALSE); - END IF; - - select_clause := select_clause || current_rank || ' AS rank'; - - -- now add the other qualifiers - IF param_audience IS NOT NULL AND array_upper(param_audience, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.audience IN ('$$ || array_to_string(param_audience, $$','$$) || $$') $$; - END IF; - - IF param_language IS NOT NULL AND array_upper(param_language, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_lang IN ('$$ || array_to_string(param_language, $$','$$) || $$') $$; - END IF; - - IF param_lit_form IS NOT NULL AND array_upper(param_lit_form, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.lit_form IN ('$$ || array_to_string(param_lit_form, $$','$$) || $$') $$; - END IF; - - IF param_types IS NOT NULL AND array_upper(param_types, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_type IN ('$$ || array_to_string(param_types, $$','$$) || $$') $$; - END IF; - - IF param_forms IS NOT NULL AND array_upper(param_forms, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_form IN ('$$ || array_to_string(param_forms, $$','$$) || $$') $$; - END IF; - - IF param_vformats IS NOT NULL AND array_upper(param_vformats, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.vr_format IN ('$$ || array_to_string(param_vformats, $$','$$) || $$') $$; - END IF; - - IF param_bib_level IS NOT NULL AND array_upper(param_bib_level, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.bib_level IN ('$$ || array_to_string(param_bib_level, $$','$$) || $$') $$; - END IF; - - IF param_before IS NOT NULL AND param_before <> '' THEN - where_clause = where_clause || $$ AND mrd.date1 <= $$ || quote_literal(param_before) || ' '; - END IF; - - IF param_after IS NOT NULL AND param_after <> '' THEN - where_clause = where_clause || $$ AND mrd.date1 >= $$ || quote_literal(param_after) || ' '; - END IF; - - IF param_during IS NOT NULL AND param_during <> '' THEN - where_clause = where_clause || $$ AND $$ || quote_literal(param_during) || $$ BETWEEN mrd.date1 AND mrd.date2 $$; - END IF; - - IF param_between IS NOT NULL AND array_upper(param_between, 1) > 1 THEN - where_clause = where_clause || $$ AND mrd.date1 BETWEEN '$$ || array_to_string(param_between, $$' AND '$$) || $$' $$; - END IF; - - core_rel_query := select_clause || from_clause || where_clause || - ' GROUP BY 1 ORDER BY 4' || CASE WHEN sort_desc THEN ' DESC' ELSE ' ASC' END || ';'; - --RAISE NOTICE 'Base Query: %', core_rel_query; - - IF param_search_ou > 0 THEN - IF param_depth IS NOT NULL THEN - SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth ); - ELSE - SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou ); - END IF; - ELSIF param_search_ou < 0 THEN - SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou; - ELSIF param_search_ou = 0 THEN - -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure. - END IF; - - OPEN core_cursor FOR EXECUTE core_rel_query; - - LOOP - - FETCH core_cursor INTO core_result; - EXIT WHEN NOT FOUND; - - - IF total_count % 1000 = 0 THEN - -- RAISE NOTICE ' % total, % checked so far ... ', total_count, check_count; - END IF; - - IF core_chk_limit > 0 AND total_count - core_skip_chk + 1 >= core_chk_limit THEN - total_count := total_count + 1; - CONTINUE; - END IF; - - total_count := total_count + 1; - - CONTINUE WHEN param_skip_chk IS NOT NULL and total_count < param_skip_chk; - - check_count := check_count + 1; - - PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all deleted ... ', core_result.records; - deleted_count := deleted_count + 1; - CONTINUE; - END IF; - - PERFORM 1 - FROM biblio.record_entry b - JOIN config.bib_source s ON (b.source = s.id) - WHERE s.transcendant - AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); - - IF FOUND THEN - -- RAISE NOTICE ' % were all transcendant ... ', core_result.records; - visible_count := visible_count + 1; - - current_res.id = core_result.id; - current_res.rel = core_result.rel; - - tmp_int := 1; - IF metarecord THEN - SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; - END IF; - - IF tmp_int = 1 THEN - current_res.record = core_result.records[1]; - ELSE - current_res.record = NULL; - END IF; - - RETURN NEXT current_res; - - CONTINUE; - END IF; - - IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - IF staff IS NULL OR NOT staff THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - JOIN actor.org_unit a ON (cp.circ_lib = a.id) - JOIN asset.copy_location cl ON (cp.location = cl.id) - JOIN config.copy_status cs ON (cp.status = cs.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cs.opac_visible - AND cl.opac_visible - AND cp.opac_visible - AND a.opac_visible - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - ELSE - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - JOIN actor.org_unit a ON (cp.circ_lib = a.id) - JOIN asset.copy_location cl ON (cp.location = cl.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF NOT FOUND THEN - - PERFORM 1 - FROM asset.call_number cn - WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - END IF; - - visible_count := visible_count + 1; - - current_res.id = core_result.id; - current_res.rel = core_result.rel; - - tmp_int := 1; - IF metarecord THEN - SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; - END IF; - - IF tmp_int = 1 THEN - current_res.record = core_result.records[1]; - ELSE - current_res.record = NULL; - END IF; - - RETURN NEXT current_res; - - IF visible_count % 1000 = 0 THEN - -- RAISE NOTICE ' % visible so far ... ', visible_count; - END IF; - - END LOOP; - - current_res.id = NULL; - current_res.rel = NULL; - current_res.record = NULL; - current_res.total = total_count; - current_res.checked = check_count; - current_res.deleted = deleted_count; - current_res.visible = visible_count; - current_res.excluded = excluded_count; - - CLOSE core_cursor; - - RETURN NEXT current_res; - -END; -$func$ LANGUAGE PLPGSQL; - --- This index, right here, is the reason for this change. -DROP INDEX IF EXISTS metabib.metabib_full_rec_value_idx; - --- So, on to it. --- Move the table out of the way ... -ALTER TABLE metabib.full_rec RENAME TO real_full_rec; - --- ... and let the trigger management functions know about the change ... -CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$ - DROP TRIGGER zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$ - - TRUNCATE TABLE reporter.materialized_simple_record; - - INSERT INTO reporter.materialized_simple_record - (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn) - SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record; - - CREATE TRIGGER zzz_update_materialized_simple_record_tgr - AFTER INSERT OR UPDATE OR DELETE ON metabib.real_full_rec - FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_sync(); - -$$ LANGUAGE SQL; - --- ... replace the table with a suitable view, which applies the index contstraint we'll use ... -CREATE OR REPLACE VIEW metabib.full_rec AS - SELECT id, - record, - tag, - ind1, - ind2, - subfield, - SUBSTRING(value,1,1024) AS value, - index_vector - FROM metabib.real_full_rec; - --- ... now some rules to transform DML against the view into DML against the underlying table ... -CREATE OR REPLACE RULE metabib_full_rec_insert_rule - AS ON INSERT TO metabib.full_rec - DO INSTEAD - INSERT INTO metabib.real_full_rec VALUES ( - COALESCE(NEW.id, NEXTVAL('metabib.full_rec_id_seq'::REGCLASS)), - NEW.record, - NEW.tag, - NEW.ind1, - NEW.ind2, - NEW.subfield, - NEW.value, - NEW.index_vector - ); - -CREATE OR REPLACE RULE metabib_full_rec_update_rule - AS ON UPDATE TO metabib.full_rec - DO INSTEAD - UPDATE metabib.real_full_rec SET - id = NEW.id, - record = NEW.record, - tag = NEW.tag, - ind1 = NEW.ind1, - ind2 = NEW.ind2, - subfield = NEW.subfield, - value = NEW.value, - index_vector = NEW.index_vector - WHERE id = OLD.id; - -CREATE OR REPLACE RULE metabib_full_rec_delete_rule - AS ON DELETE TO metabib.full_rec - DO INSTEAD - DELETE FROM metabib.real_full_rec WHERE id = OLD.id; - --- ... and last, but not least, create a fore-shortened index on the value column. -CREATE INDEX metabib_full_rec_value_idx ON metabib.real_full_rec (substring(value,1,1024)); - - -CREATE OR REPLACE FUNCTION explode_array(anyarray) RETURNS SETOF anyelement AS $BODY$ - SELECT ($1)[s] FROM generate_series(1, array_upper($1, 1)) AS s; -$BODY$ -LANGUAGE 'sql' IMMUTABLE; - --- NOTE: current config.item_type should get sip2_media_type and magnetic_media columns - --- New table needed to handle circ modifiers inside the DB. Will still require --- central admin. The circ_modifier column on asset.copy will become an fkey to this table. -CREATE TABLE config.circ_modifier ( - code TEXT PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - description TEXT NOT NULL, - sip2_media_type TEXT NOT NULL, - magnetic_media BOOL NOT NULL DEFAULT TRUE -); - -UPDATE asset.copy SET circ_modifier = UPPER(circ_modifier) WHERE circ_modifier IS NOT NULL AND circ_modifier <> ''; -UPDATE asset.copy SET circ_modifier = NULL WHERE circ_modifier = ''; - -INSERT INTO config.circ_modifier (code, name, description, sip2_media_type ) - SELECT DISTINCT - UPPER(circ_modifier), - UPPER(circ_modifier), - LOWER(circ_modifier), - '001' - FROM asset.copy - WHERE circ_modifier IS NOT NULL; - --- add an fkey pointing to the new circ mod table -ALTER TABLE asset.copy ADD CONSTRAINT circ_mod_fkey FOREIGN KEY (circ_modifier) REFERENCES config.circ_modifier (code) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; - --- config table to hold the vr_format names -CREATE TABLE config.videorecording_format_map ( - code TEXT PRIMARY KEY, - value TEXT NOT NULL -); - -INSERT INTO config.videorecording_format_map VALUES ('a','Beta'); -INSERT INTO config.videorecording_format_map VALUES ('b','VHS'); -INSERT INTO config.videorecording_format_map VALUES ('c','U-matic'); -INSERT INTO config.videorecording_format_map VALUES ('d','EIAJ'); -INSERT INTO config.videorecording_format_map VALUES ('e','Type C'); -INSERT INTO config.videorecording_format_map VALUES ('f','Quadruplex'); -INSERT INTO config.videorecording_format_map VALUES ('g','Laserdisc'); -INSERT INTO config.videorecording_format_map VALUES ('h','CED'); -INSERT INTO config.videorecording_format_map VALUES ('i','Betacam'); -INSERT INTO config.videorecording_format_map VALUES ('j','Betacam SP'); -INSERT INTO config.videorecording_format_map VALUES ('k','Super-VHS'); -INSERT INTO config.videorecording_format_map VALUES ('m','M-II'); -INSERT INTO config.videorecording_format_map VALUES ('o','D-2'); -INSERT INTO config.videorecording_format_map VALUES ('p','8 mm.'); -INSERT INTO config.videorecording_format_map VALUES ('q','Hi-8 mm.'); -INSERT INTO config.videorecording_format_map VALUES ('u','Unknown'); -INSERT INTO config.videorecording_format_map VALUES ('v','DVD'); -INSERT INTO config.videorecording_format_map VALUES ('z','Other'); - -CREATE TABLE config.circ_matrix_matchpoint ( - id SERIAL PRIMARY KEY, - active BOOL NOT NULL DEFAULT TRUE, - org_unit INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best" - grp INT NOT NULL REFERENCES permission.grp_tree (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top applicable group from the group tree; will need descendents and prox functions for filtering - circ_modifier TEXT REFERENCES config.circ_modifier (code) DEFERRABLE INITIALLY DEFERRED, - marc_type TEXT REFERENCES config.item_type_map (code) DEFERRABLE INITIALLY DEFERRED, - marc_form TEXT REFERENCES config.item_form_map (code) DEFERRABLE INITIALLY DEFERRED, - marc_vr_format TEXT REFERENCES config.videorecording_format_map (code) DEFERRABLE INITIALLY DEFERRED, - ref_flag BOOL, - is_renewal BOOL, - usr_age_lower_bound INTERVAL, - usr_age_upper_bound INTERVAL, - CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal) -); -INSERT INTO config.circ_matrix_matchpoint (org_unit,grp) VALUES (1,1); - - --- Tests to determine if circ can occur for this item at this location for this patron -CREATE TABLE config.circ_matrix_test ( - matchpoint INT PRIMARY KEY NOT NULL REFERENCES config.circ_matrix_matchpoint (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - circulate BOOL NOT NULL DEFAULT TRUE, -- Hard "can't circ" flag requiring an override - max_items_out INT, -- Total current active circulations must be less than this, NULL means skip (always pass) - max_overdue INT, -- Total overdue active circulations must be less than this, NULL means skip (always pass) - max_fines NUMERIC(8,2), -- Total fines owed must be less than this, NULL means skip (always pass) - org_depth INT, -- Set to the top OU for the max-out applicability range - script_test TEXT -- filename or javascript source ?? -); - --- Tests for max items out by circ_modifier -CREATE TABLE config.circ_matrix_circ_mod_test ( - id SERIAL PRIMARY KEY, - matchpoint INT NOT NULL REFERENCES config.circ_matrix_matchpoint (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - items_out INT NOT NULL, -- Total current active circulations must be less than this, NULL means skip (always pass) - circ_mod TEXT NOT NULL REFERENCES config.circ_modifier (code) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED-- circ_modifier type that the max out applies to -); - - --- How to circ, assuming tests pass -CREATE TABLE config.circ_matrix_ruleset ( - matchpoint INT PRIMARY KEY REFERENCES config.circ_matrix_matchpoint (id) DEFERRABLE INITIALLY DEFERRED, - duration_rule INT NOT NULL REFERENCES config.rule_circ_duration (id) DEFERRABLE INITIALLY DEFERRED, - recurring_fine_rule INT NOT NULL REFERENCES config.rule_recuring_fine (id) DEFERRABLE INITIALLY DEFERRED, - max_fine_rule INT NOT NULL REFERENCES config.rule_max_fine (id) DEFERRABLE INITIALLY DEFERRED -); - -CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS INT AS $func$ -DECLARE - current_group permission.grp_tree%ROWTYPE; - user_object actor.usr%ROWTYPE; - item_object asset.copy%ROWTYPE; - rec_descriptor metabib.rec_descriptor%ROWTYPE; - current_mp config.circ_matrix_matchpoint%ROWTYPE; - matchpoint config.circ_matrix_matchpoint%ROWTYPE; -BEGIN - SELECT INTO user_object * FROM actor.usr WHERE id = match_user; - SELECT INTO item_object * FROM asset.copy WHERE id = match_item; - SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number; - SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile; - - LOOP - -- for each potential matchpoint for this ou and group ... - FOR current_mp IN - SELECT m.* - FROM config.circ_matrix_matchpoint m - JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id) - LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id) - WHERE m.grp = current_group.id AND m.active - ORDER BY CASE WHEN p.prox IS NULL THEN 999 ELSE p.prox END, - CASE WHEN m.is_renewal = renewal THEN 64 ELSE 0 END + - CASE WHEN m.circ_modifier IS NOT NULL THEN 32 ELSE 0 END + - CASE WHEN m.marc_type IS NOT NULL THEN 16 ELSE 0 END + - CASE WHEN m.marc_form IS NOT NULL THEN 8 ELSE 0 END + - CASE WHEN m.marc_vr_format IS NOT NULL THEN 4 ELSE 0 END + - CASE WHEN m.ref_flag IS NOT NULL THEN 2 ELSE 0 END + - CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 0.5 ELSE 0 END + - CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP - - IF current_mp.circ_modifier IS NOT NULL THEN - CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier; - END IF; - - IF current_mp.marc_type IS NOT NULL THEN - IF item_object.circ_as_type IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type; - ELSE - CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type; - END IF; - END IF; - - IF current_mp.marc_form IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form; - END IF; - - IF current_mp.marc_vr_format IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format; - END IF; - - IF current_mp.ref_flag IS NOT NULL THEN - CONTINUE WHEN current_mp.ref_flag <> item_object.ref; - END IF; - - IF current_mp.usr_age_lower_bound IS NOT NULL THEN - CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob); - END IF; - - IF current_mp.usr_age_upper_bound IS NOT NULL THEN - CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob); - END IF; - - - -- everything was undefined or matched - matchpoint = current_mp; - - EXIT WHEN matchpoint.id IS NOT NULL; - END LOOP; - - EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL; - - SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent; - END LOOP; - - RETURN matchpoint.id; -END; -$func$ LANGUAGE plpgsql; - - -CREATE TYPE action.matrix_test_result AS ( success BOOL, matchpoint INT, fail_part TEXT ); -CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.matrix_test_result AS $func$ -DECLARE - matchpoint_id INT; - user_object actor.usr%ROWTYPE; - item_object asset.copy%ROWTYPE; - item_status_object config.copy_status%ROWTYPE; - item_location_object asset.copy_location%ROWTYPE; - result action.matrix_test_result; - circ_test config.circ_matrix_test%ROWTYPE; - out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE; - items_out INT; - items_overdue INT; - overdue_orgs INT[]; - current_fines NUMERIC(8,2) := 0.0; - tmp_fines NUMERIC(8,2); - tmp_groc RECORD; - tmp_circ RECORD; - done BOOL := FALSE; -BEGIN - result.success := TRUE; - - -- Fail if the user is BARRED - SELECT INTO user_object * FROM actor.usr WHERE id = match_user; - - -- Fail if we couldn't find a user - IF user_object.id IS NULL THEN - result.fail_part := 'no_user'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - IF user_object.barred IS TRUE THEN - result.fail_part := 'actor.usr.barred'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - -- Fail if the item can't circulate - SELECT INTO item_object * FROM asset.copy WHERE id = match_item; - IF item_object.circulate IS FALSE THEN - result.fail_part := 'asset.copy.circulate'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - -- Fail if the item isn't in a circulateable status on a non-renewal - IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN - result.fail_part := 'asset.copy.status'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - ELSIF renewal AND item_object.status <> 1 THEN - result.fail_part := 'asset.copy.status'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - -- Fail if the item can't circulate because of the shelving location - SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location; - IF item_location_object.circulate IS FALSE THEN - result.fail_part := 'asset.copy_location.circulate'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - SELECT INTO matchpoint_id action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal); - result.matchpoint := matchpoint_id; - - SELECT INTO circ_test * from config.circ_matrix_test WHERE matchpoint = result.matchpoint; - - IF circ_test.org_depth IS NOT NULL THEN - SELECT INTO overdue_orgs ARRAY_ACCUM(id) FROM actor.org_unit_descendants( circ_ou, circ_test.org_depth ); - END IF; - - -- Fail if we couldn't find a set of tests - IF result.matchpoint IS NULL THEN - result.fail_part := 'no_matchpoint'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - -- Fail if the test is set to hard non-circulating - IF circ_test.circulate IS FALSE THEN - result.fail_part := 'config.circ_matrix_test.circulate'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - -- Fail if the user has too many items checked out - IF circ_test.max_items_out IS NOT NULL THEN - SELECT INTO items_out COUNT(*) - FROM action.circulation - WHERE usr = match_user - AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND circ_lib IN ( SELECT * FROM explode_array(overdue_orgs) ))) - AND checkin_time IS NULL - AND (stop_fines IN ('MAXFINES','LONGOVERDUE') OR stop_fines IS NULL); - IF items_out >= circ_test.max_items_out THEN - result.fail_part := 'config.circ_matrix_test.max_items_out'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END IF; - - -- Fail if the user has too many items with specific circ_modifiers checked out - FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = matchpoint_id LOOP - SELECT INTO items_out COUNT(*) - FROM action.circulation circ - JOIN asset.copy cp ON (cp.id = circ.target_copy) - WHERE circ.usr = match_user - AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND circ_lib IN ( SELECT * FROM explode_array(overdue_orgs) ))) - AND circ.checkin_time IS NULL - AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL) - AND cp.circ_modifier = out_by_circ_mod.circ_mod; - IF items_out >= out_by_circ_mod.items_out THEN - result.fail_part := 'config.circ_matrix_circ_mod_test'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END LOOP; - - -- Fail if the user has too many overdue items - IF circ_test.max_overdue IS NOT NULL THEN - SELECT INTO items_overdue COUNT(*) - FROM action.circulation - WHERE usr = match_user - AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND circ_lib IN ( SELECT * FROM explode_array(overdue_orgs) ))) - AND checkin_time IS NULL - AND due_date < NOW() - AND (stop_fines IN ('MAXFINES','LONGOVERDUE') OR stop_fines IS NULL); - IF items_overdue >= circ_test.max_overdue THEN - result.fail_part := 'config.circ_matrix_test.max_overdue'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END IF; - - -- Fail if the user has a high fine balance - IF circ_test.max_fines IS NOT NULL THEN - FOR tmp_groc IN SELECT * FROM money.grocery WHERE usr = match_usr AND xact_finish IS NULL AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND billing_location IN ( SELECT * FROM explode_array(overdue_orgs) ))) LOOP - SELECT INTO tmp_fines SUM( amount ) FROM money.billing WHERE xact = tmp_groc.id AND NOT voided; - current_fines = current_fines + COALESCE(tmp_fines, 0.0); - SELECT INTO tmp_fines SUM( amount ) FROM money.payment WHERE xact = tmp_groc.id AND NOT voided; - current_fines = current_fines - COALESCE(tmp_fines, 0.0); - END LOOP; - - FOR tmp_circ IN SELECT * FROM action.circulation WHERE usr = match_usr AND xact_finish IS NULL AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND circ_lib IN ( SELECT * FROM explode_array(overdue_orgs) ))) LOOP - SELECT INTO tmp_fines SUM( amount ) FROM money.billing WHERE xact = tmp_circ.id AND NOT voided; - current_fines = current_fines + COALESCE(tmp_fines, 0.0); - SELECT INTO tmp_fines SUM( amount ) FROM money.payment WHERE xact = tmp_circ.id AND NOT voided; - current_fines = current_fines - COALESCE(tmp_fines, 0.0); - END LOOP; - - IF current_fines >= circ_test.max_fines THEN - result.fail_part := 'config.circ_matrix_test.max_fines'; - result.success := FALSE; - RETURN NEXT result; - done := TRUE; - END IF; - END IF; - - -- If we passed everything, return the successful matchpoint id - IF NOT done THEN - RETURN NEXT result; - END IF; - - RETURN; -END; -$func$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION action.item_user_circ_test( INT, BIGINT, INT ) RETURNS SETOF action.matrix_test_result AS $func$ - SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE ); -$func$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION action.item_user_renew_test( INT, BIGINT, INT ) RETURNS SETOF action.matrix_test_result AS $func$ - SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE ); -$func$ LANGUAGE SQL; - - -CREATE TABLE config.hold_matrix_matchpoint ( - id SERIAL PRIMARY KEY, - active BOOL NOT NULL DEFAULT TRUE, - user_home_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best" - request_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best" - pickup_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best" - item_owning_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best" - item_circ_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best" - usr_grp INT REFERENCES permission.grp_tree (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top applicable group from the group tree; will need descendents and prox functions for filtering - requestor_grp INT NOT NULL REFERENCES permission.grp_tree (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top applicable group from the group tree; will need descendents and prox functions for filtering - circ_modifier TEXT REFERENCES config.circ_modifier (code) DEFERRABLE INITIALLY DEFERRED, - marc_type TEXT REFERENCES config.item_type_map (code) DEFERRABLE INITIALLY DEFERRED, - marc_form TEXT REFERENCES config.item_form_map (code) DEFERRABLE INITIALLY DEFERRED, - marc_vr_format TEXT REFERENCES config.videorecording_format_map (code) DEFERRABLE INITIALLY DEFERRED, - ref_flag BOOL, - CONSTRAINT hous_once_per_grp_loc_mod_marc UNIQUE (user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, requestor_grp, usr_grp, circ_modifier, marc_type, marc_form, marc_vr_format) -); -INSERT INTO config.hold_matrix_matchpoint (requestor_grp) VALUES (1); - - --- Tests to determine if hold against a specific copy is possible for a user at (and from) a location -CREATE TABLE config.hold_matrix_test ( - matchpoint INT PRIMARY KEY REFERENCES config.hold_matrix_matchpoint (id) DEFERRABLE INITIALLY DEFERRED, - holdable BOOL NOT NULL DEFAULT TRUE, -- Hard "can't hold" flag requiring an override - distance_is_from_owner BOOL NOT NULL DEFAULT FALSE, -- How to calculate transit_range. True means owning lib, false means copy circ lib - transit_range INT REFERENCES actor.org_unit_type (id) DEFERRABLE INITIALLY DEFERRED, -- Can circ inside range of cn.owner/cp.circ_lib at depth of the org_unit_type specified here - max_holds INT, -- Total hold requests must be less than this, NULL means skip (always pass) - include_frozen_holds BOOL NOT NULL DEFAULT TRUE, -- Include frozen hold requests in the count for max_holds test - stop_blocked_user BOOL NOT NULL DEFAULT FALSE, -- Stop users who cannot check out items from placing holds - age_hold_protect_rule INT REFERENCES config.rule_age_hold_protect (id) DEFERRABLE INITIALLY DEFERRED -- still not sure we want to move this off the copy -); - -CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS INT AS $func$ -DECLARE - current_requestor_group permission.grp_tree%ROWTYPE; - root_ou actor.org_unit%ROWTYPE; - requestor_object actor.usr%ROWTYPE; - user_object actor.usr%ROWTYPE; - item_object asset.copy%ROWTYPE; - item_cn_object asset.call_number%ROWTYPE; - rec_descriptor metabib.rec_descriptor%ROWTYPE; - current_mp_weight FLOAT; - matchpoint_weight FLOAT; - tmp_weight FLOAT; - current_mp config.hold_matrix_matchpoint%ROWTYPE; - matchpoint config.hold_matrix_matchpoint%ROWTYPE; -BEGIN - SELECT INTO root_ou * FROM actor.org_unit WHERE parent_ou IS NULL; - SELECT INTO user_object * FROM actor.usr WHERE id = match_user; - SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor; - SELECT INTO item_object * FROM asset.copy WHERE id = match_item; - SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number; - SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record; - SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile; - - LOOP - -- for each potential matchpoint for this ou and group ... - FOR current_mp IN - SELECT m.* - FROM config.hold_matrix_matchpoint m - WHERE m.requestor_grp = current_requestor_group.id AND m.active - ORDER BY CASE WHEN m.circ_modifier IS NOT NULL THEN 16 ELSE 0 END + - CASE WHEN m.marc_type IS NOT NULL THEN 8 ELSE 0 END + - CASE WHEN m.marc_form IS NOT NULL THEN 4 ELSE 0 END + - CASE WHEN m.marc_vr_format IS NOT NULL THEN 2 ELSE 0 END + - CASE WHEN m.ref_flag IS NOT NULL THEN 1 ELSE 0 END DESC LOOP - - current_mp_weight := 5.0; - - IF current_mp.circ_modifier IS NOT NULL THEN - CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier; - END IF; - - IF current_mp.marc_type IS NOT NULL THEN - IF item_object.circ_as_type IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type; - ELSE - CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type; - END IF; - END IF; - - IF current_mp.marc_form IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form; - END IF; - - IF current_mp.marc_vr_format IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format; - END IF; - - IF current_mp.ref_flag IS NOT NULL THEN - CONTINUE WHEN current_mp.ref_flag <> item_object.ref; - END IF; - - - -- caclulate the rule match weight - IF current_mp.item_owning_ou IS NOT NULL AND current_mp.item_owning_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - IF current_mp.item_circ_ou IS NOT NULL AND current_mp.item_circ_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - IF current_mp.pickup_ou IS NOT NULL AND current_mp.pickup_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - IF current_mp.request_ou IS NOT NULL AND current_mp.request_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - IF current_mp.user_home_ou IS NOT NULL AND current_mp.user_home_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - -- set the matchpoint if we found the best one - IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN - matchpoint = current_mp; - matchpoint_weight = current_mp_weight; - END IF; - - END LOOP; - - EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL; - - SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent; - END LOOP; - - RETURN matchpoint.id; -END; -$func$ LANGUAGE plpgsql; - - -CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS SETOF action.matrix_test_result AS $func$ -DECLARE - matchpoint_id INT; - user_object actor.usr%ROWTYPE; - age_protect_object config.rule_age_hold_protect%ROWTYPE; - transit_range_ou_type actor.org_unit_type%ROWTYPE; - transit_source actor.org_unit%ROWTYPE; - item_object asset.copy%ROWTYPE; - result action.matrix_test_result; - hold_test config.hold_matrix_test%ROWTYPE; - hold_count INT; - hold_transit_prox INT; - frozen_hold_count INT; - patron_penalties INT; - done BOOL := FALSE; -BEGIN - SELECT INTO user_object * FROM actor.usr WHERE id = match_user; - - -- Fail if we couldn't find a user - IF user_object.id IS NULL THEN - result.fail_part := 'no_user'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - -- Fail if user is barred - IF user_object.barred IS TRUE THEN - result.fail_part := 'actor.usr.barred'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - SELECT INTO item_object * FROM asset.copy WHERE id = match_item; - - -- Fail if we couldn't find a copy - IF item_object.id IS NULL THEN - result.fail_part := 'no_item'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor); - - -- Fail if we couldn't find any matchpoint (requires a default) - IF matchpoint_id IS NULL THEN - result.fail_part := 'no_matchpoint'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - SELECT INTO hold_test * FROM config.hold_matrix_test WHERE matchpoint = matchpoint_id; - - result.matchpoint := matchpoint_id; - result.success := TRUE; - - IF hold_test.holdable IS FALSE THEN - result.fail_part := 'config.hold_matrix_test.holdable'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - IF hold_test.transit_range IS NOT NULL THEN - SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range; - IF hold_test.distance_is_from_owner THEN - SELECT INTO transit_source ou.* FROM actor.org_unit ou JOIN asset.call_number cn ON (cn.owning_lib = ou.id) WHERE cn.id = item_object.call_number; - ELSE - SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib; - END IF; - - PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou; - - IF NOT FOUND THEN - result.fail_part := 'transit_range'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END IF; - - IF hold_test.stop_blocked_user IS TRUE THEN - SELECT INTO patron_penalties COUNT(*) - FROM actor.usr_standing_penalty - WHERE usr = match_user; - - IF items_out > 0 THEN - result.fail_part := 'config.hold_matrix_test.stop_blocked_user'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END IF; - - IF hold_test.max_holds IS NOT NULL THEN - SELECT INTO hold_count COUNT(*) - FROM action.hold_request - WHERE usr = match_user - AND fulfillment_time IS NULL - AND cancel_time IS NULL - AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END; - - IF items_out >= hold_test.max_holds THEN - result.fail_part := 'config.hold_matrix_test.max_holds'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END IF; - - IF item_object.age_protect IS NOT NULL THEN - SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect; - - IF item_object.create_date + age_protect_object.age > NOW() THEN - IF hold_test.distance_is_from_owner THEN - SELECT INTO hold_transit_prox prox FROM actor.org_unit_prox WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou; - ELSE - SELECT INTO hold_transit_prox prox FROM actor.org_unit_prox WHERE from_org = item_object.circ_lib AND to_org = pickup_ou; - END IF; - - IF hold_transit_prox > age_protect_object.prox THEN - result.fail_part := 'config.rule_age_hold_protect.prox'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END IF; - END IF; - - IF NOT done THEN - RETURN NEXT result; - END IF; - - RETURN; -END; -$func$ LANGUAGE plpgsql; - -CREATE SCHEMA vandelay; - -CREATE TABLE vandelay.queue ( - id BIGSERIAL PRIMARY KEY, - owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - name TEXT NOT NULL, - complete BOOL NOT NULL DEFAULT FALSE, - queue_type TEXT NOT NULL DEFAULT 'bib' CHECK (queue_type IN ('bib','authority')), - CONSTRAINT vand_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type) -); - -CREATE TABLE vandelay.queued_record ( - id BIGSERIAL PRIMARY KEY, - create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - import_time TIMESTAMP WITH TIME ZONE, - purpose TEXT NOT NULL DEFAULT 'import' CHECK (purpose IN ('import','overlay')), - marc TEXT NOT NULL -); - - - -/* Bib stuff at the top */ ----------------------------------------------------- - -CREATE TABLE vandelay.bib_attr_definition ( - id SERIAL PRIMARY KEY, - code TEXT UNIQUE NOT NULL, - description TEXT, - xpath TEXT NOT NULL, - remove TEXT NOT NULL DEFAULT '', - ident BOOL NOT NULL DEFAULT FALSE -); - --- Each TEXT field (other than 'name') should hold an XPath predicate for pulling the data needed --- DROP TABLE vandelay.import_item_attr_definition CASCADE; -CREATE TABLE vandelay.import_item_attr_definition ( - id BIGSERIAL PRIMARY KEY, - owner INT NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - name TEXT NOT NULL, - tag TEXT NOT NULL, - keep BOOL NOT NULL DEFAULT FALSE, - owning_lib TEXT, - circ_lib TEXT, - call_number TEXT, - copy_number TEXT, - status TEXT, - location TEXT, - circulate TEXT, - deposit TEXT, - deposit_amount TEXT, - ref TEXT, - holdable TEXT, - price TEXT, - barcode TEXT, - circ_modifier TEXT, - circ_as_type TEXT, - alert_message TEXT, - opac_visible TEXT, - pub_note_title TEXT, - pub_note TEXT, - priv_note_title TEXT, - priv_note TEXT, - CONSTRAINT vand_import_item_attr_def_idx UNIQUE (owner,name) -); - -CREATE TABLE vandelay.bib_queue ( - queue_type TEXT NOT NULL DEFAULT 'bib' CHECK (queue_type = 'bib'), - item_attr_def BIGINT REFERENCES vandelay.import_item_attr_definition (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - CONSTRAINT vand_bib_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type) -) INHERITS (vandelay.queue); -ALTER TABLE vandelay.bib_queue ADD PRIMARY KEY (id); - -CREATE TABLE vandelay.queued_bib_record ( - queue INT NOT NULL REFERENCES vandelay.bib_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - bib_source INT REFERENCES config.bib_source (id) DEFERRABLE INITIALLY DEFERRED, - imported_as INT REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED -) INHERITS (vandelay.queued_record); -ALTER TABLE vandelay.queued_bib_record ADD PRIMARY KEY (id); - -CREATE TABLE vandelay.queued_bib_record_attr ( - id BIGSERIAL PRIMARY KEY, - record BIGINT NOT NULL REFERENCES vandelay.queued_bib_record (id) DEFERRABLE INITIALLY DEFERRED, - field INT NOT NULL REFERENCES vandelay.bib_attr_definition (id) DEFERRABLE INITIALLY DEFERRED, - attr_value TEXT NOT NULL -); - -CREATE TABLE vandelay.bib_match ( - id BIGSERIAL PRIMARY KEY, - field_type TEXT NOT NULL CHECK (field_type in ('isbn','tcn_value','id')), - matched_attr INT REFERENCES vandelay.queued_bib_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - queued_record BIGINT REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - eg_record BIGINT REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED -); - --- DROP TABLE vandelay.import_item CASCADE; -CREATE TABLE vandelay.import_item ( - id BIGSERIAL PRIMARY KEY, - record BIGINT NOT NULL REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - definition BIGINT NOT NULL REFERENCES vandelay.import_item_attr_definition (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - owning_lib INT, - circ_lib INT, - call_number TEXT, - copy_number INT, - status INT, - location INT, - circulate BOOL, - deposit BOOL, - deposit_amount NUMERIC(8,2), - ref BOOL, - holdable BOOL, - price NUMERIC(8,2), - barcode TEXT, - circ_modifier TEXT, - circ_as_type TEXT, - alert_message TEXT, - pub_note TEXT, - priv_note TEXT, - opac_visible BOOL -); - -CREATE TABLE vandelay.import_bib_trash_fields ( - id BIGSERIAL PRIMARY KEY, - owner INT NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - field TEXT NOT NULL, - CONSTRAINT vand_import_bib_trash_fields_idx UNIQUE (owner,field) -); - -CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$ - - use MARC::Record; - use MARC::File::XML (BinaryEncoding => 'UTF-8'); - - my $xml = shift; - my $field_spec = shift; - - my $r = MARC::Record->new_from_xml( $xml ); - $r->delete_field( $_ ) for ( $r->field( $field_spec ) ); - - $xml = $r->as_xml_record; - $xml =~ s/^<\?.+?\?>$//mo; - $xml =~ s/\n//sgo; - $xml =~ s/>\s+ '') THEN - INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value); - END IF; - - END LOOP; - - RETURN NULL; -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$ -DECLARE - queue_rec RECORD; - item_rule RECORD; - item_data vandelay.import_item%ROWTYPE; -BEGIN - - SELECT * INTO queue_rec FROM vandelay.bib_queue WHERE id = NEW.queue; - - FOR item_rule IN SELECT r.* FROM actor.org_unit_ancestors( queue_rec.owner ) o JOIN vandelay.import_item_attr_definition r ON ( r.owner = o.id ) LOOP - FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, item_rule.id::BIGINT ) LOOP - INSERT INTO vandelay.import_item ( - record, - definition, - owning_lib, - circ_lib, - call_number, - copy_number, - status, - location, - circulate, - deposit, - deposit_amount, - ref, - holdable, - price, - barcode, - circ_modifier, - circ_as_type, - alert_message, - pub_note, - priv_note, - opac_visible - ) VALUES ( - NEW.id, - item_data.definition, - item_data.owning_lib, - item_data.circ_lib, - item_data.call_number, - item_data.copy_number, - item_data.status, - item_data.location, - item_data.circulate, - item_data.deposit, - item_data.deposit_amount, - item_data.ref, - item_data.holdable, - item_data.price, - item_data.barcode, - item_data.circ_modifier, - item_data.circ_as_type, - item_data.alert_message, - item_data.pub_note, - item_data.priv_note, - item_data.opac_visible - ); - END LOOP; - END LOOP; - - RETURN NULL; -END; -$func$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$ -DECLARE - attr RECORD; - eg_rec RECORD; -BEGIN - FOR attr IN SELECT a.* FROM vandelay.queued_bib_record_attr a JOIN vandelay.bib_attr_definition d ON (d.id = a.field) WHERE record = NEW.id AND d.ident IS TRUE LOOP - - -- All numbers? check for an id match - IF (attr.attr_value ~ $r$^\d+$$r$) THEN - FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP - INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id); - END LOOP; - END IF; - - -- Looks like an ISBN? check for an isbn match - IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN - FOR eg_rec IN EXECUTE $$SELECT * FROM metabib.full_rec fr WHERE fr.value LIKE LOWER('$$ || attr.attr_value || $$%') AND fr.tag = '020' AND fr.subfield = 'a'$$ LOOP - PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE; - IF FOUND THEN - INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record); - END IF; - END LOOP; - - -- subcheck for isbn-as-tcn - FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP - INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id); - END LOOP; - END IF; - - -- check for an OCLC tcn_value match - IF (attr.attr_value ~ $r$^o\d+$$r$) THEN - FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP - INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id); - END LOOP; - END IF; - - -- check for a direct tcn_value match - FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP - INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id); - END LOOP; - - END LOOP; - - RETURN NULL; -END; -$func$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$ -BEGIN - DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id; - DELETE FROM vandelay.import_item WHERE record = OLD.id; - - IF TG_OP = 'UPDATE' THEN - RETURN NEW; - END IF; - RETURN OLD; -END; -$$ LANGUAGE PLPGSQL; - -CREATE TRIGGER cleanup_bib_trigger - BEFORE UPDATE OR DELETE ON vandelay.queued_bib_record - FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_bib_marc(); - -CREATE TRIGGER ingest_bib_trigger - AFTER INSERT OR UPDATE ON vandelay.queued_bib_record - FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_marc(); - -CREATE TRIGGER ingest_item_trigger - AFTER INSERT OR UPDATE ON vandelay.queued_bib_record - FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_items(); - -CREATE TRIGGER zz_match_bibs_trigger - AFTER INSERT OR UPDATE ON vandelay.queued_bib_record - FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record(); - - -/* Authority stuff down here */ ---------------------------------------- -CREATE TABLE vandelay.authority_attr_definition ( - id SERIAL PRIMARY KEY, - code TEXT UNIQUE NOT NULL, - description TEXT, - xpath TEXT NOT NULL, - remove TEXT NOT NULL DEFAULT '', - ident BOOL NOT NULL DEFAULT FALSE -); - -CREATE TABLE vandelay.authority_queue ( - queue_type TEXT NOT NULL DEFAULT 'authority' CHECK (queue_type = 'authority'), - CONSTRAINT vand_authority_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type) -) INHERITS (vandelay.queue); -ALTER TABLE vandelay.authority_queue ADD PRIMARY KEY (id); - -CREATE TABLE vandelay.queued_authority_record ( - queue INT NOT NULL REFERENCES vandelay.authority_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - imported_as INT REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED -) INHERITS (vandelay.queued_record); -ALTER TABLE vandelay.queued_authority_record ADD PRIMARY KEY (id); - -CREATE TABLE vandelay.queued_authority_record_attr ( - id BIGSERIAL PRIMARY KEY, - record BIGINT NOT NULL REFERENCES vandelay.queued_authority_record (id) DEFERRABLE INITIALLY DEFERRED, - field INT NOT NULL REFERENCES vandelay.authority_attr_definition (id) DEFERRABLE INITIALLY DEFERRED, - attr_value TEXT NOT NULL -); - -CREATE TABLE vandelay.authority_match ( - id BIGSERIAL PRIMARY KEY, - matched_attr INT REFERENCES vandelay.queued_authority_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - queued_record BIGINT REFERENCES vandelay.queued_authority_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - eg_record BIGINT REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED -); - -CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$ -DECLARE - value TEXT; - atype TEXT; - adef RECORD; -BEGIN - FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP - - SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id; - IF (value IS NOT NULL AND value <> '') THEN - INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value); - END IF; - - END LOOP; - - RETURN NULL; -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$ -BEGIN - DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id; - IF TG_OP = 'UPDATE' THEN - RETURN NEW; - END IF; - RETURN OLD; -END; -$$ LANGUAGE PLPGSQL; - -CREATE TRIGGER cleanup_authority_trigger - BEFORE UPDATE OR DELETE ON vandelay.queued_authority_record - FOR EACH ROW EXECUTE PROCEDURE vandelay.cleanup_authority_marc(); - -CREATE TRIGGER ingest_authority_trigger - AFTER INSERT OR UPDATE ON vandelay.queued_authority_record - FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_authority_marc(); - -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (1, 'title', oils_i18n_gettext(1, 'Title of work', 'vqbrad', 'description'),'//*[@tag="245"]/*[contains("abcmnopr",@code)]'); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (2, 'author', oils_i18n_gettext(2, 'Author of work', 'vqbrad', 'description'),'//*[@tag="100" or @tag="110" or @tag="113"]/*[contains("ad",@code)]'); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (3, 'language', oils_i18n_gettext(3, 'Language of work', 'vqbrad', 'description'),'//*[@tag="240"]/*[@code="l"][1]'); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (4, 'pagination', oils_i18n_gettext(4, 'Pagination', 'vqbrad', 'description'),'//*[@tag="300"]/*[@code="a"][1]'); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident, remove ) VALUES (5, 'isbn',oils_i18n_gettext(5, 'ISBN', 'vqbrad', 'description'),'//*[@tag="020"]/*[@code="a"]', TRUE, $r$(?:-|\s.+$)$r$); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident, remove ) VALUES (6, 'issn',oils_i18n_gettext(6, 'ISSN', 'vqbrad', 'description'),'//*[@tag="022"]/*[@code="a"]', TRUE, $r$(?:-|\s.+$)$r$); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (7, 'price',oils_i18n_gettext(7, 'Price', 'vqbrad', 'description'),'//*[@tag="020" or @tag="022"]/*[@code="c"][1]'); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (8, 'rec_identifier',oils_i18n_gettext(8, 'Accession Number', 'vqbrad', 'description'),'//*[@tag="001"]', TRUE); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (9, 'eg_tcn',oils_i18n_gettext(9, 'TCN Value', 'vqbrad', 'description'),'//*[@tag="901"]/*[@code="a"]', TRUE); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (10, 'eg_tcn_source',oils_i18n_gettext(10, 'TCN Source', 'vqbrad', 'description'),'//*[@tag="901"]/*[@code="b"]', TRUE); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (11, 'eg_identifier',oils_i18n_gettext(11, 'Internal ID', 'vqbrad', 'description'),'//*[@tag="901"]/*[@code="c"]', TRUE); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (12, 'publisher',oils_i18n_gettext(12, 'Publisher', 'vqbrad', 'description'),'//*[@tag="260"]/*[@code="b"][1]'); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, remove ) VALUES (13, 'pubdate',oils_i18n_gettext(13, 'Publication Date', 'vqbrad', 'description'),'//*[@tag="260"]/*[@code="c"][1]',$r$\D$r$); -INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (14, 'edition',oils_i18n_gettext(14, 'Edition', 'vqbrad', 'description'),'//*[@tag="250"]/*[@code="a"][1]'); - -INSERT INTO vandelay.import_item_attr_definition ( - owner, name, tag, owning_lib, circ_lib, location, - call_number, circ_modifier, barcode, price, copy_number, - circulate, ref, holdable, opac_visible, status -) VALUES ( - 1, - 'Evergreen 852 export format', - '852', - '[@code = "b"][1]', - '[@code = "b"][2]', - 'c', - 'j', - 'g', - 'p', - 'y', - 't', - '[@code = "x" and text() = "circulating"]', - '[@code = "x" and text() = "reference"]', - '[@code = "x" and text() = "holdable"]', - '[@code = "x" and text() = "visible"]', - 'z' -); - -INSERT INTO vandelay.import_item_attr_definition ( - owner, - name, - tag, - owning_lib, - location, - call_number, - circ_modifier, - barcode, - price, - status -) VALUES ( - 1, - 'Unicorn Import format -- 999', - '999', - 'm', - 'l', - 'a', - 't', - 'i', - 'p', - 'k' -); - -CREATE OR REPLACE VIEW extend_reporter.global_bibs_by_holding_update AS - SELECT DISTINCT ON (id) id, holding_update, update_type - FROM (SELECT b.id, - LAST(cp.create_date) AS holding_update, - 'add' AS update_type - FROM biblio.record_entry b - JOIN asset.call_number cn ON (cn.record = b.id) - JOIN asset.copy cp ON (cp.call_number = cn.id) - WHERE NOT cp.deleted - AND b.id > 0 - GROUP BY b.id - UNION - SELECT b.id, - LAST(cp.edit_date) AS holding_update, - 'delete' AS update_type - FROM biblio.record_entry b - JOIN asset.call_number cn ON (cn.record = b.id) - JOIN asset.copy cp ON (cp.call_number = cn.id) - WHERE cp.deleted - AND b.id > 0 - GROUP BY b.id)x - ORDER BY id, holding_update; - -INSERT INTO vandelay.authority_attr_definition ( code, description, xpath, ident ) VALUES ('rec_identifier','Identifier','//*[@tag="001"]', TRUE); - -UPDATE config.xml_transform SET xslt=$$ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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:variable name="str"> - <xsl:for-each select="marc:subfield"> - <xsl:if test="(contains('adfklmor',@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 - - - 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 - - - - - - - - <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:value-of> - </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 - - - - - - - - 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 - - - - - - - - - - - - - - - - - - - <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> - <xsl:call-template name="part"></xsl:call-template> - - - - - - - - - - - - - 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .:,;/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$$ WHERE name = 'mods32'; - -COMMIT; - -INSERT INTO config.circ_matrix_ruleset (matchpoint,duration_rule,recurring_fine_rule,max_fine_rule) - SELECT 1, d.id, f.id, m.id - FROM config.rule_circ_duration d - JOIN config.rule_recuring_fine f ON (f.name = d.name) - JOIN config.rule_max_fine m ON (f.name = m.name) - WHERE m.name = 'default'; - diff --git a/Open-ILS/src/sql/Pg/1.4-shadow_full_rec-upgrade-db.sql b/Open-ILS/src/sql/Pg/1.4-shadow_full_rec-upgrade-db.sql deleted file mode 100644 index bf449345e2..0000000000 --- a/Open-ILS/src/sql/Pg/1.4-shadow_full_rec-upgrade-db.sql +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2008 Equinox Software, Inc. - * Mike Rylander - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - - -BEGIN; - --- To avoid any updates while we're doin' our thing... -SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; - --- This index, right here, is the reason for this change. -DROP INDEX metabib.metabib_full_rec_value_idx; - --- So, on to it. --- Move the table out of the way ... -ALTER TABLE metabib.full_rec RENAME TO real_full_rec; - --- ... and let the trigger management functions know about the change ... -CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$ - DROP TRIGGER zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$ - - TRUNCATE TABLE reporter.materialized_simple_record; - - INSERT INTO reporter.materialized_simple_record - (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn) - SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record; - - CREATE TRIGGER zzz_update_materialized_simple_record_tgr - AFTER INSERT OR UPDATE OR DELETE ON metabib.real_full_rec - FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_sync(); - -$$ LANGUAGE SQL; - --- ... replace the table with a suitable view, which applies the index contstraint we'll use ... -CREATE OR REPLACE VIEW metabib.full_rec AS - SELECT id, - record, - tag, - ind1, - ind2, - subfield, - SUBSTRING(value,1,1024) AS value, - index_vector - FROM metabib.real_full_rec; - --- ... now some rules to transform DML against the view into DML against the underlying table ... -CREATE OR REPLACE RULE metabib_full_rec_insert_rule - AS ON INSERT TO metabib.full_rec - DO INSTEAD - INSERT INTO metabib.real_full_rec VALUES ( - COALESCE(NEW.id, NEXTVAL('metabib.full_rec_id_seq'::REGCLASS)), - NEW.record, - NEW.tag, - NEW.ind1, - NEW.ind2, - NEW.subfield, - NEW.value, - NEW.index_vector - ); - -CREATE OR REPLACE RULE metabib_full_rec_update_rule - AS ON UPDATE TO metabib.full_rec - DO INSTEAD - UPDATE metabib.real_full_rec SET - id = NEW.id, - record = NEW.record, - tag = NEW.tag, - ind1 = NEW.ind1, - ind2 = NEW.ind2, - subfield = NEW.subfield, - value = NEW.value, - index_vector = NEW.index_vector - WHERE id = OLD.id; - -CREATE OR REPLACE RULE metabib_full_rec_delete_rule - AS ON DELETE TO metabib.full_rec - DO INSTEAD - DELETE FROM metabib.real_full_rec WHERE id = OLD.id; - --- ... and last, but not least, create a fore-shortened index on the value column. -CREATE INDEX metabib_full_rec_value_idx ON metabib.real_full_rec (substring(value,1,1024)); - --- Wheeee... -COMMIT; - diff --git a/Open-ILS/src/sql/Pg/1.4.0.5-1.6.0.0-upgrade-db.sql b/Open-ILS/src/sql/Pg/1.4.0.5-1.6.0.0-upgrade-db.sql deleted file mode 100644 index 2cf99050e5..0000000000 --- a/Open-ILS/src/sql/Pg/1.4.0.5-1.6.0.0-upgrade-db.sql +++ /dev/null @@ -1,7506 +0,0 @@ -/* - * Copyright (C) 2009 Equinox Software, Inc. - * Mike Rylander - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - - - -DROP SCHEMA IF EXISTS acq CASCADE; -DROP SCHEMA IF EXISTS serial CASCADE; - -BEGIN; - -INSERT INTO config.upgrade_log (version) VALUES ('1.6.0.0'); - -CREATE TABLE config.standing_penalty ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL UNIQUE, - label TEXT NOT NULL, - block_list TEXT -); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (1,'PATRON_EXCEEDS_FINES','Patron exceeds fine threshold','CIRC|HOLD|RENEW'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (2,'PATRON_EXCEEDS_OVERDUE_COUNT','Patron exceeds max overdue item threshold','CIRC|HOLD|RENEW'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (3,'PATRON_EXCEEDS_CHECKOUT_COUNT','Patron exceeds max checked out item threshold','CIRC'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (4,'PATRON_EXCEEDS_COLLECTIONS_WARNING','Patron exceeds pre-collections warning fine threshold','CIRC|HOLD|RENEW'); - -INSERT INTO config.standing_penalty (id,name,label) VALUES (20,'ALERT_NOTE','Alerting Note, no blocks'); -INSERT INTO config.standing_penalty (id,name,label) VALUES (21,'SILENT_NOTE','Note, no blocks'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (22,'STAFF_C','Alerting block on Circ','CIRC'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (23,'STAFF_CH','Alerting block on Circ and Hold','CIRC|HOLD'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (24,'STAFF_CR','Alerting block on Circ and Renew','CIRC|RENEW'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (25,'STAFF_CHR','Alerting block on Circ, Hold and Renew','CIRC|HOLD|RENEW'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (26,'STAFF_HR','Alerting block on Hold and Renew','HOLD|RENEW'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (27,'STAFF_H','Alerting block on Hold','HOLD'); -INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (28,'STAFF_R','Alerting block on Renew','RENEW'); - -SELECT SETVAL('config.standing_penalty_id_seq', 100); - -CREATE TABLE config.billing_type ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - owner INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - default_price NUMERIC(6,2), - CONSTRAINT billing_type_once_per_lib UNIQUE (name, owner) -); - -INSERT INTO config.billing_type (id, name, owner) VALUES ( 1, 'Overdue Materials', 1); -INSERT INTO config.billing_type (id, name, owner) VALUES ( 2, 'Long Overdue Collection Fee', 1); -INSERT INTO config.billing_type (id, name, owner) VALUES ( 3, 'Lost Materials', 1); -INSERT INTO config.billing_type (id, name, owner) VALUES ( 4, 'Lost Materials Processing Fee', 1); -INSERT INTO config.billing_type (id, name, owner) VALUES ( 5, 'System: Deposit', 1); -INSERT INTO config.billing_type (id, name, owner) VALUES ( 6, 'System: Rental', 1); -INSERT INTO config.billing_type (id, name, owner) VALUES ( 7, 'Damaged Item', 1); -INSERT INTO config.billing_type (id, name, owner) VALUES ( 8, 'Damaged Item Processing Fee', 1); -INSERT INTO config.billing_type (id, name, owner) VALUES ( 9, 'Notification Fee', 1); - -SELECT SETVAL('config.billing_type_id_seq'::TEXT, 100); - -ALTER TABLE actor.usr ADD COLUMN alias TEXT; -ALTER TABLE actor.usr ADD COLUMN juvenile BOOL; -ALTER TABLE auditor.actor_usr_history ADD COLUMN alias TEXT; -ALTER TABLE auditor.actor_usr_history ADD COLUMN juvenile BOOL; - -ALTER TABLE actor.usr ALTER COLUMN juvenile SET DEFAULT FALSE; -UPDATE actor.usr SET juvenile=FALSE; - - -DROP TABLE actor.usr_standing_penalty; -CREATE TABLE actor.usr_standing_penalty ( - id SERIAL PRIMARY KEY, - org_unit INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - usr INT NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - standing_penalty INT NOT NULL REFERENCES config.standing_penalty (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - staff INT REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - set_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - stop_date TIMESTAMP WITH TIME ZONE, - note TEXT -); -CREATE INDEX actor_usr_standing_penalty_usr_idx ON actor.usr_standing_penalty (usr); - - -ALTER TABLE actor.usr_address ADD COLUMN pending BOOL; -ALTER TABLE actor.usr_address ADD COLUMN replaces INT; -ALTER TABLE auditor.actor_usr_address_history ADD COLUMN pending BOOL; -ALTER TABLE auditor.actor_usr_address_history ADD COLUMN replaces INT; - -ALTER TABLE actor.usr_address ALTER COLUMN pending SET DEFAULT FALSE; -UPDATE actor.usr_address SET pending = FALSE; - - -CREATE TABLE permission.grp_penalty_threshold ( - id SERIAL PRIMARY KEY, - grp INT NOT NULL REFERENCES permission.grp_tree (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - org_unit INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - penalty INT NOT NULL REFERENCES config.standing_penalty (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - threshold NUMERIC(8,2) NOT NULL, - CONSTRAINT penalty_grp_once UNIQUE (grp,penalty) -); - -INSERT INTO permission.grp_penalty_threshold (grp,org_unit,penalty,threshold) VALUES (1,1,1,10.0); -INSERT INTO permission.grp_penalty_threshold (grp,org_unit,penalty,threshold) VALUES (1,1,2,10.0); -INSERT INTO permission.grp_penalty_threshold (grp,org_unit,penalty,threshold) VALUES (1,1,3,10.0); -SELECT SETVAL('permission.grp_penalty_threshold_id_seq'::TEXT, (SELECT MAX(id) FROM permission.grp_penalty_threshold)); - - - -CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd( - user_id IN INTEGER, - perm_code IN TEXT -) -RETURNS SETOF INTEGER AS $$ --- --- Return a set of all the org units for which a given user has a given --- permission, granted directly (not through inheritance from a parent --- org unit). --- --- The permissions apply to a minimum depth of the org unit hierarchy, --- for the org unit(s) to which the user is assigned. (They also apply --- to the subordinates of those org units, but we don't report the --- subordinates here.) --- --- For purposes of this function, the permission.usr_work_ou_map table --- defines which users belong to which org units. I.e. we ignore the --- home_ou column of actor.usr. --- --- The result set may contain duplicates, which should be eliminated --- by a DISTINCT clause. --- -DECLARE - b_super BOOLEAN; - n_perm INTEGER; - n_min_depth INTEGER; - n_work_ou INTEGER; - n_curr_ou INTEGER; - n_depth INTEGER; - n_curr_depth INTEGER; -BEGIN - -- - -- Check for superuser - -- - SELECT INTO b_super - super_user - FROM - actor.usr - WHERE - id = user_id; - -- - IF NOT FOUND THEN - return; -- No user? No permissions. - ELSIF b_super THEN - -- - -- Super user has all permissions everywhere - -- - FOR n_work_ou IN - SELECT - id - FROM - actor.org_unit - WHERE - parent_ou IS NULL - LOOP - RETURN NEXT n_work_ou; - END LOOP; - RETURN; - END IF; - -- - -- Translate the permission name - -- to a numeric permission id - -- - SELECT INTO n_perm - id - FROM - permission.perm_list - WHERE - code = perm_code; - -- - IF NOT FOUND THEN - RETURN; -- No such permission - END IF; - -- - -- Find the highest-level org unit (i.e. the minimum depth) - -- to which the permission is applied for this user - -- - -- This query is modified from the one in permission.usr_perms(). - -- - SELECT INTO n_min_depth - min( depth ) - FROM ( - SELECT depth - FROM permission.usr_perm_map upm - WHERE upm.usr = user_id - AND upm.perm = n_perm - UNION - SELECT gpm.depth - FROM permission.grp_perm_map gpm - WHERE gpm.perm = n_perm - AND gpm.grp IN ( - SELECT (permission.grp_ancestors( - (SELECT profile FROM actor.usr WHERE id = user_id) - )).id - ) - UNION - SELECT p.depth - FROM permission.grp_perm_map p - WHERE p.perm = n_perm - AND p.grp IN ( - SELECT (permission.grp_ancestors(m.grp)).id - FROM permission.usr_grp_map m - WHERE m.usr = user_id - ) - ) AS x; - -- - IF NOT FOUND THEN - RETURN; -- No such permission for this user - END IF; - -- - -- Identify the org units to which the user is assigned. Note that - -- we pay no attention to the home_ou column in actor.usr. - -- - FOR n_work_ou IN - SELECT - work_ou - FROM - permission.usr_work_ou_map - WHERE - usr = user_id - LOOP -- For each org unit to which the user is assigned - -- - -- Determine the level of the org unit by a lookup in actor.org_unit_type. - -- We take it on faith that this depth agrees with the actual hierarchy - -- defined in actor.org_unit. - -- - SELECT INTO n_depth - type.depth - FROM - actor.org_unit_type type - INNER JOIN actor.org_unit ou - ON ( ou.ou_type = type.id ) - WHERE - ou.id = n_work_ou; - -- - IF NOT FOUND THEN - CONTINUE; -- Maybe raise exception? - END IF; - -- - -- Compare the depth of the work org unit to the - -- minimum depth, and branch accordingly - -- - IF n_depth = n_min_depth THEN - -- - -- The org unit is at the right depth, so return it. - -- - RETURN NEXT n_work_ou; - ELSIF n_depth > n_min_depth THEN - -- - -- Traverse the org unit tree toward the root, - -- until you reach the minimum depth determined above - -- - n_curr_depth := n_depth; - n_curr_ou := n_work_ou; - WHILE n_curr_depth > n_min_depth LOOP - SELECT INTO n_curr_ou - parent_ou - FROM - actor.org_unit - WHERE - id = n_curr_ou; - -- - IF FOUND THEN - n_curr_depth := n_curr_depth - 1; - ELSE - -- - -- This can happen only if the hierarchy defined in - -- actor.org_unit is corrupted, or out of sync with - -- the depths defined in actor.org_unit_type. - -- Maybe we should raise an exception here, instead - -- of silently ignoring the problem. - -- - n_curr_ou = NULL; - EXIT; - END IF; - END LOOP; - -- - IF n_curr_ou IS NOT NULL THEN - RETURN NEXT n_curr_ou; - END IF; - ELSE - -- - -- The permission applies only at a depth greater than the work org unit. - -- Use connectby() to find all dependent org units at the specified depth. - -- - FOR n_curr_ou IN - SELECT ou::INTEGER - FROM connectby( - 'actor.org_unit', -- table name - 'id', -- key column - 'parent_ou', -- recursive foreign key - n_work_ou::TEXT, -- id of starting point - (n_min_depth - n_depth) -- max depth to search, relative - ) -- to starting point - AS t( - ou text, -- dependent org unit - parent_ou text, -- (ignore) - level int -- depth relative to starting point - ) - WHERE - level = n_min_depth - n_depth - LOOP - RETURN NEXT n_curr_ou; - END LOOP; - END IF; - -- - END LOOP; - -- - RETURN; - -- -END; -$$ LANGUAGE 'plpgsql'; - - -CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_all_nd( - user_id IN INTEGER, - perm_code IN TEXT -) -RETURNS SETOF INTEGER AS $$ --- --- Return a set of all the org units for which a given user has a given --- permission, granted either directly or through inheritance from a parent --- org unit. --- --- The permissions apply to a minimum depth of the org unit hierarchy, and --- to the subordinates of those org units, for the org unit(s) to which the --- user is assigned. --- --- For purposes of this function, the permission.usr_work_ou_map table --- assigns users to org units. I.e. we ignore the home_ou column of actor.usr. --- --- The result set may contain duplicates, which should be eliminated --- by a DISTINCT clause. --- -DECLARE - n_head_ou INTEGER; - n_child_ou INTEGER; -BEGIN - FOR n_head_ou IN - SELECT DISTINCT * FROM permission.usr_has_perm_at_nd( user_id, perm_code ) - LOOP - -- - -- The permission applies only at a depth greater than the work org unit. - -- Use connectby() to find all dependent org units at the specified depth. - -- - FOR n_child_ou IN - SELECT ou::INTEGER - FROM connectby( - 'actor.org_unit', -- table name - 'id', -- key column - 'parent_ou', -- recursive foreign key - n_head_ou::TEXT, -- id of starting point - 0 -- no limit on search depth - ) - AS t( - ou text, -- dependent org unit - parent_ou text, -- (ignore) - level int -- (ignore) - ) - LOOP - RETURN NEXT n_child_ou; - END LOOP; - END LOOP; - -- - RETURN; - -- -END; -$$ LANGUAGE 'plpgsql'; - - -CREATE OR REPLACE FUNCTION permission.usr_has_perm_at( - user_id IN INTEGER, - perm_code IN TEXT -) -RETURNS SETOF INTEGER AS $$ -SELECT DISTINCT * FROM permission.usr_has_perm_at_nd( $1, $2 ); -$$ LANGUAGE 'sql'; - - -CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_all( - user_id IN INTEGER, - perm_code IN TEXT -) -RETURNS SETOF INTEGER AS $$ -SELECT DISTINCT * FROM permission.usr_has_perm_at_all_nd( $1, $2 ); -$$ LANGUAGE 'sql'; - - - -CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$ -DECLARE - - owning_lib TEXT; - circ_lib TEXT; - call_number TEXT; - copy_number TEXT; - status TEXT; - location TEXT; - circulate TEXT; - deposit TEXT; - deposit_amount TEXT; - ref TEXT; - holdable TEXT; - price TEXT; - barcode TEXT; - circ_modifier TEXT; - circ_as_type TEXT; - alert_message TEXT; - opac_visible TEXT; - pub_note TEXT; - priv_note TEXT; - - attr_def RECORD; - tmp_attr_set RECORD; - attr_set vandelay.import_item%ROWTYPE; - - xpath TEXT; - -BEGIN - - SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id; - - IF FOUND THEN - - attr_set.definition := attr_def.id; - - -- Build the combined XPath - - owning_lib := - CASE - WHEN attr_def.owning_lib IS NULL THEN 'null()' - WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib - END; - - circ_lib := - CASE - WHEN attr_def.circ_lib IS NULL THEN 'null()' - WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib - END; - - call_number := - CASE - WHEN attr_def.call_number IS NULL THEN 'null()' - WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number - END; - - copy_number := - CASE - WHEN attr_def.copy_number IS NULL THEN 'null()' - WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number - END; - - status := - CASE - WHEN attr_def.status IS NULL THEN 'null()' - WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status - END; - - location := - CASE - WHEN attr_def.location IS NULL THEN 'null()' - WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location - END; - - circulate := - CASE - WHEN attr_def.circulate IS NULL THEN 'null()' - WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate - END; - - deposit := - CASE - WHEN attr_def.deposit IS NULL THEN 'null()' - WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit - END; - - deposit_amount := - CASE - WHEN attr_def.deposit_amount IS NULL THEN 'null()' - WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount - END; - - ref := - CASE - WHEN attr_def.ref IS NULL THEN 'null()' - WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref - END; - - holdable := - CASE - WHEN attr_def.holdable IS NULL THEN 'null()' - WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable - END; - - price := - CASE - WHEN attr_def.price IS NULL THEN 'null()' - WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price - END; - - barcode := - CASE - WHEN attr_def.barcode IS NULL THEN 'null()' - WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode - END; - - circ_modifier := - CASE - WHEN attr_def.circ_modifier IS NULL THEN 'null()' - WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier - END; - - circ_as_type := - CASE - WHEN attr_def.circ_as_type IS NULL THEN 'null()' - WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type - END; - - alert_message := - CASE - WHEN attr_def.alert_message IS NULL THEN 'null()' - WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message - END; - - opac_visible := - CASE - WHEN attr_def.opac_visible IS NULL THEN 'null()' - WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible - END; - - pub_note := - CASE - WHEN attr_def.pub_note IS NULL THEN 'null()' - WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note - END; - priv_note := - CASE - WHEN attr_def.priv_note IS NULL THEN 'null()' - WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]' - ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note - END; - - - xpath := - owning_lib || '|' || - circ_lib || '|' || - call_number || '|' || - copy_number || '|' || - status || '|' || - location || '|' || - circulate || '|' || - deposit || '|' || - deposit_amount || '|' || - ref || '|' || - holdable || '|' || - price || '|' || - barcode || '|' || - circ_modifier || '|' || - circ_as_type || '|' || - alert_message || '|' || - pub_note || '|' || - priv_note || '|' || - opac_visible; - - -- RAISE NOTICE 'XPath: %', xpath; - - FOR tmp_attr_set IN - SELECT * - FROM xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id ) - AS t( id BIGINT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT, - dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT, - circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT ) - LOOP - - tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g'); - tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g'); - - tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' ); - tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' ); - - SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT - SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT - SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT - - SELECT id INTO attr_set.location - FROM asset.copy_location - WHERE LOWER(name) = LOWER(tmp_attr_set.cl) - AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT - - attr_set.circulate := - LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1') - OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL - - attr_set.deposit := - LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1') - OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL - - attr_set.holdable := - LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1') - OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL - - attr_set.opac_visible := - LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1') - OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL - - attr_set.ref := - LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1') - OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL - - attr_set.copy_number := tmp_attr_set.cnum::INT; -- INT, - attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2), - attr_set.price := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2), - - attr_set.call_number := tmp_attr_set.cn; -- TEXT - attr_set.barcode := tmp_attr_set.bc; -- TEXT, - attr_set.circ_modifier := tmp_attr_set.circ_mod; -- TEXT, - attr_set.circ_as_type := tmp_attr_set.circ_as; -- TEXT, - attr_set.alert_message := tmp_attr_set.amessage; -- TEXT, - attr_set.pub_note := tmp_attr_set.note; -- TEXT, - attr_set.priv_note := tmp_attr_set.pnote; -- TEXT, - attr_set.alert_message := tmp_attr_set.amessage; -- TEXT, - - RETURN NEXT attr_set; - - END LOOP; - - END IF; - -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION actor.org_unit_ancestors ( INT ) RETURNS SETOF actor.org_unit AS $$ - SELECT a.* - FROM connectby('actor.org_unit'::text,'parent_ou'::text,'id'::text,'name'::text,$1::text,100,'.'::text) - AS t(keyid text, parent_keyid text, level int, branch text,pos int) - JOIN actor.org_unit a ON a.id::text = t.keyid::text - JOIN actor.org_unit_type tp ON tp.id = a.ou_type - ORDER BY tp.depth, a.name; -$$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$ -DECLARE - setting RECORD; - cur_org INT; -BEGIN - cur_org := org_id; - LOOP - SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name; - IF FOUND THEN - RETURN NEXT setting; - END IF; - SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org; - EXIT WHEN cur_org IS NULL; - END LOOP; - RETURN; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION actor.org_unit_ancestor_setting( TEXT, INT) IS $$ -/** -* Search "up" the org_unit tree until we find the first occurrence of an -* org_unit_setting with the given name. -*/ -$$; - - -ALTER TABLE asset.copy_tranparency_map RENAME TO copy_transparency_map; -ALTER TABLE asset.copy_transparency_map RENAME COLUMN tansparency TO transparency; - -CREATE TABLE asset.uri ( - id SERIAL PRIMARY KEY, - href TEXT NOT NULL, - label TEXT, - use_restriction TEXT, - active BOOL NOT NULL DEFAULT TRUE -); - -CREATE TABLE asset.uri_call_number_map ( - id BIGSERIAL PRIMARY KEY, - uri INT NOT NULL REFERENCES asset.uri (id), - call_number INT NOT NULL REFERENCES asset.call_number (id), - CONSTRAINT uri_cn_once UNIQUE (uri,call_number) -); -CREATE INDEX asset_uri_call_number_map_cn_idx ON asset.uri_call_number_map (call_number); - ------------------------------ - -CREATE TABLE container.copy_bucket_type ( - code TEXT PRIMARY KEY, - label TEXT NOT NULL UNIQUE -); - - -CREATE TABLE container.copy_bucket_note ( - id SERIAL PRIMARY KEY, - bucket INT NOT NULL REFERENCES container.copy_bucket (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - note TEXT NOT NULL -); - -ALTER TABLE container.copy_bucket_item ADD COLUMN pos INT; -CREATE INDEX copy_bucket_item_bucket_idx ON container.copy_bucket_item (bucket); - -CREATE TABLE container.copy_bucket_item_note ( - id SERIAL PRIMARY KEY, - item INT NOT NULL REFERENCES container.copy_bucket_item (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - note TEXT NOT NULL -); - ------------------------------ - -CREATE TABLE container.call_number_bucket_type ( - code TEXT PRIMARY KEY, - label TEXT NOT NULL UNIQUE -); - -ALTER TABLE container.call_number_bucket_item ADD COLUMN pos INT; -CREATE INDEX call_number_bucket_item_bucket_idx ON container.call_number_bucket_item (bucket); - -CREATE TABLE container.call_number_bucket_note ( - id SERIAL PRIMARY KEY, - bucket INT NOT NULL REFERENCES container.call_number_bucket (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - note TEXT NOT NULL -); - - -CREATE TABLE container.call_number_bucket_item_note ( - id SERIAL PRIMARY KEY, - item INT NOT NULL REFERENCES container.call_number_bucket_item (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - note TEXT NOT NULL -); - ---------------------------- - -CREATE TABLE container.biblio_record_entry_bucket_type ( - code TEXT PRIMARY KEY, - label TEXT NOT NULL UNIQUE -); - - -CREATE TABLE container.biblio_record_entry_bucket_note ( - id SERIAL PRIMARY KEY, - bucket INT NOT NULL REFERENCES container.biblio_record_entry_bucket (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - note TEXT NOT NULL -); - -ALTER TABLE container.biblio_record_entry_bucket_item ADD COLUMN pos INT; -CREATE INDEX biblio_record_entry_bucket_item_bucket_idx ON container.biblio_record_entry_bucket_item (bucket); - -CREATE TABLE container.biblio_record_entry_bucket_item_note ( - id SERIAL PRIMARY KEY, - item INT NOT NULL REFERENCES container.biblio_record_entry_bucket_item (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - note TEXT NOT NULL -); - ---------------------------- - -CREATE TABLE container.user_bucket_type ( - code TEXT PRIMARY KEY, - label TEXT NOT NULL UNIQUE -); - -CREATE TABLE container.user_bucket_note ( - id SERIAL PRIMARY KEY, - bucket INT NOT NULL REFERENCES container.user_bucket (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - note TEXT NOT NULL -); - -ALTER TABLE container.user_bucket_item ADD COLUMN pos INT; -CREATE INDEX user_bucket_item_bucket_idx ON container.user_bucket_item (bucket); - -CREATE TABLE container.user_bucket_item_note ( - id SERIAL PRIMARY KEY, - item INT NOT NULL REFERENCES container.user_bucket_item (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - note TEXT NOT NULL -); - ------------------------------ - -INSERT INTO config.billing_type (name,owner) SELECT DISTINCT billing_type, 1 FROM money.billing WHERE billing_type NOT IN (SELECT name FROM config.billing_type); -ALTER TABLE money.billing ADD COLUMN btype INT; - -UPDATE money.billing SET btype = config.billing_type.id FROM config.billing_type WHERE config.billing_type.name = money.billing.billing_type; -ALTER TABLE money.billing ALTER COLUMN btype SET NOT NULL; -ALTER TABLE money.billing ADD CONSTRAINT btype_fkey FOREIGN KEY (btype) REFERENCES config.billing_type (id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED; - - -CREATE TABLE money.materialized_billable_xact_summary AS - SELECT * FROM money.billable_xact_summary WHERE 1=0; - -CREATE INDEX money_mat_summary_id_idx ON money.materialized_billable_xact_summary (id); -CREATE INDEX money_mat_summary_usr_idx ON money.materialized_billable_xact_summary (usr); -CREATE INDEX money_mat_summary_xact_start_idx ON money.materialized_billable_xact_summary (xact_start); - -/* AFTER trigger only! */ -CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$ -BEGIN - INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type) - VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]); - RETURN NEW; -END; -$$ LANGUAGE PLPGSQL; - -/* BEFORE or AFTER trigger only! */ -CREATE OR REPLACE FUNCTION money.mat_summary_update () RETURNS TRIGGER AS $$ -BEGIN - UPDATE money.materialized_billable_xact_summary - SET usr = NEW.usr, - xact_start = NEW.xact_start, - xact_finish = NEW.xact_finish - WHERE id = NEW.id; - RETURN NEW; -END; -$$ LANGUAGE PLPGSQL; - -/* AFTER trigger only! */ -CREATE OR REPLACE FUNCTION money.mat_summary_delete () RETURNS TRIGGER AS $$ -BEGIN - DELETE FROM money.materialized_billable_xact_summary WHERE id = OLD.id; - RETURN OLD; -END; -$$ LANGUAGE PLPGSQL; - -CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery'); -CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update (); -CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete (); - - - -/* BEFORE or AFTER trigger */ -CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$ -BEGIN - IF NOT NEW.voided THEN - UPDATE money.materialized_billable_xact_summary - SET total_owed = total_owed + NEW.amount, - last_billing_ts = NEW.billing_ts, - last_billing_note = NEW.note, - last_billing_type = NEW.billing_type, - balance_owed = balance_owed + NEW.amount - WHERE id = NEW.xact; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE PLPGSQL; - -/* AFTER trigger only! */ -CREATE OR REPLACE FUNCTION money.materialized_summary_billing_update () RETURNS TRIGGER AS $$ -DECLARE - old_billing money.billing%ROWTYPE; - old_voided money.billing%ROWTYPE; -BEGIN - - SELECT * INTO old_billing FROM money.billing WHERE xact = NEW.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1; - SELECT * INTO old_voided FROM money.billing WHERE xact = NEW.xact ORDER BY billing_ts DESC LIMIT 1; - - IF NEW.voided AND NOT OLD.voided THEN - IF OLD.id = old_voided.id THEN - UPDATE money.materialized_billable_xact_summary - SET last_billing_ts = old_billing.billing_ts, - last_billing_note = old_billing.note, - last_billing_type = old_billing.billing_type - WHERE id = OLD.xact; - END IF; - - UPDATE money.materialized_billable_xact_summary - SET total_owed = total_owed - NEW.amount, - balance_owed = balance_owed - NEW.amount - WHERE id = NEW.xact; - - ELSIF NOT NEW.voided AND OLD.voided THEN - - IF OLD.id = old_billing.id THEN - UPDATE money.materialized_billable_xact_summary - SET last_billing_ts = old_billing.billing_ts, - last_billing_note = old_billing.note, - last_billing_type = old_billing.billing_type - WHERE id = OLD.xact; - END IF; - - UPDATE money.materialized_billable_xact_summary - SET total_owed = total_owed + NEW.amount, - balance_owed = balance_owed + NEW.amount - WHERE id = NEW.xact; - - ELSE - UPDATE money.materialized_billable_xact_summary - SET total_owed = total_owed - (OLD.amount - NEW.amount), - balance_owed = balance_owed - (OLD.amount - NEW.amount) - WHERE id = NEW.xact; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE PLPGSQL; - -/* BEFORE trigger only! */ -CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$ -DECLARE - prev_billing money.billing%ROWTYPE; - old_billing money.billing%ROWTYPE; -BEGIN - SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1; - SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1; - - IF OLD.id = old_billing.id THEN - UPDATE money.materialized_billable_xact_summary - SET last_billing_ts = prev_billing.billing_ts, - last_billing_note = prev_billing.note, - last_billing_type = prev_billing.billing_type - WHERE id = NEW.xact; - END IF; - - IF NOT OLD.voided THEN - UPDATE money.materialized_billable_xact_summary - SET total_owed = total_owed - OLD.amount, - balance_owed = balance_owed + OLD.amount - WHERE id = OLD.xact; - END IF; - - RETURN OLD; -END; -$$ LANGUAGE PLPGSQL; - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.billing FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_billing_add (); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.billing FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_billing_update (); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.billing FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_billing_del (); - - -/* BEFORE or AFTER trigger */ -CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$ -BEGIN - IF NOT NEW.voided THEN - UPDATE money.materialized_billable_xact_summary - SET total_paid = total_paid + NEW.amount, - last_payment_ts = NEW.payment_ts, - last_payment_note = NEW.note, - last_payment_type = TG_ARGV[0], - balance_owed = balance_owed - NEW.amount - WHERE id = NEW.xact; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE PLPGSQL; - -/* AFTER trigger only! */ -CREATE OR REPLACE FUNCTION money.materialized_summary_payment_update () RETURNS TRIGGER AS $$ -DECLARE - old_payment money.payment_view%ROWTYPE; - old_voided money.payment_view%ROWTYPE; -BEGIN - - SELECT * INTO old_payment FROM money.payment_view WHERE xact = NEW.xact AND NOT voided ORDER BY payment_ts DESC LIMIT 1; - SELECT * INTO old_voided FROM money.payment_view WHERE xact = NEW.xact ORDER BY payment_ts DESC LIMIT 1; - - IF NEW.voided AND NOT OLD.voided THEN - IF OLD.id = old_voided.id THEN - UPDATE money.materialized_billable_xact_summary - SET last_payment_ts = old_payment.payment_ts, - last_payment_note = old_payment.note, - last_payment_type = old_payment.payment_type - WHERE id = OLD.xact; - END IF; - - UPDATE money.materialized_billable_xact_summary - SET total_paid = total_paid - NEW.amount, - balance_owed = balance_owed + NEW.amount - WHERE id = NEW.xact; - - ELSIF NOT NEW.voided AND OLD.voided THEN - - IF OLD.id = old_payment.id THEN - UPDATE money.materialized_billable_xact_summary - SET last_payment_ts = old_payment.payment_ts, - last_payment_note = old_payment.note, - last_payment_type = old_payment.payment_type - WHERE id = OLD.xact; - END IF; - - UPDATE money.materialized_billable_xact_summary - SET total_paid = total_paid + NEW.amount, - balance_owed = balance_owed - NEW.amount - WHERE id = NEW.xact; - - ELSE - UPDATE money.materialized_billable_xact_summary - SET total_paid = total_paid - (OLD.amount - NEW.amount), - balance_owed = balance_owed + (OLD.amount - NEW.amount) - WHERE id = NEW.xact; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE PLPGSQL; - -/* BEFORE trigger only! */ -CREATE OR REPLACE FUNCTION money.materialized_summary_payment_del () RETURNS TRIGGER AS $$ -DECLARE - prev_payment money.payment_view%ROWTYPE; - old_payment money.payment_view%ROWTYPE; -BEGIN - SELECT * INTO prev_payment FROM money.payment_view WHERE xact = OLD.xact AND NOT voided ORDER BY payment_ts DESC LIMIT 1 OFFSET 1; - SELECT * INTO old_payment FROM money.payment_view WHERE xact = OLD.xact AND NOT voided ORDER BY payment_ts DESC LIMIT 1; - - IF OLD.id = old_payment.id THEN - UPDATE money.materialized_billable_xact_summary - SET last_payment_ts = prev_payment.payment_ts, - last_payment_note = prev_payment.note, - last_payment_type = prev_payment.payment_type - WHERE id = OLD.xact; - END IF; - - IF NOT OLD.voided THEN - UPDATE money.materialized_billable_xact_summary - SET total_paid = total_paid - OLD.amount, - balance_owed = balance_owed + OLD.amount - WHERE id = OLD.xact; - END IF; - - RETURN OLD; -END; -$$ LANGUAGE PLPGSQL; - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('payment'); - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.bnm_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('bnm_payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.bnm_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('bnm_payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.bnm_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('bnm_payment'); - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.forgive_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('forgive_payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.forgive_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('forgive_payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.forgive_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('forgive_payment'); - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.work_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('work_payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.work_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('work_payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.work_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('work_payment'); - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.credit_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('credit_payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.credit_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('credit_payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.credit_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('credit_payment'); - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.goods_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('goods_payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.goods_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('goods_payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.goods_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('goods_payment'); - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.bnm_desk_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('bnm_desk_payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.bnm_desk_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('bnm_desk_payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.bnm_desk_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('bnm_desk_payment'); - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('cash_payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('cash_payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('cash_payment'); - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('check_payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('check_payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('check_payment'); - -CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.credit_card_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('credit_card_payment'); -CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.credit_card_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('credit_card_payment'); -CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.credit_card_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('credit_card_payment'); - - -CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation'); -CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update (); -CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete (); - -CREATE OR REPLACE VIEW action.billable_circulations AS - SELECT * - FROM action.circulation - WHERE xact_finish IS NULL; - -CREATE TABLE action.hold_request_cancel_cause ( - id SERIAL PRIMARY KEY, - label TEXT UNIQUE -); -INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (1,'Untargeted expiration'); -INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (2,'Hold Shelf expiration'); -INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (3,'Patron via phone'); -INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (4,'Patron in person'); -INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (5,'Staff forced'); -INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (6,'Patron via OPAC'); -SELECT SETVAL('action.hold_request_cancel_cause_id_seq', 100); - -ALTER TABLE action.hold_request ADD COLUMN cancel_cause INT; -ALTER TABLE action.hold_request ADD COLUMN cancel_note TEXT; - - -ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN juvenile_flag BOOL; -ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN circulate BOOL NOT NULL DEFAULT TRUE; -ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN duration_rule INT REFERENCES config.rule_circ_duration (id) DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN recurring_fine_rule INT REFERENCES config.rule_recurring_fine (id) DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN max_fine_rule INT REFERENCES config.rule_max_fine (id) DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN script_test TEXT; - -ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc; -ALTER TABLE config.circ_matrix_matchpoint - ADD CONSTRAINT ep_once_per_grp_loc_mod_marc - UNIQUE (grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag, juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal); - -UPDATE config.circ_matrix_matchpoint - SET duration_rule = config.circ_matrix_ruleset.duration_rule, - recurring_fine_rule = config.circ_matrix_ruleset.recurring_fine_rule, - max_fine_rule = config.circ_matrix_ruleset.max_fine_rule - FROM config.circ_matrix_ruleset - WHERE config.circ_matrix_ruleset.matchpoint = config.circ_matrix_matchpoint.id; - -DROP TABLE config.circ_matrix_ruleset; - -ALTER TABLE config.circ_matrix_circ_mod_test DROP COLUMN circ_mod; -CREATE TABLE config.circ_matrix_circ_mod_test_map ( - id SERIAL PRIMARY KEY, - circ_mod_test INT NOT NULL REFERENCES config.circ_matrix_circ_mod_test (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - circ_mod TEXT NOT NULL REFERENCES config.circ_modifier (code) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - CONSTRAINT cm_once_per_test UNIQUE (circ_mod_test, circ_mod) -); - -DROP FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ); -CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS config.circ_matrix_matchpoint AS $func$ -DECLARE - current_group permission.grp_tree%ROWTYPE; - user_object actor.usr%ROWTYPE; - item_object asset.copy%ROWTYPE; - rec_descriptor metabib.rec_descriptor%ROWTYPE; - current_mp config.circ_matrix_matchpoint%ROWTYPE; - matchpoint config.circ_matrix_matchpoint%ROWTYPE; -BEGIN - SELECT INTO user_object * FROM actor.usr WHERE id = match_user; - SELECT INTO item_object * FROM asset.copy WHERE id = match_item; - SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number; - SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile; - - LOOP - -- for each potential matchpoint for this ou and group ... - FOR current_mp IN - SELECT m.* - FROM config.circ_matrix_matchpoint m - JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id) - LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id) - WHERE m.grp = current_group.id AND m.active - ORDER BY CASE WHEN p.prox IS NULL THEN 999 ELSE p.prox END, - CASE WHEN m.is_renewal = renewal THEN 128 ELSE 0 END + - CASE WHEN m.juvenile_flag IS NOT NULL THEN 64 ELSE 0 END + - CASE WHEN m.circ_modifier IS NOT NULL THEN 32 ELSE 0 END + - CASE WHEN m.marc_type IS NOT NULL THEN 16 ELSE 0 END + - CASE WHEN m.marc_form IS NOT NULL THEN 8 ELSE 0 END + - CASE WHEN m.marc_vr_format IS NOT NULL THEN 4 ELSE 0 END + - CASE WHEN m.ref_flag IS NOT NULL THEN 2 ELSE 0 END + - CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 0.5 ELSE 0 END + - CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP - - IF current_mp.circ_modifier IS NOT NULL THEN - CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier; - END IF; - - IF current_mp.marc_type IS NOT NULL THEN - IF item_object.circ_as_type IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type; - ELSE - CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type; - END IF; - END IF; - - IF current_mp.marc_form IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form; - END IF; - - IF current_mp.marc_vr_format IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format; - END IF; - - IF current_mp.ref_flag IS NOT NULL THEN - CONTINUE WHEN current_mp.ref_flag <> item_object.ref; - END IF; - - IF current_mp.juvenile_flag IS NOT NULL THEN - CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile; - END IF; - - IF current_mp.usr_age_lower_bound IS NOT NULL THEN - CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob); - END IF; - - IF current_mp.usr_age_upper_bound IS NOT NULL THEN - CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob); - END IF; - - - -- everything was undefined or matched - matchpoint = current_mp; - - EXIT WHEN matchpoint.id IS NOT NULL; - END LOOP; - - EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL; - - SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent; - END LOOP; - - RETURN matchpoint; -END; -$func$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.matrix_test_result AS $func$ -DECLARE - user_object actor.usr%ROWTYPE; - standing_penalty config.standing_penalty%ROWTYPE; - item_object asset.copy%ROWTYPE; - item_status_object config.copy_status%ROWTYPE; - item_location_object asset.copy_location%ROWTYPE; - result action.matrix_test_result; - circ_test config.circ_matrix_matchpoint%ROWTYPE; - out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE; - circ_mod_map config.circ_matrix_circ_mod_test_map%ROWTYPE; - penalty_type TEXT; - tmp_grp INT; - items_out INT; - context_org_list INT[]; - done BOOL := FALSE; -BEGIN - result.success := TRUE; - - -- Fail if the user is BARRED - SELECT INTO user_object * FROM actor.usr WHERE id = match_user; - - -- Fail if we couldn't find a set of tests - IF user_object.id IS NULL THEN - result.fail_part := 'no_user'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - IF user_object.barred IS TRUE THEN - result.fail_part := 'actor.usr.barred'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - -- Fail if the item can't circulate - SELECT INTO item_object * FROM asset.copy WHERE id = match_item; - IF item_object.circulate IS FALSE THEN - result.fail_part := 'asset.copy.circulate'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - -- Fail if the item isn't in a circulateable status on a non-renewal - IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN - result.fail_part := 'asset.copy.status'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - ELSIF renewal AND item_object.status <> 1 THEN - result.fail_part := 'asset.copy.status'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - -- Fail if the item can't circulate because of the shelving location - SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location; - IF item_location_object.circulate IS FALSE THEN - result.fail_part := 'asset.copy_location.circulate'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal); - result.matchpoint := circ_test.id; - - SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit ); - - -- Fail if we couldn't find a set of tests - IF result.matchpoint IS NULL THEN - result.fail_part := 'no_matchpoint'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - -- Fail if the test is set to hard non-circulating - IF circ_test.circulate IS FALSE THEN - result.fail_part := 'config.circ_matrix_test.circulate'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - IF renewal THEN - penalty_type = '%RENEW%'; - ELSE - penalty_type = '%CIRC%'; - END IF; - - FOR standing_penalty IN - SELECT DISTINCT csp.* - FROM actor.usr_standing_penalty usp - JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty) - WHERE usr = match_user - AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) ) - AND (usp.stop_date IS NULL or usp.stop_date > NOW()) - AND csp.block_list LIKE penalty_type LOOP - - result.fail_part := standing_penalty.name; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END LOOP; - - -- Fail if the user has too many items with specific circ_modifiers checked out - FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP - SELECT INTO items_out COUNT(*) - FROM action.circulation circ - JOIN asset.copy cp ON (cp.id = circ.target_copy) - WHERE circ.usr = match_user - AND circ_lib IN ( SELECT * FROM explode_array(context_org_list) ) - AND circ.checkin_time IS NULL - AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL) - AND cp.circ_modifier IN (SELECT circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = out_by_circ_mod.id); - IF items_out >= out_by_circ_mod.items_out THEN - result.fail_part := 'config.circ_matrix_circ_mod_test'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END LOOP; - - -- If we passed everything, return the successful matchpoint id - IF NOT done THEN - RETURN NEXT result; - END IF; - - RETURN; -END; -$func$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$ -DECLARE - user_object actor.usr%ROWTYPE; - new_sp_row actor.usr_standing_penalty%ROWTYPE; - existing_sp_row actor.usr_standing_penalty%ROWTYPE; - collections_fines permission.grp_penalty_threshold%ROWTYPE; - max_fines permission.grp_penalty_threshold%ROWTYPE; - max_overdue permission.grp_penalty_threshold%ROWTYPE; - max_items_out permission.grp_penalty_threshold%ROWTYPE; - tmp_grp INT; - items_overdue INT; - items_out INT; - context_org_list INT[]; - current_fines NUMERIC(8,2) := 0.0; - tmp_fines NUMERIC(8,2); - tmp_groc RECORD; - tmp_circ RECORD; - tmp_org actor.org_unit%ROWTYPE; -BEGIN - SELECT INTO user_object * FROM actor.usr WHERE id = match_user; - - -- Max fines - SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; - - -- Fail if the user has a high fine balance - LOOP - tmp_grp := user_object.profile; - LOOP - SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id; - - IF max_fines.threshold IS NULL THEN - SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; - ELSE - EXIT; - END IF; - - IF tmp_grp IS NULL THEN - EXIT; - END IF; - END LOOP; - - IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN - EXIT; - END IF; - - SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou; - - END LOOP; - - IF max_fines.threshold IS NOT NULL THEN - - FOR existing_sp_row IN - SELECT * - FROM actor.usr_standing_penalty - WHERE usr = match_user - AND org_unit = max_fines.org_unit - AND (stop_date IS NULL or stop_date > NOW()) - AND standing_penalty = 1 - LOOP - RETURN NEXT existing_sp_row; - END LOOP; - - SELECT SUM(f.balance_owed) INTO current_fines - FROM money.materialized_billable_xact_summary f - JOIN ( - SELECT g.id - FROM money.grocery g - JOIN actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id) - WHERE usr = match_user - AND xact_finish IS NULL - UNION ALL - SELECT circ.id - FROM action.circulation circ - JOIN actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id) - WHERE usr = match_user - AND xact_finish IS NULL ) l USING (id); - - IF current_fines >= max_fines.threshold THEN - new_sp_row.usr := match_user; - new_sp_row.org_unit := max_fines.org_unit; - new_sp_row.standing_penalty := 1; - RETURN NEXT new_sp_row; - END IF; - END IF; - - -- Start over for max overdue - SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; - - -- Fail if the user has too many overdue items - LOOP - tmp_grp := user_object.profile; - LOOP - - SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id; - - IF max_overdue.threshold IS NULL THEN - SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; - ELSE - EXIT; - END IF; - - IF tmp_grp IS NULL THEN - EXIT; - END IF; - END LOOP; - - IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN - EXIT; - END IF; - - SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou; - - END LOOP; - - IF max_overdue.threshold IS NOT NULL THEN - - FOR existing_sp_row IN - SELECT * - FROM actor.usr_standing_penalty - WHERE usr = match_user - AND org_unit = max_overdue.org_unit - AND (stop_date IS NULL or stop_date > NOW()) - AND standing_penalty = 2 - LOOP - RETURN NEXT existing_sp_row; - END LOOP; - - SELECT INTO items_overdue COUNT(*) - FROM action.circulation circ - JOIN actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id) - WHERE circ.usr = match_user - AND circ.checkin_time IS NULL - AND circ.due_date < NOW() - AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL); - - IF items_overdue >= max_overdue.threshold::INT THEN - new_sp_row.usr := match_user; - new_sp_row.org_unit := max_overdue.org_unit; - new_sp_row.standing_penalty := 2; - RETURN NEXT new_sp_row; - END IF; - END IF; - - -- Start over for max out - SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; - - -- Fail if the user has too many checked out items - LOOP - tmp_grp := user_object.profile; - LOOP - SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id; - - IF max_items_out.threshold IS NULL THEN - SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; - ELSE - EXIT; - END IF; - - IF tmp_grp IS NULL THEN - EXIT; - END IF; - END LOOP; - - IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN - EXIT; - END IF; - - SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou; - - END LOOP; - - - -- Fail if the user has too many items checked out - IF max_items_out.threshold IS NOT NULL THEN - - FOR existing_sp_row IN - SELECT * - FROM actor.usr_standing_penalty - WHERE usr = match_user - AND org_unit = max_items_out.org_unit - AND (stop_date IS NULL or stop_date > NOW()) - AND standing_penalty = 3 - LOOP - RETURN NEXT existing_sp_row; - END LOOP; - - SELECT INTO items_out COUNT(*) - FROM action.circulation circ - JOIN actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id) - WHERE circ.usr = match_user - AND circ.checkin_time IS NULL - AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL); - - IF items_out >= max_items_out.threshold::INT THEN - new_sp_row.usr := match_user; - new_sp_row.org_unit := max_items_out.org_unit; - new_sp_row.standing_penalty := 3; - RETURN NEXT new_sp_row; - END IF; - END IF; - - -- Start over for collections warning - SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; - - -- Fail if the user has a collections-level fine balance - LOOP - tmp_grp := user_object.profile; - LOOP - SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id; - - IF max_fines.threshold IS NULL THEN - SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; - ELSE - EXIT; - END IF; - - IF tmp_grp IS NULL THEN - EXIT; - END IF; - END LOOP; - - IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN - EXIT; - END IF; - - SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou; - - END LOOP; - - IF max_fines.threshold IS NOT NULL THEN - - FOR existing_sp_row IN - SELECT * - FROM actor.usr_standing_penalty - WHERE usr = match_user - AND org_unit = max_fines.org_unit - AND (stop_date IS NULL or stop_date > NOW()) - AND standing_penalty = 4 - LOOP - RETURN NEXT existing_sp_row; - END LOOP; - - SELECT SUM(f.balance_owed) INTO current_fines - FROM money.materialized_billable_xact_summary f - JOIN ( - SELECT g.id - FROM money.grocery g - JOIN actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id) - WHERE usr = match_user - AND xact_finish IS NULL - UNION ALL - SELECT circ.id - FROM action.circulation circ - JOIN actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id) - WHERE usr = match_user - AND xact_finish IS NULL ) l USING (id); - - IF current_fines >= max_fines.threshold THEN - new_sp_row.usr := match_user; - new_sp_row.org_unit := max_fines.org_unit; - new_sp_row.standing_penalty := 4; - RETURN NEXT new_sp_row; - END IF; - END IF; - - - RETURN; -END; -$func$ LANGUAGE plpgsql; - - -ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN juvenile_flag BOOL; -ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN age_hold_protect_rule INT REFERENCES config.rule_age_hold_protect (id) DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN holdable BOOL NOT NULL DEFAULT TRUE; -ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN distance_is_from_owner BOOL NOT NULL DEFAULT FALSE; -ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN transit_range INT REFERENCES actor.org_unit_type (id) DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN max_holds INT; -ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN include_frozen_holds BOOL NOT NULL DEFAULT TRUE; -ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN stop_blocked_user BOOL NOT NULL DEFAULT FALSE; - -CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS INT AS $func$ -DECLARE - current_requestor_group permission.grp_tree%ROWTYPE; - root_ou actor.org_unit%ROWTYPE; - requestor_object actor.usr%ROWTYPE; - user_object actor.usr%ROWTYPE; - item_object asset.copy%ROWTYPE; - item_cn_object asset.call_number%ROWTYPE; - rec_descriptor metabib.rec_descriptor%ROWTYPE; - current_mp_weight FLOAT; - matchpoint_weight FLOAT; - tmp_weight FLOAT; - current_mp config.hold_matrix_matchpoint%ROWTYPE; - matchpoint config.hold_matrix_matchpoint%ROWTYPE; -BEGIN - SELECT INTO root_ou * FROM actor.org_unit WHERE parent_ou IS NULL; - SELECT INTO user_object * FROM actor.usr WHERE id = match_user; - SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor; - SELECT INTO item_object * FROM asset.copy WHERE id = match_item; - SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number; - SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record; - SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile; - - LOOP - -- for each potential matchpoint for this ou and group ... - FOR current_mp IN - SELECT m.* - FROM config.hold_matrix_matchpoint m - WHERE m.requestor_grp = current_requestor_group.id AND m.active - ORDER BY CASE WHEN m.circ_modifier IS NOT NULL THEN 16 ELSE 0 END + - CASE WHEN m.juvenile_flag IS NOT NULL THEN 16 ELSE 0 END + - CASE WHEN m.marc_type IS NOT NULL THEN 8 ELSE 0 END + - CASE WHEN m.marc_form IS NOT NULL THEN 4 ELSE 0 END + - CASE WHEN m.marc_vr_format IS NOT NULL THEN 2 ELSE 0 END + - CASE WHEN m.ref_flag IS NOT NULL THEN 1 ELSE 0 END DESC LOOP - - current_mp_weight := 5.0; - - IF current_mp.circ_modifier IS NOT NULL THEN - CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier; - END IF; - - IF current_mp.marc_type IS NOT NULL THEN - IF item_object.circ_as_type IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type; - ELSE - CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type; - END IF; - END IF; - - IF current_mp.marc_form IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form; - END IF; - - IF current_mp.marc_vr_format IS NOT NULL THEN - CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format; - END IF; - - IF current_mp.juvenile_flag IS NOT NULL THEN - CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile; - END IF; - - IF current_mp.ref_flag IS NOT NULL THEN - CONTINUE WHEN current_mp.ref_flag <> item_object.ref; - END IF; - - - -- caclulate the rule match weight - IF current_mp.item_owning_ou IS NOT NULL AND current_mp.item_owning_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - IF current_mp.item_circ_ou IS NOT NULL AND current_mp.item_circ_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - IF current_mp.pickup_ou IS NOT NULL AND current_mp.pickup_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - IF current_mp.request_ou IS NOT NULL AND current_mp.request_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - IF current_mp.user_home_ou IS NOT NULL AND current_mp.user_home_ou <> root_ou.id THEN - SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT; - current_mp_weight := current_mp_weight - tmp_weight; - END IF; - - -- set the matchpoint if we found the best one - IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN - matchpoint = current_mp; - matchpoint_weight = current_mp_weight; - END IF; - - END LOOP; - - EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL; - - SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent; - END LOOP; - - RETURN matchpoint.id; -END; -$func$ LANGUAGE plpgsql; - - -CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS SETOF action.matrix_test_result AS -$func$ -DECLARE - matchpoint_id INT; - user_object actor.usr%ROWTYPE; - age_protect_object config.rule_age_hold_protect%ROWTYPE; - standing_penalty config.standing_penalty%ROWTYPE; - transit_range_ou_type actor.org_unit_type%ROWTYPE; - transit_source actor.org_unit%ROWTYPE; - item_object asset.copy%ROWTYPE; - result action.matrix_test_result; - hold_test config.hold_matrix_matchpoint%ROWTYPE; - hold_count INT; - hold_transit_prox INT; - frozen_hold_count INT; - context_org_list INT[]; - done BOOL := FALSE; -BEGIN - SELECT INTO user_object * FROM actor.usr WHERE id = match_user; - SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou ); - - -- Fail if we couldn't find a user - IF user_object.id IS NULL THEN - result.fail_part := 'no_user'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - -- Fail if user is barred - IF user_object.barred IS TRUE THEN - result.fail_part := 'actor.usr.barred'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - SELECT INTO item_object * FROM asset.copy WHERE id = match_item; - - -- Fail if we couldn't find a copy - IF item_object.id IS NULL THEN - result.fail_part := 'no_item'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor); - - -- Fail if we couldn't find any matchpoint (requires a default) - IF matchpoint_id IS NULL THEN - result.fail_part := 'no_matchpoint'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - RETURN; - END IF; - - SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id; - - result.matchpoint := hold_test.id; - result.success := TRUE; - - IF hold_test.holdable IS FALSE THEN - result.fail_part := 'config.hold_matrix_test.holdable'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - - IF hold_test.transit_range IS NOT NULL THEN - SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range; - IF hold_test.distance_is_from_owner THEN - SELECT INTO transit_source ou.* FROM actor.org_unit ou JOIN asset.call_number cn ON (cn.owning_lib = ou.id) WHERE cn.id = item_object.call_number; - ELSE - SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib; - END IF; - - PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou; - - IF NOT FOUND THEN - result.fail_part := 'transit_range'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END IF; - - FOR standing_penalty IN - SELECT DISTINCT csp.* - FROM actor.usr_standing_penalty usp - JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty) - WHERE usr = match_user - AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) ) - AND (usp.stop_date IS NULL or usp.stop_date > NOW()) - AND csp.block_list LIKE '%HOLD%' LOOP - - result.fail_part := standing_penalty.name; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END LOOP; - - IF hold_test.stop_blocked_user IS TRUE THEN - FOR standing_penalty IN - SELECT DISTINCT csp.* - FROM actor.usr_standing_penalty usp - JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty) - WHERE usr = match_user - AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) ) - AND (usp.stop_date IS NULL or usp.stop_date > NOW()) - AND csp.block_list LIKE '%CIRC%' LOOP - - result.fail_part := standing_penalty.name; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END LOOP; - END IF; - - IF hold_test.max_holds IS NOT NULL THEN - SELECT INTO hold_count COUNT(*) - FROM action.hold_request - WHERE usr = match_user - AND fulfillment_time IS NULL - AND cancel_time IS NULL - AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END; - - IF hold_count >= hold_test.max_holds THEN - result.fail_part := 'config.hold_matrix_test.max_holds'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END IF; - - IF item_object.age_protect IS NOT NULL THEN - SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect; - - IF item_object.create_date + age_protect_object.age > NOW() THEN - IF hold_test.distance_is_from_owner THEN - SELECT INTO hold_transit_prox prox FROM actor.org_unit_prox WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou; - ELSE - SELECT INTO hold_transit_prox prox FROM actor.org_unit_prox WHERE from_org = item_object.circ_lib AND to_org = pickup_ou; - END IF; - - IF hold_transit_prox > age_protect_object.prox THEN - result.fail_part := 'config.rule_age_hold_protect.prox'; - result.success := FALSE; - done := TRUE; - RETURN NEXT result; - END IF; - END IF; - END IF; - - IF NOT done THEN - RETURN NEXT result; - END IF; - - RETURN; -END; -$func$ LANGUAGE plpgsql; - -CREATE SCHEMA acq; - - --- Tables - - -CREATE TABLE acq.currency_type ( - code TEXT PRIMARY KEY, - label TEXT -); - --- Use the ISO 4217 abbreviations for currency codes -INSERT INTO acq.currency_type (code, label) VALUES ('USD','US Dollars'); -INSERT INTO acq.currency_type (code, label) VALUES ('CAN','Canadian Dollars'); -INSERT INTO acq.currency_type (code, label) VALUES ('EUR','Euros'); - -CREATE TABLE acq.exchange_rate ( - id SERIAL PRIMARY KEY, - from_currency TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED, - to_currency TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED, - ratio NUMERIC NOT NULL, - CONSTRAINT exchange_rate_from_to_once UNIQUE (from_currency,to_currency) -); - -INSERT INTO acq.exchange_rate (from_currency,to_currency,ratio) VALUES ('USD','CAN',1.2); -INSERT INTO acq.exchange_rate (from_currency,to_currency,ratio) VALUES ('USD','EUR',0.5); - -CREATE TABLE acq.provider ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - owner INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, - currency_type TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED, - code TEXT UNIQUE, - holding_tag TEXT, - CONSTRAINT provider_name_once_per_owner UNIQUE (name,owner) -); - -CREATE TABLE acq.provider_holding_subfield_map ( - id SERIAL PRIMARY KEY, - provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED, - name TEXT NOT NULL, -- barcode, price, etc - subfield TEXT NOT NULL, - CONSTRAINT name_once_per_provider UNIQUE (provider,name) -); - -CREATE TABLE acq.provider_address ( - id SERIAL PRIMARY KEY, - valid BOOL NOT NULL DEFAULT TRUE, - address_type TEXT, - provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED, - street1 TEXT NOT NULL, - street2 TEXT, - city TEXT NOT NULL, - county TEXT, - state TEXT NOT NULL, - country TEXT NOT NULL, - post_code TEXT NOT NULL -); - -CREATE TABLE acq.provider_contact ( - id SERIAL PRIMARY KEY, - provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED, - name TEXT NULL NULL, - role TEXT, -- free-form.. e.g. "our sales guy" - email TEXT, - phone TEXT -); - -CREATE TABLE acq.provider_contact_address ( - id SERIAL PRIMARY KEY, - valid BOOL NOT NULL DEFAULT TRUE, - address_type TEXT, - contact INT NOT NULL REFERENCES acq.provider_contact (id) DEFERRABLE INITIALLY DEFERRED, - street1 TEXT NOT NULL, - street2 TEXT, - city TEXT NOT NULL, - county TEXT, - state TEXT NOT NULL, - country TEXT NOT NULL, - post_code TEXT NOT NULL -); - - -CREATE TABLE acq.funding_source ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - owner INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, - currency_type TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED, - code TEXT UNIQUE, - CONSTRAINT funding_source_name_once_per_owner UNIQUE (name,owner) -); - -CREATE TABLE acq.funding_source_credit ( - id SERIAL PRIMARY KEY, - funding_source INT NOT NULL REFERENCES acq.funding_source (id) DEFERRABLE INITIALLY DEFERRED, - amount NUMERIC NOT NULL, - note TEXT -); - -CREATE TABLE acq.fund ( - id SERIAL PRIMARY KEY, - org INT NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - name TEXT NOT NULL, - year INT NOT NULL DEFAULT EXTRACT( YEAR FROM NOW() ), - currency_type TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED, - code TEXT UNIQUE, - CONSTRAINT name_once_per_org_year UNIQUE (org,name,year) -); - -CREATE TABLE acq.fund_debit ( - id SERIAL PRIMARY KEY, - fund INT NOT NULL REFERENCES acq.fund (id) DEFERRABLE INITIALLY DEFERRED, - origin_amount NUMERIC NOT NULL, -- pre-exchange-rate amount - origin_currency_type TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED, - amount NUMERIC NOT NULL, - encumbrance BOOL NOT NULL DEFAULT TRUE, - debit_type TEXT NOT NULL, - xfer_destination INT REFERENCES acq.fund (id) DEFERRABLE INITIALLY DEFERRED, - create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() -); - -CREATE TABLE acq.fund_allocation ( - id SERIAL PRIMARY KEY, - funding_source INT NOT NULL REFERENCES acq.funding_source (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - fund INT NOT NULL REFERENCES acq.fund (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - amount NUMERIC, - percent NUMERIC CHECK (percent IS NULL OR percent BETWEEN 0.0 AND 100.0), - allocator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - note TEXT, - create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - CONSTRAINT allocation_amount_or_percent CHECK ((percent IS NULL AND amount IS NOT NULL) OR (percent IS NOT NULL AND amount IS NULL)) -); - - -CREATE TABLE acq.picklist ( - id SERIAL PRIMARY KEY, - owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - org_unit INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, - name TEXT NOT NULL, - create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - CONSTRAINT name_once_per_owner UNIQUE (name,owner) -); - -CREATE TABLE acq.purchase_order ( - id SERIAL PRIMARY KEY, - owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - ordering_agency INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, - create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED, - state TEXT NOT NULL DEFAULT 'new' -); -CREATE INDEX po_owner_idx ON acq.purchase_order (owner); -CREATE INDEX po_provider_idx ON acq.purchase_order (provider); -CREATE INDEX po_state_idx ON acq.purchase_order (state); - -CREATE TABLE acq.po_note ( - id SERIAL PRIMARY KEY, - purchase_order INT NOT NULL REFERENCES acq.purchase_order (id) DEFERRABLE INITIALLY DEFERRED, - creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - value TEXT NOT NULL -); -CREATE INDEX po_note_po_idx ON acq.po_note (purchase_order); - -CREATE TABLE acq.lineitem ( - id BIGSERIAL PRIMARY KEY, - creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - selector INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - provider INT REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED, - purchase_order INT REFERENCES acq.purchase_order (id) DEFERRABLE INITIALLY DEFERRED, - picklist INT REFERENCES acq.picklist (id) DEFERRABLE INITIALLY DEFERRED, - expected_recv_time TIMESTAMP WITH TIME ZONE, - create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - marc TEXT NOT NULL, - eg_bib_id INT REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED, - source_label TEXT, - item_count INT NOT NULL DEFAULT 0, - state TEXT NOT NULL DEFAULT 'new', - CONSTRAINT picklist_or_po CHECK (picklist IS NOT NULL OR purchase_order IS NOT NULL) -); -CREATE INDEX li_po_idx ON acq.lineitem (purchase_order); -CREATE INDEX li_pl_idx ON acq.lineitem (picklist); - -CREATE TABLE acq.lineitem_note ( - id SERIAL PRIMARY KEY, - lineitem INT NOT NULL REFERENCES acq.lineitem (id) DEFERRABLE INITIALLY DEFERRED, - creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - value TEXT NOT NULL -); -CREATE INDEX li_note_li_idx ON acq.lineitem_note (lineitem); - -CREATE TABLE acq.lineitem_detail ( - id BIGSERIAL PRIMARY KEY, - lineitem INT NOT NULL REFERENCES acq.lineitem (id) DEFERRABLE INITIALLY DEFERRED, - fund INT REFERENCES acq.fund (id) DEFERRABLE INITIALLY DEFERRED, - fund_debit INT REFERENCES acq.fund_debit (id) DEFERRABLE INITIALLY DEFERRED, - eg_copy_id BIGINT REFERENCES asset.copy (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - barcode TEXT, - cn_label TEXT, - note TEXT, - collection_code TEXT, - circ_modifier TEXT REFERENCES config.circ_modifier (code) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - owning_lib INT REFERENCES actor.org_unit (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - location INT REFERENCES asset.copy_location (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - recv_time TIMESTAMP WITH TIME ZONE -); - -CREATE INDEX li_detail_li_idx ON acq.lineitem_detail (lineitem); - -CREATE TABLE acq.lineitem_attr_definition ( - id BIGSERIAL PRIMARY KEY, - code TEXT NOT NULL, - description TEXT NOT NULL, - remove TEXT NOT NULL DEFAULT '', - ident BOOL NOT NULL DEFAULT FALSE -); - -CREATE TABLE acq.lineitem_marc_attr_definition ( - id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'), - xpath TEXT NOT NULL -) INHERITS (acq.lineitem_attr_definition); - -CREATE TABLE acq.lineitem_provider_attr_definition ( - id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'), - xpath TEXT NOT NULL, - provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED -) INHERITS (acq.lineitem_attr_definition); - -CREATE TABLE acq.lineitem_generated_attr_definition ( - id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'), - xpath TEXT NOT NULL -) INHERITS (acq.lineitem_attr_definition); - -CREATE TABLE acq.lineitem_usr_attr_definition ( - id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'), - usr INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED -) INHERITS (acq.lineitem_attr_definition); - -CREATE TABLE acq.lineitem_local_attr_definition ( - id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq') -) INHERITS (acq.lineitem_attr_definition); - -CREATE TABLE acq.lineitem_attr ( - id BIGSERIAL PRIMARY KEY, - definition BIGINT NOT NULL, - lineitem BIGINT NOT NULL REFERENCES acq.lineitem (id) DEFERRABLE INITIALLY DEFERRED, - attr_type TEXT NOT NULL, - attr_name TEXT NOT NULL, - attr_value TEXT NOT NULL -); - -CREATE INDEX li_attr_li_idx ON acq.lineitem_attr (lineitem); -CREATE INDEX li_attr_value_idx ON acq.lineitem_attr (attr_value); -CREATE INDEX li_attr_definition_idx ON acq.lineitem_attr (definition); - - --- Seed data - - -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('title','Title of work','//*[@tag="245"]/*[contains("abcmnopr",@code)]'); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('author','Author of work','//*[@tag="100" or @tag="110" or @tag="113"]/*[contains("ad",@code)]'); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('language','Language of work','//*[@tag="240"]/*[@code="l"][1]'); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('pagination','Pagination','//*[@tag="300"]/*[@code="a"][1]'); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove ) VALUES ('isbn','ISBN','//*[@tag="020"]/*[@code="a"]', $r$(?:-|\s.+$)$r$); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove ) VALUES ('issn','ISSN','//*[@tag="022"]/*[@code="a"]', $r$(?:-|\s.+$)$r$); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('price','Price','//*[@tag="020" or @tag="022"]/*[@code="c"][1]'); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('identifier','Identifier','//*[@tag="001"]'); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('publisher','Publisher','//*[@tag="260"]/*[@code="b"][1]'); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('pubdate','Publication Date','//*[@tag="260"]/*[@code="c"][1]'); -INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('edition','Edition','//*[@tag="250"]/*[@code="a"][1]'); - -INSERT INTO acq.lineitem_local_attr_definition ( code, description ) VALUES ('estimated_price', 'Estimated Price'); - - -CREATE TABLE acq.distribution_formula ( - id SERIAL PRIMARY KEY, - owner INT NOT NULL - REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED, - name TEXT NOT NULL, - skip_count INT NOT NULL DEFAULT 0, - CONSTRAINT acqdf_name_once_per_owner UNIQUE (name, owner) -); - -CREATE TABLE acq.distribution_formula_entry ( - id SERIAL PRIMARY KEY, - formula INTEGER NOT NULL REFERENCES acq.distribution_formula(id) - ON DELETE CASCADE - DEFERRABLE INITIALLY DEFERRED, - position INTEGER NOT NULL, - item_count INTEGER NOT NULL, - owning_lib INTEGER REFERENCES actor.org_unit(id) - DEFERRABLE INITIALLY DEFERRED, - location INTEGER REFERENCES asset.copy_location(id), - CONSTRAINT acqdfe_lib_once_per_formula UNIQUE( formula, position ), - CONSTRAINT acqdfe_must_be_somewhere - CHECK( owning_lib IS NOT NULL OR location IS NOT NULL ) -); - -CREATE TABLE acq.fund_tag ( - id SERIAL PRIMARY KEY, - owner INT NOT NULL - REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED, - name TEXT NOT NULL, - CONSTRAINT acqft_tag_once_per_owner UNIQUE (name, owner) -); - -CREATE TABLE acq.fund_tag_map ( - id SERIAL PRIMARY KEY, - fund INTEGER NOT NULL REFERENCES acq.fund(id) - DEFERRABLE INITIALLY DEFERRED, - tag INTEGER REFERENCES acq.fund_tag(id) - ON DELETE CASCADE - DEFERRABLE INITIALLY DEFERRED, - CONSTRAINT acqftm_fund_once_per_tag UNIQUE( fund, tag ) -); - --- Functions - -CREATE TYPE acq.flat_lineitem_holding_subfield AS (lineitem int, holding int, subfield text, data text); -CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$ -DECLARE - counter INT; - lida acq.flat_lineitem_holding_subfield%ROWTYPE; -BEGIN - - SELECT COUNT(*) INTO counter - FROM xpath_table( - 'id', - 'marc', - 'acq.lineitem', - '//*[@tag="' || tag || '"]', - 'id=' || lineitem - ) as t(i int,c text); - - FOR i IN 1 .. counter LOOP - FOR lida IN - SELECT * - FROM ( SELECT id,i,t,v - FROM xpath_table( - 'id', - 'marc', - 'acq.lineitem', - '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' || - '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]', - 'id=' || lineitem - ) as t(id int,t text,v text) - )x - LOOP - RETURN NEXT lida; - END LOOP; - END LOOP; - - RETURN; -END; -$$ LANGUAGE PLPGSQL; - -CREATE TYPE acq.flat_lineitem_detail AS (lineitem int, holding int, attr text, data text); -CREATE OR REPLACE FUNCTION acq.extract_provider_holding_data ( lineitem_i int ) RETURNS SETOF acq.flat_lineitem_detail AS $$ -DECLARE - prov_i INT; - tag_t TEXT; - lida acq.flat_lineitem_detail%ROWTYPE; -BEGIN - SELECT provider INTO prov_i FROM acq.lineitem WHERE id = lineitem_i; - IF NOT FOUND THEN RETURN; END IF; - - SELECT holding_tag INTO tag_t FROM acq.provider WHERE id = prov_i; - IF NOT FOUND OR tag_t IS NULL THEN RETURN; END IF; - - FOR lida IN - SELECT lineitem_i, - h.holding, - a.name, - h.data - FROM acq.extract_holding_attr_table( lineitem_i, tag_t ) h - JOIN acq.provider_holding_subfield_map a USING (subfield) - WHERE a.provider = prov_i - LOOP - RETURN NEXT lida; - END LOOP; - - RETURN; -END; -$$ LANGUAGE PLPGSQL; - --- select * from acq.extract_provider_holding_data(699); - -CREATE OR REPLACE FUNCTION public.extract_acq_marc_field ( BIGINT, TEXT, TEXT) RETURNS TEXT AS $$ - SELECT public.extract_marc_field('acq.lineitem', $1, $2, $3); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $$ -DECLARE - value TEXT; - atype TEXT; - prov INT; - adef RECORD; - xpath_string TEXT; -BEGIN - FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP - - SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid; - - IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN - IF (atype = 'lineitem_provider_attr_definition') THEN - SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id; - CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider; - END IF; - - IF (atype = 'lineitem_provider_attr_definition') THEN - SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id; - ELSIF (atype = 'lineitem_marc_attr_definition') THEN - SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id; - ELSIF (atype = 'lineitem_generated_attr_definition') THEN - SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id; - END IF; - - SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id; - - IF (value IS NOT NULL AND value <> '') THEN - INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value) - VALUES (NEW.id, adef.id, atype, adef.code, value); - END IF; - - END IF; - - END LOOP; - - RETURN NULL; -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION public.cleanup_acq_marc ( ) RETURNS TRIGGER AS $$ -BEGIN - IF TG_OP = 'UPDATE' THEN - DELETE FROM acq.lineitem_attr - WHERE lineitem = OLD.id AND attr_type IN ('lineitem_provider_attr_definition', 'lineitem_marc_attr_definition','lineitem_generated_attr_definition'); - RETURN NEW; - ELSE - DELETE FROM acq.lineitem_attr WHERE lineitem = OLD.id; - RETURN OLD; - END IF; -END; -$$ LANGUAGE PLPGSQL; - -CREATE TRIGGER cleanup_lineitem_trigger - BEFORE UPDATE OR DELETE ON acq.lineitem - FOR EACH ROW EXECUTE PROCEDURE public.cleanup_acq_marc(); - -CREATE TRIGGER ingest_lineitem_trigger - AFTER INSERT OR UPDATE ON acq.lineitem - FOR EACH ROW EXECUTE PROCEDURE public.ingest_acq_marc(); - -CREATE OR REPLACE FUNCTION acq.exchange_ratio ( from_ex TEXT, to_ex TEXT ) RETURNS NUMERIC AS $$ -DECLARE - rat NUMERIC; -BEGIN - IF from_ex = to_ex THEN - RETURN 1.0; - END IF; - - SELECT ratio INTO rat FROM acq.exchange_rate WHERE from_currency = from_ex AND to_currency = to_ex; - - IF FOUND THEN - RETURN rat; - ELSE - SELECT ratio INTO rat FROM acq.exchange_rate WHERE from_currency = to_ex AND to_currency = from_ex; - IF FOUND THEN - RETURN 1.0/rat; - END IF; - END IF; - - RETURN NULL; - -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION acq.exchange_ratio ( TEXT, TEXT, NUMERIC ) RETURNS NUMERIC AS $$ - SELECT $3 * acq.exchange_ratio($1, $2); -$$ LANGUAGE SQL; - -CREATE OR REPLACE VIEW acq.funding_source_credit_total AS - SELECT funding_source, - SUM(amount) AS amount - FROM acq.funding_source_credit - GROUP BY 1; - -CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS - SELECT funding_source, - SUM(amount)::NUMERIC(100,2) AS amount - FROM ( - SELECT funding_source, - SUM(a.amount)::NUMERIC(100,2) AS amount - FROM acq.fund_allocation a - WHERE a.percent IS NULL - GROUP BY 1 - UNION ALL - SELECT funding_source, - SUM( (SELECT SUM(amount) FROM acq.funding_source_credit c WHERE c.funding_source = a.funding_source) * (a.percent/100.0) )::NUMERIC(100,2) AS amount - FROM acq.fund_allocation a - WHERE a.amount IS NULL - GROUP BY 1 - ) x - GROUP BY 1; - -CREATE OR REPLACE VIEW acq.funding_source_balance AS - SELECT COALESCE(c.funding_source, a.funding_source) AS funding_source, - SUM(COALESCE(c.amount,0.0) - COALESCE(a.amount,0.0))::NUMERIC(100,2) AS amount - FROM acq.funding_source_credit_total c - FULL JOIN acq.funding_source_allocation_total a USING (funding_source) - GROUP BY 1; - -CREATE OR REPLACE VIEW acq.fund_allocation_total AS - SELECT fund, - SUM(amount)::NUMERIC(100,2) AS amount - FROM ( - SELECT fund, - SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount - FROM acq.fund_allocation a - JOIN acq.fund f ON (a.fund = f.id) - JOIN acq.funding_source s ON (a.funding_source = s.id) - WHERE a.percent IS NULL - GROUP BY 1 - UNION ALL - SELECT fund, - SUM( (SELECT SUM(amount) FROM acq.funding_source_credit c WHERE c.funding_source = a.funding_source) * acq.exchange_ratio(s.currency_type, f.currency_type) * (a.percent/100.0) )::NUMERIC(100,2) AS amount - FROM acq.fund_allocation a - JOIN acq.fund f ON (a.fund = f.id) - JOIN acq.funding_source s ON (a.funding_source = s.id) - WHERE a.amount IS NULL - GROUP BY 1 - ) x - GROUP BY 1; - -CREATE OR REPLACE VIEW acq.fund_debit_total AS - SELECT id AS fund, - encumbrance, - SUM(amount) AS amount - FROM acq.fund_debit - GROUP BY 1,2; - -CREATE OR REPLACE VIEW acq.fund_encumbrance_total AS - SELECT fund, - SUM(amount) AS amount - FROM acq.fund_debit_total - WHERE encumbrance IS TRUE - GROUP BY 1; - -CREATE OR REPLACE VIEW acq.fund_spent_total AS - SELECT fund, - SUM(amount) AS amount - FROM acq.fund_debit_total - WHERE encumbrance IS FALSE - GROUP BY 1; - -CREATE OR REPLACE VIEW acq.fund_combined_balance AS - SELECT c.fund, - c.amount - COALESCE(d.amount,0.0) AS amount - FROM acq.fund_allocation_total c - LEFT JOIN acq.fund_debit_total d USING (fund); - -CREATE OR REPLACE VIEW acq.fund_spent_balance AS - SELECT c.fund, - c.amount - COALESCE(d.amount,0.0) AS amount - FROM acq.fund_allocation_total c - LEFT JOIN acq.fund_spent_total d USING (fund); - -CREATE SCHEMA serial; - -CREATE TABLE serial.record_entry ( - id BIGSERIAL PRIMARY KEY, - record BIGINT REFERENCES biblio.record_entry (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - owning_lib INT NOT NULL DEFAULT 1 REFERENCES actor.org_unit (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - creator INT NOT NULL DEFAULT 1, - editor INT NOT NULL DEFAULT 1, - source INT, - create_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - edit_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - active BOOL NOT NULL DEFAULT TRUE, - deleted BOOL NOT NULL DEFAULT FALSE, - marc TEXT NOT NULL, - last_xact_id TEXT NOT NULL -); -CREATE INDEX serial_record_entry_creator_idx ON serial.record_entry ( creator ); -CREATE INDEX serial_record_entry_editor_idx ON serial.record_entry ( editor ); -CREATE INDEX serial_record_entry_owning_lib_idx ON serial.record_entry ( owning_lib, deleted ); - -CREATE TABLE serial.subscription ( - id SERIAL PRIMARY KEY, - callnumber BIGINT REFERENCES asset.call_number (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - uri INT REFERENCES asset.uri (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, - start_date DATE NOT NULL, - end_date DATE -- interpret NULL as current subscription -); - -CREATE TABLE serial.binding_unit ( - id SERIAL PRIMARY KEY, - subscription INT NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - label TEXT NOT NULL, - CONSTRAINT bu_label_once_per_sub UNIQUE (subscription, label) -); - -CREATE TABLE serial.issuance ( - id SERIAL PRIMARY KEY, - subscription INT NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - target_copy BIGINT REFERENCES asset.copy (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - location BIGINT REFERENCES asset.copy_location(id) DEFERRABLE INITIALLY DEFERRED, - binding_unit INT REFERENCES serial.binding_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - label TEXT -); - -CREATE TABLE serial.bib_summary ( - id SERIAL PRIMARY KEY, - subscription INT UNIQUE NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - generated_coverage TEXT NOT NULL, - textual_holdings TEXT -); - -CREATE TABLE serial.sup_summary ( - id SERIAL PRIMARY KEY, - subscription INT UNIQUE NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - generated_coverage TEXT NOT NULL, - textual_holdings TEXT -); - -CREATE TABLE serial.index_summary ( - id SERIAL PRIMARY KEY, - subscription INT UNIQUE NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - generated_coverage TEXT NOT NULL, - textual_holdings TEXT -); - - -CREATE OR REPLACE FUNCTION search.staged_fts ( - - param_search_ou INT, - param_depth INT, - param_searches TEXT, -- JSON hash, to be turned into a resultset via search.parse_search_args - param_statuses INT[], - param_locations INT[], - param_audience TEXT[], - param_language TEXT[], - param_lit_form TEXT[], - param_types TEXT[], - param_forms TEXT[], - param_vformats TEXT[], - param_bib_level TEXT[], - param_before TEXT, - param_after TEXT, - param_during TEXT, - param_between TEXT[], - param_pref_lang TEXT, - param_pref_lang_multiplier REAL, - param_sort TEXT, - param_sort_desc BOOL, - metarecord BOOL, - staff BOOL, - param_rel_limit INT, - param_chk_limit INT, - param_skip_chk INT - -) RETURNS SETOF search.search_result AS $func$ -DECLARE - - current_res search.search_result%ROWTYPE; - query_part search.search_args%ROWTYPE; - phrase_query_part search.search_args%ROWTYPE; - rank_adjust_id INT; - core_rel_limit INT; - core_chk_limit INT; - core_skip_chk INT; - rank_adjust search.relevance_adjustment%ROWTYPE; - query_table TEXT; - tmp_text TEXT; - tmp_int INT; - current_rank TEXT; - ranks TEXT[] := '{}'; - query_table_alias TEXT; - from_alias_array TEXT[] := '{}'; - used_ranks TEXT[] := '{}'; - mb_field INT; - mb_field_list INT[]; - search_org_list INT[]; - select_clause TEXT := 'SELECT'; - from_clause TEXT := ' FROM metabib.metarecord_source_map m JOIN metabib.rec_descriptor mrd ON (m.source = mrd.record) '; - where_clause TEXT := ' WHERE 1=1 '; - mrd_used BOOL := FALSE; - sort_desc BOOL := FALSE; - - core_result RECORD; - core_cursor REFCURSOR; - core_rel_query TEXT; - vis_limit_query TEXT; - inner_where_clause TEXT; - - total_count INT := 0; - check_count INT := 0; - deleted_count INT := 0; - visible_count INT := 0; - excluded_count INT := 0; - -BEGIN - - core_rel_limit := COALESCE( param_rel_limit, 25000 ); - core_chk_limit := COALESCE( param_chk_limit, 1000 ); - core_skip_chk := COALESCE( param_skip_chk, 1 ); - - IF metarecord THEN - select_clause := select_clause || ' m.metarecord as id, array_accum(distinct m.source) as records,'; - ELSE - select_clause := select_clause || ' m.source as id, array_accum(distinct m.source) as records,'; - END IF; - - -- first we need to construct the base query - FOR query_part IN SELECT * FROM search.parse_search_args(param_searches) WHERE term_type = 'fts_query' LOOP - - inner_where_clause := 'index_vector @@ ' || query_part.term; - - IF query_part.field_name IS NOT NULL THEN - - SELECT id INTO mb_field - FROM config.metabib_field - WHERE field_class = query_part.field_class - AND name = query_part.field_name; - - IF FOUND THEN - inner_where_clause := inner_where_clause || - ' AND ' || 'field = ' || mb_field; - END IF; - - END IF; - - -- moving on to the rank ... - SELECT * INTO query_part - FROM search.parse_search_args(param_searches) - WHERE term_type = 'fts_rank' - AND table_alias = query_part.table_alias; - - current_rank := query_part.term || ' * ' || query_part.table_alias || '_weight.weight'; - - IF query_part.field_name IS NOT NULL THEN - - SELECT array_accum(distinct id) INTO mb_field_list - FROM config.metabib_field - WHERE field_class = query_part.field_class - AND name = query_part.field_name; - - ELSE - - SELECT array_accum(distinct id) INTO mb_field_list - FROM config.metabib_field - WHERE field_class = query_part.field_class; - - END IF; - - FOR rank_adjust IN SELECT * FROM search.relevance_adjustment WHERE active AND field IN ( SELECT * FROM search.explode_array( mb_field_list ) ) LOOP - - IF NOT rank_adjust.bump_type = ANY (used_ranks) THEN - - IF rank_adjust.bump_type = 'first_word' THEN - SELECT term INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word' - ORDER BY id - LIMIT 1; - - tmp_text := query_part.table_alias || '.value ILIKE ' || quote_literal( tmp_text || '%' ); - - ELSIF rank_adjust.bump_type = 'word_order' THEN - SELECT array_to_string( array_accum( term ), '%' ) INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word'; - - tmp_text := query_part.table_alias || '.value ILIKE ' || quote_literal( '%' || tmp_text || '%' ); - - ELSIF rank_adjust.bump_type = 'full_match' THEN - SELECT array_to_string( array_accum( term ), E'\\s+' ) INTO tmp_text - FROM search.parse_search_args(param_searches) - WHERE table_alias = query_part.table_alias AND term_type = 'word'; - - tmp_text := query_part.table_alias || '.value ~ ' || quote_literal( '^' || tmp_text || E'\\W*$' ); - - END IF; - - - IF tmp_text IS NOT NULL THEN - current_rank := current_rank || ' * ( CASE WHEN ' || tmp_text || - ' THEN ' || rank_adjust.multiplier || '::REAL ELSE 1.0 END )'; - END IF; - - used_ranks := array_append( used_ranks, rank_adjust.bump_type ); - - END IF; - - END LOOP; - - ranks := array_append( ranks, current_rank ); - used_ranks := '{}'; - - FOR phrase_query_part IN - SELECT * - FROM search.parse_search_args(param_searches) - WHERE term_type = 'phrase' - AND table_alias = query_part.table_alias LOOP - - tmp_text := replace( phrase_query_part.term, '*', E'\\*' ); - tmp_text := replace( tmp_text, '?', E'\\?' ); - tmp_text := replace( tmp_text, '+', E'\\+' ); - tmp_text := replace( tmp_text, '|', E'\\|' ); - tmp_text := replace( tmp_text, '(', E'\\(' ); - tmp_text := replace( tmp_text, ')', E'\\)' ); - tmp_text := replace( tmp_text, '[', E'\\[' ); - tmp_text := replace( tmp_text, ']', E'\\]' ); - - inner_where_clause := inner_where_clause || ' AND ' || 'value ~* ' || quote_literal( E'(^|\\W+)' || regexp_replace(tmp_text, E'\\s+',E'\\\\s+','g') || E'(\\W+|\$)' ); - - END LOOP; - - query_table := search.pick_table(query_part.field_class); - - from_clause := from_clause || - ' JOIN ( SELECT * FROM ' || query_table || ' WHERE ' || inner_where_clause || - CASE WHEN core_rel_limit > 0 THEN ' LIMIT ' || core_rel_limit::TEXT ELSE '' END || ' ) AS ' || query_part.table_alias || - ' ON ( m.source = ' || query_part.table_alias || '.source )' || - ' JOIN config.metabib_field AS ' || query_part.table_alias || '_weight' || - ' ON ( ' || query_part.table_alias || '.field = ' || query_part.table_alias || '_weight.id AND ' || query_part.table_alias || '_weight.search_field)'; - - from_alias_array := array_append(from_alias_array, query_part.table_alias); - - END LOOP; - - IF param_pref_lang IS NOT NULL AND param_pref_lang_multiplier IS NOT NULL THEN - current_rank := ' CASE WHEN mrd.item_lang = ' || quote_literal( param_pref_lang ) || - ' THEN ' || param_pref_lang_multiplier || '::REAL ELSE 1.0 END '; - - -- ranks := array_append( ranks, current_rank ); - END IF; - - current_rank := ' AVG( ( (' || array_to_string( ranks, ') + (' ) || ') ) * ' || current_rank || ' ) '; - select_clause := select_clause || current_rank || ' AS rel,'; - - sort_desc = param_sort_desc; - - IF param_sort = 'pubdate' THEN - - tmp_text := '999999'; - IF param_sort_desc THEN tmp_text := '0'; END IF; - - current_rank := $$ COALESCE( FIRST(NULLIF(REGEXP_REPLACE(mrd.date1, E'\\D+', '9', 'g'),'')), $$ || quote_literal(tmp_text) || $$ )::INT $$; - - ELSIF param_sort = 'title' THEN - - tmp_text := 'zzzzzz'; - IF param_sort_desc THEN tmp_text := ' '; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\d+'),'0')::INT + 1 )) - FROM metabib.full_rec frt - WHERE frt.record = m.source - AND frt.tag = '245' - AND frt.subfield = 'a' - LIMIT 1 - )),$$ || quote_literal(tmp_text) || $$)) - $$; - - ELSIF param_sort = 'author' THEN - - tmp_text := 'zzzzzz'; - IF param_sort_desc THEN tmp_text := ' '; END IF; - - current_rank := $$ - ( COALESCE( FIRST (( - SELECT LTRIM(fra.value) - FROM metabib.full_rec fra - WHERE fra.record = m.source - AND fra.tag LIKE '1%' - AND fra.subfield = 'a' - ORDER BY fra.tag::text::int - LIMIT 1 - )),$$ || quote_literal(tmp_text) || $$)) - $$; - - ELSIF param_sort = 'create_date' THEN - current_rank := $$( FIRST (( SELECT create_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )$$; - ELSIF param_sort = 'edit_date' THEN - current_rank := $$( FIRST (( SELECT edit_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )$$; - ELSE - sort_desc := NOT COALESCE(param_sort_desc, FALSE); - END IF; - - select_clause := select_clause || current_rank || ' AS rank'; - - -- now add the other qualifiers - IF param_audience IS NOT NULL AND array_upper(param_audience, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.audience IN ('$$ || array_to_string(param_audience, $$','$$) || $$') $$; - END IF; - - IF param_language IS NOT NULL AND array_upper(param_language, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_lang IN ('$$ || array_to_string(param_language, $$','$$) || $$') $$; - END IF; - - IF param_lit_form IS NOT NULL AND array_upper(param_lit_form, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.lit_form IN ('$$ || array_to_string(param_lit_form, $$','$$) || $$') $$; - END IF; - - IF param_types IS NOT NULL AND array_upper(param_types, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_type IN ('$$ || array_to_string(param_types, $$','$$) || $$') $$; - END IF; - - IF param_forms IS NOT NULL AND array_upper(param_forms, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.item_form IN ('$$ || array_to_string(param_forms, $$','$$) || $$') $$; - END IF; - - IF param_vformats IS NOT NULL AND array_upper(param_vformats, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.vr_format IN ('$$ || array_to_string(param_vformats, $$','$$) || $$') $$; - END IF; - - IF param_bib_level IS NOT NULL AND array_upper(param_bib_level, 1) > 0 THEN - where_clause = where_clause || $$ AND mrd.bib_level IN ('$$ || array_to_string(param_bib_level, $$','$$) || $$') $$; - END IF; - - IF param_before IS NOT NULL AND param_before <> '' THEN - where_clause = where_clause || $$ AND mrd.date1 <= $$ || quote_literal(param_before) || ' '; - END IF; - - IF param_after IS NOT NULL AND param_after <> '' THEN - where_clause = where_clause || $$ AND mrd.date1 >= $$ || quote_literal(param_after) || ' '; - END IF; - - IF param_during IS NOT NULL AND param_during <> '' THEN - where_clause = where_clause || $$ AND $$ || quote_literal(param_during) || $$ BETWEEN mrd.date1 AND mrd.date2 $$; - END IF; - - IF param_between IS NOT NULL AND array_upper(param_between, 1) > 1 THEN - where_clause = where_clause || $$ AND mrd.date1 BETWEEN '$$ || array_to_string(param_between, $$' AND '$$) || $$' $$; - END IF; - - core_rel_query := select_clause || from_clause || where_clause || - ' GROUP BY 1 ORDER BY 4' || CASE WHEN sort_desc THEN ' DESC' ELSE ' ASC' END || ';'; - --RAISE NOTICE 'Base Query: %', core_rel_query; - - IF param_search_ou > 0 THEN - IF param_depth IS NOT NULL THEN - SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth ); - ELSE - SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou ); - END IF; - ELSIF param_search_ou < 0 THEN - SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou; - ELSIF param_search_ou = 0 THEN - -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure. - END IF; - - OPEN core_cursor FOR EXECUTE core_rel_query; - - LOOP - - FETCH core_cursor INTO core_result; - EXIT WHEN NOT FOUND; - - - IF total_count % 1000 = 0 THEN - -- RAISE NOTICE ' % total, % checked so far ... ', total_count, check_count; - END IF; - - IF core_chk_limit > 0 AND total_count - core_skip_chk + 1 >= core_chk_limit THEN - total_count := total_count + 1; - CONTINUE; - END IF; - - total_count := total_count + 1; - - CONTINUE WHEN param_skip_chk IS NOT NULL and total_count < param_skip_chk; - - check_count := check_count + 1; - - PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all deleted ... ', core_result.records; - deleted_count := deleted_count + 1; - CONTINUE; - END IF; - - PERFORM 1 - FROM biblio.record_entry b - JOIN config.bib_source s ON (b.source = s.id) - WHERE s.transcendant - AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) ); - - IF FOUND THEN - -- RAISE NOTICE ' % were all transcendant ... ', core_result.records; - visible_count := visible_count + 1; - - current_res.id = core_result.id; - current_res.rel = core_result.rel; - - tmp_int := 1; - IF metarecord THEN - SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; - END IF; - - IF tmp_int = 1 THEN - current_res.record = core_result.records[1]; - ELSE - current_res.record = NULL; - END IF; - - RETURN NEXT current_res; - - CONTINUE; - END IF; - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.uri_call_number_map map ON (map.call_number = cn.id) - JOIN asset.uri uri ON (map.uri = uri.id) - WHERE NOT cn.deleted - AND cn.label = '##URI##' - AND uri.active - AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - LIMIT 1; - - IF FOUND THEN - -- RAISE NOTICE ' % have at least one URI ... ', core_result.records; - visible_count := visible_count + 1; - - current_res.id = core_result.id; - current_res.rel = core_result.rel; - - tmp_int := 1; - IF metarecord THEN - SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; - END IF; - - IF tmp_int = 1 THEN - current_res.record = core_result.records[1]; - ELSE - current_res.record = NULL; - END IF; - - RETURN NEXT current_res; - - CONTINUE; - END IF; - - IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - IF staff IS NULL OR NOT staff THEN - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - JOIN actor.org_unit a ON (cp.circ_lib = a.id) - JOIN asset.copy_location cl ON (cp.location = cl.id) - JOIN config.copy_status cs ON (cp.status = cs.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cs.opac_visible - AND cl.opac_visible - AND cp.opac_visible - AND a.opac_visible - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF NOT FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - ELSE - - PERFORM 1 - FROM asset.call_number cn - JOIN asset.copy cp ON (cp.call_number = cn.id) - JOIN actor.org_unit a ON (cp.circ_lib = a.id) - JOIN asset.copy_location cl ON (cp.location = cl.id) - WHERE NOT cn.deleted - AND NOT cp.deleted - AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) ) - AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF NOT FOUND THEN - - PERFORM 1 - FROM asset.call_number cn - WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) ) - LIMIT 1; - - IF FOUND THEN - -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records; - excluded_count := excluded_count + 1; - CONTINUE; - END IF; - - END IF; - - END IF; - - visible_count := visible_count + 1; - - current_res.id = core_result.id; - current_res.rel = core_result.rel; - - tmp_int := 1; - IF metarecord THEN - SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id; - END IF; - - IF tmp_int = 1 THEN - current_res.record = core_result.records[1]; - ELSE - current_res.record = NULL; - END IF; - - RETURN NEXT current_res; - - IF visible_count % 1000 = 0 THEN - -- RAISE NOTICE ' % visible so far ... ', visible_count; - END IF; - - END LOOP; - - current_res.id = NULL; - current_res.rel = NULL; - current_res.record = NULL; - current_res.total = total_count; - current_res.checked = check_count; - current_res.deleted = deleted_count; - current_res.visible = visible_count; - current_res.excluded = excluded_count; - - CLOSE core_cursor; - - RETURN NEXT current_res; - -END; -$func$ LANGUAGE PLPGSQL; - - -CREATE TABLE config.idl_field_doc ( - id BIGSERIAL PRIMARY KEY, - fm_class TEXT NOT NULL, - field TEXT NOT NULL, - owner INT NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - string TEXT NOT NULL -); -CREATE UNIQUE INDEX idl_field_doc_identity ON config.idl_field_doc (fm_class,field,owner); - - -INSERT INTO config.xml_transform VALUES ( 'mods33', 'http://www.loc.gov/mods/v3', 'mods33', ''); - -INSERT INTO container.copy_bucket_type (code,label) VALUES ('misc', 'Miscellaneous'); -INSERT INTO container.copy_bucket_type (code,label) VALUES ('staff_client', 'General Staff Client container'); -INSERT INTO container.call_number_bucket_type (code,label) VALUES ('misc', 'Miscellaneous'); -INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('misc', 'Miscellaneous'); -INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('staff_client', 'General Staff Client container'); -INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('bookbag', 'Book Bag'); -INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('reading_list', 'Reading List'); - -INSERT INTO container.user_bucket_type (code,label) VALUES ('misc', 'Miscellaneous'); -INSERT INTO container.user_bucket_type (code,label) VALUES ('folks', 'Friends'); -INSERT INTO container.user_bucket_type (code,label) VALUES ('folks:pub_book_bags.view', 'List Published Book Bags'); -INSERT INTO container.user_bucket_type (code,label) VALUES ('folks:pub_book_bags.add', 'Add to Published Book Bags'); -INSERT INTO container.user_bucket_type (code,label) VALUES ('folks:circ.view', 'View Circulations'); -INSERT INTO container.user_bucket_type (code,label) VALUES ('folks:circ.renew', 'Renew Circulations'); -INSERT INTO container.user_bucket_type (code,label) VALUES ('folks:circ.checkout', 'Checkout Items'); -INSERT INTO container.user_bucket_type (code,label) VALUES ('folks:hold.view', 'View Holds'); -INSERT INTO container.user_bucket_type (code,label) VALUES ('folks:hold.cancel', 'Cancel Holds'); - - - -CREATE SCHEMA action_trigger; - -CREATE TABLE action_trigger.hook ( - key TEXT PRIMARY KEY, - core_type TEXT NOT NULL, - description TEXT, - passive BOOL NOT NULL DEFAULT FALSE -); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('checkout','circ','Item checked out to user'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('checkin','circ','Item checked in'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('lost','circ','Circulating Item marked Lost'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('lost.found','circ','Lost Circulating Item checked in'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('lost.auto','circ','Circulating Item automatically marked lost'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('claims_returned','circ','Circulating Item marked Claims Returned'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('claims_returned.found','circ','Claims Returned Circulating Item is checked in'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('missing','acp','Item marked Missing'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('missing.found','acp','Missing Item checked in'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('transit.start','acp','An Item is placed into transit'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('transit.finish','acp','An Item is received from a transit'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('hold_request.success','ahr','A hold is succefully placed'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('hold_request.failure','ahr','A hold is attempted by not succefully placed'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('hold.capture','ahr','A targeted Item is captured for a hold'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('hold.available','ahr','A held item is ready for pickup'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('hold_transit.start','ahtc','A hold-captured Item is placed into transit'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('hold_transit.finish','ahtc','A hold-captured Item is received from a transit'); -INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ('checkout.due','circ','Checked out Item is Due',TRUE); -INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ('penalty.PATRON_EXCEEDS_FINES','ausp','Patron has exceeded allowed fines',TRUE); -INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ('penalty.PATRON_EXCEEDS_OVERDUE_COUNT','ausp','Patron has exceeded allowed overdue count',TRUE); -INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ('penalty.PATRON_EXCEEDS_CHECKOUT_COUNT','ausp','Patron has exceeded allowed checkout count',TRUE); -INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ('penalty.PATRON_EXCEEDS_COLLECTIONS_WARNING','ausp','Patron has exceeded maximum fine amount for collections department warning',TRUE); -INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ('format.po.jedi','acqpo','Formats a Purchase Order as a JEDI document',TRUE); -INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ('format.po.html','acqpo','Formats a Purchase Order as an HTML document',TRUE); -INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ('format.po.pdf','acqpo','Formats a Purchase Order as a PDF document',TRUE); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('damaged','acp','Item marked damaged'); -INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('checkout.damaged','circ','A circulating item is marked damaged and the patron is fined'); --- and much more, I'm sure - --- Specialized collection modules. Given an FM object, gather some info and return a scalar or ref. -CREATE TABLE action_trigger.collector ( - module TEXT PRIMARY KEY, -- All live under the OpenILS::Trigger::Collector:: namespace - description TEXT -); -INSERT INTO action_trigger.collector (module,description) VALUES ('fourty_two','Returns the answer to life, the universe and everything'); ---INSERT INTO action_trigger.collector (module,description) VALUES ('CircCountsByCircMod','Count of Circulations for a User, broken down by circulation modifier'); - --- Simple tests on an FM object from hook.core_type to test for "should we still do this." -CREATE TABLE action_trigger.validator ( - module TEXT PRIMARY KEY, -- All live under the OpenILS::Trigger::Validator:: namespace - description TEXT -); -INSERT INTO action_trigger.validator (module,description) VALUES ('fourty_two','Returns the answer to life, the universe and everything'); -INSERT INTO action_trigger.validator (module,description) VALUES ('NOOP_True','Always returns true -- validation always passes'); -INSERT INTO action_trigger.validator (module,description) VALUES ('NOOP_False','Always returns false -- validation always fails'); -INSERT INTO action_trigger.validator (module,description) VALUES ('CircIsOpen','Check that the circulation is still open'); -INSERT INTO action_trigger.validator (module,description) VALUES ('HoldIsAvailable','Check that an item is on the hold shelf'); -INSERT INTO action_trigger.validator (module,description) VALUES ('CircIsOverdue','Check that the circulation is overdue'); - --- After an event passes validation (action_trigger.validator), the reactor processes it. -CREATE TABLE action_trigger.reactor ( - module TEXT PRIMARY KEY, -- All live under the OpenILS::Trigger::Reactor:: namespace - description TEXT -); -INSERT INTO action_trigger.reactor (module,description) VALUES ('fourty_two','Returns the answer to life, the universe and everything'); -INSERT INTO action_trigger.reactor (module,description) VALUES ('NOOP_True','Always returns true -- reaction always passes'); -INSERT INTO action_trigger.reactor (module,description) VALUES ('NOOP_False','Always returns false -- reaction always fails'); -INSERT INTO action_trigger.reactor (module,description) VALUES ('SendEmail','Send an email based on a user-defined template'); -INSERT INTO action_trigger.reactor (module,description) VALUES ('MarkItemLost','Marks a circulation and associated item as lost'); -INSERT INTO action_trigger.reactor (module,description) VALUES ('ApplyCircFee','Applies a billing with a pre-defined amount to a circulation'); -INSERT INTO action_trigger.reactor (module,description) VALUES ('ProcessTemplate', 'Processes the configured template'); - --- After an event is reacted to (either succes or failure) a cleanup module is run against the resulting environment -CREATE TABLE action_trigger.cleanup ( - module TEXT PRIMARY KEY, -- All live under the OpenILS::Trigger::Cleanup:: namespace - description TEXT -); -INSERT INTO action_trigger.cleanup (module,description) VALUES ('fourty_two','Returns the answer to life, the universe and everything'); -INSERT INTO action_trigger.cleanup (module,description) VALUES ('NOOP_True','Always returns true -- cleanup always passes'); -INSERT INTO action_trigger.cleanup (module,description) VALUES ('NOOP_False','Always returns false -- cleanup always fails'); -INSERT INTO action_trigger.cleanup (module,description) VALUES ('ClearAllPending','Remove all future, pending notifications for this target'); - -CREATE TABLE action_trigger.event_definition ( - id SERIAL PRIMARY KEY, - active BOOL NOT NULL DEFAULT TRUE, - owner INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, - name TEXT NOT NULL, - hook TEXT NOT NULL REFERENCES action_trigger.hook (key) DEFERRABLE INITIALLY DEFERRED, - validator TEXT NOT NULL REFERENCES action_trigger.validator (module) DEFERRABLE INITIALLY DEFERRED, - reactor TEXT NOT NULL REFERENCES action_trigger.reactor (module) DEFERRABLE INITIALLY DEFERRED, - cleanup_success TEXT REFERENCES action_trigger.cleanup (module) DEFERRABLE INITIALLY DEFERRED, - cleanup_failure TEXT REFERENCES action_trigger.cleanup (module) DEFERRABLE INITIALLY DEFERRED, - delay INTERVAL NOT NULL DEFAULT '5 minutes', - delay_field TEXT, -- for instance, xact_start on a circ hook ... look for fields on hook.core_type where datatype=timestamp? If not set, delay from now() - group_field TEXT, -- field from this.hook.core_type to batch event targets together on, fed into reactor a group at a time. - template TEXT, -- the TT block. will have an 'environment' hash (or array of hashes, grouped events) built up by validator and collector(s), which can be modified. - CONSTRAINT ev_def_owner_hook_val_react_clean_delay_once UNIQUE (owner, hook, validator, reactor, delay, delay_field), - CONSTRAINT ev_def_name_owner_once UNIQUE (owner, name) -); - -CREATE TABLE action_trigger.environment ( - id SERIAL PRIMARY KEY, - event_def INT NOT NULL REFERENCES action_trigger.event_definition (id) DEFERRABLE INITIALLY DEFERRED, - path TEXT, -- fields to flesh. given a hook with a core_type of circ, imagine circ_lib.parent_ou expanding to - -- {flesh: 2, flesh_fields: {circ: ['circ_lib'], aou: ['parent_ou']}} ... default is to flesh all - -- at flesh depth 1 - collector TEXT REFERENCES action_trigger.collector (module) DEFERRABLE INITIALLY DEFERRED, -- if set, given the object at 'path', return some data - -- to be stashed at environment.