LP 1497307: Fix 2.8.4 to 2.9.0 upgrade script.
authorJason Stephenson <jstephenson@mvlc.org>
Fri, 18 Sep 2015 15:06:07 +0000 (11:06 -0400)
committerJason Stephenson <jstephenson@mvlc.org>
Fri, 18 Sep 2015 19:18:37 +0000 (15:18 -0400)
Chris Sharp noticed another ERROR: cannot ALTER TABLE
"record_attr_definition" because it has pending trigger events
with the 2.8.4 to 2.9.0 upgrade script.  I have rearranged the
transactions so that there are now 3.

1. One for the initial inserts and other changes up to 0937.

2. One for the two alter table statements in 0937.

3. One for 0938 to the end (0944).

This resolves the issue and works for me on an upgrade from a
clean 2.8.4 installation.

Signed-off-by: Jason Stephenson <jstephenson@mvlc.org>
Signed-off-by: Chris Sharp <csharp@georgialibraries.org>
Open-ILS/src/sql/Pg/version-upgrade/2.8.4-2.9.0-upgrade-db.sql

index 61de588..a61e7a9 100644 (file)
@@ -22197,6 +22197,9 @@ INSERT INTO permission.perm_list ( code, description ) VALUES
     'Allow administration of MARC tag tables', 'ppl', 'description'
  ));
 
+COMMIT;
+
+BEGIN;
 
 SELECT evergreen.upgrade_deps_block_check('0937', :eg_version);
 
@@ -22206,726 +22209,30 @@ ALTER TABLE config.record_attr_definition
 ALTER TABLE config.coded_value_map
     ADD COLUMN concept_uri TEXT;
 
+COMMIT;
 
-SELECT evergreen.upgrade_deps_block_check('0939', :eg_version);
-
---create hook for actor.usr.expire_date
-INSERT INTO action_trigger.hook (key, core_type, description, passive)
-    VALUES ('au.expired', 'au', 'A user account has expired', 't');
-       
---SQL to create event definition for 30 day account pre-expiration notice
---Inactive, owned by top of org tree by default.  Modify to suit needs.
---Can set reactor to 'ProcessTemplate' for testing.  Will generate emails in DB, but not actually send.
-
-INSERT INTO action_trigger.event_definition (
-    active, owner, name, hook, 
-    validator, reactor, delay, delay_field,
-    max_delay, repeat_delay, template
-)  VALUES (
-    'f', '1', '30 Day Account Expiration Courtesy Notice', 'au.expired',
-    'NOOP_True', 'SendEmail', '-30 days', 'expire_date',
-    '-29 days', '30 days',
-$$
-[%- USE date -%]
-[%- user = target -%]
-[%- lib = target.home_ou -%]
-To: [%- params.recipient_email || user.email %]
-From: [%- helpers.get_org_setting(target.home_ou.id, 'org.bounced_emails') || lib.email || params.sender_email || default_sender %]
-Reply-To: [%- helpers.get_org_setting(target.home_ou.id, 'org.bounced_emails') || lib.email || params.sender_email || default_sender %]
-Subject: Courtesy Notice - Library Account Expiration in 30 days
-Auto-Submitted: auto-generated
-
-Dear [% user.first_given_name %] [% user.family_name %],
-
-Our records indicate your library account is due to expire in 30 days.  Please visit your local library at your convenience to renew your account in order to avoid a disruption in access to library service.
-
-Sincerely,
-[% lib.name %]
-
-Contact your library for more information:
-
-[% lib.name %]
-[%- SET addr = lib.mailing_address -%]
-[%- IF !addr -%] [%- SET addr = lib.billing_address -%] [%- END %]
-[% addr.street1 %] [% addr.street2 %]
-[% addr.city %], [% addr.state %]
-[% addr.post_code %]
-[% lib.phone %]
-
-$$);
-       
---insert environment values
-INSERT INTO action_trigger.environment (event_def, path) VALUES
-    (CURRVAL('action_trigger.event_definition_id_seq'), 'home_ou.mailing_address'),
-    (CURRVAL('action_trigger.event_definition_id_seq'), 'home_ou.billing_address');
-       
-
-SELECT evergreen.upgrade_deps_block_check('0942', :eg_version);
-
-CREATE OR REPLACE FUNCTION authority.extract_thesaurus( marcxml TEXT ) RETURNS TEXT AS $func$
-DECLARE
-    thes_code TEXT;
-BEGIN
-    thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
-    IF thes_code IS NULL THEN
-        thes_code := '|';
-    ELSIF thes_code = 'z' THEN
-        thes_code := COALESCE( oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml), '' );
-    END IF;
-    RETURN thes_code;
-END;
-$func$ LANGUAGE PLPGSQL STABLE STRICT;
-
--- Intended to be used in a unique index on authority.record_entry like so:
--- CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
---   ON authority.record_entry (heading)
---   WHERE deleted IS FALSE or deleted = FALSE;
-CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT, no_thesaurus BOOL ) RETURNS TEXT AS $func$
-DECLARE
-    acsaf           authority.control_set_authority_field%ROWTYPE;
-    tag_used        TEXT;
-    nfi_used        TEXT;
-    sf              TEXT;
-    sf_node         TEXT;
-    tag_node        TEXT;
-    thes_code       TEXT;
-    cset            INT;
-    heading_text    TEXT;
-    tmp_text        TEXT;
-    first_sf        BOOL;
-    auth_id         INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT; 
-BEGIN
-    SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
-
-    IF cset IS NULL THEN
-        SELECT  control_set INTO cset
-          FROM  authority.control_set_authority_field
-          WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
-          LIMIT 1;
-    END IF;
-
-    heading_text := '';
-    FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
-        tag_used := acsaf.tag;
-        nfi_used := acsaf.nfi;
-        first_sf := TRUE;
-
-        FOR tag_node IN SELECT unnest(oils_xpath('//*[@tag="'||tag_used||'"]',marcxml)) LOOP
-            FOR sf_node IN SELECT unnest(oils_xpath('./*[contains("'||acsaf.sf_list||'",@code)]',tag_node)) LOOP
-
-                tmp_text := oils_xpath_string('.', sf_node);
-                sf := oils_xpath_string('./@code', sf_node);
-
-                IF first_sf AND tmp_text IS NOT NULL AND nfi_used IS NOT NULL THEN
-
-                    tmp_text := SUBSTRING(
-                        tmp_text FROM
-                        COALESCE(
-                            NULLIF(
-                                REGEXP_REPLACE(
-                                    oils_xpath_string('./@ind'||nfi_used, tag_node),
-                                    $$\D+$$,
-                                    '',
-                                    'g'
-                                ),
-                                ''
-                            )::INT,
-                            0
-                        ) + 1
-                    );
-
-                END IF;
-
-                first_sf := FALSE;
-
-                IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
-                    heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
-                END IF;
-            END LOOP;
-
-            EXIT WHEN heading_text <> '';
-        END LOOP;
-
-        EXIT WHEN heading_text <> '';
-    END LOOP;
-
-    IF heading_text <> '' THEN
-        IF no_thesaurus IS TRUE THEN
-            heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
-        ELSE
-            thes_code := authority.extract_thesaurus(marcxml);
-            heading_text := tag_used || '_' || COALESCE(nfi_used,'-') || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
-        END IF;
-    ELSE
-        heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
-    END IF;
-
-    RETURN heading_text;
-END;
-$func$ LANGUAGE PLPGSQL STABLE STRICT;
-
-ALTER TABLE authority.simple_heading ADD COLUMN thesaurus TEXT;
-CREATE INDEX authority_simple_heading_thesaurus_idx ON authority.simple_heading (thesaurus);
-
-CREATE OR REPLACE FUNCTION authority.simple_heading_set( marcxml TEXT ) RETURNS SETOF authority.simple_heading AS $func$
-DECLARE
-    res             authority.simple_heading%ROWTYPE;
-    acsaf           authority.control_set_authority_field%ROWTYPE;
-    tag_used        TEXT;
-    nfi_used        TEXT;
-    sf              TEXT;
-    cset            INT;
-    heading_text    TEXT;
-    joiner_text     TEXT;
-    sort_text       TEXT;
-    tmp_text        TEXT;
-    tmp_xml         TEXT;
-    first_sf        BOOL;
-    auth_id         INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT; 
-BEGIN
-
-    SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
-
-    IF cset IS NULL THEN
-        SELECT  control_set INTO cset
-          FROM  authority.control_set_authority_field
-          WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
-          LIMIT 1;
-    END IF;
-
-    res.record := auth_id;
-    res.thesaurus := authority.extract_thesaurus(marcxml);
-
-    FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
-
-        res.atag := acsaf.id;
-        tag_used := acsaf.tag;
-        nfi_used := acsaf.nfi;
-        joiner_text := COALESCE(acsaf.joiner, ' ');
-
-        FOR tmp_xml IN SELECT UNNEST(XPATH('//*[@tag="'||tag_used||'"]', marcxml::XML)::TEXT[]) LOOP
-
-            heading_text := COALESCE(
-                oils_xpath_string('./*[contains("'||acsaf.display_sf_list||'",@code)]', tmp_xml, joiner_text),
-                ''
-            );
-
-            IF nfi_used IS NOT NULL THEN
-
-                sort_text := SUBSTRING(
-                    heading_text FROM
-                    COALESCE(
-                        NULLIF(
-                            REGEXP_REPLACE(
-                                oils_xpath_string('./@ind'||nfi_used, tmp_xml::TEXT),
-                                $$\D+$$,
-                                '',
-                                'g'
-                            ),
-                            ''
-                        )::INT,
-                        0
-                    ) + 1
-                );
-
-            ELSE
-                sort_text := heading_text;
-            END IF;
+BEGIN;
 
