[open-ils-commits] [GIT] Evergreen ILS branch master updated. f6a2c84f3273fcf0f6c0067229d6356fecd41582

Evergreen Git git at git.evergreen-ils.org
Fri Mar 23 12:39:48 EDT 2012


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  f6a2c84f3273fcf0f6c0067229d6356fecd41582 (commit)
       via  8b2546e3a7ac9938a0725dffeec25224c48061e8 (commit)
       via  97776542e5cf8f6cc9adf0ad4830ffa20c1bf205 (commit)
       via  c0c51fa1cc5ced201f290fcd4a880f802e8e36ac (commit)
       via  2cce485ffc953176760b4972ffc7f2944661f4b6 (commit)
       via  7ebf143c86c7cbb88dc18987861cd823c0afc5c1 (commit)
       via  96559ac3f8b95745d64967ec8d42a0024dfd7504 (commit)
      from  94656bc7edb68ee2f3d1d4eecb128a2837f55d33 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit f6a2c84f3273fcf0f6c0067229d6356fecd41582
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Mar 23 12:45:38 2012 -0400

    Stamping upgrade script for in-db unAPI + TPAC cleanup from Dan Scott
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 202392e..2f98300 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -86,7 +86,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0687', :eg_version); -- tsbere/miker
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0690', :eg_version); -- denials/miker
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql b/Open-ILS/src/sql/Pg/upgrade/0690.schema.unapi_limit_rank.sql
similarity index 99%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
rename to Open-ILS/src/sql/Pg/upgrade/0690.schema.unapi_limit_rank.sql
index 7682777..58de924 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0690.schema.unapi_limit_rank.sql
@@ -1,4 +1,4 @@
--- Evergreen DB patch XXXX.schema.unapi_limit_rank.sql
+-- Evergreen DB patch 0690.schema.unapi_limit_rank.sql
 --
 -- Rewrite the in-database unapi functions to include per-object limits and
 -- offsets, such as a maximum number of copies and call numbers for given
@@ -17,7 +17,7 @@
 BEGIN;
 
 -- check whether patch can be applied
-SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('0690', :eg_version);
 
 -- The simplest way to apply all of these changes is just to replace the unapi
 -- schema entirely -- the following is a copy of 990.schema.unapi.sql with
diff --git a/Open-ILS/src/sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql b/Open-ILS/src/sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql
index b59bcc0..0be440c 100644
--- a/Open-ILS/src/sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql
+++ b/Open-ILS/src/sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql
@@ -13817,4 +13817,1195 @@ $func$ LANGUAGE PLPGSQL;
 DROP FUNCTION IF EXISTS metabib.reingest_metabib_field_entries(BIGINT);
 
 
+-- Evergreen DB patch 0690.schema.unapi_limit_rank.sql
+--
+-- Rewrite the in-database unapi functions to include per-object limits and
+-- offsets, such as a maximum number of copies and call numbers for given
+-- bib record via the HSTORE syntax (for example, 'acn => 5, acp => 10' would
+-- limit to a maximum of 5 call numbers for the bib, with up to 10 copies per
+-- call number).
+--
+-- Add some notion of "preferred library" that will provide copy counts
+-- and optionally affect the sorting of returned copies.
+--
+-- Sort copies by availability, preferring the most available copies.
+--
+-- Return located URIs.
+--
+--
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0690', :eg_version);
+
+-- The simplest way to apply all of these changes is just to replace the unapi
+-- schema entirely -- the following is a copy of 990.schema.unapi.sql with
+-- the initial COMMIT in place in case the upgrade_deps_block_check fails;
+-- if it does, then the attempt to create the unapi schema in the following
+-- transaction will also fail. Not graceful, but safe!
+DROP SCHEMA IF EXISTS unapi CASCADE;
+
+CREATE SCHEMA unapi;
+
+CREATE OR REPLACE FUNCTION evergreen.org_top()
+RETURNS SETOF actor.org_unit AS $$
+    SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;
+$$ LANGUAGE SQL STABLE
+ROWS 1;
+
+CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT)
+RETURNS anyarray AS $$
+    SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
+RETURNS INTEGER AS $$
+    WITH search_libs AS (
+        SELECT id, distance FROM actor.org_unit_descendants_distance($2)
+    )
+    SELECT COALESCE(
+        (SELECT -10000 FROM actor.org_unit
+         WHERE $1 = $3 AND id = $3 AND $2 IN (
+                SELECT id FROM actor.org_unit WHERE parent_ou IS NULL
+             )
+        ),
+        (SELECT distance FROM search_libs WHERE id = $1),
+        10000
+    );
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
+RETURNS INTEGER AS $$
+    WITH totally_available AS (
+        SELECT id, 0 AS avail_rank
+        FROM config.copy_status
+        WHERE opac_visible IS TRUE
+            AND copy_active IS TRUE
+            AND id != 1 -- "Checked out"
+    ), almost_available AS (
+        SELECT id, 10 AS avail_rank
+        FROM config.copy_status
+        WHERE holdable IS TRUE
+            AND opac_visible IS TRUE
+            AND copy_active IS FALSE
+            OR id = 1 -- "Checked out"
+    )
+    SELECT COALESCE(
+        (SELECT avail_rank FROM totally_available WHERE $1 IN (id)),
+        (SELECT avail_rank FROM almost_available WHERE $1 IN (id)),
+        100
+    );
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
+    bibid BIGINT, 
+    ouid INT,
+    depth INT DEFAULT NULL,
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    pref_lib INT DEFAULT NULL
+) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
+    SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
+        SELECT acn.id, aou.name, acn.label_sortkey,
+            evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
+            RANK() OVER w
+        FROM asset.call_number acn
+            JOIN asset.copy acp ON (acn.id = acp.call_number)
+            JOIN actor.org_unit_descendants( $2, COALESCE(
+                $3, (
+                    SELECT depth
+                    FROM actor.org_unit_type aout
+                        INNER JOIN actor.org_unit ou ON ou_type = aout.id
+                    WHERE ou.id = $2
+                ), $6)
+            ) AS aou ON (acp.circ_lib = aou.id)
+        WHERE acn.record = $1
+            AND acn.deleted IS FALSE
+            AND acp.deleted IS FALSE
+        GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
+        WINDOW w AS (
+            ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
+        )
+    ) AS ua
+    GROUP BY ua.id, ua.name, ua.label_sortkey
+    ORDER BY rank, ua.name, ua.label_sortkey
+    LIMIT ($4 -> 'acn')::INT
+    OFFSET ($5 -> 'acn')::INT;
+$$
+LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION evergreen.located_uris (
+    bibid BIGINT, 
+    ouid INT,
+    pref_lib INT DEFAULT NULL
+) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
+      FROM asset.call_number acn
+           INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
+           INNER JOIN asset.uri auri ON auri.id = auricnm.uri
+           INNER JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
+      WHERE acn.record = $1
+          AND acn.deleted IS FALSE
+          AND auri.active IS TRUE
+    UNION
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
+      FROM asset.call_number acn
+           INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
+           INNER JOIN asset.uri auri ON auri.id = auricnm.uri
+           INNER JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
+      WHERE acn.record = $1
+          AND acn.deleted IS FALSE
+          AND auri.active IS TRUE;
+$$
+LANGUAGE SQL STABLE;
+
+CREATE TABLE unapi.bre_output_layout (
+    name                TEXT    PRIMARY KEY,
+    transform           TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+    mime_type           TEXT    NOT NULL,
+    feed_top            TEXT    NOT NULL,
+    holdings_element    TEXT,
+    title_element       TEXT,
+    description_element TEXT,
+    creator_element     TEXT,
+    update_ts_element   TEXT
+);
+
+INSERT INTO unapi.bre_output_layout
+    (name,           transform, mime_type,              holdings_element, feed_top,         title_element, description_element, creator_element, update_ts_element)
+        VALUES
+    ('holdings_xml', NULL,      'application/xml',      NULL,             'hxml',           NULL,          NULL,                NULL,            NULL),
+    ('marcxml',      'marcxml', 'application/marc+xml', 'record',         'collection',     NULL,          NULL,                NULL,            NULL),
+    ('mods32',       'mods32',  'application/mods+xml', 'mods',           'modsCollection', NULL,          NULL,                NULL,            NULL)
+;
+
+-- Dummy functions, so we can create the real ones out of order
+CREATE OR REPLACE FUNCTION unapi.aou    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acnp   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acns   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acn    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.ssub   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sdist  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sstr   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sitem  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sunit  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sisum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sbsum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sssum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.siss   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.auri   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acpn   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acl    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.ccs    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.bre (
+    obj_id BIGINT,
+    format TEXT,
+    ename TEXT,
+    includes TEXT[],
+    org TEXT,
+    depth INT DEFAULT NULL,
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.bmp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.mra    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.circ   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.holdings_xml (
+    bid BIGINT,
+    ouid INT,
+    org TEXT,
+    depth INT DEFAULT NULL,
+    includes TEXT[] DEFAULT NULL::TEXT[],
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.memoize (classname TEXT, obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+DECLARE
+    key     TEXT;
+    output  XML;
+BEGIN
+    key :=
+        'id'        || COALESCE(obj_id::TEXT,'') ||
+        'format'    || COALESCE(format::TEXT,'') ||
+        'ename'     || COALESCE(ename::TEXT,'') ||
+        'includes'  || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
+        'org'       || COALESCE(org::TEXT,'') ||
+        'depth'     || COALESCE(depth::TEXT,'') ||
+        'slimit'    || COALESCE(slimit::TEXT,'') ||
+        'soffset'   || COALESCE(soffset::TEXT,'') ||
+        'include_xmlns'   || COALESCE(include_xmlns::TEXT,'');
+    -- RAISE NOTICE 'memoize key: %', key;
+
+    key := MD5(key);
+    -- RAISE NOTICE 'memoize hash: %', key;
+
+    -- XXX cache logic ... memcached? table?
+
+    EXECUTE $$SELECT unapi.$$ || classname || $$( $1, $2, $3, $4, $5, $6, $7, $8, $9);$$ INTO output USING obj_id, format, ename, includes, org, depth, slimit, soffset, include_xmlns;
+    RETURN output;
+END;
+$F$ LANGUAGE PLPGSQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$
+DECLARE
+    layout          unapi.bre_output_layout%ROWTYPE;
+    transform       config.xml_transform%ROWTYPE;
+    item_format     TEXT;
+    tmp_xml         TEXT;
+    xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
+    ouid            INT;
+    element_list    TEXT[];
+BEGIN
+
+    IF org = '-' OR org IS NULL THEN
+        SELECT shortname INTO org FROM evergreen.org_top();
+    END IF;
+
+    SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
+    SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
+
+    IF layout.name IS NULL THEN
+        RETURN NULL::XML;
+    END IF;
+
+    SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
+    xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
+
+    -- Gather the bib xml
+    SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
+
+    IF layout.title_element IS NOT NULL THEN
+        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
+    END IF;
+
+    IF layout.description_element IS NOT NULL THEN
+        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
+    END IF;
+
+    IF layout.creator_element IS NOT NULL THEN
+        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
+    END IF;
+
+    IF layout.update_ts_element IS NOT NULL THEN
+        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts;
+    END IF;
+
+    IF unapi_url IS NOT NULL THEN
+        EXECUTE $$SELECT XMLCONCAT( XMLELEMENT( name link, XMLATTRIBUTES( 'http://www.w3.org/1999/xhtml' AS xmlns, 'unapi-server' AS rel, $1 AS href, 'unapi' AS title)), $2)$$ INTO tmp_xml USING unapi_url, tmp_xml::XML;
+    END IF;
+
+    IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
+
+    element_list := regexp_split_to_array(layout.feed_top,E'\\.');
+    FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
+        EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
+    END LOOP;
+
+    RETURN tmp_xml::XML;
+END;
+$F$ LANGUAGE PLPGSQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.bre (
+    obj_id BIGINT,
+    format TEXT,
+    ename TEXT,
+    includes TEXT[],
+    org TEXT,
+    depth INT DEFAULT NULL,
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$
+DECLARE
+    me      biblio.record_entry%ROWTYPE;
+    layout  unapi.bre_output_layout%ROWTYPE;
+    xfrm    config.xml_transform%ROWTYPE;
+    ouid    INT;
+    tmp_xml TEXT;
+    top_el  TEXT;
+    output  XML;
+    hxml    XML;
+    axml    XML;
+BEGIN
+
+    IF org = '-' OR org IS NULL THEN
+        SELECT shortname INTO org FROM evergreen.org_top();
+    END IF;
+
+    SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
+
+    IF ouid IS NULL THEN
+        RETURN NULL::XML;
+    END IF;
+
+    IF format = 'holdings_xml' THEN -- the special case
+        output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
+        RETURN output;
+    END IF;
+
+    SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
+
+    IF layout.name IS NULL THEN
+        RETURN NULL::XML;
+    END IF;
+
+    SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
+
+    SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
+
+    -- grab SVF if we need them
+    IF ('mra' = ANY (includes)) THEN 
+        axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
+    ELSE
+        axml := NULL::XML;
+    END IF;
+
+    -- grab holdings if we need them
+    IF ('holdings_xml' = ANY (includes)) THEN 
+        hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
+    ELSE
+        hxml := NULL::XML;
+    END IF;
+
+
+    -- generate our item node
+
+
+    IF format = 'marcxml' THEN
+        tmp_xml := me.marc;
+        IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
+           tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
+        END IF; 
+    ELSE
+        tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
+    END IF;
+
+    top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
+
+    IF axml IS NOT NULL THEN 
+        tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
+    END IF;
+
+    IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
+        tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
+    END IF;
+
+    IF ('bre.unapi' = ANY (includes)) THEN 
+        output := REGEXP_REPLACE(
+            tmp_xml,
+            '</' || top_el || '>(.*?)',
+            XMLELEMENT(
+                name abbr,
+                XMLATTRIBUTES(
+                    'http://www.w3.org/1999/xhtml' AS xmlns,
+                    'unapi-id' AS class,
+                    'tag:open-ils.org:U2 at bre/' || obj_id || '/' || org AS title
+                )
+            )::TEXT || '</' || top_el || E'>\\1'
+        );
+    ELSE
+        output := tmp_xml;
+    END IF;
+
+    output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
+    RETURN output;
+END;
+$F$ LANGUAGE PLPGSQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.holdings_xml (
+    bid BIGINT,
+    ouid INT,
+    org TEXT,
+    depth INT DEFAULT NULL,
+    includes TEXT[] DEFAULT NULL::TEXT[],
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$
+     SELECT  XMLELEMENT(
+                 name holdings,
+                 XMLATTRIBUTES(
+                    CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                    CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2 at bre/' || $1 || '/' || $3 ELSE NULL END AS id
+                 ),
+                 XMLELEMENT(
+                     name counts,
+                     (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
+                         SELECT  XMLELEMENT(
+                                     name count,
+                                     XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
+                                 )::text
+                           FROM  asset.opac_ou_record_copy_count($2,  $1)
+                                     UNION
+                         SELECT  XMLELEMENT(
+                                     name count,
+                                     XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
+                                 )::text
+                           FROM  asset.staff_ou_record_copy_count($2, $1)
+                                     UNION
+                         SELECT  XMLELEMENT(
+                                     name count,
+                                     XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
+                                 )::text
+                           FROM  asset.opac_ou_record_copy_count($9,  $1)
+                                     ORDER BY 1
+                     )x)
+                 ),
+                 CASE 
+                     WHEN ('bmp' = ANY ($5)) THEN
+                        XMLELEMENT(
+                            name monograph_parts,
+                            (SELECT XMLAGG(bmp) FROM (
+                                SELECT  unapi.bmp( id, 'xml', 'monograph_part', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE)
+                                  FROM  biblio.monograph_part
+                                  WHERE record = $1
+                            )x)
+                        )
+                     ELSE NULL
+                 END,
+                 XMLELEMENT(
+                     name volumes,
+                     (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
+                        -- Physical copies
+                        SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
+                        FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9) AS y
+                        UNION ALL
+                        -- Located URIs
+                        SELECT unapi.acn(uris.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), 0, name, label_sortkey
+                        FROM evergreen.located_uris($1, $2, $9) AS uris
+                     )x)
+                 ),
+                 CASE WHEN ('ssub' = ANY ($5)) THEN 
+                     XMLELEMENT(
+                         name subscriptions,
+                         (SELECT XMLAGG(ssub) FROM (
+                            SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
+                              FROM  serial.subscription
+                              WHERE record_entry = $1
+                        )x)
+                     )
+                 ELSE NULL END,
+                 CASE WHEN ('acp' = ANY ($5)) THEN 
+                     XMLELEMENT(
+                         name foreign_copies,
+                         (SELECT XMLAGG(acp) FROM (
+                            SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
+                              FROM  biblio.peer_bib_copy_map p
+                                    JOIN asset.copy c ON (p.target_copy = c.id)
+                              WHERE NOT c.deleted AND p.peer_record = $1
+                            LIMIT ($6 -> 'acp')::INT
+                            OFFSET ($7 -> 'acp')::INT
+                        )x)
+                     )
+                 ELSE NULL END
+             );
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name subscription,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        'tag:open-ils.org:U2 at ssub/' || id AS id,
+                        'tag:open-ils.org:U2 at aou/' || owning_lib AS owning_lib,
+                        start_date AS start, end_date AS end, expected_date_offset
+                    ),
+                    CASE 
+                        WHEN ('sdist' = ANY ($4)) THEN
+                            XMLELEMENT( name distributions,
+                                (SELECT XMLAGG(sdist) FROM (
+                                    SELECT  unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
+                                      FROM  serial.distribution
+                                      WHERE subscription = ssub.id
+                                )x)
+                            )
+                        ELSE NULL
+                    END
+                )
+          FROM  serial.subscription ssub
+          WHERE id = $1
+          GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name distribution,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        'tag:open-ils.org:U2 at sdist/' || id AS id,
+            			'tag:open-ils.org:U2 at acn/' || receive_call_number AS receive_call_number,
+			            'tag:open-ils.org:U2 at acn/' || bind_call_number AS bind_call_number,
+                        unit_label_prefix, label, unit_label_suffix, summary_method
+                    ),
+                    unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
+                    CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+                    CASE 
+                        WHEN ('sstr' = ANY ($4)) THEN
+                            XMLELEMENT( name streams,
+                                (SELECT XMLAGG(sstr) FROM (
+                                    SELECT  unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
+                                      FROM  serial.stream
+                                      WHERE distribution = sdist.id
+                                )x)
+                            )
+                        ELSE NULL
+                    END,
+                    XMLELEMENT( name summaries,
+                        CASE 
+                            WHEN ('sbsum' = ANY ($4)) THEN
+                                (SELECT XMLAGG(sbsum) FROM (
+                                    SELECT  unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
+                                      FROM  serial.basic_summary
+                                      WHERE distribution = sdist.id
+                                )x)
+                            ELSE NULL
+                        END,
+                        CASE 
+                            WHEN ('sisum' = ANY ($4)) THEN
+                                (SELECT XMLAGG(sisum) FROM (
+                                    SELECT  unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
+                                      FROM  serial.index_summary
+                                      WHERE distribution = sdist.id
+                                )x)
+                            ELSE NULL
+                        END,
+                        CASE 
+                            WHEN ('sssum' = ANY ($4)) THEN
+                                (SELECT XMLAGG(sssum) FROM (
+                                    SELECT  unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
+                                      FROM  serial.supplement_summary
+                                      WHERE distribution = sdist.id
+                                )x)
+                            ELSE NULL
+                        END
+                    )
+                )
+          FROM  serial.distribution sdist
+          WHERE id = $1
+          GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT  XMLELEMENT(
+                name stream,
+                XMLATTRIBUTES(
+                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                    'tag:open-ils.org:U2 at sstr/' || id AS id,
+                    routing_label
+                ),
+                CASE WHEN distribution IS NOT NULL AND ('sdist' = ANY ($4)) THEN unapi.sssum( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+                CASE 
+                    WHEN ('sitem' = ANY ($4)) THEN
+                        XMLELEMENT( name items,
+                            (SELECT XMLAGG(sitem) FROM (
+                                SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
+                                  FROM  serial.item
+                                  WHERE stream = sstr.id
+                            )x)
+                        )
+                    ELSE NULL
+                END
+            )
+      FROM  serial.stream sstr
+      WHERE id = $1
+      GROUP BY id, routing_label, distribution;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT  XMLELEMENT(
+                name issuance,
+                XMLATTRIBUTES(
+                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                    'tag:open-ils.org:U2 at siss/' || id AS id,
+                    create_date, edit_date, label, date_published,
+                    holding_code, holding_type, holding_link_id
+                ),
+                CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+                CASE 
+                    WHEN ('sitem' = ANY ($4)) THEN
+                        XMLELEMENT( name items,
+                            (SELECT XMLAGG(sitem) FROM (
+                                SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
+                                  FROM  serial.item
+                                  WHERE issuance = sstr.id
+                            )x)
+                        )
+                    ELSE NULL
+                END
+            )
+      FROM  serial.issuance sstr
+      WHERE id = $1
+      GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name serial_item,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        'tag:open-ils.org:U2 at sitem/' || id AS id,
+                        'tag:open-ils.org:U2 at siss/' || issuance AS issuance,
+                        date_expected, date_received
+                    ),
+                    CASE WHEN issuance IS NOT NULL AND ('siss' = ANY ($4)) THEN unapi.siss( issuance, $2, 'issuance', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+                    CASE WHEN stream IS NOT NULL AND ('sstr' = ANY ($4)) THEN unapi.sstr( stream, $2, 'stream', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+                    CASE WHEN unit IS NOT NULL AND ('sunit' = ANY ($4)) THEN unapi.sunit( unit, $2, 'serial_unit', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+                    CASE WHEN uri IS NOT NULL AND ('auri' = ANY ($4)) THEN unapi.auri( uri, $2, 'uri', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END
+--                    XMLELEMENT( name notes,
+--                        CASE 
+--                            WHEN ('acpn' = ANY ($4)) THEN
+--                                (SELECT XMLAGG(acpn) FROM (
+--                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
+--                                      FROM  asset.copy_note
+--                                      WHERE owning_copy = cp.id AND pub
+--                                )x)
+--                            ELSE NULL
+--                        END
+--                    )
+                )
+          FROM  serial.item sitem
+          WHERE id = $1;
+$F$ LANGUAGE SQL STABLE;
+
+
+CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT  XMLELEMENT(
+                name serial_summary,
+                XMLATTRIBUTES(
+                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                    'tag:open-ils.org:U2 at sbsum/' || id AS id,
+                    'sssum' AS type, generated_coverage, textual_holdings, show_generated
+                ),
+                CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
+            )
+      FROM  serial.supplement_summary ssum
+      WHERE id = $1
+      GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT  XMLELEMENT(
+                name serial_summary,
+                XMLATTRIBUTES(
+                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                    'tag:open-ils.org:U2 at sbsum/' || id AS id,
+                    'sbsum' AS type, generated_coverage, textual_holdings, show_generated
+                ),
+                CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
+            )
+      FROM  serial.basic_summary ssum
+      WHERE id = $1
+      GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT  XMLELEMENT(
+                name serial_summary,
+                XMLATTRIBUTES(
+                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                    'tag:open-ils.org:U2 at sbsum/' || id AS id,
+                    'sisum' AS type, generated_coverage, textual_holdings, show_generated
+                ),
+                CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
+            )
+      FROM  serial.index_summary ssum
+      WHERE id = $1
+      GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
+$F$ LANGUAGE SQL STABLE;
+
+
+CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+DECLARE
+    output XML;
+BEGIN
+    IF ename = 'circlib' THEN
+        SELECT  XMLELEMENT(
+                    name circlib,
+                    XMLATTRIBUTES(
+                        'http://open-ils.org/spec/actors/v1' AS xmlns,
+                        id AS ident
+                    ),
+                    name
+                ) INTO output
+          FROM  actor.org_unit aou
+          WHERE id = obj_id;
+    ELSE
+        EXECUTE $$SELECT  XMLELEMENT(
+                    name $$ || ename || $$,
+                    XMLATTRIBUTES(
+                        'http://open-ils.org/spec/actors/v1' AS xmlns,
+                        'tag:open-ils.org:U2 at aou/' || id AS id,
+                        shortname, name, opac_visible
+                    )
+                )
+          FROM  actor.org_unit aou
+         WHERE id = $1 $$ INTO output USING obj_id;
+    END IF;
+
+    RETURN output;
+
+END;
+$F$ LANGUAGE PLPGSQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT  XMLELEMENT(
+                name location,
+                XMLATTRIBUTES(
+                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                    id AS ident,
+                    holdable,
+                    opac_visible,
+                    label_prefix AS prefix,
+                    label_suffix AS suffix
+                ),
+                name
+            )
+      FROM  asset.copy_location
+      WHERE id = $1;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT  XMLELEMENT(
+                name status,
+                XMLATTRIBUTES(
+                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                    id AS ident,
+                    holdable,
+                    opac_visible
+                ),
+                name
+            )
+      FROM  config.copy_status
+      WHERE id = $1;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name copy_note,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        create_date AS date,
+                        title
+                    ),
+                    value
+                )
+          FROM  asset.copy_note
+          WHERE id = $1;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name statcat,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        sc.name,
+                        sc.opac_visible
+                    ),
+                    asce.value
+                )
+          FROM  asset.stat_cat_entry asce
+                JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
+          WHERE asce.id = $1;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name monograph_part,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        'tag:open-ils.org:U2 at bmp/' || id AS id,
+                        id AS ident,
+                        label,
+                        label_sortkey,
+                        'tag:open-ils.org:U2 at bre/' || record AS record
+                    ),
+                    CASE 
+                        WHEN ('acp' = ANY ($4)) THEN
+                            XMLELEMENT( name copies,
+                                (SELECT XMLAGG(acp) FROM (
+                                    SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
+                                      FROM  asset.copy cp
+                                            JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
+                                      WHERE cpm.part = $1
+                                          AND cp.deleted IS FALSE
+                                      ORDER BY COALESCE(cp.copy_number,0), cp.barcode
+                                      LIMIT ($7 -> 'acp')::INT
+                                      OFFSET ($8 -> 'acp')::INT
+
+                                )x)
+                            )
+                        ELSE NULL
+                    END,
+                    CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE) ELSE NULL END
+                )
+          FROM  biblio.monograph_part
+          WHERE id = $1
+          GROUP BY id, label, label_sortkey, record;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name copy,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        'tag:open-ils.org:U2 at acp/' || id AS id, id AS copy_id,
+                        create_date, edit_date, copy_number, circulate, deposit,
+                        ref, holdable, deleted, deposit_amount, price, barcode,
+                        circ_modifier, circ_as_type, opac_visible, age_protect
+                    ),
+                    unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
+                    unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
+                    unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
+                    unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
+                    CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+                    CASE 
+                        WHEN ('acpn' = ANY ($4)) THEN
+                            XMLELEMENT( name copy_notes,
+                                (SELECT XMLAGG(acpn) FROM (
+                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
+                                      FROM  asset.copy_note
+                                      WHERE owning_copy = cp.id AND pub
+                                )x)
+                            )
+                        ELSE NULL
+                    END,
+                    CASE 
+                        WHEN ('ascecm' = ANY ($4)) THEN
+                            XMLELEMENT( name statcats,
+                                (SELECT XMLAGG(ascecm) FROM (
+                                    SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
+                                      FROM  asset.stat_cat_entry_copy_map
+                                      WHERE owning_copy = cp.id
+                                )x)
+                            )
+                        ELSE NULL
+                    END,
+                    CASE
+                        WHEN ('bre' = ANY ($4)) THEN
+                            XMLELEMENT( name foreign_records,
+                                (SELECT XMLAGG(bre) FROM (
+                                    SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
+                                      FROM  biblio.peer_bib_copy_map
+                                      WHERE target_copy = cp.id
+                                )x)
+
+                            )
+                        ELSE NULL
+                    END,
+                    CASE 
+                        WHEN ('bmp' = ANY ($4)) THEN
+                            XMLELEMENT( name monograph_parts,
+                                (SELECT XMLAGG(bmp) FROM (
+                                    SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
+                                      FROM  asset.copy_part_map
+                                      WHERE target_copy = cp.id
+                                )x)
+                            )
+                        ELSE NULL
+                    END,
+                    CASE 
+                        WHEN ('circ' = ANY ($4)) THEN
+                            XMLELEMENT( name current_circulation,
+                                (SELECT XMLAGG(circ) FROM (
+                                    SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
+                                      FROM  action.circulation
+                                      WHERE target_copy = cp.id
+                                            AND checkin_time IS NULL
+                                )x)
+                            )
+                        ELSE NULL
+                    END
+                )
+          FROM  asset.copy cp
+          WHERE id = $1
+              AND cp.deleted IS FALSE
+          GROUP BY id, status, location, circ_lib, call_number, create_date,
+              edit_date, copy_number, circulate, deposit, ref, holdable,
+              deleted, deposit_amount, price, barcode, circ_modifier,
+              circ_as_type, opac_visible, age_protect;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name serial_unit,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        'tag:open-ils.org:U2 at acp/' || id AS id, id AS copy_id,
+                        create_date, edit_date, copy_number, circulate, deposit,
+                        ref, holdable, deleted, deposit_amount, price, barcode,
+                        circ_modifier, circ_as_type, opac_visible, age_protect,
+                        status_changed_time, floating, mint_condition,
+                        detailed_contents, sort_key, summary_contents, cost 
+                    ),
+                    unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
+                    unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
+                    unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8),
+                    unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8),
+                    CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+                    XMLELEMENT( name copy_notes,
+                        CASE 
+                            WHEN ('acpn' = ANY ($4)) THEN
+                                (SELECT XMLAGG(acpn) FROM (
+                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE)
+                                      FROM  asset.copy_note
+                                      WHERE owning_copy = cp.id AND pub
+                                )x)
+                            ELSE NULL
+                        END
+                    ),
+                    XMLELEMENT( name statcats,
+                        CASE 
+                            WHEN ('ascecm' = ANY ($4)) THEN
+                                (SELECT XMLAGG(ascecm) FROM (
+                                    SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
+                                      FROM  asset.stat_cat_entry_copy_map
+                                      WHERE owning_copy = cp.id
+                                )x)
+                            ELSE NULL
+                        END
+                    ),
+                    XMLELEMENT( name foreign_records,
+                        CASE
+                            WHEN ('bre' = ANY ($4)) THEN
+                                (SELECT XMLAGG(bre) FROM (
+                                    SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
+                                      FROM  biblio.peer_bib_copy_map
+                                      WHERE target_copy = cp.id
+                                )x)
+                            ELSE NULL
+                        END
+                    ),
+                    CASE 
+                        WHEN ('bmp' = ANY ($4)) THEN
+                            XMLELEMENT( name monograph_parts,
+                                (SELECT XMLAGG(bmp) FROM (
+                                    SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
+                                      FROM  asset.copy_part_map
+                                      WHERE target_copy = cp.id
+                                )x)
+                            )
+                        ELSE NULL
+                    END,
+                    CASE 
+                        WHEN ('circ' = ANY ($4)) THEN
+                            XMLELEMENT( name current_circulation,
+                                (SELECT XMLAGG(circ) FROM (
+                                    SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
+                                      FROM  action.circulation
+                                      WHERE target_copy = cp.id
+                                            AND checkin_time IS NULL
+                                )x)
+                            )
+                        ELSE NULL
+                    END
+                )
+          FROM  serial.unit cp
+          WHERE id = $1
+              AND cp.deleted IS FALSE
+          GROUP BY id, status, location, circ_lib, call_number, create_date,
+              edit_date, copy_number, circulate, floating, mint_condition,
+              deposit, ref, holdable, deleted, deposit_amount, price,
+              barcode, circ_modifier, circ_as_type, opac_visible,
+              status_changed_time, detailed_contents, sort_key,
+              summary_contents, cost, age_protect;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name volume,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        'tag:open-ils.org:U2 at acn/' || acn.id AS id,
+                        acn.id AS vol_id, o.shortname AS lib,
+                        o.opac_visible AS opac_visible,
+                        deleted, label, label_sortkey, label_class, record
+                    ),
+                    unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
+                    CASE 
+                        WHEN ('acp' = ANY ($4)) THEN
+                            CASE WHEN $6 IS NOT NULL THEN
+                                XMLELEMENT( name copies,
+                                    (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
+                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
+                                            evergreen.rank_cp_status(cp.status) AS rank_avail
+                                          FROM  asset.copy cp
+                                                JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
+                                          WHERE cp.call_number = acn.id
+                                              AND cp.deleted IS FALSE
+                                          ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
+                                          LIMIT ($7 -> 'acp')::INT
+                                          OFFSET ($8 -> 'acp')::INT
+                                    )x)
+                                )
+                            ELSE
+                                XMLELEMENT( name copies,
+                                    (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
+                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
+                                            evergreen.rank_cp_status(cp.status) AS rank_avail
+                                          FROM  asset.copy cp
+                                                JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
+                                          WHERE cp.call_number = acn.id
+                                              AND cp.deleted IS FALSE
+                                          ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
+                                          LIMIT ($7 -> 'acp')::INT
+                                          OFFSET ($8 -> 'acp')::INT
+                                    )x)
+                                )
+                            END
+                        ELSE NULL
+                    END,
+                    XMLELEMENT(
+                        name uris,
+                        (SELECT XMLAGG(auri) FROM (SELECT unapi.auri(uri,'xml','uri', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE call_number = acn.id)x)
+                    ),
+                    unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
+                    unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
+                    CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END
+                ) AS x
+          FROM  asset.call_number acn
+                JOIN actor.org_unit o ON (o.id = acn.owning_lib)
+          WHERE acn.id = $1
+              AND acn.deleted IS FALSE
+          GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name call_number_prefix,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        id AS ident,
+                        label,
+                        'tag:open-ils.org:U2 at aou/' || owning_lib AS owning_lib,
+                        label_sortkey
+                    )
+                )
+          FROM  asset.call_number_prefix
+          WHERE id = $1;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name call_number_suffix,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        id AS ident,
+                        label,
+                        'tag:open-ils.org:U2 at aou/' || owning_lib AS owning_lib,
+                        label_sortkey
+                    )
+                )
+          FROM  asset.call_number_suffix
+          WHERE id = $1;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name uri,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+                        'tag:open-ils.org:U2 at auri/' || uri.id AS id,
+                        use_restriction,
+                        href,
+                        label
+                    ),
+                    CASE 
+                        WHEN ('acn' = ANY ($4)) THEN
+                            XMLELEMENT( name copies,
+                                (SELECT XMLAGG(acn) FROM (SELECT unapi.acn( call_number, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'auri'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE uri = uri.id)x)
+                            )
+                        ELSE NULL
+                    END
+                ) AS x
+          FROM  asset.uri uri
+          WHERE uri.id = $1
+          GROUP BY uri.id, use_restriction, href, label;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.mra ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+        SELECT  XMLELEMENT(
+                    name attributes,
+                    XMLATTRIBUTES(
+                        CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
+                        'tag:open-ils.org:U2 at mra/' || mra.id AS id,
+                        'tag:open-ils.org:U2 at bre/' || mra.id AS record
+                    ),
+                    (SELECT XMLAGG(foo.y)
+                      FROM (SELECT XMLELEMENT(
+                                name field,
+                                XMLATTRIBUTES(
+                                    key AS name,
+                                    cvm.value AS "coded-value",
+                                    rad.filter,
+                                    rad.sorter
+                                ),
+                                x.value
+                            )
+                           FROM EACH(mra.attrs) AS x
+                                JOIN config.record_attr_definition rad ON (x.key = rad.name)
+                                LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = x.key AND code = x.value)
+                        )foo(y)
+                    )
+                )
+          FROM  metabib.record_attr mra
+          WHERE mra.id = $1;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.circ (obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT XMLELEMENT(
+        name circ,
+        XMLATTRIBUTES(
+            CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+            'tag:open-ils.org:U2 at circ/' || id AS id,
+            xact_start,
+            due_date
+        ),
+        CASE WHEN ('aou' = ANY ($4)) THEN unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+        CASE WHEN ('acp' = ANY ($4)) THEN unapi.acp( circ_lib, $2, 'target_copy', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END
+    )
+    FROM action.circulation
+    WHERE id = $1;
+$F$ LANGUAGE SQL STABLE;
+
+/*
+
+ -- Some test queries
+
+SELECT unapi.memoize( 'bre', 1,'mods32','','{holdings_xml,acp}'::TEXT[], 'SYS1');
+SELECT unapi.memoize( 'bre', 1,'marcxml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
+SELECT unapi.memoize( 'bre', 1,'holdings_xml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
+
+SELECT unapi.biblio_record_entry_feed('{1}'::BIGINT[],'mods32','{holdings_xml,acp}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://c64/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+
+SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{holdings_xml}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'mods32','{holdings_xml}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+
+SELECT unapi.biblio_record_entry_feed('{216}'::BIGINT[],'marcxml','{}'::TEXT[], 'BR1');
+EXPLAIN ANALYZE SELECT unapi.bre(216,'marcxml','record','{holdings_xml,bre.unapi}'::TEXT[], 'BR1');
+EXPLAIN ANALYZE SELECT unapi.bre(216,'holdings_xml','record','{}'::TEXT[], 'BR1');
+EXPLAIN ANALYZE SELECT unapi.holdings_xml(216,4,'BR1',2,'{bre}'::TEXT[]);
+EXPLAIN ANALYZE SELECT unapi.bre(216,'mods32','record','{}'::TEXT[], 'BR1');
+
+-- Limit to 5 call numbers, 5 copies, with a preferred library of 4 (BR1), in SYS2 at a depth of 0
+EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,acnp,acns,bmp}','SYS2',0,'acn=>5,acp=>5',NULL,TRUE,4);
+
+*/
+
 COMMIT;

commit 8b2546e3a7ac9938a0725dffeec25224c48061e8
Author: Dan Scott <dan at coffeecode.net>
Date:   Wed Mar 21 20:00:01 2012 -0400

    TPAC: Show preferred library copies only in consortial scope
    
    A refinement of the preferred library display: only when the user is at
    the consortial scope should the preferred library copies float to the
    top. At all other scopes, the preferred library copies will appear in
    their proper place in the org unit hierarchy (or not at all if outside
    of the chosen scope).
    
    Signed-off-by: Dan Scott <dan at coffeecode.net>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
index 62133c9..bc74bd1 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
@@ -245,14 +245,6 @@ sub mk_copy_query {
                 }
             }
         };