-            IF heading_text IS NOT NULL AND heading_text <> '' THEN
-                res.value := heading_text;
-                res.sort_value := public.naco_normalize(sort_text);
-                res.index_vector = to_tsvector('keyword'::regconfig, res.sort_value);
-                RETURN NEXT res;
-            END IF;
+SELECT evergreen.upgrade_deps_block_check('0938', :eg_version);
 
-        END LOOP;
-
-    END LOOP;
-
-    RETURN;
-END;
-
-$func$ LANGUAGE PLPGSQL STABLE STRICT;
--- AFTER UPDATE OR INSERT trigger for authority.record_entry
-CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
-DECLARE
-    ashs    authority.simple_heading%ROWTYPE;
-    mbe_row metabib.browse_entry%ROWTYPE;
-    mbe_id  BIGINT;
-    ash_id  BIGINT;
-BEGIN
-
-    IF NEW.deleted IS TRUE THEN -- If this authority is deleted
-        DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
-        DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
-        DELETE FROM authority.simple_heading WHERE record = NEW.id;
-          -- Should remove matching $0 from controlled fields at the same time?
-
-        -- XXX What do we about the actual linking subfields present in
-        -- authority records that target this one when this happens?
-        DELETE FROM authority.authority_linking
-            WHERE source = NEW.id OR target = NEW.id;
-
-        RETURN NEW; -- and we're done
-    END IF;
-
-    IF TG_OP = 'UPDATE' THEN -- re-ingest?
-        PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
-
-        IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
-            RETURN NEW;
-        END IF;
-
-        -- Unless there's a setting stopping us, propagate these updates to any linked bib records
-        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_auto_update' AND enabled;
-
-        IF NOT FOUND THEN
-            PERFORM authority.propagate_changes(NEW.id);
-        END IF;
-       
-        DELETE FROM authority.simple_heading WHERE record = NEW.id;
-        DELETE FROM authority.authority_linking WHERE source = NEW.id;
-    END IF;
-
-    INSERT INTO authority.authority_linking (source, target, field)
-        SELECT source, target, field FROM authority.calculate_authority_linking(
-            NEW.id, NEW.control_set, NEW.marc::XML
-        );
-
-    FOR ashs IN SELECT * FROM authority.simple_heading_set(NEW.marc) LOOP
-
-        INSERT INTO authority.simple_heading (record,atag,value,sort_value,thesaurus)
-            VALUES (ashs.record, ashs.atag, ashs.value, ashs.sort_value, ashs.thesaurus);
-            ash_id := CURRVAL('authority.simple_heading_id_seq'::REGCLASS);
-
-        SELECT INTO mbe_row * FROM metabib.browse_entry
-            WHERE value = ashs.value AND sort_value = ashs.sort_value;
-
-        IF FOUND THEN
-            mbe_id := mbe_row.id;
-        ELSE
-            INSERT INTO metabib.browse_entry
-                ( value, sort_value ) VALUES
-                ( ashs.value, ashs.sort_value );
-
-            mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
-        END IF;
-
-        INSERT INTO metabib.browse_entry_simple_heading_map (entry,simple_heading) VALUES (mbe_id,ash_id);
-
-    END LOOP;
-
-    -- Flatten and insert the afr data
-    PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
-    IF NOT FOUND THEN
-        PERFORM authority.reingest_authority_full_rec(NEW.id);
-        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
-        IF NOT FOUND THEN
-            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
-        END IF;
-    END IF;
-
-    RETURN NEW;
-END;
-$func$ LANGUAGE PLPGSQL;
-
-DROP FUNCTION IF EXISTS authority.atag_search_heading_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.btag_search_heading_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.axis_search_heading_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.atag_search_heading(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.btag_search_heading(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.axis_search_heading(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.simple_heading_search_heading(INT[], TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.atag_search_rank_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.btag_search_rank_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.axis_search_rank_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.atag_search_rank(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.btag_search_rank(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.axis_search_rank(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.simple_heading_search_rank(INT[], TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.atag_browse_top_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.btag_browse_top_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.axis_browse_top_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.atag_browse_top(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.btag_browse_top(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.axis_browse_top(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.simple_heading_browse_top(INT[], TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.atag_browse_center_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.btag_browse_center_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.axis_browse_center_refs(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.atag_browse_center(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.btag_browse_center(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.axis_browse_center(TEXT, TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.simple_heading_browse_center(INT[], TEXT, INT, INT);
-DROP FUNCTION IF EXISTS authority.simple_heading_find_pivot(INT[], TEXT);
-
--- Support function used to find the pivot for alpha-heading-browse style searching
-CREATE OR REPLACE FUNCTION authority.simple_heading_find_pivot( a INT[], q TEXT, thesauruses TEXT DEFAULT '' ) RETURNS TEXT AS $$
-DECLARE
-    sort_value_row  RECORD;
-    value_row       RECORD;
-    t_term          TEXT;
-BEGIN
-
-    t_term := public.naco_normalize(q);
-
-    SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
-                + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
-            ash.sort_value
-    INTO  sort_value_row
-    FROM  authority.simple_heading ash
-    WHERE ash.atag = ANY (a)
-            AND ash.sort_value >= t_term
-            AND CASE thesauruses
-                WHEN '' THEN TRUE
-                ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
-                END
-    ORDER BY rank DESC, ash.sort_value
-    LIMIT 1;
-
-    SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
-                + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
-            ash.sort_value
-    INTO  value_row
-    FROM  authority.simple_heading ash
-    WHERE ash.atag = ANY (a)
-            AND ash.value >= t_term
-            AND CASE thesauruses
-                WHEN '' THEN TRUE
-                ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
-                END
-    ORDER BY rank DESC, ash.sort_value
-    LIMIT 1;
-
-    IF value_row.rank > sort_value_row.rank THEN
-        RETURN value_row.sort_value;
-    ELSE
-        RETURN sort_value_row.sort_value;
-    END IF;
-END;
-$$ LANGUAGE PLPGSQL;
-
-CREATE OR REPLACE FUNCTION authority.simple_heading_browse_center( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-DECLARE
-    pivot_sort_value    TEXT;
-    boffset             INT DEFAULT 0;
-    aoffset             INT DEFAULT 0;
-    blimit              INT DEFAULT 0;
-    alimit              INT DEFAULT 0;
-BEGIN
-
-    pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q,thesauruses);
-
-    IF page = 0 THEN
-        blimit := pagesize / 2;
-        alimit := blimit;
-
-        IF pagesize % 2 <> 0 THEN
-            alimit := alimit + 1;
-        END IF;
-    ELSE
-        blimit := pagesize;
-        alimit := blimit;
-
-        boffset := pagesize / 2;
-        aoffset := boffset;
-
-        IF pagesize % 2 <> 0 THEN
-            boffset := boffset + 1;
-        END IF;
-    END IF;
-
-    IF page <= 0 THEN
-        -- "bottom" half of the browse results
-        RETURN QUERY
-            SELECT id FROM (
-                SELECT  ash.id,
-                        row_number() over ()
-                FROM  authority.simple_heading ash
-                WHERE ash.atag = ANY (atag_list)
-                        AND CASE thesauruses
-                            WHEN '' THEN TRUE
-                            ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
-                            END
-                        AND ash.sort_value < pivot_sort_value
-                ORDER BY ash.sort_value DESC
-                LIMIT blimit
-                OFFSET ABS(page) * pagesize - boffset
-            ) x ORDER BY row_number DESC;
-    END IF;
-
-    IF page >= 0 THEN
-        -- "bottom" half of the browse results
-        RETURN QUERY
-            SELECT  ash.id
-            FROM  authority.simple_heading ash
-            WHERE ash.atag = ANY (atag_list)
-                    AND CASE thesauruses
-                        WHEN '' THEN TRUE
-                        ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
-                        END
-                    AND ash.sort_value >= pivot_sort_value
-            ORDER BY ash.sort_value
-            LIMIT alimit
-            OFFSET ABS(page) * pagesize - aoffset;
-    END IF;
-END;
-$$ LANGUAGE PLPGSQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.axis_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.btag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.atag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.axis_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.btag_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.atag_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-
-CREATE OR REPLACE FUNCTION authority.simple_heading_browse_top( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-DECLARE
-    pivot_sort_value    TEXT;
-BEGIN
-
-    pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q,thesauruses);
-
-    IF page < 0 THEN
-         -- "bottom" half of the browse results
-        RETURN QUERY
-            SELECT id FROM (
-                SELECT  ash.id,
-                        row_number() over ()
-                FROM  authority.simple_heading ash
-                WHERE ash.atag = ANY (atag_list)
-                        AND CASE thesauruses
-                            WHEN '' THEN TRUE
-                            ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
-                            END
-                        AND ash.sort_value < pivot_sort_value
-                ORDER BY ash.sort_value DESC
-                LIMIT pagesize
-                OFFSET (ABS(page) - 1) * pagesize
-            ) x ORDER BY row_number DESC;
-    END IF;
-
-    IF page >= 0 THEN
-         -- "bottom" half of the browse results
-        RETURN QUERY
-            SELECT  ash.id
-            FROM  authority.simple_heading ash
-            WHERE ash.atag = ANY (atag_list)
-                AND CASE thesauruses
-                    WHEN '' THEN TRUE
-                    ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
-                    END
-                    AND ash.sort_value >= pivot_sort_value
-            ORDER BY ash.sort_value
-            LIMIT pagesize
-            OFFSET ABS(page) * pagesize ;
-    END IF;
-END;
-$$ LANGUAGE PLPGSQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.axis_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.btag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.atag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.axis_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.btag_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.atag_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-
-CREATE OR REPLACE FUNCTION authority.simple_heading_search_rank( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT  ash.id
-      FROM  authority.simple_heading ash,
-            public.naco_normalize($2) t(term),
-            plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
-      WHERE ash.atag = ANY ($1)
-            AND ash.index_vector @@ ptsq.term
-            AND CASE $5
-                WHEN '' THEN TRUE
-                ELSE ash.thesaurus = ANY(regexp_split_to_array($5, ','))
-                END
-      ORDER BY ts_rank_cd(ash.index_vector,ptsq.term,14)::numeric
-                    + CASE WHEN ash.sort_value LIKE t.term || '%' THEN 2 ELSE 0 END
-                    + CASE WHEN ash.value LIKE t.term || '%' THEN 1 ELSE 0 END DESC
-      LIMIT $4
-      OFFSET $4 * $3;
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.axis_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.btag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.atag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.axis_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.btag_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.atag_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-
-CREATE OR REPLACE FUNCTION authority.simple_heading_search_heading( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT  ash.id
-      FROM  authority.simple_heading ash,
-            public.naco_normalize($2) t(term),
-            plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
-      WHERE ash.atag = ANY ($1)
-            AND ash.index_vector @@ ptsq.term
-            AND CASE $5
-                WHEN '' THEN TRUE
-                ELSE ash.thesaurus = ANY(regexp_split_to_array($5, ','))
-                END
-      ORDER BY ash.sort_value
-      LIMIT $4
-      OFFSET $4 * $3;
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.axis_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.btag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.atag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.axis_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.btag_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-CREATE OR REPLACE FUNCTION authority.atag_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
-    SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)
-$$ LANGUAGE SQL ROWS 10;
-
-
-\qecho
-\qecho Updating the thesaurus codes in authority.simple_heading;
-\qecho This may take a while in databases with many authority records.
-\qecho
-UPDATE authority.simple_heading a
-SET thesaurus = authority.extract_thesaurus(b.marc)
-FROM authority.record_entry b
-WHERE a.record = b.id;
-
-
-SELECT evergreen.upgrade_deps_block_check('0943', :eg_version);
-
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'AUT', 6, 1, 'z');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GeoDiv', '008', 'AUT', 6, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Roman', '008', 'AUT', 7, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('CatLang', '008', 'AUT', 8, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Kind', '008', 'AUT', 9, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Rules', '008', 'AUT', 10, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Subj', '008', 'AUT', 11, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Series', '008', 'AUT', 12, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('SerNum', '008', 'AUT', 13, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('NameUse', '008', 'AUT', 14, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('SubjUse', '008', 'AUT', 15, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('SerUse', '008', 'AUT', 16, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TypeSubd', '008', 'AUT', 17, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GovtAgn', '008', 'AUT', 28, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('RefStatus', '008', 'AUT', 29, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('UpdStatus', '008', 'AUT', 31, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Name', '008', 'AUT', 32, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Status', '008', 'AUT', 33, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ModRec', '008', 'AUT', 38, 1, ' ');
-INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Source', '008', 'AUT', 39, 1, ' ');
-
-
-SELECT evergreen.upgrade_deps_block_check('0944', :eg_version);
-
-INSERT INTO permission.perm_list (id, code, description)
-    VALUES (
-        563,
-        'ADJUST_BILLS',
-        oils_i18n_gettext(
-            563,
-            'Allow a user to adjust a bill (generally to zero)',
-            'ppl',
-            'description'
-        )
-    );
-
-COMMIT;
-
-BEGIN;
-
--- Running 0938 out of order because we cannot update a table and
--- alter it within the same transaction.
-
-SELECT evergreen.upgrade_deps_block_check('0938', :eg_version);
-
-INSERT INTO config.record_attr_definition (name, label, format, xpath, vocabulary) VALUES (
-    'content_type', oils_i18n_gettext('content_type', 'Content Type', 'ccvm', 'label'),
-    'marcxml',
-    $$//marc:datafield[@tag='336']/marc:subfield[@code='a']$$,
-    'http://rdaregistry.info/termList/RDAContentType'
-);
-INSERT INTO config.record_attr_definition (name, label, format, xpath, vocabulary) VALUES (
-    'media_type', oils_i18n_gettext('media_type', 'Media Type', 'ccvm', 'label'),
-    'marcxml',
-    $$//marc:datafield[@tag='337']/marc:subfield[@code='a']$$,
-    'http://rdaregistry.info/termList/RDAMediaType'
-);
-INSERT INTO config.record_attr_definition (name, label, format, xpath, vocabulary) VALUES (
-    'carrier_type', oils_i18n_gettext('carrier_type', 'Carrier Type', 'ccvm', 'label'),
-    'marcxml',
-    $$//marc:datafield[@tag='338']/marc:subfield[@code='a']$$,
-    'http://rdaregistry.info/termList/RDACarrierType'
-);
+INSERT INTO config.record_attr_definition (name, label, format, xpath, vocabulary) VALUES (
+    'content_type', oils_i18n_gettext('content_type', 'Content Type', 'ccvm', 'label'),
+    'marcxml',
+    $$//marc:datafield[@tag='336']/marc:subfield[@code='a']$$,
+    'http://rdaregistry.info/termList/RDAContentType'
+);
+INSERT INTO config.record_attr_definition (name, label, format, xpath, vocabulary) VALUES (
+    'media_type', oils_i18n_gettext('media_type', 'Media Type', 'ccvm', 'label'),
+    'marcxml',
+    $$//marc:datafield[@tag='337']/marc:subfield[@code='a']$$,
+    'http://rdaregistry.info/termList/RDAMediaType'
+);
+INSERT INTO config.record_attr_definition (name, label, format, xpath, vocabulary) VALUES (
+    'carrier_type', oils_i18n_gettext('carrier_type', 'Carrier Type', 'ccvm', 'label'),
+    'marcxml',
+    $$//marc:datafield[@tag='338']/marc:subfield[@code='a']$$,
+    'http://rdaregistry.info/termList/RDACarrierType'
+);
 
 -- RDA content type, media type, and carrier type
 INSERT INTO config.coded_value_map (id, ctype, code, value, concept_uri)
@@ -23241,11 +22548,704 @@ INSERT INTO config.coded_value_map (id, ctype, code, value, concept_uri)
   oils_i18n_gettext(711, 'audio cartridge', 'ccvm', 'value'),
   'http://rdaregistry.info/termList/RDACarrierType/1002');
 
-UPDATE config.marc_subfield set value_ctype = 'content_type'
-WHERE  tag = '336' AND code = 'a' AND marc_record_type = 'biblio';
-UPDATE config.marc_subfield set value_ctype = 'media_type'
-WHERE  tag = '337' AND code = 'a' AND marc_record_type = 'biblio';
-UPDATE config.marc_subfield set value_ctype = 'carrier_type'
-WHERE  tag = '338' AND code = 'a' AND marc_record_type = 'biblio';
+UPDATE config.marc_subfield set value_ctype = 'content_type'
+WHERE  tag = '336' AND code = 'a' AND marc_record_type = 'biblio';
+UPDATE config.marc_subfield set value_ctype = 'media_type'
+WHERE  tag = '337' AND code = 'a' AND marc_record_type = 'biblio';
+UPDATE config.marc_subfield set value_ctype = 'carrier_type'
+WHERE  tag = '338' AND code = 'a' AND marc_record_type = 'biblio';
+
+SELECT evergreen.upgrade_deps_block_check('0939', :eg_version);
+
+--create hook for actor.usr.expire_date
+INSERT INTO action_trigger.hook (key, core_type, description, passive)
+    VALUES ('au.expired', 'au', 'A user account has expired', 't');
+       
+--SQL to create event definition for 30 day account pre-expiration notice
+--Inactive, owned by top of org tree by default.  Modify to suit needs.
+--Can set reactor to 'ProcessTemplate' for testing.  Will generate emails in DB, but not actually send.
+
+INSERT INTO action_trigger.event_definition (
+    active, owner, name, hook, 
+    validator, reactor, delay, delay_field,
+    max_delay, repeat_delay, template
+)  VALUES (
+    'f', '1', '30 Day Account Expiration Courtesy Notice', 'au.expired',
+    'NOOP_True', 'SendEmail', '-30 days', 'expire_date',
+    '-29 days', '30 days',
+$$
+[%- USE date -%]
+[%- user = target -%]
+[%- lib = target.home_ou -%]
+To: [%- params.recipient_email || user.email %]
+From: [%- helpers.get_org_setting(target.home_ou.id, 'org.bounced_emails') || lib.email || params.sender_email || default_sender %]
+Reply-To: [%- helpers.get_org_setting(target.home_ou.id, 'org.bounced_emails') || lib.email || params.sender_email || default_sender %]
+Subject: Courtesy Notice - Library Account Expiration in 30 days
+Auto-Submitted: auto-generated
+
+Dear [% user.first_given_name %] [% user.family_name %],
+
+Our records indicate your library account is due to expire in 30 days.  Please visit your local library at your convenience to renew your account in order to avoid a disruption in access to library service.
+
+Sincerely,
+[% lib.name %]
+
+Contact your library for more information:
+
+[% lib.name %]
+[%- SET addr = lib.mailing_address -%]
+[%- IF !addr -%] [%- SET addr = lib.billing_address -%] [%- END %]
+[% addr.street1 %] [% addr.street2 %]
+[% addr.city %], [% addr.state %]
+[% addr.post_code %]
+[% lib.phone %]
+
+$$);
+       
+--insert environment values
+INSERT INTO action_trigger.environment (event_def, path) VALUES
+    (CURRVAL('action_trigger.event_definition_id_seq'), 'home_ou.mailing_address'),
+    (CURRVAL('action_trigger.event_definition_id_seq'), 'home_ou.billing_address');
+       
+
+SELECT evergreen.upgrade_deps_block_check('0942', :eg_version);
+
+CREATE OR REPLACE FUNCTION authority.extract_thesaurus( marcxml TEXT ) RETURNS TEXT AS $func$
+DECLARE
+    thes_code TEXT;
+BEGIN
+    thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
+    IF thes_code IS NULL THEN
+        thes_code := '|';
+    ELSIF thes_code = 'z' THEN
+        thes_code := COALESCE( oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml), '' );
+    END IF;
+    RETURN thes_code;
+END;
+$func$ LANGUAGE PLPGSQL STABLE STRICT;
+
+-- Intended to be used in a unique index on authority.record_entry like so:
+-- CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
+--   ON authority.record_entry (heading)
+--   WHERE deleted IS FALSE or deleted = FALSE;
+CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT, no_thesaurus BOOL ) RETURNS TEXT AS $func$
+DECLARE
+    acsaf           authority.control_set_authority_field%ROWTYPE;
+    tag_used        TEXT;
+    nfi_used        TEXT;
+    sf              TEXT;
+    sf_node         TEXT;
+    tag_node        TEXT;
+    thes_code       TEXT;
+    cset            INT;
+    heading_text    TEXT;
+    tmp_text        TEXT;
+    first_sf        BOOL;
+    auth_id         INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT; 
+BEGIN
+    SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
+
+    IF cset IS NULL THEN
+        SELECT  control_set INTO cset
+          FROM  authority.control_set_authority_field
+          WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
+          LIMIT 1;
+    END IF;
+
+    heading_text := '';
+    FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
+        tag_used := acsaf.tag;
+        nfi_used := acsaf.nfi;
+        first_sf := TRUE;
+
+        FOR tag_node IN SELECT unnest(oils_xpath('//*[@tag="'||tag_used||'"]',marcxml)) LOOP
+            FOR sf_node IN SELECT unnest(oils_xpath('./*[contains("'||acsaf.sf_list||'",@code)]',tag_node)) LOOP
+
+                tmp_text := oils_xpath_string('.', sf_node);
+                sf := oils_xpath_string('./@code', sf_node);
+
+                IF first_sf AND tmp_text IS NOT NULL AND nfi_used IS NOT NULL THEN
+
+                    tmp_text := SUBSTRING(
+                        tmp_text FROM
+                        COALESCE(
+                            NULLIF(
+                                REGEXP_REPLACE(
+                                    oils_xpath_string('./@ind'||nfi_used, tag_node),
+                                    $$\D+$$,
+                                    '',
+                                    'g'
+                                ),
+                                ''
+                            )::INT,
+                            0
+                        ) + 1
+                    );
+
+                END IF;
+
+                first_sf := FALSE;
+
+                IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
+                    heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
+                END IF;
+            END LOOP;
+
+            EXIT WHEN heading_text <> '';
+        END LOOP;
+
+        EXIT WHEN heading_text <> '';
+    END LOOP;
+
+    IF heading_text <> '' THEN
+        IF no_thesaurus IS TRUE THEN
+            heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
+        ELSE
+            thes_code := authority.extract_thesaurus(marcxml);
+            heading_text := tag_used || '_' || COALESCE(nfi_used,'-') || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
+        END IF;
+    ELSE
+        heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
+    END IF;
+
+    RETURN heading_text;
+END;
+$func$ LANGUAGE PLPGSQL STABLE STRICT;
+
+ALTER TABLE authority.simple_heading ADD COLUMN thesaurus TEXT;
+CREATE INDEX authority_simple_heading_thesaurus_idx ON authority.simple_heading (thesaurus);
+
+CREATE OR REPLACE FUNCTION authority.simple_heading_set( marcxml TEXT ) RETURNS SETOF authority.simple_heading AS $func$
+DECLARE
+    res             authority.simple_heading%ROWTYPE;
+    acsaf           authority.control_set_authority_field%ROWTYPE;
+    tag_used        TEXT;
+    nfi_used        TEXT;
+    sf              TEXT;
+    cset            INT;
+    heading_text    TEXT;
+    joiner_text     TEXT;
+    sort_text       TEXT;
+    tmp_text        TEXT;
+    tmp_xml         TEXT;
+    first_sf        BOOL;
+    auth_id         INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT; 
+BEGIN
+
+    SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
+
+    IF cset IS NULL THEN
+        SELECT  control_set INTO cset
+          FROM  authority.control_set_authority_field
+          WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
+          LIMIT 1;
+    END IF;
+
+    res.record := auth_id;
+    res.thesaurus := authority.extract_thesaurus(marcxml);
+
+    FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
+
+        res.atag := acsaf.id;
+        tag_used := acsaf.tag;
+        nfi_used := acsaf.nfi;
+        joiner_text := COALESCE(acsaf.joiner, ' ');
+
+        FOR tmp_xml IN SELECT UNNEST(XPATH('//*[@tag="'||tag_used||'"]', marcxml::XML)::TEXT[]) LOOP
+
+            heading_text := COALESCE(
+                oils_xpath_string('./*[contains("'||acsaf.display_sf_list||'",@code)]', tmp_xml, joiner_text),
+                ''
+            );
+
+            IF nfi_used IS NOT NULL THEN
+
+                sort_text := SUBSTRING(
+                    heading_text FROM
+                    COALESCE(
+                        NULLIF(
+                            REGEXP_REPLACE(
+                                oils_xpath_string('./@ind'||nfi_used, tmp_xml::TEXT),
+                                $$\D+$$,
+                                '',
+                                'g'
+                            ),
+                            ''
+                        )::INT,
+                        0
+                    ) + 1
+                );
+
+            ELSE
+                sort_text := heading_text;
+            END IF;
+
+            IF heading_text IS NOT NULL AND heading_text <> '' THEN
+                res.value := heading_text;
+                res.sort_value := public.naco_normalize(sort_text);
+                res.index_vector = to_tsvector('keyword'::regconfig, res.sort_value);
+                RETURN NEXT res;
+            END IF;
+
+        END LOOP;
+
+    END LOOP;
+
+    RETURN;
+END;
+
+$func$ LANGUAGE PLPGSQL STABLE STRICT;
+-- AFTER UPDATE OR INSERT trigger for authority.record_entry
+CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
+DECLARE
+    ashs    authority.simple_heading%ROWTYPE;
+    mbe_row metabib.browse_entry%ROWTYPE;
+    mbe_id  BIGINT;
+    ash_id  BIGINT;
+BEGIN
+
+    IF NEW.deleted IS TRUE THEN -- If this authority is deleted
+        DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
+        DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
+        DELETE FROM authority.simple_heading WHERE record = NEW.id;
+          -- Should remove matching $0 from controlled fields at the same time?
+
+        -- XXX What do we about the actual linking subfields present in
+        -- authority records that target this one when this happens?
+        DELETE FROM authority.authority_linking
+            WHERE source = NEW.id OR target = NEW.id;
+
+        RETURN NEW; -- and we're done
+    END IF;
+
+    IF TG_OP = 'UPDATE' THEN -- re-ingest?
+        PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
+
+        IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
+            RETURN NEW;
+        END IF;
+
+        -- Unless there's a setting stopping us, propagate these updates to any linked bib records
+        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_auto_update' AND enabled;
+
+        IF NOT FOUND THEN
+            PERFORM authority.propagate_changes(NEW.id);
+        END IF;
+       
+        DELETE FROM authority.simple_heading WHERE record = NEW.id;
+        DELETE FROM authority.authority_linking WHERE source = NEW.id;
+    END IF;
+
+    INSERT INTO authority.authority_linking (source, target, field)
+        SELECT source, target, field FROM authority.calculate_authority_linking(
+            NEW.id, NEW.control_set, NEW.marc::XML
+        );
+
+    FOR ashs IN SELECT * FROM authority.simple_heading_set(NEW.marc) LOOP
+
+        INSERT INTO authority.simple_heading (record,atag,value,sort_value,thesaurus)
+            VALUES (ashs.record, ashs.atag, ashs.value, ashs.sort_value, ashs.thesaurus);
+            ash_id := CURRVAL('authority.simple_heading_id_seq'::REGCLASS);
+
+        SELECT INTO mbe_row * FROM metabib.browse_entry
+            WHERE value = ashs.value AND sort_value = ashs.sort_value;
+
+        IF FOUND THEN
+            mbe_id := mbe_row.id;
+        ELSE
+            INSERT INTO metabib.browse_entry
+                ( value, sort_value ) VALUES
+                ( ashs.value, ashs.sort_value );
+
+            mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
+        END IF;
+
+        INSERT INTO metabib.browse_entry_simple_heading_map (entry,simple_heading) VALUES (mbe_id,ash_id);
+
+    END LOOP;
+
+    -- Flatten and insert the afr data
+    PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
+    IF NOT FOUND THEN
+        PERFORM authority.reingest_authority_full_rec(NEW.id);
+        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
+        IF NOT FOUND THEN
+            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
+        END IF;
+    END IF;
+
+    RETURN NEW;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+DROP FUNCTION IF EXISTS authority.atag_search_heading_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.btag_search_heading_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.axis_search_heading_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.atag_search_heading(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.btag_search_heading(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.axis_search_heading(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.simple_heading_search_heading(INT[], TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.atag_search_rank_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.btag_search_rank_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.axis_search_rank_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.atag_search_rank(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.btag_search_rank(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.axis_search_rank(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.simple_heading_search_rank(INT[], TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.atag_browse_top_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.btag_browse_top_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.axis_browse_top_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.atag_browse_top(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.btag_browse_top(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.axis_browse_top(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.simple_heading_browse_top(INT[], TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.atag_browse_center_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.btag_browse_center_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.axis_browse_center_refs(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.atag_browse_center(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.btag_browse_center(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.axis_browse_center(TEXT, TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.simple_heading_browse_center(INT[], TEXT, INT, INT);
+DROP FUNCTION IF EXISTS authority.simple_heading_find_pivot(INT[], TEXT);
+
+-- Support function used to find the pivot for alpha-heading-browse style searching
+CREATE OR REPLACE FUNCTION authority.simple_heading_find_pivot( a INT[], q TEXT, thesauruses TEXT DEFAULT '' ) RETURNS TEXT AS $$
+DECLARE
+    sort_value_row  RECORD;
+    value_row       RECORD;
+    t_term          TEXT;
+BEGIN
+
+    t_term := public.naco_normalize(q);
+
+    SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
+                + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
+            ash.sort_value
+    INTO  sort_value_row
+    FROM  authority.simple_heading ash
+    WHERE ash.atag = ANY (a)
+            AND ash.sort_value >= t_term
+            AND CASE thesauruses
+                WHEN '' THEN TRUE
+                ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
+                END
+    ORDER BY rank DESC, ash.sort_value
+    LIMIT 1;
+
+    SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
+                + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
+            ash.sort_value
+    INTO  value_row
+    FROM  authority.simple_heading ash
+    WHERE ash.atag = ANY (a)
+            AND ash.value >= t_term
+            AND CASE thesauruses
+                WHEN '' THEN TRUE
+                ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
+                END
+    ORDER BY rank DESC, ash.sort_value
+    LIMIT 1;
+
+    IF value_row.rank > sort_value_row.rank THEN
+        RETURN value_row.sort_value;
+    ELSE
+        RETURN sort_value_row.sort_value;
+    END IF;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION authority.simple_heading_browse_center( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+DECLARE
+    pivot_sort_value    TEXT;
+    boffset             INT DEFAULT 0;
+    aoffset             INT DEFAULT 0;
+    blimit              INT DEFAULT 0;
+    alimit              INT DEFAULT 0;
+BEGIN
+
+    pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q,thesauruses);
+
+    IF page = 0 THEN
+        blimit := pagesize / 2;
+        alimit := blimit;
+
+        IF pagesize % 2 <> 0 THEN
+            alimit := alimit + 1;
+        END IF;
+    ELSE
+        blimit := pagesize;
+        alimit := blimit;
+
+        boffset := pagesize / 2;
+        aoffset := boffset;
+
+        IF pagesize % 2 <> 0 THEN
+            boffset := boffset + 1;
+        END IF;
+    END IF;
+
+    IF page <= 0 THEN
+        -- "bottom" half of the browse results
+        RETURN QUERY
+            SELECT id FROM (
+                SELECT  ash.id,
+                        row_number() over ()
+                FROM  authority.simple_heading ash
+                WHERE ash.atag = ANY (atag_list)
+                        AND CASE thesauruses
+                            WHEN '' THEN TRUE
+                            ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
+                            END
+                        AND ash.sort_value < pivot_sort_value
+                ORDER BY ash.sort_value DESC
+                LIMIT blimit
+                OFFSET ABS(page) * pagesize - boffset
+            ) x ORDER BY row_number DESC;
+    END IF;
+
+    IF page >= 0 THEN
+        -- "bottom" half of the browse results
+        RETURN QUERY
+            SELECT  ash.id
+            FROM  authority.simple_heading ash
+            WHERE ash.atag = ANY (atag_list)
+                    AND CASE thesauruses
+                        WHEN '' THEN TRUE
+                        ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
+                        END
+                    AND ash.sort_value >= pivot_sort_value
+            ORDER BY ash.sort_value
+            LIMIT alimit
+            OFFSET ABS(page) * pagesize - aoffset;
+    END IF;
+END;
+$$ LANGUAGE PLPGSQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.axis_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.btag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.atag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.axis_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.btag_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.atag_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+
+CREATE OR REPLACE FUNCTION authority.simple_heading_browse_top( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+DECLARE
+    pivot_sort_value    TEXT;
+BEGIN
+
+    pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q,thesauruses);
+
+    IF page < 0 THEN
+         -- "bottom" half of the browse results
+        RETURN QUERY
+            SELECT id FROM (
+                SELECT  ash.id,
+                        row_number() over ()
+                FROM  authority.simple_heading ash
+                WHERE ash.atag = ANY (atag_list)
+                        AND CASE thesauruses
+                            WHEN '' THEN TRUE
+                            ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
+                            END
+                        AND ash.sort_value < pivot_sort_value
+                ORDER BY ash.sort_value DESC
+                LIMIT pagesize
+                OFFSET (ABS(page) - 1) * pagesize
+            ) x ORDER BY row_number DESC;
+    END IF;
+
+    IF page >= 0 THEN
+         -- "bottom" half of the browse results
+        RETURN QUERY
+            SELECT  ash.id
+            FROM  authority.simple_heading ash
+            WHERE ash.atag = ANY (atag_list)
+                AND CASE thesauruses
+                    WHEN '' THEN TRUE
+                    ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
+                    END
+                    AND ash.sort_value >= pivot_sort_value
+            ORDER BY ash.sort_value
+            LIMIT pagesize
+            OFFSET ABS(page) * pagesize ;
+    END IF;
+END;
+$$ LANGUAGE PLPGSQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.axis_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.btag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.atag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.axis_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.btag_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.atag_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+
+CREATE OR REPLACE FUNCTION authority.simple_heading_search_rank( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT  ash.id
+      FROM  authority.simple_heading ash,
+            public.naco_normalize($2) t(term),
+            plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
+      WHERE ash.atag = ANY ($1)
+            AND ash.index_vector @@ ptsq.term
+            AND CASE $5
+                WHEN '' THEN TRUE
+                ELSE ash.thesaurus = ANY(regexp_split_to_array($5, ','))
+                END
+      ORDER BY ts_rank_cd(ash.index_vector,ptsq.term,14)::numeric
+                    + CASE WHEN ash.sort_value LIKE t.term || '%' THEN 2 ELSE 0 END
+                    + CASE WHEN ash.value LIKE t.term || '%' THEN 1 ELSE 0 END DESC
+      LIMIT $4
+      OFFSET $4 * $3;
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.axis_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.btag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.atag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.axis_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.btag_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.atag_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+
+CREATE OR REPLACE FUNCTION authority.simple_heading_search_heading( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT  ash.id
+      FROM  authority.simple_heading ash,
+            public.naco_normalize($2) t(term),
+            plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
+      WHERE ash.atag = ANY ($1)
+            AND ash.index_vector @@ ptsq.term
+            AND CASE $5
+                WHEN '' THEN TRUE
+                ELSE ash.thesaurus = ANY(regexp_split_to_array($5, ','))
+                END
+      ORDER BY ash.sort_value
+      LIMIT $4
+      OFFSET $4 * $3;
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.axis_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.btag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.atag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.axis_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.btag_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+CREATE OR REPLACE FUNCTION authority.atag_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10, thesauruses TEXT DEFAULT '' ) RETURNS SETOF BIGINT AS $$
+    SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)
+$$ LANGUAGE SQL ROWS 10;
+
+
+\qecho
+\qecho Updating the thesaurus codes in authority.simple_heading;
+\qecho This may take a while in databases with many authority records.
+\qecho
+UPDATE authority.simple_heading a
+SET thesaurus = authority.extract_thesaurus(b.marc)
+FROM authority.record_entry b
+WHERE a.record = b.id;
+
+
+SELECT evergreen.upgrade_deps_block_check('0943', :eg_version);
+
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'AUT', 6, 1, 'z');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GeoDiv', '008', 'AUT', 6, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Roman', '008', 'AUT', 7, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('CatLang', '008', 'AUT', 8, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Kind', '008', 'AUT', 9, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Rules', '008', 'AUT', 10, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Subj', '008', 'AUT', 11, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Series', '008', 'AUT', 12, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('SerNum', '008', 'AUT', 13, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('NameUse', '008', 'AUT', 14, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('SubjUse', '008', 'AUT', 15, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('SerUse', '008', 'AUT', 16, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TypeSubd', '008', 'AUT', 17, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GovtAgn', '008', 'AUT', 28, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('RefStatus', '008', 'AUT', 29, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('UpdStatus', '008', 'AUT', 31, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Name', '008', 'AUT', 32, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Status', '008', 'AUT', 33, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ModRec', '008', 'AUT', 38, 1, ' ');
+INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Source', '008', 'AUT', 39, 1, ' ');
+
+
+SELECT evergreen.upgrade_deps_block_check('0944', :eg_version);
+
+INSERT INTO permission.perm_list (id, code, description)
+    VALUES (
+        563,
+        'ADJUST_BILLS',
+        oils_i18n_gettext(
+            563,
+            'Allow a user to adjust a bill (generally to zero)',
+            'ppl',
+            'description'
+        )
+    );
 
 COMMIT;
+