-        if ($org != $pref_ou) {
-            $query->{from}->{acp}->{aou}->{filter}->{id}->{in}->{select}->{aou} = [{
-                column => 'id',
-                transform => 'actor.org_unit_descendants_pref_lib',
-                result_field => 'id',
-                params => [$depth, $pref_ou]
-            }];
-        }
     };
 
     # Filter hidden items if this is the public catalog
diff --git a/Open-ILS/src/sql/Pg/020.schema.functions.sql b/Open-ILS/src/sql/Pg/020.schema.functions.sql
index 35afbcd..ca4306e 100644
--- a/Open-ILS/src/sql/Pg/020.schema.functions.sql
+++ b/Open-ILS/src/sql/Pg/020.schema.functions.sql
@@ -152,21 +152,6 @@ CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT ) RETURNS SETOF actor
     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
 $$ LANGUAGE SQL ROWS 1;
 
-CREATE OR REPLACE FUNCTION actor.org_unit_descendants_pref_lib (
-    ou INT,
-    depth INT,
-    pref_ou INT
-) RETURNS SETOF actor.org_unit AS $$
-    SELECT ou.* FROM actor.org_unit_descendants($1, $2) AS ou
-    UNION
-    SELECT ou.* FROM actor.org_unit ou WHERE id = $3;
-$$ LANGUAGE SQL STABLE ROWS 1;
-
-COMMENT ON FUNCTION actor.org_unit_descendants_pref_lib( INT, INT, INT) IS $$
-Returns the descendants by depth of the specified library, but adds the
-preferred library if it is not contained in the set of descendants.
-$$;
-
 CREATE OR REPLACE FUNCTION actor.org_unit_descendants_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
     WITH RECURSIVE org_unit_descendants_distance(id, distance) AS (
             SELECT $1, 0
diff --git a/Open-ILS/src/sql/Pg/990.schema.unapi.sql b/Open-ILS/src/sql/Pg/990.schema.unapi.sql
index 45eca9f..ac33c66 100644
--- a/Open-ILS/src/sql/Pg/990.schema.unapi.sql
+++ b/Open-ILS/src/sql/Pg/990.schema.unapi.sql
@@ -20,7 +20,11 @@ RETURNS INTEGER AS $$
         SELECT id, distance FROM actor.org_unit_descendants_distance($2)
     )
     SELECT COALESCE(
-        (SELECT -10000 FROM actor.org_unit WHERE $1 = $3 AND id = $3),
+        (SELECT -10000 FROM actor.org_unit
+         WHERE $1 = $3 AND id = $3 AND $2 IN (
+                SELECT id FROM actor.org_unit WHERE parent_ou IS NULL
+             )
+        ),
         (SELECT distance FROM search_libs WHERE id = $1),
         10000
     );
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
index 5d71d97..7682777 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
@@ -19,21 +19,6 @@ BEGIN;
 -- check whether patch can be applied
 SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
 
-CREATE OR REPLACE FUNCTION actor.org_unit_descendants_pref_lib (
-    ou INT,
-    depth INT,
-    pref_ou INT
-) RETURNS SETOF actor.org_unit AS $$
-    SELECT ou.* FROM actor.org_unit_descendants($1, $2) AS ou
-    UNION
-    SELECT ou.* FROM actor.org_unit ou WHERE id = $3;
-$$ LANGUAGE SQL STABLE ROWS 1;
-
-COMMENT ON FUNCTION actor.org_unit_descendants_pref_lib( INT, INT, INT) IS $$
-Returns the descendants by depth of the specified library, but adds the
-preferred library if it is not contained in the set of descendants.
-$$;
-
 -- The simplest way to apply all of these changes is just to replace the unapi
 -- schema entirely -- the following is a copy of 990.schema.unapi.sql with
 -- the initial COMMIT in place in case the upgrade_deps_block_check fails;
@@ -61,7 +46,11 @@ RETURNS INTEGER AS $$
         SELECT id, distance FROM actor.org_unit_descendants_distance($2)
     )
     SELECT COALESCE(
-        (SELECT -10000 FROM actor.org_unit WHERE $1 = $3 AND id = $3),
+        (SELECT -10000 FROM actor.org_unit
+         WHERE $1 = $3 AND id = $3 AND $2 IN (
+                SELECT id FROM actor.org_unit WHERE parent_ou IS NULL
+             )
+        ),
         (SELECT distance FROM search_libs WHERE id = $1),
         10000
     );

commit 97776542e5cf8f6cc9adf0ad4830ffa20c1bf205
Author: Dan Scott <dan at coffeecode.net>
Date:   Wed Mar 21 00:14:56 2012 -0400

    TPAC: Sort copies from preferred library first
    
    This commit makes copies from the preferred library (if any) sort first
    in the displayed copy list in search results and record details, even
    if the search scope would otherwise not include the preferred library's
    copies at all.
    
    Signed-off-by: Dan Scott <dan at coffeecode.net>
    Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
index 307e321..62133c9 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
@@ -212,7 +212,7 @@ sub mk_copy_query {
 
         order_by => [
             { class => "aou", field => 'id', 
-              transform => 'evergreen.rank_ou', params => [$org]
+              transform => 'evergreen.rank_ou', params => [$org, $pref_ou]
             },
             {class => 'aou', field => 'name'}, 
             {class => 'acn', field => 'label'},
@@ -235,7 +235,7 @@ sub mk_copy_query {
                     in => {
                         select => {aou => [{
                             column => 'id', 
-                            transform => 'actor.org_unit_descendants', 
+                            transform => 'actor.org_unit_descendants',
                             result_field => 'id', 
                             params => [$depth]
                         }]},
@@ -244,6 +244,14 @@ sub mk_copy_query {
                     }
                 }
             }
+        };
+        if ($org != $pref_ou) {
+            $query->{from}->{acp}->{aou}->{filter}->{id}->{in}->{select}->{aou} = [{
+                column => 'id',
+                transform => 'actor.org_unit_descendants_pref_lib',
+                result_field => 'id',
+                params => [$depth, $pref_ou]
+            }];
         }
     };
 
diff --git a/Open-ILS/src/sql/Pg/020.schema.functions.sql b/Open-ILS/src/sql/Pg/020.schema.functions.sql
index ca4306e..35afbcd 100644
--- a/Open-ILS/src/sql/Pg/020.schema.functions.sql
+++ b/Open-ILS/src/sql/Pg/020.schema.functions.sql
@@ -152,6 +152,21 @@ CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT ) RETURNS SETOF actor
     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
 $$ LANGUAGE SQL ROWS 1;
 
+CREATE OR REPLACE FUNCTION actor.org_unit_descendants_pref_lib (
+    ou INT,
+    depth INT,
+    pref_ou INT
+) RETURNS SETOF actor.org_unit AS $$
+    SELECT ou.* FROM actor.org_unit_descendants($1, $2) AS ou
+    UNION
+    SELECT ou.* FROM actor.org_unit ou WHERE id = $3;
+$$ LANGUAGE SQL STABLE ROWS 1;
+
+COMMENT ON FUNCTION actor.org_unit_descendants_pref_lib( INT, INT, INT) IS $$
+Returns the descendants by depth of the specified library, but adds the
+preferred library if it is not contained in the set of descendants.
+$$;
+
 CREATE OR REPLACE FUNCTION actor.org_unit_descendants_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
     WITH RECURSIVE org_unit_descendants_distance(id, distance) AS (
             SELECT $1, 0
diff --git a/Open-ILS/src/sql/Pg/990.schema.unapi.sql b/Open-ILS/src/sql/Pg/990.schema.unapi.sql
index bac6a48..45eca9f 100644
--- a/Open-ILS/src/sql/Pg/990.schema.unapi.sql
+++ b/Open-ILS/src/sql/Pg/990.schema.unapi.sql
@@ -14,22 +14,18 @@ RETURNS anyarray AS $$
     SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
 $$ LANGUAGE SQL STABLE;
 
-DROP FUNCTION IF EXISTS evergreen.rank_ou(INT, INT, INT);
 CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
 RETURNS INTEGER AS $$
-    WITH pref_libs AS (
-        SELECT id, distance FROM actor.org_unit_descendants_distance(COALESCE($3, $2))
-    ), search_libs AS (
+    WITH search_libs AS (
         SELECT id, distance FROM actor.org_unit_descendants_distance($2)
     )
     SELECT COALESCE(
-        (SELECT distance - 10000 FROM pref_libs WHERE id = $1),
+        (SELECT -10000 FROM actor.org_unit WHERE $1 = $3 AND id = $3),
         (SELECT distance FROM search_libs WHERE id = $1),
         10000
     );
 $$ LANGUAGE SQL STABLE;
 
-DROP FUNCTION IF EXISTS evergreen.rank_cp_status(INT);
 CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
 RETURNS INTEGER AS $$
     WITH totally_available AS (
@@ -58,11 +54,12 @@ CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
     ouid INT,
     depth INT DEFAULT NULL,
     slimit HSTORE DEFAULT NULL,
-    soffset HSTORE DEFAULT NULL
+    soffset HSTORE DEFAULT NULL,
+    pref_lib INT DEFAULT NULL
 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
         SELECT acn.id, aou.name, acn.label_sortkey,
-            evergreen.rank_ou(aou.id, $2), evergreen.rank_cp_status(acp.status),
+            evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
             RANK() OVER w
         FROM asset.call_number acn
             JOIN asset.copy acp ON (acn.id = acp.call_number)
@@ -72,14 +69,14 @@ CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
                     FROM actor.org_unit_type aout
                         INNER JOIN actor.org_unit ou ON ou_type = aout.id
                     WHERE ou.id = $2
-                ))
+                ), $6)
             ) AS aou ON (acp.circ_lib = aou.id)
         WHERE acn.record = $1
             AND acn.deleted IS FALSE
             AND acp.deleted IS FALSE
         GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
         WINDOW w AS (
-            ORDER BY evergreen.rank_ou(aou.id, $2), evergreen.rank_cp_status(acp.status)
+            ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
         )
     ) AS ua
     GROUP BY ua.id, ua.name, ua.label_sortkey
@@ -94,7 +91,7 @@ CREATE OR REPLACE FUNCTION evergreen.located_uris (
     ouid INT,
     pref_lib INT DEFAULT NULL
 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
-    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2) AS pref_ou
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
       FROM asset.call_number acn
            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
@@ -103,7 +100,7 @@ CREATE OR REPLACE FUNCTION evergreen.located_uris (
           AND acn.deleted IS FALSE
           AND auri.active IS TRUE
     UNION
-    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2) AS pref_ou
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
       FROM asset.call_number acn
            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
@@ -439,7 +436,7 @@ RETURNS XML AS $F$
                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
                         -- Physical copies
                         SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
-                        FROM evergreen.ranked_volumes($1, $2, $4, $6, $7) AS y
+                        FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9) AS y
                         UNION ALL
                         -- Located URIs
                         SELECT unapi.acn(uris.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), 0, name, label_sortkey
@@ -1165,4 +1162,3 @@ EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,ac
 */
 
 COMMIT;
-
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
index 9daf37c..5d71d97 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
@@ -19,6 +19,21 @@ BEGIN;
 -- check whether patch can be applied
 SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
 
+CREATE OR REPLACE FUNCTION actor.org_unit_descendants_pref_lib (
+    ou INT,
+    depth INT,
+    pref_ou INT
+) RETURNS SETOF actor.org_unit AS $$
+    SELECT ou.* FROM actor.org_unit_descendants($1, $2) AS ou
+    UNION
+    SELECT ou.* FROM actor.org_unit ou WHERE id = $3;
+$$ LANGUAGE SQL STABLE ROWS 1;
+
+COMMENT ON FUNCTION actor.org_unit_descendants_pref_lib( INT, INT, INT) IS $$
+Returns the descendants by depth of the specified library, but adds the
+preferred library if it is not contained in the set of descendants.
+$$;
+
 -- The simplest way to apply all of these changes is just to replace the unapi
 -- schema entirely -- the following is a copy of 990.schema.unapi.sql with
 -- the initial COMMIT in place in case the upgrade_deps_block_check fails;
@@ -26,6 +41,7 @@ SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
 -- transaction will also fail. Not graceful, but safe!
 DROP SCHEMA IF EXISTS unapi CASCADE;
 
+BEGIN;
 CREATE SCHEMA unapi;
 
 CREATE OR REPLACE FUNCTION evergreen.org_top()
@@ -39,22 +55,18 @@ RETURNS anyarray AS $$
     SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
 $$ LANGUAGE SQL STABLE;
 
-DROP FUNCTION IF EXISTS evergreen.rank_ou(INT, INT, INT);
 CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
 RETURNS INTEGER AS $$
-    WITH pref_libs AS (
-        SELECT id, distance FROM actor.org_unit_descendants_distance(COALESCE($3, $2))
-    ), search_libs AS (
+    WITH search_libs AS (
         SELECT id, distance FROM actor.org_unit_descendants_distance($2)
     )
     SELECT COALESCE(
-        (SELECT distance - 10000 FROM pref_libs WHERE id = $1),
+        (SELECT -10000 FROM actor.org_unit WHERE $1 = $3 AND id = $3),
         (SELECT distance FROM search_libs WHERE id = $1),
         10000
     );
 $$ LANGUAGE SQL STABLE;
 
-DROP FUNCTION IF EXISTS evergreen.rank_cp_status(INT);
 CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
 RETURNS INTEGER AS $$
     WITH totally_available AS (
@@ -83,11 +95,12 @@ CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
     ouid INT,
     depth INT DEFAULT NULL,
     slimit HSTORE DEFAULT NULL,
-    soffset HSTORE DEFAULT NULL
+    soffset HSTORE DEFAULT NULL,
+    pref_lib INT DEFAULT NULL
 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
         SELECT acn.id, aou.name, acn.label_sortkey,
-            evergreen.rank_ou(aou.id, $2), evergreen.rank_cp_status(acp.status),
+            evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
             RANK() OVER w
         FROM asset.call_number acn
             JOIN asset.copy acp ON (acn.id = acp.call_number)
@@ -97,14 +110,14 @@ CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
                     FROM actor.org_unit_type aout
                         INNER JOIN actor.org_unit ou ON ou_type = aout.id
                     WHERE ou.id = $2
-                ))
+                ), $6)
             ) AS aou ON (acp.circ_lib = aou.id)
         WHERE acn.record = $1
             AND acn.deleted IS FALSE
             AND acp.deleted IS FALSE
         GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
         WINDOW w AS (
-            ORDER BY evergreen.rank_ou(aou.id, $2), evergreen.rank_cp_status(acp.status)
+            ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
         )
     ) AS ua
     GROUP BY ua.id, ua.name, ua.label_sortkey
@@ -119,7 +132,7 @@ CREATE OR REPLACE FUNCTION evergreen.located_uris (
     ouid INT,
     pref_lib INT DEFAULT NULL
 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
-    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2) AS pref_ou
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
       FROM asset.call_number acn
            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
@@ -128,7 +141,7 @@ CREATE OR REPLACE FUNCTION evergreen.located_uris (
           AND acn.deleted IS FALSE
           AND auri.active IS TRUE
     UNION
-    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2) AS pref_ou
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
       FROM asset.call_number acn
            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
@@ -464,7 +477,7 @@ RETURNS XML AS $F$
                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
                         -- Physical copies
                         SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
-                        FROM evergreen.ranked_volumes($1, $2, $4, $6, $7) AS y
+                        FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9) AS y
                         UNION ALL
                         -- Located URIs
                         SELECT unapi.acn(uris.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), 0, name, label_sortkey
@@ -1190,4 +1203,3 @@ EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,ac
 */
 
 COMMIT;
-

commit c0c51fa1cc5ced201f290fcd4a880f802e8e36ac
Author: Dan Scott <dan at coffeecode.net>
Date:   Wed Mar 21 00:40:42 2012 -0400

    TPAC: Fix filtering of org units in record details
    
    The syntax for the filtering clause isn't quite right for the
    non-opac-visible org units; remove the braces and win.
    
    Signed-off-by: Dan Scott <dan at coffeecode.net>
    Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
index a0372ac..307e321 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
@@ -252,7 +252,7 @@ sub mk_copy_query {
         $query->{where}->{'+acp'}->{opac_visible} = 't';
         $query->{from}->{'acp'}->{'acpl'}->{filter} = {opac_visible => 't'};
         $query->{from}->{'acp'}->{'ccs'}->{filter} = {opac_visible => 't'};
-        $query->{where}->{'+aou'}->{opac_visible} = {'t'};
+        $query->{where}->{'+aou'}->{opac_visible} = 't';
     }
 
     return $query;

commit 2cce485ffc953176760b4972ffc7f2944661f4b6
Author: Dan Scott <dan at coffeecode.net>
Date:   Tue Mar 20 22:00:11 2012 -0400

    TPAC / unapi: Overhaul copy and located URI display
    
    In unauthenticated mode, sort returned copies in the following order:
    
    1. Search library proximity
    2. Search library name
    3. (Search results only) Call number, by most available copy status
    4. Call number label
    5. Copy status availability
    
    Also return & display the copy counts ("x of y copies are available")
    for the search library and each of its parents.
    
    We can also determine "preferred library" as follows, taking the first
    match:
    
    1. A new GET param, "plib", specifying the number of the OU
    2. If authenticated, the user's "search library" preference setting;
    3. If authenticated, the user's home library;
    4. Physical location in which the search is being issued (if any)
    
    Given a preferred library:
    
    1. Promote the copies (if any) for the preferred library to the top of
       the copy display list.
    2. Include the copy count for the preferred library if it is outside of
       the scope of the search library;
    3. In record details, give users the ability to jump to their preferred
       library scope via "Show preferred library" beside the preferred
       library copy count, if applicable. If your search library is located
       beneath your pref library in the org hierarchy, then you'll get just
       the "copy depth" link instead.
    
    Also, untangle the located URIs from physical call numbers in
    in-database unapi.
    
    We had been facing the limitation of URIs being limited by the maximum
    number of call numbers, with the result that URIs may or may not have
    been displayed. Now, we take the bold tack of returning all URIs that
    are in scope for both the search library and the preferred library. No
    limiting is currently possible, but in the realistic worst case scenario
    of both search lib and pref lib being set to an OU at depth 4, we'll get
    a maximum of 8 different URIs back to display. (More realistically, it's
    unlikely that a different URI will be set for each level of the
    hierarchy.)
    
    Some of the more granular code changes are as follows:
    
    * Add attributes to in-db unapi acp, sunit, holdings_xml
    
    This gives us more flexibility in displaying due dates within search
    results or other contexts in which holdings are displayed. The
    age_protect, copy_id, and vol_id attributes are required by the record
    summary display.
    
    * Enable TPAC to display records via HSTORE-based unapi
    
    Now that unapi requires HSTORE-based LIMIT and OFFSET arguments, take
    the simplest possible approach to get get_records_and_facets() working
    again.
    
    In search results, we want to limit both the number of copies we get
    back (defaulting to a maximum of 5 copies per call number) and the
    number of call numbers we get back (defaulting to a maximum of 5 call
    numbers), so that we don't get 50 call numbers each with 5 copies back
    and wind up grinding Evergreen's performance down to a halt.
    
    Rough thought is that the caller can filter through the (maximum) 25
    hits that are returned to present their preferred results. For example,
    a site could opt to show 5 different call numbers with just one copy per
    call number, or 5 different copies for a single call number, or all 25.
    
    * Avoid deleted call numbers, copies, and sunits
    
    The rewrite of unapi removed the filters for deleted call numbers that
    were added to master in LP# 893315 / commit hash 47a16f516d. Fix this
    to prevent returning pages of results that would show nothing in the
    TPAC because all of the call numbers were deleted.
    
    * Display multiple URIs correctly
    
    We weren't clearing the hash each time that we added it to the list of
    URIs, and that ended up duplicating the URI rather than adding distinct
    URIs.
    
    * Take copy availability into account for copy order
    
    This is most important in the LIMITed list of copies returned to the
    TPAC search results page, but also useful on the record details page.
    
    This commit adds "copy status ranking" into the result ordering, such
    that more available copies will be returned first in the list (and thus,
    in the case of search results where call numbers and copies are
    typically limited (currently to 5 of 5), ensuring that the most
    available copies are likely to be seen by users).
    
    The "availability" ranking for a copy is defined by a new function,
    evergreen.rank_cp_status(), that takes criteria from the
    config.copy_status table and ranks it in three tiers, as follows:
    
    1. Most available =  opac_visible and copy_active are both TRUE
    2. Almost available = holdable and opac_visible are TRUE, but
       copy_active is FALSE, or status ID = 1 ("Checked out")
    3. Unavailable = anything else.
    
    Thus, changing copy availability ranking (such as demoting "On holds
    shelf" from the "most available" status to "almost available"), is
    simply a matter of tweaking the evergreen.rank_cp_status() function.
    
    * unapi database functions: mark STABLE
    
    Thomas Berezansky suggested marking one of the new database functions in
    the in-database unapi suite as STABLE to help the optimizer, which lead
    to us realizing that everything can be marked as stable. This should
    enable the planner to generate better plans than if left to the default
    pessimistic level of VOLATILE.
    
    Signed-off-by: Dan Scott <dan at coffeecode.net>
    Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
index e2cf2bc..a0372ac 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
@@ -15,6 +15,8 @@ sub load_record {
     $ctx->{page} = 'record';  
 
     my $org = $self->_get_search_lib();
+    my $org_name = $ctx->{get_aou}->($org)->shortname;
+    my $pref_ou = $self->_get_pref_lib();
     my $depth = $self->cgi->param('depth');
     $depth = $ctx->{get_aou}->($org)->ou_type->depth 
         unless defined $depth; # can be 0
@@ -41,10 +43,15 @@ sub load_record {
     my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
     my $copy_rec = $cstore->request(
         'open-ils.cstore.json_query.atomic', 
-        $self->mk_copy_query($rec_id, $org, $copy_depth, $copy_limit, $copy_offset)
+        $self->mk_copy_query($rec_id, $org, $copy_depth, $copy_limit, $copy_offset, $pref_ou)
     );
 
-    my (undef, @rec_data) = $self->get_records_and_facets([$rec_id], undef, {flesh => '{holdings_xml,bmp,mra,acp,acnp,acns}'});
+    my (undef, @rec_data) = $self->get_records_and_facets([$rec_id], undef, {
+        flesh => '{holdings_xml,bmp,mra,acp,acnp,acns}',
+        site => $org_name,
+        depth => $depth,
+        pref_lib => $pref_ou
+    });
     $ctx->{bre_id} = $rec_data[0]->{id};
     $ctx->{marc_xml} = $rec_data[0]->{marc_xml};
 
@@ -143,6 +150,7 @@ sub mk_copy_query {
     my $depth = shift;
     my $copy_limit = shift;
     my $copy_offset = shift;
+    my $pref_ou = shift;
 
     my $query = {
         select => {
@@ -203,24 +211,20 @@ sub mk_copy_query {
         },
 
         order_by => [
+            { class => "aou", field => 'id', 
+              transform => 'evergreen.rank_ou', params => [$org]
+            },
             {class => 'aou', field => 'name'}, 
-            {class => 'acn', field => 'label'}
+            {class => 'acn', field => 'label'},
+            { class => "acp", field => 'status',
+              transform => 'evergreen.rank_cp_status'
+            }
         ],
 
         limit => $copy_limit,
         offset => $copy_offset
     };
 
-    # XXX In the future, $sort_org should be understood to be an abstration
-    # that refers to something configurable, not necessariyl physical_loc.
-
-    if (my $sort_org = $self->ctx->{physical_loc}) {
-        unshift @{$query->{order_by}}, {
-            class => 'acp', field => 'circ_lib', transform => 'numeric_eq',
-            params => [$sort_org], direction => 'desc'
-        };
-    }
-
     if($org != $self->ctx->{aou_tree}->()->id) { 
         # no need to add the org join filter if we're not actually filtering
         $query->{from}->{acp}->{aou} = {
@@ -248,7 +252,7 @@ sub mk_copy_query {
         $query->{where}->{'+acp'}->{opac_visible} = 't';
         $query->{from}->{'acp'}->{'acpl'}->{filter} = {opac_visible => 't'};
         $query->{from}->{'acp'}->{'ccs'}->{filter} = {opac_visible => 't'};
-        $query->{from}->{'acp'}->{'aou'}->{filter} = {opac_visible => 't'};
+        $query->{where}->{'+aou'}->{opac_visible} = {'t'};
     }
 
     return $query;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm
index 5c13a83..d687de2 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm
@@ -279,6 +279,7 @@ sub load_rresults {
     my @facets = $cgi->param('facet');
     my $limit = $self->_get_search_limit;
     $ctx->{search_ou} = $self->_get_search_lib();
+    $ctx->{pref_ou} = $self->_get_pref_lib() || $ctx->{search_ou};
     my $offset = $page * $limit;
     my $metarecord = $cgi->param('metarecord');
     my $results; 
@@ -375,7 +376,8 @@ sub load_rresults {
         {
             flesh => '{holdings_xml,mra,acp,acnp,acns,bmp}',
             site => $site,
-            depth => $depth
+            depth => $depth,
+            pref_lib => $ctx->{pref_ou},
         }
     );
 
@@ -525,6 +527,7 @@ sub marc_expert_search {
     my $page = $self->cgi->param("page") || 0;
     my $limit = $self->_get_search_limit;
     $self->ctx->{search_ou} = $self->_get_search_lib();
+    $self->ctx->{pref_ou} = $self->_get_pref_lib();
     my $offset = $page * $limit;
 
     $self->ctx->{records} = [];
@@ -572,7 +575,10 @@ sub marc_expert_search {
     }
 
     my ($facets, @data) = $self->get_records_and_facets(
-        $self->ctx->{ids}, undef, {flesh => "{holdings_xml,mra,acnp,acns}"}
+        $self->ctx->{ids}, undef, {
+            flesh => "{holdings_xml,mra,acnp,acns}",
+            pref_lib => $self->ctx->{pref_ou},
+        }
     );
 
     $self->ctx->{records} = [@data];
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
index b7c015b..e9b61f4 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
@@ -209,7 +209,8 @@ sub get_records_and_facets {
             $unapi_args->{flesh}, 
             $unapi_args->{site}, 
             $unapi_args->{depth}, 
-            $unapi_args->{flesh_depth}, 
+            'acn=>' . $unapi_args->{flesh_depth} . ',acp=>' . $unapi_args->{flesh_depth}, 
+            undef, undef, $unapi_args->{pref_lib}
         ]}
     ) for @$rec_ids;
 
@@ -292,6 +293,20 @@ sub _get_search_lib {
     $loc = $self->cgi->param('loc');
     return $loc if $loc;
 
+    my $pref_lib = $self->_get_pref_lib();
+    return $pref_lib if $pref_lib;
+
+    return $ctx->{aou_tree}->()->id;
+}
+
+sub _get_pref_lib {
+    my $self = shift;
+    my $ctx = $self->ctx;
+
+    # plib param takes precedence
+    my $plib = $self->cgi->param('plib');
+    return $plib if $plib;
+
     if ($ctx->{user}) {
         # See if the user has a search library preference
         my $lset = $self->editor->search_actor_user_setting({
@@ -308,7 +323,6 @@ sub _get_search_lib {
         return $self->cgi->param('physical_loc');
     }
 
-    return $ctx->{aou_tree}->()->id;
 }
 
 # This is defensively coded since we don't do much manual reading from the
diff --git a/Open-ILS/src/sql/Pg/990.schema.unapi.sql b/Open-ILS/src/sql/Pg/990.schema.unapi.sql
index 8ba8efc..bac6a48 100644
--- a/Open-ILS/src/sql/Pg/990.schema.unapi.sql
+++ b/Open-ILS/src/sql/Pg/990.schema.unapi.sql
@@ -3,8 +3,116 @@ DROP SCHEMA IF EXISTS unapi CASCADE;
 BEGIN;
 CREATE SCHEMA unapi;
 
-CREATE OR REPLACE FUNCTION evergreen.org_top() RETURNS SETOF actor.org_unit AS $$ SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1; $$ LANGUAGE SQL ROWS 1;
-CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT) RETURNS anyarray AS $$ SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2; $$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION evergreen.org_top()
+RETURNS SETOF actor.org_unit AS $$
+    SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;
+$$ LANGUAGE SQL STABLE
+ROWS 1;
+
+CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT)
+RETURNS anyarray AS $$
+    SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
+$$ LANGUAGE SQL STABLE;
+
+DROP FUNCTION IF EXISTS evergreen.rank_ou(INT, INT, INT);
+CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
+RETURNS INTEGER AS $$
+    WITH pref_libs AS (
+        SELECT id, distance FROM actor.org_unit_descendants_distance(COALESCE($3, $2))
+    ), search_libs AS (
+        SELECT id, distance FROM actor.org_unit_descendants_distance($2)
+    )
+    SELECT COALESCE(
+        (SELECT distance - 10000 FROM pref_libs WHERE id = $1),
+        (SELECT distance FROM search_libs WHERE id = $1),
+        10000
+    );
+$$ LANGUAGE SQL STABLE;
+
+DROP FUNCTION IF EXISTS evergreen.rank_cp_status(INT);
+CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
+RETURNS INTEGER AS $$
+    WITH totally_available AS (
+        SELECT id, 0 AS avail_rank
+        FROM config.copy_status
+        WHERE opac_visible IS TRUE
+            AND copy_active IS TRUE
+            AND id != 1 -- "Checked out"
+    ), almost_available AS (
+        SELECT id, 10 AS avail_rank
+        FROM config.copy_status
+        WHERE holdable IS TRUE
+            AND opac_visible IS TRUE
+            AND copy_active IS FALSE
+            OR id = 1 -- "Checked out"
+    )
+    SELECT COALESCE(
+        (SELECT avail_rank FROM totally_available WHERE $1 IN (id)),
+        (SELECT avail_rank FROM almost_available WHERE $1 IN (id)),
+        100
+    );
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
+    bibid BIGINT, 
+    ouid INT,
+    depth INT DEFAULT NULL,
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL
+) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
+    SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
+        SELECT acn.id, aou.name, acn.label_sortkey,
+            evergreen.rank_ou(aou.id, $2), evergreen.rank_cp_status(acp.status),
+            RANK() OVER w
+        FROM asset.call_number acn
+            JOIN asset.copy acp ON (acn.id = acp.call_number)
+            JOIN actor.org_unit_descendants( $2, COALESCE(
+                $3, (
+                    SELECT depth
+                    FROM actor.org_unit_type aout
+                        INNER JOIN actor.org_unit ou ON ou_type = aout.id
+                    WHERE ou.id = $2
+                ))
+            ) AS aou ON (acp.circ_lib = aou.id)
+        WHERE acn.record = $1
+            AND acn.deleted IS FALSE
+            AND acp.deleted IS FALSE
+        GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
+        WINDOW w AS (
+            ORDER BY evergreen.rank_ou(aou.id, $2), evergreen.rank_cp_status(acp.status)
+        )
+    ) AS ua
+    GROUP BY ua.id, ua.name, ua.label_sortkey
+    ORDER BY rank, ua.name, ua.label_sortkey
+    LIMIT ($4 -> 'acn')::INT
+    OFFSET ($5 -> 'acn')::INT;
+$$
+LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION evergreen.located_uris (
+    bibid BIGINT, 
+    ouid INT,
+    pref_lib INT DEFAULT NULL
+) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2) AS pref_ou
+      FROM asset.call_number acn
+           INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
+           INNER JOIN asset.uri auri ON auri.id = auricnm.uri
+           INNER JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
+      WHERE acn.record = $1
+          AND acn.deleted IS FALSE
+          AND auri.active IS TRUE
+    UNION
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2) AS pref_ou
+      FROM asset.call_number acn
+           INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
+           INNER JOIN asset.uri auri ON auri.id = auricnm.uri
+           INNER JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
+      WHERE acn.record = $1
+          AND acn.deleted IS FALSE
+          AND auri.active IS TRUE;
+$$
+LANGUAGE SQL STABLE;
 
 CREATE TABLE unapi.bre_output_layout (
     name                TEXT    PRIMARY KEY,
@@ -27,31 +135,56 @@ INSERT INTO unapi.bre_output_layout
 ;
 
 -- Dummy functions, so we can create the real ones out of order
-CREATE OR REPLACE FUNCTION unapi.aou    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acnp   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acns   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acn    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.ssub   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sdist  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sstr   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sitem  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sunit  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sisum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sbsum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sssum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.siss   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.auri   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acpn   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acl    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.ccs    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.bre    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.bmp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.mra    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-
-CREATE OR REPLACE FUNCTION unapi.holdings_xml ( bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.aou    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acnp   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acns   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acn    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.ssub   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sdist  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sstr   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sitem  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sunit  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sisum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sbsum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sssum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.siss   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.auri   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acpn   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acl    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.ccs    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.bre (
+    obj_id BIGINT,
+    format TEXT,
+    ename TEXT,
+    includes TEXT[],
+    org TEXT,
+    depth INT DEFAULT NULL,
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.bmp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.mra    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.circ   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.holdings_xml (
+    bid BIGINT,
+    ouid INT,
+    org TEXT,
+    depth INT DEFAULT NULL,
+    includes TEXT[] DEFAULT NULL::TEXT[],
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.memoize (classname TEXT, obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
 DECLARE
@@ -78,7 +211,7 @@ BEGIN
     EXECUTE $$SELECT unapi.$$ || classname || $$( $1, $2, $3, $4, $5, $6, $7, $8, $9);$$ INTO output USING obj_id, format, ename, includes, org, depth, slimit, soffset, include_xmlns;
     RETURN output;
 END;
-$F$ LANGUAGE PLPGSQL;
+$F$ LANGUAGE PLPGSQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$
 DECLARE
@@ -137,9 +270,21 @@ BEGIN
 
     RETURN tmp_xml::XML;
 END;
-$F$ LANGUAGE PLPGSQL;
-
-CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+$F$ LANGUAGE PLPGSQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.bre (
+    obj_id BIGINT,
+    format TEXT,
+    ename TEXT,
+    includes TEXT[],
+    org TEXT,
+    depth INT DEFAULT NULL,
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$
 DECLARE
     me      biblio.record_entry%ROWTYPE;
     layout  unapi.bre_output_layout%ROWTYPE;
@@ -184,9 +329,9 @@ BEGIN
         axml := NULL::XML;
     END IF;
 
-    -- grab hodlings if we need them
+    -- grab holdings if we need them
     IF ('holdings_xml' = ANY (includes)) THEN 
-        hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
+        hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
     ELSE
         hxml := NULL::XML;
     END IF;
@@ -234,9 +379,20 @@ BEGIN
     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
     RETURN output;
 END;
-$F$ LANGUAGE PLPGSQL;
-
-CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$
+$F$ LANGUAGE PLPGSQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.holdings_xml (
+    bid BIGINT,
+    ouid INT,
+    org TEXT,
+    depth INT DEFAULT NULL,
+    includes TEXT[] DEFAULT NULL::TEXT[],
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$
      SELECT  XMLELEMENT(
                  name holdings,
                  XMLATTRIBUTES(
@@ -257,6 +413,12 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
                                  )::text
                            FROM  asset.staff_ou_record_copy_count($2, $1)
+                                     UNION
+                         SELECT  XMLELEMENT(
+                                     name count,
+                                     XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
+                                 )::text
+                           FROM  asset.opac_ou_record_copy_count($9,  $1)
                                      ORDER BY 1
                      )x)
                  ),
@@ -272,39 +434,18 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                         )
                      ELSE NULL
                  END,
-                 CASE WHEN $4 IS NULL THEN
-                     XMLELEMENT(
-                         name volumes,
-                         (SELECT XMLAGG(acn) FROM (
-                            SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE) FROM (
-                                SELECT  distinct acn.id, acn.label_sortkey
-                                  FROM  asset.call_number acn
-                                        JOIN asset.copy acp ON (acn.id = acp.call_number)
-                                        JOIN actor.org_unit_descendants( $2 ) aoud ON (acp.circ_lib = aoud.id)
-                                  WHERE acn.record = $1
-                                  ORDER BY acn.label_sortkey
-                                  LIMIT ($6 -> 'acn')::INT
-                                  OFFSET ($7 -> 'acn')::INT
-                            )y
-                         )x)
-                     )
-                 ELSE
-                     XMLELEMENT(
-                         name volumes,
-                         (SELECT XMLAGG(acn) FROM (
-                            SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE) FROM (
-                                SELECT  distinct acn.id, acn.label_sortkey
-                                  FROM  asset.call_number acn
-                                        JOIN asset.copy acp ON (acn.id = acp.call_number)
-                                        JOIN actor.org_unit_descendants( $2, $4 ) aoud ON (acp.circ_lib = aoud.id)
-                                  WHERE acn.record = $1
-                                  ORDER BY acn.label_sortkey
-                                  LIMIT ($6 -> 'acn')::INT
-                                  OFFSET ($7 -> 'acn')::INT
-                            )y
-                         )x)
-                     )
-                 END,
+                 XMLELEMENT(
+                     name volumes,
+                     (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
+                        -- Physical copies
+                        SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
+                        FROM evergreen.ranked_volumes($1, $2, $4, $6, $7) AS y
+                        UNION ALL
+                        -- Located URIs
+                        SELECT unapi.acn(uris.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), 0, name, label_sortkey
+                        FROM evergreen.located_uris($1, $2, $9) AS uris
+                     )x)
+                 ),
                  CASE WHEN ('ssub' = ANY ($5)) THEN 
                      XMLELEMENT(
                          name subscriptions,
@@ -319,7 +460,7 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                      XMLELEMENT(
                          name foreign_copies,
                          (SELECT XMLAGG(acp) FROM (
-                            SELECT  unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
+                            SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
                               FROM  biblio.peer_bib_copy_map p
                                     JOIN asset.copy c ON (p.target_copy = c.id)
                               WHERE NOT c.deleted AND p.peer_record = $1
@@ -329,7 +470,7 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                      )
                  ELSE NULL END
              );
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -355,7 +496,7 @@ CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT,  ename TEXT,
           FROM  serial.subscription ssub
           WHERE id = $1
           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -413,7 +554,7 @@ CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT
           FROM  serial.distribution sdist
           WHERE id = $1
           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -439,7 +580,7 @@ CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT,
       FROM  serial.stream sstr
       WHERE id = $1
       GROUP BY id, routing_label, distribution;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -466,7 +607,7 @@ CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT,
       FROM  serial.issuance sstr
       WHERE id = $1
       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -495,7 +636,7 @@ CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT
                 )
           FROM  serial.item sitem
           WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 
 CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
@@ -511,7 +652,7 @@ CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT
       FROM  serial.supplement_summary ssum
       WHERE id = $1
       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -526,7 +667,7 @@ CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT
       FROM  serial.basic_summary ssum
       WHERE id = $1
       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -541,7 +682,7 @@ CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT
       FROM  serial.index_summary ssum
       WHERE id = $1
       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 
 CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
@@ -575,7 +716,7 @@ BEGIN
     RETURN output;
 
 END;
-$F$ LANGUAGE PLPGSQL;
+$F$ LANGUAGE PLPGSQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -592,7 +733,7 @@ CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT,
             )
       FROM  asset.copy_location
       WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -607,7 +748,7 @@ CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT,
             )
       FROM  config.copy_status
       WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -621,7 +762,7 @@ CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT,
                 )
           FROM  asset.copy_note
           WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -636,7 +777,7 @@ CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEX
           FROM  asset.stat_cat_entry asce
                 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
           WHERE asce.id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -657,6 +798,7 @@ CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT,
                                       FROM  asset.copy cp
                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
                                       WHERE cpm.part = $1
+                                          AND cp.deleted IS FALSE
                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
                                       LIMIT ($7 -> 'acp')::INT
                                       OFFSET ($8 -> 'acp')::INT
@@ -670,17 +812,17 @@ CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT,
           FROM  biblio.monograph_part
           WHERE id = $1
           GROUP BY id, label, label_sortkey, record;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name copy,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
-                        'tag:open-ils.org:U2 at acp/' || id AS id,
+                        'tag:open-ils.org:U2 at acp/' || id AS id, id AS copy_id,
                         create_date, edit_date, copy_number, circulate, deposit,
                         ref, holdable, deleted, deposit_amount, price, barcode,
-                        circ_modifier, circ_as_type, opac_visible
+                        circ_modifier, circ_as_type, opac_visible, age_protect
                     ),
                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
@@ -731,23 +873,40 @@ CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT,
                                 )x)
                             )
                         ELSE NULL
+                    END,
+                    CASE 
+                        WHEN ('circ' = ANY ($4)) THEN
+                            XMLELEMENT( name current_circulation,
+                                (SELECT XMLAGG(circ) FROM (
+                                    SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
+                                      FROM  action.circulation
+                                      WHERE target_copy = cp.id
+                                            AND checkin_time IS NULL
+                                )x)
+                            )
+                        ELSE NULL
                     END
                 )
           FROM  asset.copy cp
           WHERE id = $1
-          GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible;
-$F$ LANGUAGE SQL;
+              AND cp.deleted IS FALSE
+          GROUP BY id, status, location, circ_lib, call_number, create_date,
+              edit_date, copy_number, circulate, deposit, ref, holdable,
+              deleted, deposit_amount, price, barcode, circ_modifier,
+              circ_as_type, opac_visible, age_protect;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name serial_unit,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
-                        'tag:open-ils.org:U2 at acp/' || id AS id,
+                        'tag:open-ils.org:U2 at acp/' || id AS id, id AS copy_id,
                         create_date, edit_date, copy_number, circulate, deposit,
                         ref, holdable, deleted, deposit_amount, price, barcode,
-                        circ_modifier, circ_as_type, opac_visible, status_changed_time,
-                        floating, mint_condition, detailed_contents, sort_key, summary_contents, cost 
+                        circ_modifier, circ_as_type, opac_visible, age_protect,
+                        status_changed_time, floating, mint_condition,
+                        detailed_contents, sort_key, summary_contents, cost 
                     ),
                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
@@ -797,13 +956,30 @@ CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT
                                 )x)
                             )
                         ELSE NULL
+                    END,
+                    CASE 
+                        WHEN ('circ' = ANY ($4)) THEN
+                            XMLELEMENT( name current_circulation,
+                                (SELECT XMLAGG(circ) FROM (
+                                    SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
+                                      FROM  action.circulation
+                                      WHERE target_copy = cp.id
+                                            AND checkin_time IS NULL
+                                )x)
+                            )
+                        ELSE NULL
                     END
                 )
           FROM  serial.unit cp
           WHERE id = $1
-          GROUP BY  id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, floating, mint_condition,
-                    deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible, status_changed_time, detailed_contents, sort_key, summary_contents, cost;
-$F$ LANGUAGE SQL;
+              AND cp.deleted IS FALSE
+          GROUP BY id, status, location, circ_lib, call_number, create_date,
+              edit_date, copy_number, circulate, floating, mint_condition,
+              deposit, ref, holdable, deleted, deposit_amount, price,
+              barcode, circ_modifier, circ_as_type, opac_visible,
+              status_changed_time, detailed_contents, sort_key,
+              summary_contents, cost, age_protect;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -811,7 +987,7 @@ CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                         'tag:open-ils.org:U2 at acn/' || acn.id AS id,
-                        o.shortname AS lib,
+                        acn.id AS vol_id, o.shortname AS lib,
                         o.opac_visible AS opac_visible,
                         deleted, label, label_sortkey, label_class, record
                     ),
@@ -820,24 +996,28 @@ CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT,
                         WHEN ('acp' = ANY ($4)) THEN
                             CASE WHEN $6 IS NOT NULL THEN
                                 XMLELEMENT( name copies,
-                                    (SELECT XMLAGG(acp) FROM (
-                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
+                                    (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
+                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
+                                            evergreen.rank_cp_status(cp.status) AS rank_avail
                                           FROM  asset.copy cp
                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
                                           WHERE cp.call_number = acn.id
-                                          ORDER BY COALESCE(cp.copy_number,0), cp.barcode
+                                              AND cp.deleted IS FALSE
+                                          ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
                                           LIMIT ($7 -> 'acp')::INT
                                           OFFSET ($8 -> 'acp')::INT
                                     )x)
                                 )
                             ELSE
                                 XMLELEMENT( name copies,
-                                    (SELECT XMLAGG(acp) FROM (
-                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
+                                    (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
+                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
+                                            evergreen.rank_cp_status(cp.status) AS rank_avail
                                           FROM  asset.copy cp
                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
                                           WHERE cp.call_number = acn.id
-                                          ORDER BY COALESCE(cp.copy_number,0), cp.barcode
+                                              AND cp.deleted IS FALSE
+                                          ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
                                           LIMIT ($7 -> 'acp')::INT
                                           OFFSET ($8 -> 'acp')::INT
                                     )x)
@@ -856,8 +1036,9 @@ CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT,
           FROM  asset.call_number acn
                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
           WHERE acn.id = $1
+              AND acn.deleted IS FALSE
           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -872,7 +1053,7 @@ CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT,  ename TEXT,
                 )
           FROM  asset.call_number_prefix
           WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -887,7 +1068,7 @@ CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT,  ename TEXT,
                 )
           FROM  asset.call_number_suffix
           WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -910,7 +1091,7 @@ CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT,
           FROM  asset.uri uri
           WHERE uri.id = $1
           GROUP BY uri.id, use_restriction, href, label;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.mra ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -939,7 +1120,23 @@ CREATE OR REPLACE FUNCTION unapi.mra ( obj_id BIGINT, format TEXT,  ename TEXT,
                 )
           FROM  metabib.record_attr mra
           WHERE mra.id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.circ (obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT XMLELEMENT(
+        name circ,
+        XMLATTRIBUTES(
+            CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+            'tag:open-ils.org:U2 at circ/' || id AS id,
+            xact_start,
+            due_date
+        ),
+        CASE WHEN ('aou' = ANY ($4)) THEN unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+        CASE WHEN ('acp' = ANY ($4)) THEN unapi.acp( circ_lib, $2, 'target_copy', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END
+    )
+    FROM action.circulation
+    WHERE id = $1;
+$F$ LANGUAGE SQL STABLE;
 
 /*
 
@@ -962,6 +1159,9 @@ EXPLAIN ANALYZE SELECT unapi.bre(216,'holdings_xml','record','{}'::TEXT[], 'BR1'
 EXPLAIN ANALYZE SELECT unapi.holdings_xml(216,4,'BR1',2,'{bre}'::TEXT[]);
 EXPLAIN ANALYZE SELECT unapi.bre(216,'mods32','record','{}'::TEXT[], 'BR1');
 
+-- Limit to 5 call numbers, 5 copies, with a preferred library of 4 (BR1), in SYS2 at a depth of 0
+EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,acnp,acns,bmp}','SYS2',0,'acn=>5,acp=>5',NULL,TRUE,4);
+
 */
 
 COMMIT;
diff --git a/Open-ILS/src/sql/Pg/990.schema.unapi.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
similarity index 79%
copy from Open-ILS/src/sql/Pg/990.schema.unapi.sql
copy to Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
index 8ba8efc..9daf37c 100644
--- a/Open-ILS/src/sql/Pg/990.schema.unapi.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.unapi_limit_rank.sql
@@ -1,10 +1,143 @@
+-- Evergreen DB patch XXXX.schema.unapi_limit_rank.sql
+--
+-- Rewrite the in-database unapi functions to include per-object limits and
+-- offsets, such as a maximum number of copies and call numbers for given
+-- bib record via the HSTORE syntax (for example, 'acn => 5, acp => 10' would
+-- limit to a maximum of 5 call numbers for the bib, with up to 10 copies per
+-- call number).
+--
+-- Add some notion of "preferred library" that will provide copy counts
+-- and optionally affect the sorting of returned copies.
+--
+-- Sort copies by availability, preferring the most available copies.
+--
+-- Return located URIs.
+--
+--
+BEGIN;
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+-- The simplest way to apply all of these changes is just to replace the unapi
+-- schema entirely -- the following is a copy of 990.schema.unapi.sql with
+-- the initial COMMIT in place in case the upgrade_deps_block_check fails;
+-- if it does, then the attempt to create the unapi schema in the following
+-- transaction will also fail. Not graceful, but safe!
 DROP SCHEMA IF EXISTS unapi CASCADE;
 
-BEGIN;
 CREATE SCHEMA unapi;
 
-CREATE OR REPLACE FUNCTION evergreen.org_top() RETURNS SETOF actor.org_unit AS $$ SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1; $$ LANGUAGE SQL ROWS 1;
-CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT) RETURNS anyarray AS $$ SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2; $$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION evergreen.org_top()
+RETURNS SETOF actor.org_unit AS $$
+    SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;
+$$ LANGUAGE SQL STABLE
+ROWS 1;
+
+CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT)
+RETURNS anyarray AS $$
+    SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
+$$ LANGUAGE SQL STABLE;
+
+DROP FUNCTION IF EXISTS evergreen.rank_ou(INT, INT, INT);
+CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
+RETURNS INTEGER AS $$
+    WITH pref_libs AS (
+        SELECT id, distance FROM actor.org_unit_descendants_distance(COALESCE($3, $2))
+    ), search_libs AS (
+        SELECT id, distance FROM actor.org_unit_descendants_distance($2)
+    )
+    SELECT COALESCE(
+        (SELECT distance - 10000 FROM pref_libs WHERE id = $1),
+        (SELECT distance FROM search_libs WHERE id = $1),
+        10000
+    );
+$$ LANGUAGE SQL STABLE;
+
+DROP FUNCTION IF EXISTS evergreen.rank_cp_status(INT);
+CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
+RETURNS INTEGER AS $$
+    WITH totally_available AS (
+        SELECT id, 0 AS avail_rank
+        FROM config.copy_status
+        WHERE opac_visible IS TRUE
+            AND copy_active IS TRUE
+            AND id != 1 -- "Checked out"
+    ), almost_available AS (
+        SELECT id, 10 AS avail_rank
+        FROM config.copy_status
+        WHERE holdable IS TRUE
+            AND opac_visible IS TRUE
+            AND copy_active IS FALSE
+            OR id = 1 -- "Checked out"
+    )
+    SELECT COALESCE(
+        (SELECT avail_rank FROM totally_available WHERE $1 IN (id)),
+        (SELECT avail_rank FROM almost_available WHERE $1 IN (id)),
+        100
+    );
+$$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
+    bibid BIGINT, 
+    ouid INT,
+    depth INT DEFAULT NULL,
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL
+) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
+    SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
+        SELECT acn.id, aou.name, acn.label_sortkey,
+            evergreen.rank_ou(aou.id, $2), evergreen.rank_cp_status(acp.status),
+            RANK() OVER w
+        FROM asset.call_number acn
+            JOIN asset.copy acp ON (acn.id = acp.call_number)
+            JOIN actor.org_unit_descendants( $2, COALESCE(
+                $3, (
+                    SELECT depth
+                    FROM actor.org_unit_type aout
+                        INNER JOIN actor.org_unit ou ON ou_type = aout.id
+                    WHERE ou.id = $2
+                ))
+            ) AS aou ON (acp.circ_lib = aou.id)
+        WHERE acn.record = $1
+            AND acn.deleted IS FALSE
+            AND acp.deleted IS FALSE
+        GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
+        WINDOW w AS (
+            ORDER BY evergreen.rank_ou(aou.id, $2), evergreen.rank_cp_status(acp.status)
+        )
+    ) AS ua
+    GROUP BY ua.id, ua.name, ua.label_sortkey
+    ORDER BY rank, ua.name, ua.label_sortkey
+    LIMIT ($4 -> 'acn')::INT
+    OFFSET ($5 -> 'acn')::INT;
+$$
+LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION evergreen.located_uris (
+    bibid BIGINT, 
+    ouid INT,
+    pref_lib INT DEFAULT NULL
+) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2) AS pref_ou
+      FROM asset.call_number acn
+           INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
+           INNER JOIN asset.uri auri ON auri.id = auricnm.uri
+           INNER JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
+      WHERE acn.record = $1
+          AND acn.deleted IS FALSE
+          AND auri.active IS TRUE
+    UNION
+    SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2) AS pref_ou
+      FROM asset.call_number acn
+           INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
+           INNER JOIN asset.uri auri ON auri.id = auricnm.uri
+           INNER JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
+      WHERE acn.record = $1
+          AND acn.deleted IS FALSE
+          AND auri.active IS TRUE;
+$$
+LANGUAGE SQL STABLE;
 
 CREATE TABLE unapi.bre_output_layout (
     name                TEXT    PRIMARY KEY,
@@ -27,31 +160,56 @@ INSERT INTO unapi.bre_output_layout
 ;
 
 -- Dummy functions, so we can create the real ones out of order
-CREATE OR REPLACE FUNCTION unapi.aou    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acnp   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acns   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acn    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.ssub   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sdist  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sstr   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sitem  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sunit  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sisum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sbsum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sssum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.siss   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.auri   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acpn   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acl    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.ccs    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.bre    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.bmp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.mra    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-
-CREATE OR REPLACE FUNCTION unapi.holdings_xml ( bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.aou    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acnp   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acns   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acn    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.ssub   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sdist  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sstr   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sitem  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sunit  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sisum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sbsum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.sssum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.siss   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.auri   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acpn   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.acl    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.ccs    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.bre (
+    obj_id BIGINT,
+    format TEXT,
+    ename TEXT,
+    includes TEXT[],
+    org TEXT,
+    depth INT DEFAULT NULL,
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.bmp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.mra    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+CREATE OR REPLACE FUNCTION unapi.circ   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.holdings_xml (
+    bid BIGINT,
+    ouid INT,
+    org TEXT,
+    depth INT DEFAULT NULL,
+    includes TEXT[] DEFAULT NULL::TEXT[],
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.memoize (classname TEXT, obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
 DECLARE
@@ -78,7 +236,7 @@ BEGIN
     EXECUTE $$SELECT unapi.$$ || classname || $$( $1, $2, $3, $4, $5, $6, $7, $8, $9);$$ INTO output USING obj_id, format, ename, includes, org, depth, slimit, soffset, include_xmlns;
     RETURN output;
 END;
-$F$ LANGUAGE PLPGSQL;
+$F$ LANGUAGE PLPGSQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$
 DECLARE
@@ -137,9 +295,21 @@ BEGIN
 
     RETURN tmp_xml::XML;
 END;
-$F$ LANGUAGE PLPGSQL;
-
-CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+$F$ LANGUAGE PLPGSQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.bre (
+    obj_id BIGINT,
+    format TEXT,
+    ename TEXT,
+    includes TEXT[],
+    org TEXT,
+    depth INT DEFAULT NULL,
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$
 DECLARE
     me      biblio.record_entry%ROWTYPE;
     layout  unapi.bre_output_layout%ROWTYPE;
@@ -184,9 +354,9 @@ BEGIN
         axml := NULL::XML;
     END IF;
 
-    -- grab hodlings if we need them
+    -- grab holdings if we need them
     IF ('holdings_xml' = ANY (includes)) THEN 
-        hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
+        hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
     ELSE
         hxml := NULL::XML;
     END IF;
@@ -234,9 +404,20 @@ BEGIN
     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
     RETURN output;
 END;
-$F$ LANGUAGE PLPGSQL;
-
-CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$
+$F$ LANGUAGE PLPGSQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.holdings_xml (
+    bid BIGINT,
+    ouid INT,
+    org TEXT,
+    depth INT DEFAULT NULL,
+    includes TEXT[] DEFAULT NULL::TEXT[],
+    slimit HSTORE DEFAULT NULL,
+    soffset HSTORE DEFAULT NULL,
+    include_xmlns BOOL DEFAULT TRUE,
+    pref_lib INT DEFAULT NULL
+)
+RETURNS XML AS $F$
      SELECT  XMLELEMENT(
                  name holdings,
                  XMLATTRIBUTES(
@@ -257,6 +438,12 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
                                  )::text
                            FROM  asset.staff_ou_record_copy_count($2, $1)
+                                     UNION
+                         SELECT  XMLELEMENT(
+                                     name count,
+                                     XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
+                                 )::text
+                           FROM  asset.opac_ou_record_copy_count($9,  $1)
                                      ORDER BY 1
                      )x)
                  ),
@@ -272,39 +459,18 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                         )
                      ELSE NULL
                  END,
-                 CASE WHEN $4 IS NULL THEN
-                     XMLELEMENT(
-                         name volumes,
-                         (SELECT XMLAGG(acn) FROM (
-                            SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE) FROM (
-                                SELECT  distinct acn.id, acn.label_sortkey
-                                  FROM  asset.call_number acn
-                                        JOIN asset.copy acp ON (acn.id = acp.call_number)
-                                        JOIN actor.org_unit_descendants( $2 ) aoud ON (acp.circ_lib = aoud.id)
-                                  WHERE acn.record = $1
-                                  ORDER BY acn.label_sortkey
-                                  LIMIT ($6 -> 'acn')::INT
-                                  OFFSET ($7 -> 'acn')::INT
-                            )y
-                         )x)
-                     )
-                 ELSE
-                     XMLELEMENT(
-                         name volumes,
-                         (SELECT XMLAGG(acn) FROM (
-                            SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE) FROM (
-                                SELECT  distinct acn.id, acn.label_sortkey
-                                  FROM  asset.call_number acn
-                                        JOIN asset.copy acp ON (acn.id = acp.call_number)
-                                        JOIN actor.org_unit_descendants( $2, $4 ) aoud ON (acp.circ_lib = aoud.id)
-                                  WHERE acn.record = $1
-                                  ORDER BY acn.label_sortkey
-                                  LIMIT ($6 -> 'acn')::INT
-                                  OFFSET ($7 -> 'acn')::INT
-                            )y
-                         )x)
-                     )
-                 END,
+                 XMLELEMENT(
+                     name volumes,
+                     (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
+                        -- Physical copies
+                        SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
+                        FROM evergreen.ranked_volumes($1, $2, $4, $6, $7) AS y
+                        UNION ALL
+                        -- Located URIs
+                        SELECT unapi.acn(uris.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), 0, name, label_sortkey
+                        FROM evergreen.located_uris($1, $2, $9) AS uris
+                     )x)
+                 ),
                  CASE WHEN ('ssub' = ANY ($5)) THEN 
                      XMLELEMENT(
                          name subscriptions,
@@ -319,7 +485,7 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                      XMLELEMENT(
                          name foreign_copies,
                          (SELECT XMLAGG(acp) FROM (
-                            SELECT  unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
+                            SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
                               FROM  biblio.peer_bib_copy_map p
                                     JOIN asset.copy c ON (p.target_copy = c.id)
                               WHERE NOT c.deleted AND p.peer_record = $1
@@ -329,7 +495,7 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                      )
                  ELSE NULL END
              );
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -355,7 +521,7 @@ CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT,  ename TEXT,
           FROM  serial.subscription ssub
           WHERE id = $1
           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -413,7 +579,7 @@ CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT
           FROM  serial.distribution sdist
           WHERE id = $1
           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -439,7 +605,7 @@ CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT,
       FROM  serial.stream sstr
       WHERE id = $1
       GROUP BY id, routing_label, distribution;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -466,7 +632,7 @@ CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT,
       FROM  serial.issuance sstr
       WHERE id = $1
       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -495,7 +661,7 @@ CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT
                 )
           FROM  serial.item sitem
           WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 
 CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
@@ -511,7 +677,7 @@ CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT
       FROM  serial.supplement_summary ssum
       WHERE id = $1
       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -526,7 +692,7 @@ CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT
       FROM  serial.basic_summary ssum
       WHERE id = $1
       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -541,7 +707,7 @@ CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT
       FROM  serial.index_summary ssum
       WHERE id = $1
       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 
 CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
@@ -575,7 +741,7 @@ BEGIN
     RETURN output;
 
 END;
-$F$ LANGUAGE PLPGSQL;
+$F$ LANGUAGE PLPGSQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -592,7 +758,7 @@ CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT,
             )
       FROM  asset.copy_location
       WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
@@ -607,7 +773,7 @@ CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT,
             )
       FROM  config.copy_status
       WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -621,7 +787,7 @@ CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT,
                 )
           FROM  asset.copy_note
           WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -636,7 +802,7 @@ CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEX
           FROM  asset.stat_cat_entry asce
                 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
           WHERE asce.id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -657,6 +823,7 @@ CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT,
                                       FROM  asset.copy cp
                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
                                       WHERE cpm.part = $1
+                                          AND cp.deleted IS FALSE
                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
                                       LIMIT ($7 -> 'acp')::INT
                                       OFFSET ($8 -> 'acp')::INT
@@ -670,17 +837,17 @@ CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT,
           FROM  biblio.monograph_part
           WHERE id = $1
           GROUP BY id, label, label_sortkey, record;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name copy,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
-                        'tag:open-ils.org:U2 at acp/' || id AS id,
+                        'tag:open-ils.org:U2 at acp/' || id AS id, id AS copy_id,
                         create_date, edit_date, copy_number, circulate, deposit,
                         ref, holdable, deleted, deposit_amount, price, barcode,
-                        circ_modifier, circ_as_type, opac_visible
+                        circ_modifier, circ_as_type, opac_visible, age_protect
                     ),
                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
@@ -731,23 +898,40 @@ CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT,
                                 )x)
                             )
                         ELSE NULL
+                    END,
+                    CASE 
+                        WHEN ('circ' = ANY ($4)) THEN
+                            XMLELEMENT( name current_circulation,
+                                (SELECT XMLAGG(circ) FROM (
+                                    SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
+                                      FROM  action.circulation
+                                      WHERE target_copy = cp.id
+                                            AND checkin_time IS NULL
+                                )x)
+                            )
+                        ELSE NULL
                     END
                 )
           FROM  asset.copy cp
           WHERE id = $1
-          GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible;
-$F$ LANGUAGE SQL;
+              AND cp.deleted IS FALSE
+          GROUP BY id, status, location, circ_lib, call_number, create_date,
+              edit_date, copy_number, circulate, deposit, ref, holdable,
+              deleted, deposit_amount, price, barcode, circ_modifier,
+              circ_as_type, opac_visible, age_protect;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name serial_unit,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
-                        'tag:open-ils.org:U2 at acp/' || id AS id,
+                        'tag:open-ils.org:U2 at acp/' || id AS id, id AS copy_id,
                         create_date, edit_date, copy_number, circulate, deposit,
                         ref, holdable, deleted, deposit_amount, price, barcode,
-                        circ_modifier, circ_as_type, opac_visible, status_changed_time,
-                        floating, mint_condition, detailed_contents, sort_key, summary_contents, cost 
+                        circ_modifier, circ_as_type, opac_visible, age_protect,
+                        status_changed_time, floating, mint_condition,
+                        detailed_contents, sort_key, summary_contents, cost 
                     ),
                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
@@ -797,13 +981,30 @@ CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT
                                 )x)
                             )
                         ELSE NULL
+                    END,
+                    CASE 
+                        WHEN ('circ' = ANY ($4)) THEN
+                            XMLELEMENT( name current_circulation,
+                                (SELECT XMLAGG(circ) FROM (
+                                    SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
+                                      FROM  action.circulation
+                                      WHERE target_copy = cp.id
+                                            AND checkin_time IS NULL
+                                )x)
+                            )
+                        ELSE NULL
                     END
                 )
           FROM  serial.unit cp
           WHERE id = $1
-          GROUP BY  id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, floating, mint_condition,
-                    deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible, status_changed_time, detailed_contents, sort_key, summary_contents, cost;
-$F$ LANGUAGE SQL;
+              AND cp.deleted IS FALSE
+          GROUP BY id, status, location, circ_lib, call_number, create_date,
+              edit_date, copy_number, circulate, floating, mint_condition,
+              deposit, ref, holdable, deleted, deposit_amount, price,
+              barcode, circ_modifier, circ_as_type, opac_visible,
+              status_changed_time, detailed_contents, sort_key,
+              summary_contents, cost, age_protect;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -811,7 +1012,7 @@ CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                         'tag:open-ils.org:U2 at acn/' || acn.id AS id,
-                        o.shortname AS lib,
+                        acn.id AS vol_id, o.shortname AS lib,
                         o.opac_visible AS opac_visible,
                         deleted, label, label_sortkey, label_class, record
                     ),
@@ -820,24 +1021,28 @@ CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT,
                         WHEN ('acp' = ANY ($4)) THEN
                             CASE WHEN $6 IS NOT NULL THEN
                                 XMLELEMENT( name copies,
-                                    (SELECT XMLAGG(acp) FROM (
-                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
+                                    (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
+                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
+                                            evergreen.rank_cp_status(cp.status) AS rank_avail
                                           FROM  asset.copy cp
                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
                                           WHERE cp.call_number = acn.id
-                                          ORDER BY COALESCE(cp.copy_number,0), cp.barcode
+                                              AND cp.deleted IS FALSE
+                                          ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
                                           LIMIT ($7 -> 'acp')::INT
                                           OFFSET ($8 -> 'acp')::INT
                                     )x)
                                 )
                             ELSE
                                 XMLELEMENT( name copies,
-                                    (SELECT XMLAGG(acp) FROM (
-                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
+                                    (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
+                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
+                                            evergreen.rank_cp_status(cp.status) AS rank_avail
                                           FROM  asset.copy cp
                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
                                           WHERE cp.call_number = acn.id
-                                          ORDER BY COALESCE(cp.copy_number,0), cp.barcode
+                                              AND cp.deleted IS FALSE
+                                          ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
                                           LIMIT ($7 -> 'acp')::INT
                                           OFFSET ($8 -> 'acp')::INT
                                     )x)
@@ -856,8 +1061,9 @@ CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT,
           FROM  asset.call_number acn
                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
           WHERE acn.id = $1
+              AND acn.deleted IS FALSE
           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -872,7 +1078,7 @@ CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT,  ename TEXT,
                 )
           FROM  asset.call_number_prefix
           WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -887,7 +1093,7 @@ CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT,  ename TEXT,
                 )
           FROM  asset.call_number_suffix
           WHERE id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -910,7 +1116,7 @@ CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT,
           FROM  asset.uri uri
           WHERE uri.id = $1
           GROUP BY uri.id, use_restriction, href, label;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
 
 CREATE OR REPLACE FUNCTION unapi.mra ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
@@ -939,7 +1145,23 @@ CREATE OR REPLACE FUNCTION unapi.mra ( obj_id BIGINT, format TEXT,  ename TEXT,
                 )
           FROM  metabib.record_attr mra
           WHERE mra.id = $1;
-$F$ LANGUAGE SQL;
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE FUNCTION unapi.circ (obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+    SELECT XMLELEMENT(
+        name circ,
+        XMLATTRIBUTES(
+            CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
+            'tag:open-ils.org:U2 at circ/' || id AS id,
+            xact_start,
+            due_date
+        ),
+        CASE WHEN ('aou' = ANY ($4)) THEN unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+        CASE WHEN ('acp' = ANY ($4)) THEN unapi.acp( circ_lib, $2, 'target_copy', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END
+    )
+    FROM action.circulation
+    WHERE id = $1;
+$F$ LANGUAGE SQL STABLE;
 
 /*
 
@@ -962,6 +1184,9 @@ EXPLAIN ANALYZE SELECT unapi.bre(216,'holdings_xml','record','{}'::TEXT[], 'BR1'
 EXPLAIN ANALYZE SELECT unapi.holdings_xml(216,4,'BR1',2,'{bre}'::TEXT[]);
 EXPLAIN ANALYZE SELECT unapi.bre(216,'mods32','record','{}'::TEXT[], 'BR1');
 
+-- Limit to 5 call numbers, 5 copies, with a preferred library of 4 (BR1), in SYS2 at a depth of 0
+EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,acnp,acns,bmp}','SYS2',0,'acn=>5,acp=>5',NULL,TRUE,4);
+
 */
 
 COMMIT;
diff --git a/Open-ILS/src/templates/opac/parts/misc_util.tt2 b/Open-ILS/src/templates/opac/parts/misc_util.tt2
index 25a8362..5586d60 100644
--- a/Open-ILS/src/templates/opac/parts/misc_util.tt2
+++ b/Open-ILS/src/templates/opac/parts/misc_util.tt2
@@ -129,6 +129,7 @@
 
             IF vol.label == '##URI##';
                 FOR uri IN volume.findnodes('./*[local-name()="uris"]/*[local-name()="uri"]');
+                    res = {};
                     res.href = uri.getAttribute('href');
                     res.link = uri.getAttribute('label');
                     res.note = uri.getAttribute('use_restriction');
@@ -190,6 +191,17 @@
                 args.copy_counts.$depth.$attr = node.getAttribute(attr);
             END;
         END;
+        
+        # Get preferred library copy count
+        args.plib_copy_counts = {};
+        count_type = 'pref_lib';
+        xpath = '//*[local-name()="counts"]/*[local-name()="count"][@type="' _ count_type _ '"]';
+        FOR node IN xml.findnodes(xpath);
+            FOR attr IN ['count', 'available', 'unshadow', 'transcendant', 'org_unit']; 
+                depth = node.getAttribute('depth');
+                args.plib_copy_counts.$depth.$attr = node.getAttribute(attr);
+            END;
+        END;
 
         # "mattype" == "custom marc format specifier"
         FOR icon_style IN ['mattype', 'item_type']; 
diff --git a/Open-ILS/src/templates/opac/parts/record/copy_counts.tt2 b/Open-ILS/src/templates/opac/parts/record/copy_counts.tt2
index f9f5594..47f8f44 100644
--- a/Open-ILS/src/templates/opac/parts/record/copy_counts.tt2
+++ b/Open-ILS/src/templates/opac/parts/record/copy_counts.tt2
@@ -3,19 +3,39 @@
     <ul>
     [%- depths = ctx.copy_summary.size;
         depth = 0;
+        displayed_ous = {};
         WHILE depth < depths;
             ou_avail = ctx.copy_summary.$depth.available;
             ou_id = ctx.copy_summary.$depth.org_unit;
+            ou_name = ctx.get_aou(ou_id).name;
+            displayed_ous.$ou_name = 1;
     %]
     <li>
-    [% l('[quant,_1,copy,copies] at [_2].', ou_avail, ctx.get_aou(ou_id).name) | html %]
+    [% l('[quant,_1,copy,copies] at [_2].', ou_avail, ou_name) | html %]
     [%- this_depth = ctx.get_aou(ou_id).ou_type.depth;
         IF ou_avail > 0 && this_depth != ctx.copy_depth %]
     <a href="[% mkurl('', {copy_depth => this_depth}, ['copy_offset']); %]"
-       title="[% l('Show copies at [_1]', ctx.get_aou(ou_id).name); %]">
+       title="[% l('Show copies at [_1]', ou_name); %]">
        [%- l('(Show)'); %]</a>
     [%- END; %]
     </li>
-    [%- depth = depth + 1; END %]
+    [%- depth = depth + 1;
+        END;
+
+        depth = attrs.plib_copy_counts.size - 1;
+        ou_name = ctx.get_aou(attrs.plib_copy_counts.$depth.org_unit).name;
+        ou_id = attrs.plib_copy_counts.$depth.org_unit;
+        UNLESS depth < 0 || displayed_ous.exists(ou_name);
+    %]
+    <li class="preferred">[%
+        l('[_1] of [quant,_2,copy,copies] available at [_3].',
+            attrs.plib_copy_counts.$depth.available,
+            attrs.plib_copy_counts.$depth.count,
+            ou_name) | html
+    %] <a href="[% mkurl('', {locg => ou_id}, ['copy_offset']); %]"
+       title="[% l('Show copies at [_1]', ou_name); %]">[%
+       l('(Show preferred library)');
+    %]</a></li>
+    [%- END %]
     </ul>
 </span>
diff --git a/Open-ILS/src/templates/opac/parts/result/copy_counts.tt2 b/Open-ILS/src/templates/opac/parts/result/copy_counts.tt2
index 97d5b32..943066e 100644
--- a/Open-ILS/src/templates/opac/parts/result/copy_counts.tt2
+++ b/Open-ILS/src/templates/opac/parts/result/copy_counts.tt2
@@ -1,12 +1,30 @@
 [%- depths = attrs.copy_counts.size;
     depth = 0;
+    displayed_ous = {};
     WHILE depth < depths;
+        ou_name = ctx.get_aou(attrs.copy_counts.$depth.org_unit).name;
+        displayed_ous.$ou_name = 1;
 %]
 <div class="result_count">
     [% l('[_1] of [quant,_2,copy,copies] available at [_3].',
         attrs.copy_counts.$depth.available,
         attrs.copy_counts.$depth.count,
-        ctx.get_aou(attrs.copy_counts.$depth.org_unit).name) | html
+        ou_name) | html
     %]
 </div>
-[%- depth = depth + 1; END %]
+[%- depth = depth + 1;
+    END;
+
+    depth = attrs.plib_copy_counts.size - 1;
+    ou_name = ctx.get_aou(attrs.plib_copy_counts.$depth.org_unit).name;
+    UNLESS displayed_ous.exists(ou_name);
+    
+%]
+<div class="result_count preferred">[%
+     l('[_1] of [quant,_2,copy,copies] available at [_3].',
+        attrs.plib_copy_counts.$depth.available,
+        attrs.plib_copy_counts.$depth.count,
+        ou_name) | html
+    %]<span> [% l('(Preferred)') %]</span>
+</div>
+[%- END %]

commit 7ebf143c86c7cbb88dc18987861cd823c0afc5c1
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Aug 3 14:23:11 2011 -0400

    Add per-class limit support to the tag URI parser; correct location/org thinko
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Dan Scott <dan at coffeecode.net>
    Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/TagURI.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/TagURI.pm
index e9c6546..a6679f4 100755
--- a/Open-ILS/src/perlmods/lib/OpenILS/Utils/TagURI.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/TagURI.pm
@@ -91,8 +91,8 @@ sub parse {
         $self->classname($classname);
         $self->id($id);
         $self->paging(($paging ? [ map { s/^\s*//; s/\s*$//; $_ } split(',', $paging) ] : []));
-        $self->includes(($inc? [ map { s/^\s*//; s/\s*$//; $_ } split(',', $inc) ] : []));
-        $self->org($loc);
+        $self->includes(($inc ? { map { /:/ ? split(':') : ($_,undef) } map { s/^\s*//; s/\s*$//; $_ } split(',', $inc) } : {}));
+        $self->location($loc);
         $self->depth($depth);
         $self->pathinfo($mods);
     }
@@ -122,7 +122,7 @@ sub toURI {
     $tag .= 'U2@' if ($self->version == 2);
     $tag .= $self->classname . '/' . $self->id;
     $tag .= '['. join(',', @{ $self->paging }) . ']' if defined($self->paging);
-    $tag .= '{'. join(',', @{ $self->includes }) . '}' if defined($self->includes);
+    $tag .= '{'. join(',', map { $_ . ':' . $self->includes->{$_} } keys %{ $self->includes }) . '}' if defined($self->includes);
     $tag .= '/' . $self->location if defined($self->location);
     $tag .= '/' . $self->depth if defined($self->depth);
     $tag .= '/' . $self->pathinfo if defined($self->pathinfo);

commit 96559ac3f8b95745d64967ec8d42a0024dfd7504
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Aug 2 14:44:53 2011 -0400

    Support per-class sub-object limit/offset
    
    Change the signature for in-db unapi functions so that the limit /
    offset arguments become HSTORE values, rather than plain INTEGER values.
    This gives you the ability to specify limits and offsets at both the
    volume and copy level, via the likes of 'acp=>5, acn=>10' to
    specify 5 copies and 10 volumes.
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Dan Scott <dan at coffeecode.net>
    Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>

diff --git a/Open-ILS/src/sql/Pg/990.schema.unapi.sql b/Open-ILS/src/sql/Pg/990.schema.unapi.sql
index 5b3e093..8ba8efc 100644
--- a/Open-ILS/src/sql/Pg/990.schema.unapi.sql
+++ b/Open-ILS/src/sql/Pg/990.schema.unapi.sql
@@ -27,33 +27,33 @@ INSERT INTO unapi.bre_output_layout
 ;
 
 -- Dummy functions, so we can create the real ones out of order
-CREATE OR REPLACE FUNCTION unapi.aou    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acnp   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acns   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acn    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.ssub   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sdist  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sstr   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sitem  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sunit  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sisum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sbsum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.sssum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.siss   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.auri   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acpn   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.acl    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.ccs    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.bre    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.bmp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.mra    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-
-CREATE OR REPLACE FUNCTION unapi.holdings_xml ( bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
-
-CREATE OR REPLACE FUNCTION unapi.memoize (classname TEXT, obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.aou    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.acnp   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.acns   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.acn    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.ssub   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.sdist  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.sstr   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.sitem  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.sunit  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.sisum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.sbsum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.sssum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.siss   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.auri   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.acp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.acpn   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.acl    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.ccs    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.bre    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.bmp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.mra    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION unapi.holdings_xml ( bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION unapi.memoize (classname TEXT, obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
 DECLARE
     key     TEXT;
     output  XML;
@@ -80,7 +80,7 @@ BEGIN
 END;
 $F$ LANGUAGE PLPGSQL;
 
-CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$
 DECLARE
     layout          unapi.bre_output_layout%ROWTYPE;
     transform       config.xml_transform%ROWTYPE;
@@ -109,19 +109,19 @@ BEGIN
     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
 
     IF layout.title_element IS NOT NULL THEN
-        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title, include_xmlns;
+        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
     END IF;
 
     IF layout.description_element IS NOT NULL THEN
-        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description, include_xmlns;
+        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
     END IF;
 
     IF layout.creator_element IS NOT NULL THEN
-        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator, include_xmlns;
+        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
     END IF;
 
     IF layout.update_ts_element IS NOT NULL THEN
-        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts, include_xmlns;
+        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts;
     END IF;
 
     IF unapi_url IS NOT NULL THEN
@@ -132,14 +132,14 @@ BEGIN
 
     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
-        EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, include_xmlns;
+        EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
     END LOOP;
 
     RETURN tmp_xml::XML;
 END;
 $F$ LANGUAGE PLPGSQL;
 
-CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
 DECLARE
     me      biblio.record_entry%ROWTYPE;
     layout  unapi.bre_output_layout%ROWTYPE;
@@ -236,7 +236,7 @@ BEGIN
 END;
 $F$ LANGUAGE PLPGSQL;
 
-CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$
      SELECT  XMLELEMENT(
                  name holdings,
                  XMLATTRIBUTES(
@@ -272,33 +272,39 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                         )
                      ELSE NULL
                  END,
-                 XMLELEMENT(
-                     name volumes,
-                     (SELECT XMLAGG(acn) FROM (
-                        SELECT  unapi.acn(acn.id,'xml','volume',array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE)
-                          FROM  asset.call_number acn
-                          WHERE acn.record = $1
-                                AND acn.deleted IS FALSE
-                                AND EXISTS (
-                                    SELECT  1
-                                      FROM  asset.copy acp
-                                            JOIN actor.org_unit_descendants(
-                                                $2,
-                                                (COALESCE(
-                                                    $4,
-                                                    (SELECT aout.depth
-                                                      FROM  actor.org_unit_type aout
-                                                            JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
-                                                    )
-                                                ))
-                                            ) aoud ON (acp.circ_lib = aoud.id)
-                                      LIMIT 1
-                               )
-                          ORDER BY label_sortkey
-                          LIMIT $6
-                          OFFSET $7
-                     )x)
-                 ),
+                 CASE WHEN $4 IS NULL THEN
+                     XMLELEMENT(
+                         name volumes,
+                         (SELECT XMLAGG(acn) FROM (
+                            SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE) FROM (
+                                SELECT  distinct acn.id, acn.label_sortkey
+                                  FROM  asset.call_number acn
+                                        JOIN asset.copy acp ON (acn.id = acp.call_number)
+                                        JOIN actor.org_unit_descendants( $2 ) aoud ON (acp.circ_lib = aoud.id)
+                                  WHERE acn.record = $1
+                                  ORDER BY acn.label_sortkey
+                                  LIMIT ($6 -> 'acn')::INT
+                                  OFFSET ($7 -> 'acn')::INT
+                            )y
+                         )x)
+                     )
+                 ELSE
+                     XMLELEMENT(
+                         name volumes,
+                         (SELECT XMLAGG(acn) FROM (
+                            SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE) FROM (
+                                SELECT  distinct acn.id, acn.label_sortkey
+                                  FROM  asset.call_number acn
+                                        JOIN asset.copy acp ON (acn.id = acp.call_number)
+                                        JOIN actor.org_unit_descendants( $2, $4 ) aoud ON (acp.circ_lib = aoud.id)
+                                  WHERE acn.record = $1
+                                  ORDER BY acn.label_sortkey
+                                  LIMIT ($6 -> 'acn')::INT
+                                  OFFSET ($7 -> 'acn')::INT
+                            )y
+                         )x)
+                     )
+                 END,
                  CASE WHEN ('ssub' = ANY ($5)) THEN 
                      XMLELEMENT(
                          name subscriptions,
@@ -316,65 +322,67 @@ CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, d
                             SELECT  unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
                               FROM  biblio.peer_bib_copy_map p
                                     JOIN asset.copy c ON (p.target_copy = c.id)
-                              WHERE NOT c.deleted AND peer_record = $1
+                              WHERE NOT c.deleted AND p.peer_record = $1
+                            LIMIT ($6 -> 'acp')::INT
+                            OFFSET ($7 -> 'acp')::INT
                         )x)
                      )
                  ELSE NULL END
              );
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name subscription,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                         'tag:open-ils.org:U2 at ssub/' || id AS id,
+                        'tag:open-ils.org:U2 at aou/' || owning_lib AS owning_lib,
                         start_date AS start, end_date AS end, expected_date_offset
                     ),
-                    unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8),
-                    XMLELEMENT( name distributions,
-                        CASE 
-                            WHEN ('sdist' = ANY ($4)) THEN
+                    CASE 
+                        WHEN ('sdist' = ANY ($4)) THEN
+                            XMLELEMENT( name distributions,
                                 (SELECT XMLAGG(sdist) FROM (
                                     SELECT  unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
                                       FROM  serial.distribution
                                       WHERE subscription = ssub.id
                                 )x)
-                            ELSE NULL
-                        END
-                    )
+                            )
+                        ELSE NULL
+                    END
                 )
           FROM  serial.subscription ssub
           WHERE id = $1
           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name distribution,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                         'tag:open-ils.org:U2 at sdist/' || id AS id,
-			'tag:open-ils.org:U2 at acn/' || receive_call_number AS receive_call_number,
-			'tag:open-ils.org:U2 at acn/' || bind_call_number AS bind_call_number,
+            			'tag:open-ils.org:U2 at acn/' || receive_call_number AS receive_call_number,
+			            'tag:open-ils.org:U2 at acn/' || bind_call_number AS bind_call_number,
                         unit_label_prefix, label, unit_label_suffix, summary_method
                     ),
                     unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
                     CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) ELSE NULL END,
-                    XMLELEMENT( name streams,
-                        CASE 
-                            WHEN ('sstr' = ANY ($4)) THEN
+                    CASE 
+                        WHEN ('sstr' = ANY ($4)) THEN
+                            XMLELEMENT( name streams,
                                 (SELECT XMLAGG(sstr) FROM (
                                     SELECT  unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
                                       FROM  serial.stream
                                       WHERE distribution = sdist.id
                                 )x)
-                            ELSE NULL
-                        END
-                    ),
+                            )
+                        ELSE NULL
+                    END,
                     XMLELEMENT( name summaries,
                         CASE 
-                            WHEN ('ssum' = ANY ($4)) THEN
+                            WHEN ('sbsum' = ANY ($4)) THEN
                                 (SELECT XMLAGG(sbsum) FROM (
                                     SELECT  unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
                                       FROM  serial.basic_summary
@@ -383,7 +391,7 @@ CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT
                             ELSE NULL
                         END,
                         CASE 
-                            WHEN ('ssum' = ANY ($4)) THEN
+                            WHEN ('sisum' = ANY ($4)) THEN
                                 (SELECT XMLAGG(sisum) FROM (
                                     SELECT  unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
                                       FROM  serial.index_summary
@@ -392,7 +400,7 @@ CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT
                             ELSE NULL
                         END,
                         CASE 
-                            WHEN ('ssum' = ANY ($4)) THEN
+                            WHEN ('sssum' = ANY ($4)) THEN
                                 (SELECT XMLAGG(sssum) FROM (
                                     SELECT  unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
                                       FROM  serial.supplement_summary
@@ -407,7 +415,7 @@ CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT
           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
                 name stream,
                 XMLATTRIBUTES(
@@ -416,24 +424,24 @@ CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT,
                     routing_label
                 ),
                 CASE WHEN distribution IS NOT NULL AND ('sdist' = ANY ($4)) THEN unapi.sssum( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE) ELSE NULL END,
-                XMLELEMENT( name items,
-                    CASE 
-                        WHEN ('sitem' = ANY ($4)) THEN
+                CASE 
+                    WHEN ('sitem' = ANY ($4)) THEN
+                        XMLELEMENT( name items,
                             (SELECT XMLAGG(sitem) FROM (
                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
                                   FROM  serial.item
                                   WHERE stream = sstr.id
                             )x)
-                        ELSE NULL
-                    END
-                )
+                        )
+                    ELSE NULL
+                END
             )
       FROM  serial.stream sstr
       WHERE id = $1
       GROUP BY id, routing_label, distribution;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
                 name issuance,
                 XMLATTRIBUTES(
@@ -443,24 +451,24 @@ CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT,
                     holding_code, holding_type, holding_link_id
                 ),
                 CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE) ELSE NULL END,
-                XMLELEMENT( name items,
-                    CASE 
-                        WHEN ('sitem' = ANY ($4)) THEN
+                CASE 
+                    WHEN ('sitem' = ANY ($4)) THEN
+                        XMLELEMENT( name items,
                             (SELECT XMLAGG(sitem) FROM (
                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
                                   FROM  serial.item
                                   WHERE issuance = sstr.id
                             )x)
-                        ELSE NULL
-                    END
-                )
+                        )
+                    ELSE NULL
+                END
             )
       FROM  serial.issuance sstr
       WHERE id = $1
       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name serial_item,
                     XMLATTRIBUTES(
@@ -490,7 +498,7 @@ CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT
 $F$ LANGUAGE SQL;
 
 
-CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
                 name serial_summary,
                 XMLATTRIBUTES(
@@ -505,7 +513,7 @@ CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT
       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
                 name serial_summary,
                 XMLATTRIBUTES(
@@ -520,7 +528,7 @@ CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT
       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
                 name serial_summary,
                 XMLATTRIBUTES(
@@ -536,7 +544,7 @@ CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT
 $F$ LANGUAGE SQL;
 
 
-CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
 DECLARE
     output XML;
 BEGIN
@@ -569,7 +577,7 @@ BEGIN
 END;
 $F$ LANGUAGE PLPGSQL;
 
-CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
                 name location,
                 XMLATTRIBUTES(
@@ -586,7 +594,7 @@ CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT,
       WHERE id = $1;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
     SELECT  XMLELEMENT(
                 name status,
                 XMLATTRIBUTES(
@@ -601,7 +609,7 @@ CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT,
       WHERE id = $1;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name copy_note,
                     XMLATTRIBUTES(
@@ -615,7 +623,7 @@ CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT,
           WHERE id = $1;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name statcat,
                     XMLATTRIBUTES(
@@ -630,7 +638,7 @@ CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEX
           WHERE asce.id = $1;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name monograph_part,
                     XMLATTRIBUTES(
@@ -650,8 +658,9 @@ CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT,
                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
                                       WHERE cpm.part = $1
                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
-                                      LIMIT $7
-                                      OFFSET $8
+                                      LIMIT ($7 -> 'acp')::INT
+                                      OFFSET ($8 -> 'acp')::INT
+
                                 )x)
                             )
                         ELSE NULL
@@ -663,7 +672,7 @@ CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT,
           GROUP BY id, label, label_sortkey, record;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name copy,
                     XMLATTRIBUTES(
@@ -678,40 +687,40 @@ CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT,
                     unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
                     unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
                     CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
-                    XMLELEMENT( name copy_notes,
-                        CASE 
-                            WHEN ('acpn' = ANY ($4)) THEN
+                    CASE 
+                        WHEN ('acpn' = ANY ($4)) THEN
+                            XMLELEMENT( name copy_notes,
                                 (SELECT XMLAGG(acpn) FROM (
                                     SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
                                       FROM  asset.copy_note
                                       WHERE owning_copy = cp.id AND pub
                                 )x)
-                            ELSE NULL
-                        END
-                    ),
-                    XMLELEMENT( name statcats,
-                        CASE 
-                            WHEN ('ascecm' = ANY ($4)) THEN
+                            )
+                        ELSE NULL
+                    END,
+                    CASE 
+                        WHEN ('ascecm' = ANY ($4)) THEN
+                            XMLELEMENT( name statcats,
                                 (SELECT XMLAGG(ascecm) FROM (
                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
                                       FROM  asset.stat_cat_entry_copy_map
                                       WHERE owning_copy = cp.id
                                 )x)
-                            ELSE NULL
-                        END
-                    ),
-                    XMLELEMENT( name foreign_records,
-                        CASE
-                            WHEN ('bre' = ANY ($4)) THEN
+                            )
+                        ELSE NULL
+                    END,
+                    CASE
+                        WHEN ('bre' = ANY ($4)) THEN
+                            XMLELEMENT( name foreign_records,
                                 (SELECT XMLAGG(bre) FROM (
                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
                                       FROM  biblio.peer_bib_copy_map
                                       WHERE target_copy = cp.id
                                 )x)
-                            ELSE NULL
-                        END
 
-                    ),
+                            )
+                        ELSE NULL
+                    END,
                     CASE 
                         WHEN ('bmp' = ANY ($4)) THEN
                             XMLELEMENT( name monograph_parts,
@@ -729,7 +738,7 @@ CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT,
           GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name serial_unit,
                     XMLATTRIBUTES(
@@ -777,7 +786,6 @@ CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT
                                 )x)
                             ELSE NULL
                         END
-
                     ),
                     CASE 
                         WHEN ('bmp' = ANY ($4)) THEN
@@ -797,7 +805,7 @@ CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT
                     deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible, status_changed_time, detailed_contents, sort_key, summary_contents, cost;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name volume,
                     XMLATTRIBUTES(
@@ -808,30 +816,41 @@ CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT,
                         deleted, label, label_sortkey, label_class, record
                     ),
                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
-                    XMLELEMENT( name copies,
-                        CASE 
-                            WHEN ('acp' = ANY ($4)) THEN
-                                (SELECT XMLAGG(acp) FROM (
-                                    SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
-                                      FROM  asset.copy cp
-                                            JOIN actor.org_unit_descendants(
-                                                (SELECT id FROM actor.org_unit WHERE shortname = $5),
-                                                (COALESCE($6,(SELECT aout.depth FROM actor.org_unit_type aout JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.shortname = $5))))
-                                            ) aoud ON (cp.circ_lib = aoud.id)
-                                      WHERE cp.call_number = acn.id
-                                      ORDER BY COALESCE(cp.copy_number,0), cp.barcode
-                                      LIMIT $7
-                                      OFFSET $8
-                                )x)
-                            ELSE NULL
-                        END
-                    ),
+                    CASE 
+                        WHEN ('acp' = ANY ($4)) THEN
+                            CASE WHEN $6 IS NOT NULL THEN
+                                XMLELEMENT( name copies,
+                                    (SELECT XMLAGG(acp) FROM (
+                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
+                                          FROM  asset.copy cp
+                                                JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
+                                          WHERE cp.call_number = acn.id
+                                          ORDER BY COALESCE(cp.copy_number,0), cp.barcode
+                                          LIMIT ($7 -> 'acp')::INT
+                                          OFFSET ($8 -> 'acp')::INT
+                                    )x)
+                                )
+                            ELSE
+                                XMLELEMENT( name copies,
+                                    (SELECT XMLAGG(acp) FROM (
+                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
+                                          FROM  asset.copy cp
+                                                JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
+                                          WHERE cp.call_number = acn.id
+                                          ORDER BY COALESCE(cp.copy_number,0), cp.barcode
+                                          LIMIT ($7 -> 'acp')::INT
+                                          OFFSET ($8 -> 'acp')::INT
+                                    )x)
+                                )
+                            END
+                        ELSE NULL
+                    END,
                     XMLELEMENT(
                         name uris,
                         (SELECT XMLAGG(auri) FROM (SELECT unapi.auri(uri,'xml','uri', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE call_number = acn.id)x)
                     ),
-                    CASE WHEN ('acnp' = ANY ($4)) THEN unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END,
-                    CASE WHEN ('acns' = ANY ($4)) THEN unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END,
+                    unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
+                    unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
                     CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END
                 ) AS x
           FROM  asset.call_number acn
@@ -840,37 +859,37 @@ CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT,
           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name call_number_prefix,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                         id AS ident,
                         label,
+                        'tag:open-ils.org:U2 at aou/' || owning_lib AS owning_lib,
                         label_sortkey
-                    ),
-                    unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acnp'), $5, $6, $7, $8)
+                    )
                 )
           FROM  asset.call_number_prefix
           WHERE id = $1;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name call_number_suffix,
                     XMLATTRIBUTES(
                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                         id AS ident,
                         label,
+                        'tag:open-ils.org:U2 at aou/' || owning_lib AS owning_lib,
                         label_sortkey
-                    ),
-                    unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acns'), $5, $6, $7, $8)
+                    )
                 )
           FROM  asset.call_number_suffix
           WHERE id = $1;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name uri,
                     XMLATTRIBUTES(
@@ -880,20 +899,20 @@ CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT,
                         href,
                         label
                     ),
-                    XMLELEMENT( name copies,
-                        CASE 
-                            WHEN ('acn' = ANY ($4)) THEN
+                    CASE 
+                        WHEN ('acn' = ANY ($4)) THEN
+                            XMLELEMENT( name copies,
                                 (SELECT XMLAGG(acn) FROM (SELECT unapi.acn( call_number, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'auri'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE uri = uri.id)x)
-                            ELSE NULL
-                        END
-                    )
+                            )
+                        ELSE NULL
+                    END
                 ) AS x
           FROM  asset.uri uri
           WHERE uri.id = $1
           GROUP BY uri.id, use_restriction, href, label;
 $F$ LANGUAGE SQL;
 
-CREATE OR REPLACE FUNCTION unapi.mra ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
+CREATE OR REPLACE FUNCTION unapi.mra ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
         SELECT  XMLELEMENT(
                     name attributes,
                     XMLATTRIBUTES(
@@ -930,12 +949,12 @@ SELECT unapi.memoize( 'bre', 1,'mods32','','{holdings_xml,acp}'::TEXT[], 'SYS1')
 SELECT unapi.memoize( 'bre', 1,'marcxml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
 SELECT unapi.memoize( 'bre', 1,'holdings_xml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
 
-SELECT unapi.biblio_record_entry_feed('{1}'::BIGINT[],'mods32','{holdings_xml,acp}'::TEXT[],'SYS1',NULL,1,NULL, NULL,NULL,NULL,NULL,'http://c64/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+SELECT unapi.biblio_record_entry_feed('{1}'::BIGINT[],'mods32','{holdings_xml,acp}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://c64/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
 
-SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{}'::TEXT[],'SYS1',NULL,1,NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
-EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{}'::TEXT[],'SYS1',NULL,1,NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
-EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{holdings_xml}'::TEXT[],'SYS1',NULL,1,NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
-EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'mods32','{holdings_xml}'::TEXT[],'SYS1',NULL,1,NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{holdings_xml}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
+EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'mods32','{holdings_xml}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
 
 SELECT unapi.biblio_record_entry_feed('{216}'::BIGINT[],'marcxml','{}'::TEXT[], 'BR1');
 EXPLAIN ANALYZE SELECT unapi.bre(216,'marcxml','record','{holdings_xml,bre.unapi}'::TEXT[], 'BR1');

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/src/perlmods/lib/OpenILS/Utils/TagURI.pm  |    6 +-
 .../perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm |   36 +-
 .../perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm |   10 +-
 .../perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm   |   18 +-
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/990.schema.unapi.sql           |  639 +++++++----
 .../0690.schema.unapi_limit_rank.sql}              |  665 ++++++++----
 .../sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql  | 1191 ++++++++++++++++++++
 Open-ILS/src/templates/opac/parts/misc_util.tt2    |   12 +
 .../templates/opac/parts/record/copy_counts.tt2    |   26 +-
 .../templates/opac/parts/result/copy_counts.tt2    |   22 +-
 11 files changed, 2178 insertions(+), 449 deletions(-)
 copy Open-ILS/src/sql/Pg/{990.schema.unapi.sql => upgrade/0690.schema.unapi_limit_rank.sql} (61%)


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list