LP1607922: Receiving items on page 2 of search results
[working/Evergreen.git] / Open-ILS / src / sql / Pg / 990.schema.unapi.sql
1 DROP SCHEMA IF EXISTS unapi CASCADE;
2
3 BEGIN;
4 CREATE SCHEMA unapi;
5
6 CREATE OR REPLACE FUNCTION evergreen.org_top()
7 RETURNS actor.org_unit AS $$
8     SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;
9 $$ LANGUAGE SQL STABLE;
10
11 CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT)
12 RETURNS anyarray AS $$
13     SELECT ARRAY_AGG(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
14 $$ LANGUAGE SQL STABLE;
15
16 CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
17 RETURNS INTEGER AS $$
18     SELECT COALESCE(
19
20         -- lib matches search_lib
21         (SELECT CASE WHEN $1 = $2 THEN -20000 END),
22
23         -- lib matches pref_lib
24         (SELECT CASE WHEN $1 = $3 THEN -10000 END),
25
26
27         -- pref_lib is a child of search_lib and lib is a child of pref lib.  
28         -- For example, searching CONS, pref lib is SYS1, 
29         -- copies at BR1 and BR2 sort to the front.
30         (SELECT distance - 5000
31             FROM actor.org_unit_descendants_distance($3) 
32             WHERE id = $1 AND $3 IN (
33                 SELECT id FROM actor.org_unit_descendants($2))),
34
35         -- lib is a child of search_lib
36         (SELECT distance FROM actor.org_unit_descendants_distance($2) WHERE id = $1),
37
38         -- all others pay cash
39         1000
40     );
41 $$ LANGUAGE SQL STABLE;
42
43 -- this version exists mainly to accommodate JSON query transform limitations
44 -- (the transform argument must be an IDL field, not an entire row/object)
45 -- XXX is there another way?
46 CREATE OR REPLACE FUNCTION evergreen.rank_cp(copy_id BIGINT)
47 RETURNS INTEGER AS $$
48 DECLARE
49     copy asset.copy%ROWTYPE;
50 BEGIN
51     SELECT * INTO copy FROM asset.copy WHERE id = copy_id;
52     RETURN evergreen.rank_cp(copy);
53 END;
54 $$ LANGUAGE PLPGSQL STABLE;
55
56 CREATE OR REPLACE FUNCTION evergreen.rank_cp(copy asset.copy)
57 RETURNS INTEGER AS $$
58 DECLARE
59     rank INT;
60 BEGIN
61     WITH totally_available AS (
62         SELECT id, 0 AS avail_rank
63         FROM config.copy_status
64         WHERE opac_visible IS TRUE
65             AND copy_active IS TRUE
66             AND id != 1 -- "Checked out"
67     ), almost_available AS (
68         SELECT id, 10 AS avail_rank
69         FROM config.copy_status
70         WHERE holdable IS TRUE
71             AND opac_visible IS TRUE
72             AND copy_active IS FALSE
73             OR id = 1 -- "Checked out"
74     )
75     SELECT COALESCE(
76         CASE WHEN NOT copy.opac_visible THEN 100 END,
77         (SELECT avail_rank FROM totally_available WHERE copy.status IN (id)),
78         CASE WHEN copy.holdable THEN
79             (SELECT avail_rank FROM almost_available WHERE copy.status IN (id))
80         END,
81         100
82     ) INTO rank;
83
84     RETURN rank;
85 END;
86 $$ LANGUAGE PLPGSQL STABLE;
87
88 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
89     bibid BIGINT[], 
90     ouid INT,
91     depth INT DEFAULT NULL,
92     slimit HSTORE DEFAULT NULL,
93     soffset HSTORE DEFAULT NULL,
94     pref_lib INT DEFAULT NULL,
95     includes TEXT[] DEFAULT NULL::TEXT[]
96 ) RETURNS TABLE(id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
97     WITH RECURSIVE ou_depth AS (
98         SELECT COALESCE(
99             $3,
100             (
101                 SELECT depth
102                 FROM actor.org_unit_type aout
103                     INNER JOIN actor.org_unit ou ON ou_type = aout.id
104                 WHERE ou.id = $2
105             )
106         ) AS depth
107     ), descendant_depth AS (
108         SELECT  ou.id,
109                 ou.parent_ou,
110                 out.depth
111         FROM  actor.org_unit ou
112                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
113                 JOIN anscestor_depth ad ON (ad.id = ou.id),
114                 ou_depth
115         WHERE ad.depth = ou_depth.depth
116             UNION ALL
117         SELECT  ou.id,
118                 ou.parent_ou,
119                 out.depth
120         FROM  actor.org_unit ou
121                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
122                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
123     ), anscestor_depth AS (
124         SELECT  ou.id,
125                 ou.parent_ou,
126                 out.depth
127         FROM  actor.org_unit ou
128                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
129         WHERE ou.id = $2
130             UNION ALL
131         SELECT  ou.id,
132                 ou.parent_ou,
133                 out.depth
134         FROM  actor.org_unit ou
135                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
136                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
137     ), descendants as (
138         SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id)
139     )
140
141     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
142         SELECT acn.id, owning_lib.name, acn.label_sortkey,
143             evergreen.rank_cp(acp),
144             RANK() OVER w
145         FROM asset.call_number acn
146             JOIN asset.copy acp ON (acn.id = acp.call_number)
147             JOIN descendants AS aou ON (acp.circ_lib = aou.id)
148             JOIN actor.org_unit AS owning_lib ON (acn.owning_lib = owning_lib.id)
149         WHERE acn.record = ANY ($1)
150             AND acn.deleted IS FALSE
151             AND acp.deleted IS FALSE
152             AND CASE WHEN ('exclude_invisible_acn' = ANY($7)) THEN 
153                 EXISTS (
154                     WITH basevm AS (SELECT c_attrs FROM  asset.patron_default_visibility_mask()),
155                          circvm AS (SELECT search.calculate_visibility_attribute_test('circ_lib', ARRAY[acp.circ_lib]) AS mask)
156                     SELECT  1 
157                       FROM  basevm, circvm, asset.copy_vis_attr_cache acvac
158                       WHERE acvac.vis_attr_vector @@ (basevm.c_attrs || '&' || circvm.mask)::query_int
159                             AND acvac.target_copy = acp.id
160                             AND acvac.record = acn.record
161                 ) ELSE TRUE END
162         GROUP BY acn.id, evergreen.rank_cp(acp), owning_lib.name, acn.label_sortkey, aou.id
163         WINDOW w AS (
164             ORDER BY 
165                 COALESCE(
166                     CASE WHEN aou.id = $2 THEN -20000 END,
167                     CASE WHEN aou.id = $6 THEN -10000 END,
168                     (SELECT distance - 5000
169                         FROM actor.org_unit_descendants_distance($6) as x
170                         WHERE x.id = aou.id AND $6 IN (
171                             SELECT q.id FROM actor.org_unit_descendants($2) as q)),
172                     (SELECT e.distance FROM actor.org_unit_descendants_distance($2) as e WHERE e.id = aou.id),
173                     1000
174                 ),
175                 evergreen.rank_cp(acp)
176         )
177     ) AS ua
178     GROUP BY ua.id, ua.name, ua.label_sortkey
179     ORDER BY rank, ua.name, ua.label_sortkey
180     LIMIT ($4 -> 'acn')::INT
181     OFFSET ($5 -> 'acn')::INT;
182 $$ LANGUAGE SQL STABLE ROWS 10;
183
184 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes
185     ( bibid BIGINT, ouid INT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, pref_lib INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[] )
186     RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT)
187     AS $$ SELECT * FROM evergreen.ranked_volumes(ARRAY[$1],$2,$3,$4,$5,$6,$7) $$ LANGUAGE SQL STABLE;
188
189
190 CREATE OR REPLACE FUNCTION evergreen.located_uris (
191     bibid BIGINT[], 
192     ouid INT,
193     pref_lib INT DEFAULT NULL
194 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
195     WITH all_orgs AS (SELECT COALESCE( enabled, FALSE ) AS flag FROM config.global_flag WHERE name = 'opac.located_uri.act_as_copy')
196     SELECT DISTINCT ON (id) * FROM (
197     SELECT acn.id, COALESCE(aou.name,aoud.name), acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
198       FROM asset.call_number acn
199            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number
200            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
201            LEFT JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
202            LEFT JOIN actor.org_unit_descendants( COALESCE($3, $2) ) aoud ON (acn.owning_lib = aoud.id),
203            all_orgs
204       WHERE acn.record = ANY ($1)
205           AND acn.deleted IS FALSE
206           AND auri.active IS TRUE
207           AND ((NOT all_orgs.flag AND aou.id IS NOT NULL) OR (all_orgs.flag AND COALESCE(aou.id,aoud.id) IS NOT NULL))
208     UNION
209     SELECT acn.id, COALESCE(aou.name,aoud.name) AS name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
210       FROM asset.call_number acn
211            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number
212            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
213            LEFT JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
214            LEFT JOIN actor.org_unit_descendants( $2 ) aoud ON (acn.owning_lib = aoud.id),
215            all_orgs
216       WHERE acn.record = ANY ($1)
217           AND acn.deleted IS FALSE
218           AND auri.active IS TRUE
219           AND ((NOT all_orgs.flag AND aou.id IS NOT NULL) OR (all_orgs.flag AND COALESCE(aou.id,aoud.id) IS NOT NULL)))x
220     ORDER BY id, pref_ou DESC;
221 $$
222 LANGUAGE SQL STABLE;
223
224 CREATE OR REPLACE FUNCTION evergreen.located_uris ( bibid BIGINT, ouid INT, pref_lib INT DEFAULT NULL)
225     RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT)
226     AS $$ SELECT * FROM evergreen.located_uris(ARRAY[$1],$2,$3) $$ LANGUAGE SQL STABLE;
227
228 CREATE TABLE unapi.bre_output_layout (
229     name                TEXT    PRIMARY KEY,
230     transform           TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
231     mime_type           TEXT    NOT NULL,
232     feed_top            TEXT    NOT NULL,
233     holdings_element    TEXT,
234     title_element       TEXT,
235     description_element TEXT,
236     creator_element     TEXT,
237     update_ts_element   TEXT
238 );
239
240 INSERT INTO unapi.bre_output_layout
241     (name,           transform, mime_type,              holdings_element, feed_top,         title_element, description_element, creator_element, update_ts_element)
242         VALUES
243     ('holdings_xml', NULL,      'application/xml',      NULL,             'hxml',           NULL,          NULL,                NULL,            NULL),
244     ('marcxml',      'marcxml', 'application/marc+xml', 'record',         'collection',     NULL,          NULL,                NULL,            NULL),
245     ('mods32',       'mods32',  'application/mods+xml', 'mods',           'modsCollection', NULL,          NULL,                NULL,            NULL)
246 ;
247
248 -- Dummy functions, so we can create the real ones out of order
249 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;
250 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;
251 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;
252 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;
253 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;
254 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;
255 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;
256 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;
257 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;
258 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;
259 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;
260 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;
261 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;
262 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;
263 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;
264 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;
265 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;
266 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;
267 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;
268 CREATE OR REPLACE FUNCTION unapi.bre (
269     obj_id BIGINT,
270     format TEXT,
271     ename TEXT,
272     includes TEXT[],
273     org TEXT,
274     depth INT DEFAULT NULL,
275     slimit HSTORE DEFAULT NULL,
276     soffset HSTORE DEFAULT NULL,
277     include_xmlns BOOL DEFAULT TRUE,
278     pref_lib INT DEFAULT NULL
279 )
280 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
281 CREATE OR REPLACE FUNCTION unapi.mmr (
282     obj_id BIGINT,
283     format TEXT,
284     ename TEXT,
285     includes TEXT[],
286     org TEXT,
287     depth INT DEFAULT NULL,
288     slimit HSTORE DEFAULT NULL,
289     soffset HSTORE DEFAULT NULL,
290     include_xmlns BOOL DEFAULT TRUE,
291     pref_lib INT DEFAULT NULL
292 )
293 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
294 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;
295 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;
296 CREATE OR REPLACE FUNCTION unapi.mmr_mra    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, pref_lib INT DEFAULT NULL) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
297 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;
298
299 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
300     bid BIGINT,
301     ouid INT,
302     org TEXT,
303     depth INT DEFAULT NULL,
304     includes TEXT[] DEFAULT NULL::TEXT[],
305     slimit HSTORE DEFAULT NULL,
306     soffset HSTORE DEFAULT NULL,
307     include_xmlns BOOL DEFAULT TRUE,
308     pref_lib INT DEFAULT NULL
309 )
310 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
311
312 CREATE OR REPLACE FUNCTION unapi.mmr_holdings_xml (
313     mid BIGINT,
314     ouid INT,
315     org TEXT,
316     depth INT DEFAULT NULL,
317     includes TEXT[] DEFAULT NULL::TEXT[],
318     slimit HSTORE DEFAULT NULL,
319     soffset HSTORE DEFAULT NULL,
320     include_xmlns BOOL DEFAULT TRUE,
321     pref_lib INT DEFAULT NULL
322 )
323 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
324
325 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL, pref_lib INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
326
327 CREATE OR REPLACE FUNCTION unapi.metabib_virtual_record_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL, pref_lib INT DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
328
329 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$
330 DECLARE
331     key     TEXT;
332     output  XML;
333 BEGIN
334     key :=
335         'id'        || COALESCE(obj_id::TEXT,'') ||
336         'format'    || COALESCE(format::TEXT,'') ||
337         'ename'     || COALESCE(ename::TEXT,'') ||
338         'includes'  || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
339         'org'       || COALESCE(org::TEXT,'') ||
340         'depth'     || COALESCE(depth::TEXT,'') ||
341         'slimit'    || COALESCE(slimit::TEXT,'') ||
342         'soffset'   || COALESCE(soffset::TEXT,'') ||
343         'include_xmlns'   || COALESCE(include_xmlns::TEXT,'');
344     -- RAISE NOTICE 'memoize key: %', key;
345
346     key := MD5(key);
347     -- RAISE NOTICE 'memoize hash: %', key;
348
349     -- XXX cache logic ... memcached? table?
350
351     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;
352     RETURN output;
353 END;
354 $F$ LANGUAGE PLPGSQL STABLE;
355
356 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL, pref_lib INT DEFAULT NULL ) RETURNS XML AS $F$
357 DECLARE
358     layout          unapi.bre_output_layout%ROWTYPE;
359     transform       config.xml_transform%ROWTYPE;
360     item_format     TEXT;
361     tmp_xml         TEXT;
362     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
363     ouid            INT;
364     element_list    TEXT[];
365 BEGIN
366
367     IF org = '-' OR org IS NULL THEN
368         SELECT shortname INTO org FROM evergreen.org_top();
369     END IF;
370
371     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
372     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
373
374     IF layout.name IS NULL THEN
375         RETURN NULL::XML;
376     END IF;
377
378     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
379     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
380
381     -- Gather the bib xml
382     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns, pref_lib)) INTO tmp_xml FROM UNNEST( id_list ) i;
383
384     IF layout.title_element IS NOT NULL THEN
385         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
386     END IF;
387
388     IF layout.description_element IS NOT NULL THEN
389         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
390     END IF;
391
392     IF layout.creator_element IS NOT NULL THEN
393         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
394     END IF;
395
396     IF layout.update_ts_element IS NOT NULL THEN
397         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;
398     END IF;
399
400     IF unapi_url IS NOT NULL THEN
401         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;
402     END IF;
403
404     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
405
406     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
407     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
408         EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
409     END LOOP;
410
411     RETURN tmp_xml::XML;
412 END;
413 $F$ LANGUAGE PLPGSQL STABLE;
414
415 CREATE OR REPLACE FUNCTION unapi.metabib_virtual_record_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL, pref_lib INT DEFAULT NULL ) RETURNS XML AS $F$
416 DECLARE
417     layout          unapi.bre_output_layout%ROWTYPE;
418     transform       config.xml_transform%ROWTYPE;
419     item_format     TEXT;
420     tmp_xml         TEXT;
421     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
422     ouid            INT;
423     element_list    TEXT[];
424 BEGIN
425
426     IF org = '-' OR org IS NULL THEN
427         SELECT shortname INTO org FROM evergreen.org_top();
428     END IF;
429
430     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
431     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
432
433     IF layout.name IS NULL THEN
434         RETURN NULL::XML;
435     END IF;
436
437     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
438     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
439
440     -- Gather the bib xml
441     SELECT XMLAGG( unapi.mmr(i, format, '', includes, org, depth, slimit, soffset, include_xmlns, pref_lib)) INTO tmp_xml FROM UNNEST( id_list ) i;
442
443     IF layout.title_element IS NOT NULL THEN
444         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
445     END IF;
446
447     IF layout.description_element IS NOT NULL THEN
448         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
449     END IF;
450
451     IF layout.creator_element IS NOT NULL THEN
452         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
453     END IF;
454
455     IF layout.update_ts_element IS NOT NULL THEN
456         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;
457     END IF;
458
459     IF unapi_url IS NOT NULL THEN
460         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;
461     END IF;
462
463     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
464
465     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
466     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
467         EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
468     END LOOP;
469
470     RETURN tmp_xml::XML;
471 END;
472 $F$ LANGUAGE PLPGSQL STABLE;
473
474 CREATE OR REPLACE FUNCTION unapi.bre (
475     obj_id BIGINT,
476     format TEXT,
477     ename TEXT,
478     includes TEXT[],
479     org TEXT,
480     depth INT DEFAULT NULL,
481     slimit HSTORE DEFAULT NULL,
482     soffset HSTORE DEFAULT NULL,
483     include_xmlns BOOL DEFAULT TRUE,
484     pref_lib INT DEFAULT NULL
485 )
486 RETURNS XML AS $F$
487 DECLARE
488     me      biblio.record_entry%ROWTYPE;
489     layout  unapi.bre_output_layout%ROWTYPE;
490     xfrm    config.xml_transform%ROWTYPE;
491     ouid    INT;
492     tmp_xml TEXT;
493     top_el  TEXT;
494     output  XML;
495     hxml    XML;
496     axml    XML;
497     source  XML;
498 BEGIN
499
500     IF org = '-' OR org IS NULL THEN
501         SELECT shortname INTO org FROM evergreen.org_top();
502     END IF;
503
504     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
505
506     IF ouid IS NULL THEN
507         RETURN NULL::XML;
508     END IF;
509
510     IF format = 'holdings_xml' THEN -- the special case
511         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
512         RETURN output;
513     END IF;
514
515     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
516
517     IF layout.name IS NULL THEN
518         RETURN NULL::XML;
519     END IF;
520
521     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
522
523     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
524
525     -- grab bib_source, if any
526     IF ('cbs' = ANY (includes) AND me.source IS NOT NULL) THEN
527         source := unapi.cbs(me.source,NULL,NULL,NULL,NULL);
528     ELSE
529         source := NULL::XML;
530     END IF;
531
532     -- grab SVF if we need them
533     IF ('mra' = ANY (includes)) THEN 
534         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
535     ELSE
536         axml := NULL::XML;
537     END IF;
538
539     -- grab holdings if we need them
540     IF ('holdings_xml' = ANY (includes)) THEN 
541         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
542     ELSE
543         hxml := NULL::XML;
544     END IF;
545
546
547     -- generate our item node
548
549
550     IF format = 'marcxml' THEN
551         tmp_xml := me.marc;
552         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
553            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
554         END IF; 
555     ELSE
556         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
557     END IF;
558
559     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
560
561     IF source IS NOT NULL THEN
562         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', source || '</' || top_el || E'>\\1');
563     END IF;
564
565     IF axml IS NOT NULL THEN 
566         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
567     END IF;
568
569     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
570         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
571     END IF;
572
573     IF ('bre.unapi' = ANY (includes)) THEN 
574         output := REGEXP_REPLACE(
575             tmp_xml,
576             '</' || top_el || '>(.*?)',
577             XMLELEMENT(
578                 name abbr,
579                 XMLATTRIBUTES(
580                     'http://www.w3.org/1999/xhtml' AS xmlns,
581                     'unapi-id' AS class,
582                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
583                 )
584             )::TEXT || '</' || top_el || E'>\\1'
585         );
586     ELSE
587         output := tmp_xml;
588     END IF;
589
590     IF ('bre.extern' = ANY (includes)) THEN 
591         output := REGEXP_REPLACE(
592             tmp_xml,
593             '</' || top_el || '>(.*?)',
594             XMLELEMENT(
595                 name extern,
596                 XMLATTRIBUTES(
597                     'http://open-ils.org/spec/biblio/v1' AS xmlns,
598                     me.creator AS creator,
599                     me.editor AS editor,
600                     me.create_date AS create_date,
601                     me.edit_date AS edit_date,
602                     me.quality AS quality,
603                     me.fingerprint AS fingerprint,
604                     me.tcn_source AS tcn_source,
605                     me.tcn_value AS tcn_value,
606                     me.owner AS owner,
607                     me.share_depth AS share_depth,
608                     me.active AS active,
609                     me.deleted AS deleted
610                 )
611             )::TEXT || '</' || top_el || E'>\\1'
612         );
613     ELSE
614         output := tmp_xml;
615     END IF;
616
617     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
618     RETURN output;
619 END;
620 $F$ LANGUAGE PLPGSQL STABLE;
621
622 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
623     bid BIGINT,
624     ouid INT,
625     org TEXT,
626     depth INT DEFAULT NULL,
627     includes TEXT[] DEFAULT NULL::TEXT[],
628     slimit HSTORE DEFAULT NULL,
629     soffset HSTORE DEFAULT NULL,
630     include_xmlns BOOL DEFAULT TRUE,
631     pref_lib INT DEFAULT NULL
632 )
633 RETURNS XML AS $F$
634      SELECT  XMLELEMENT(
635                  name holdings,
636                  XMLATTRIBUTES(
637                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
638                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id,
639                     (SELECT record_has_holdable_copy FROM asset.record_has_holdable_copy($1)) AS has_holdable
640                  ),
641                  XMLELEMENT(
642                      name counts,
643                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
644                          SELECT  XMLELEMENT(
645                                      name count,
646                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
647                                  )::text
648                            FROM  asset.opac_ou_record_copy_count($2,  $1)
649                                      UNION
650                          SELECT  XMLELEMENT(
651                                      name count,
652                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
653                                  )::text
654                            FROM  asset.staff_ou_record_copy_count($2, $1)
655                                      UNION
656                          SELECT  XMLELEMENT(
657                                      name count,
658                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
659                                  )::text
660                            FROM  asset.opac_ou_record_copy_count($9,  $1)
661                                      ORDER BY 1
662                      )x)
663                  ),
664                  CASE 
665                      WHEN ('bmp' = ANY ($5)) THEN
666                         XMLELEMENT(
667                             name monograph_parts,
668                             (SELECT XMLAGG(bmp) FROM (
669                                 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)
670                                   FROM  biblio.monograph_part
671                                   WHERE NOT deleted AND record = $1
672                             )x)
673                         )
674                      ELSE NULL
675                  END,
676                  XMLELEMENT(
677                      name volumes,
678                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
679                         -- Physical copies
680                         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
681                         FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9, $5) AS y
682                         UNION ALL
683                         -- Located URIs
684                         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), uris.rank, name, label_sortkey
685                         FROM evergreen.located_uris($1, $2, $9) AS uris
686                      )x)
687                  ),
688                  CASE WHEN ('ssub' = ANY ($5)) THEN 
689                      XMLELEMENT(
690                          name subscriptions,
691                          (SELECT XMLAGG(ssub) FROM (
692                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
693                               FROM  serial.subscription
694                               WHERE record_entry = $1
695                         )x)
696                      )
697                  ELSE NULL END,
698                  CASE WHEN ('acp' = ANY ($5)) THEN 
699                      XMLELEMENT(
700                          name foreign_copies,
701                          (SELECT XMLAGG(acp) FROM (
702                             SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
703                               FROM  biblio.peer_bib_copy_map p
704                                     JOIN asset.copy c ON (p.target_copy = c.id)
705                               WHERE NOT c.deleted AND p.peer_record = $1
706                             LIMIT ($6 -> 'acp')::INT
707                             OFFSET ($7 -> 'acp')::INT
708                         )x)
709                      )
710                  ELSE NULL END
711              );
712 $F$ LANGUAGE SQL STABLE;
713
714 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$
715         SELECT  XMLELEMENT(
716                     name subscription,
717                     XMLATTRIBUTES(
718                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
719                         'tag:open-ils.org:U2@ssub/' || id AS id,
720                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
721                         start_date AS start, end_date AS end, expected_date_offset
722                     ),
723                     CASE 
724                         WHEN ('sdist' = ANY ($4)) THEN
725                             XMLELEMENT( name distributions,
726                                 (SELECT XMLAGG(sdist) FROM (
727                                     SELECT  unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
728                                       FROM  serial.distribution
729                                       WHERE subscription = ssub.id
730                                 )x)
731                             )
732                         ELSE NULL
733                     END
734                 )
735           FROM  serial.subscription ssub
736           WHERE id = $1
737           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
738 $F$ LANGUAGE SQL STABLE;
739
740 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$
741         SELECT  XMLELEMENT(
742                     name distribution,
743                     XMLATTRIBUTES(
744                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
745                         'tag:open-ils.org:U2@sdist/' || id AS id,
746                                 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
747                                     'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
748                         unit_label_prefix, label, unit_label_suffix, summary_method
749                     ),
750                     unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
751                     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,
752                     CASE 
753                         WHEN ('sstr' = ANY ($4)) THEN
754                             XMLELEMENT( name streams,
755                                 (SELECT XMLAGG(sstr) FROM (
756                                     SELECT  unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
757                                       FROM  serial.stream
758                                       WHERE distribution = sdist.id
759                                 )x)
760                             )
761                         ELSE NULL
762                     END,
763                     XMLELEMENT( name summaries,
764                         CASE 
765                             WHEN ('sbsum' = ANY ($4)) THEN
766                                 (SELECT XMLAGG(sbsum) FROM (
767                                     SELECT  unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
768                                       FROM  serial.basic_summary
769                                       WHERE distribution = sdist.id
770                                 )x)
771                             ELSE NULL
772                         END,
773                         CASE 
774                             WHEN ('sisum' = ANY ($4)) THEN
775                                 (SELECT XMLAGG(sisum) FROM (
776                                     SELECT  unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
777                                       FROM  serial.index_summary
778                                       WHERE distribution = sdist.id
779                                 )x)
780                             ELSE NULL
781                         END,
782                         CASE 
783                             WHEN ('sssum' = ANY ($4)) THEN
784                                 (SELECT XMLAGG(sssum) FROM (
785                                     SELECT  unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
786                                       FROM  serial.supplement_summary
787                                       WHERE distribution = sdist.id
788                                 )x)
789                             ELSE NULL
790                         END
791                     )
792                 )
793           FROM  serial.distribution sdist
794           WHERE id = $1
795           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
796 $F$ LANGUAGE SQL STABLE;
797
798 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$
799     SELECT  XMLELEMENT(
800                 name stream,
801                 XMLATTRIBUTES(
802                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
803                     'tag:open-ils.org:U2@sstr/' || id AS id,
804                     routing_label
805                 ),
806                 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,
807                 CASE 
808                     WHEN ('sitem' = ANY ($4)) THEN
809                         XMLELEMENT( name items,
810                             (SELECT XMLAGG(sitem) FROM (
811                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
812                                   FROM  serial.item
813                                   WHERE stream = sstr.id
814                             )x)
815                         )
816                     ELSE NULL
817                 END
818             )
819       FROM  serial.stream sstr
820       WHERE id = $1
821       GROUP BY id, routing_label, distribution;
822 $F$ LANGUAGE SQL STABLE;
823
824 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$
825     SELECT  XMLELEMENT(
826                 name issuance,
827                 XMLATTRIBUTES(
828                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
829                     'tag:open-ils.org:U2@siss/' || id AS id,
830                     create_date, edit_date, label, date_published,
831                     holding_code, holding_type, holding_link_id
832                 ),
833                 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,
834                 CASE 
835                     WHEN ('sitem' = ANY ($4)) THEN
836                         XMLELEMENT( name items,
837                             (SELECT XMLAGG(sitem) FROM (
838                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
839                                   FROM  serial.item
840                                   WHERE issuance = sstr.id
841                             )x)
842                         )
843                     ELSE NULL
844                 END
845             )
846       FROM  serial.issuance sstr
847       WHERE id = $1
848       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
849 $F$ LANGUAGE SQL STABLE;
850
851 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$
852         SELECT  XMLELEMENT(
853                     name serial_item,
854                     XMLATTRIBUTES(
855                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
856                         'tag:open-ils.org:U2@sitem/' || id AS id,
857                         'tag:open-ils.org:U2@siss/' || issuance AS issuance,
858                         date_expected, date_received
859                     ),
860                     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,
861                     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,
862                     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,
863                     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
864 --                    XMLELEMENT( name notes,
865 --                        CASE 
866 --                            WHEN ('acpn' = ANY ($4)) THEN
867 --                                (SELECT XMLAGG(acpn) FROM (
868 --                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
869 --                                      FROM  asset.copy_note
870 --                                      WHERE owning_copy = cp.id AND pub
871 --                                )x)
872 --                            ELSE NULL
873 --                        END
874 --                    )
875                 )
876           FROM  serial.item sitem
877           WHERE id = $1;
878 $F$ LANGUAGE SQL STABLE;
879
880
881 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$
882     SELECT  XMLELEMENT(
883                 name serial_summary,
884                 XMLATTRIBUTES(
885                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
886                     'tag:open-ils.org:U2@sbsum/' || id AS id,
887                     'sssum' AS type, generated_coverage, textual_holdings, show_generated
888                 ),
889                 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
890             )
891       FROM  serial.supplement_summary ssum
892       WHERE id = $1
893       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
894 $F$ LANGUAGE SQL STABLE;
895
896 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$
897     SELECT  XMLELEMENT(
898                 name serial_summary,
899                 XMLATTRIBUTES(
900                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
901                     'tag:open-ils.org:U2@sbsum/' || id AS id,
902                     'sbsum' AS type, generated_coverage, textual_holdings, show_generated
903                 ),
904                 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
905             )
906       FROM  serial.basic_summary ssum
907       WHERE id = $1
908       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
909 $F$ LANGUAGE SQL STABLE;
910
911 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$
912     SELECT  XMLELEMENT(
913                 name serial_summary,
914                 XMLATTRIBUTES(
915                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
916                     'tag:open-ils.org:U2@sbsum/' || id AS id,
917                     'sisum' AS type, generated_coverage, textual_holdings, show_generated
918                 ),
919                 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
920             )
921       FROM  serial.index_summary ssum
922       WHERE id = $1
923       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
924 $F$ LANGUAGE SQL STABLE;
925
926
927 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$
928 DECLARE
929     output XML;
930 BEGIN
931     IF ename = 'circlib' THEN
932         SELECT  XMLELEMENT(
933                     name circlib,
934                     XMLATTRIBUTES(
935                         'http://open-ils.org/spec/actors/v1' AS xmlns,
936                         id AS ident
937                     ),
938                     name
939                 ) INTO output
940           FROM  actor.org_unit aou
941           WHERE id = obj_id;
942     ELSE
943         EXECUTE $$SELECT  XMLELEMENT(
944                     name $$ || ename || $$,
945                     XMLATTRIBUTES(
946                         'http://open-ils.org/spec/actors/v1' AS xmlns,
947                         'tag:open-ils.org:U2@aou/' || id AS id,
948                         shortname, name, opac_visible
949                     )
950                 )
951           FROM  actor.org_unit aou
952          WHERE id = $1 $$ INTO output USING obj_id;
953     END IF;
954
955     RETURN output;
956
957 END;
958 $F$ LANGUAGE PLPGSQL STABLE;
959
960 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$
961     SELECT  XMLELEMENT(
962                 name location,
963                 XMLATTRIBUTES(
964                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
965                     id AS ident,
966                     holdable,
967                     opac_visible,
968                     label_prefix AS prefix,
969                     label_suffix AS suffix
970                 ),
971                 name
972             )
973       FROM  asset.copy_location
974       WHERE id = $1;
975 $F$ LANGUAGE SQL STABLE;
976
977 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$
978     SELECT  XMLELEMENT(
979                 name status,
980                 XMLATTRIBUTES(
981                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
982                     id AS ident,
983                     holdable,
984                     opac_visible
985                 ),
986                 name
987             )
988       FROM  config.copy_status
989       WHERE id = $1;
990 $F$ LANGUAGE SQL STABLE;
991
992 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$
993         SELECT  XMLELEMENT(
994                     name copy_note,
995                     XMLATTRIBUTES(
996                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
997                         create_date AS date,
998                         title
999                     ),
1000                     value
1001                 )
1002           FROM  asset.copy_note
1003           WHERE id = $1;
1004 $F$ LANGUAGE SQL STABLE;
1005
1006 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$
1007         SELECT  XMLELEMENT(
1008                     name statcat,
1009                     XMLATTRIBUTES(
1010                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1011                         sc.name,
1012                         sc.opac_visible
1013                     ),
1014                     asce.value
1015                 )
1016           FROM  asset.stat_cat_entry asce
1017                 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
1018           WHERE asce.id = $1;
1019 $F$ LANGUAGE SQL STABLE;
1020
1021 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$
1022         SELECT  XMLELEMENT(
1023                     name monograph_part,
1024                     XMLATTRIBUTES(
1025                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1026                         'tag:open-ils.org:U2@bmp/' || id AS id,
1027                         id AS ident,
1028                         label,
1029                         label_sortkey,
1030                         'tag:open-ils.org:U2@bre/' || record AS record
1031                     ),
1032                     CASE 
1033                         WHEN ('acp' = ANY ($4)) THEN
1034                             XMLELEMENT( name copies,
1035                                 (SELECT XMLAGG(acp) FROM (
1036                                     SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
1037                                       FROM  asset.copy cp
1038                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
1039                                       WHERE cpm.part = $1
1040                                           AND cp.deleted IS FALSE
1041                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
1042                                       LIMIT ($7 -> 'acp')::INT
1043                                       OFFSET ($8 -> 'acp')::INT
1044
1045                                 )x)
1046                             )
1047                         ELSE NULL
1048                     END,
1049                     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
1050                 )
1051           FROM  biblio.monograph_part
1052           WHERE NOT deleted AND id = $1
1053           GROUP BY id, label, label_sortkey, record;
1054 $F$ LANGUAGE SQL STABLE;
1055
1056 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$
1057         SELECT  XMLELEMENT(
1058                     name copy,
1059                     XMLATTRIBUTES(
1060                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1061                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
1062                         create_date, edit_date, copy_number, circulate, deposit,
1063                         ref, holdable, deleted, deposit_amount, price, barcode,
1064                         circ_modifier, circ_as_type, opac_visible, age_protect
1065                     ),
1066                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
1067                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
1068                     unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
1069                     unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
1070                     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,
1071                     CASE 
1072                         WHEN ('acpn' = ANY ($4)) THEN
1073                             XMLELEMENT( name copy_notes,
1074                                 (SELECT XMLAGG(acpn) FROM (
1075                                     SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
1076                                       FROM  asset.copy_note
1077                                       WHERE owning_copy = cp.id AND pub
1078                                 )x)
1079                             )
1080                         ELSE NULL
1081                     END,
1082                     CASE 
1083                         WHEN ('ascecm' = ANY ($4)) THEN
1084                             XMLELEMENT( name statcats,
1085                                 (SELECT XMLAGG(ascecm) FROM (
1086                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
1087                                       FROM  asset.stat_cat_entry_copy_map
1088                                       WHERE owning_copy = cp.id
1089                                 )x)
1090                             )
1091                         ELSE NULL
1092                     END,
1093                     CASE
1094                         WHEN ('bre' = ANY ($4)) THEN
1095                             XMLELEMENT( name foreign_records,
1096                                 (SELECT XMLAGG(bre) FROM (
1097                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
1098                                       FROM  biblio.peer_bib_copy_map
1099                                       WHERE target_copy = cp.id
1100                                 )x)
1101
1102                             )
1103                         ELSE NULL
1104                     END,
1105                     CASE 
1106                         WHEN ('bmp' = ANY ($4)) THEN
1107                             XMLELEMENT( name monograph_parts,
1108                                 (SELECT XMLAGG(bmp) FROM (
1109                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
1110                                       FROM  asset.copy_part_map
1111                                       WHERE target_copy = cp.id
1112                                 )x)
1113                             )
1114                         ELSE NULL
1115                     END,
1116                     CASE 
1117                         WHEN ('circ' = ANY ($4)) THEN
1118                             XMLELEMENT( name current_circulation,
1119                                 (SELECT XMLAGG(circ) FROM (
1120                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
1121                                       FROM  action.circulation
1122                                       WHERE target_copy = cp.id
1123                                             AND checkin_time IS NULL
1124                                 )x)
1125                             )
1126                         ELSE NULL
1127                     END
1128                 )
1129           FROM  asset.copy cp
1130           WHERE id = $1
1131               AND cp.deleted IS FALSE
1132           GROUP BY id, status, location, circ_lib, call_number, create_date,
1133               edit_date, copy_number, circulate, deposit, ref, holdable,
1134               deleted, deposit_amount, price, barcode, circ_modifier,
1135               circ_as_type, opac_visible, age_protect;
1136 $F$ LANGUAGE SQL STABLE;
1137
1138 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$
1139         SELECT  XMLELEMENT(
1140                     name serial_unit,
1141                     XMLATTRIBUTES(
1142                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1143                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
1144                         create_date, edit_date, copy_number, circulate, deposit,
1145                         ref, holdable, deleted, deposit_amount, price, barcode,
1146                         circ_modifier, circ_as_type, opac_visible, age_protect,
1147                         status_changed_time, floating, mint_condition,
1148                         detailed_contents, sort_key, summary_contents, cost 
1149                     ),
1150                     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),
1151                     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),
1152                     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),
1153                     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),
1154                     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,
1155                     XMLELEMENT( name copy_notes,
1156                         CASE 
1157                             WHEN ('acpn' = ANY ($4)) THEN
1158                                 (SELECT XMLAGG(acpn) FROM (
1159                                     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)
1160                                       FROM  asset.copy_note
1161                                       WHERE owning_copy = cp.id AND pub
1162                                 )x)
1163                             ELSE NULL
1164                         END
1165                     ),
1166                     XMLELEMENT( name statcats,
1167                         CASE 
1168                             WHEN ('ascecm' = ANY ($4)) THEN
1169                                 (SELECT XMLAGG(ascecm) FROM (
1170                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
1171                                       FROM  asset.stat_cat_entry_copy_map
1172                                       WHERE owning_copy = cp.id
1173                                 )x)
1174                             ELSE NULL
1175                         END
1176                     ),
1177                     XMLELEMENT( name foreign_records,
1178                         CASE
1179                             WHEN ('bre' = ANY ($4)) THEN
1180                                 (SELECT XMLAGG(bre) FROM (
1181                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
1182                                       FROM  biblio.peer_bib_copy_map
1183                                       WHERE target_copy = cp.id
1184                                 )x)
1185                             ELSE NULL
1186                         END
1187                     ),
1188                     CASE 
1189                         WHEN ('bmp' = ANY ($4)) THEN
1190                             XMLELEMENT( name monograph_parts,
1191                                 (SELECT XMLAGG(bmp) FROM (
1192                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
1193                                       FROM  asset.copy_part_map
1194                                       WHERE target_copy = cp.id
1195                                 )x)
1196                             )
1197                         ELSE NULL
1198                     END,
1199                     CASE 
1200                         WHEN ('circ' = ANY ($4)) THEN
1201                             XMLELEMENT( name current_circulation,
1202                                 (SELECT XMLAGG(circ) FROM (
1203                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
1204                                       FROM  action.circulation
1205                                       WHERE target_copy = cp.id
1206                                             AND checkin_time IS NULL
1207                                 )x)
1208                             )
1209                         ELSE NULL
1210                     END
1211                 )
1212           FROM  serial.unit cp
1213           WHERE id = $1
1214               AND cp.deleted IS FALSE
1215           GROUP BY id, status, location, circ_lib, call_number, create_date,
1216               edit_date, copy_number, circulate, floating, mint_condition,
1217               deposit, ref, holdable, deleted, deposit_amount, price,
1218               barcode, circ_modifier, circ_as_type, opac_visible,
1219               status_changed_time, detailed_contents, sort_key,
1220               summary_contents, cost, age_protect;
1221 $F$ LANGUAGE SQL STABLE;
1222
1223 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$
1224         SELECT  XMLELEMENT(
1225                     name volume,
1226                     XMLATTRIBUTES(
1227                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1228                         'tag:open-ils.org:U2@acn/' || acn.id AS id,
1229                         acn.id AS vol_id, o.shortname AS lib,
1230                         o.opac_visible AS opac_visible,
1231                         deleted, label, label_sortkey, label_class, record
1232                     ),
1233                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
1234                     CASE 
1235                         WHEN ('acp' = ANY ($4)) THEN
1236                             CASE WHEN $6 IS NOT NULL THEN
1237                                 XMLELEMENT( name copies,
1238                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
1239                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
1240                                             evergreen.rank_cp(cp) AS rank_avail
1241                                           FROM  asset.copy cp
1242                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
1243                                           WHERE cp.call_number = acn.id
1244                                               AND cp.deleted IS FALSE
1245                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
1246                                           LIMIT ($7 -> 'acp')::INT
1247                                           OFFSET ($8 -> 'acp')::INT
1248                                     )x)
1249                                 )
1250                             ELSE
1251                                 XMLELEMENT( name copies,
1252                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
1253                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
1254                                             evergreen.rank_cp(cp) AS rank_avail
1255                                           FROM  asset.copy cp
1256                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
1257                                           WHERE cp.call_number = acn.id
1258                                               AND cp.deleted IS FALSE
1259                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
1260                                           LIMIT ($7 -> 'acp')::INT
1261                                           OFFSET ($8 -> 'acp')::INT
1262                                     )x)
1263                                 )
1264                             END
1265                         ELSE NULL
1266                     END,
1267                     XMLELEMENT(
1268                         name uris,
1269                         (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)
1270                     ),
1271                     unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
1272                     unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
1273                     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
1274                 ) AS x
1275           FROM  asset.call_number acn
1276                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
1277           WHERE acn.id = $1
1278               AND acn.deleted IS FALSE
1279           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
1280 $F$ LANGUAGE SQL STABLE;
1281
1282 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$
1283         SELECT  XMLELEMENT(
1284                     name call_number_prefix,
1285                     XMLATTRIBUTES(
1286                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1287                         id AS ident,
1288                         label,
1289                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
1290                         label_sortkey
1291                     )
1292                 )
1293           FROM  asset.call_number_prefix
1294           WHERE id = $1;
1295 $F$ LANGUAGE SQL STABLE;
1296
1297 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$
1298         SELECT  XMLELEMENT(
1299                     name call_number_suffix,
1300                     XMLATTRIBUTES(
1301                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1302                         id AS ident,
1303                         label,
1304                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
1305                         label_sortkey
1306                     )
1307                 )
1308           FROM  asset.call_number_suffix
1309           WHERE id = $1;
1310 $F$ LANGUAGE SQL STABLE;
1311
1312 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$
1313         SELECT  XMLELEMENT(
1314                     name uri,
1315                     XMLATTRIBUTES(
1316                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1317                         'tag:open-ils.org:U2@auri/' || uri.id AS id,
1318                         use_restriction,
1319                         href,
1320                         label
1321                     ),
1322                     CASE 
1323                         WHEN ('acn' = ANY ($4)) THEN
1324                             XMLELEMENT( name copies,
1325                                 (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)
1326                             )
1327                         ELSE NULL
1328                     END
1329                 ) AS x
1330           FROM  asset.uri uri
1331           WHERE uri.id = $1
1332           GROUP BY uri.id, use_restriction, href, label;
1333 $F$ LANGUAGE SQL STABLE;
1334
1335 CREATE OR REPLACE FUNCTION unapi.cbs ( 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$
1336     SELECT  XMLELEMENT(
1337                 name bib_source,
1338                 XMLATTRIBUTES(
1339                     NULL AS xmlns, -- TODO needs equivalent to http://open-ils.org/spec/holdings/v1
1340                     id AS ident,
1341                     quality,
1342                     transcendant,
1343                     can_have_copies
1344                 ),
1345                 source
1346             )
1347       FROM  config.bib_source
1348       WHERE id = $1;
1349 $F$ LANGUAGE SQL STABLE;
1350
1351 CREATE OR REPLACE FUNCTION unapi.mra (
1352     obj_id BIGINT,
1353     format TEXT,
1354     ename TEXT,
1355     includes TEXT[],
1356     org TEXT,
1357     depth INT DEFAULT NULL,
1358     slimit HSTORE DEFAULT NULL,
1359     soffset HSTORE DEFAULT NULL,
1360     include_xmlns BOOL DEFAULT TRUE
1361 ) RETURNS XML AS $F$
1362     SELECT  XMLELEMENT(
1363         name attributes,
1364         XMLATTRIBUTES(
1365             CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
1366             'tag:open-ils.org:U2@mra/' || $1 AS id, 
1367             'tag:open-ils.org:U2@bre/' || $1 AS record 
1368         ),  
1369         (SELECT XMLAGG(foo.y)
1370           FROM (
1371             SELECT  XMLELEMENT(
1372                         name field,
1373                         XMLATTRIBUTES(
1374                             mra.attr AS name,
1375                             cvm.value AS "coded-value",
1376                             cvm.id AS "cvmid",
1377                             rad.composite,
1378                             rad.multi,
1379                             rad.filter,
1380                             rad.sorter
1381                         ),
1382                         mra.value
1383                     )
1384               FROM  metabib.record_attr_flat mra
1385                     JOIN config.record_attr_definition rad ON (mra.attr = rad.name)
1386                     LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = mra.attr AND code = mra.value)
1387               WHERE mra.id = $1
1388             )foo(y)
1389         )   
1390     )   
1391 $F$ LANGUAGE SQL STABLE;
1392
1393 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$
1394     SELECT XMLELEMENT(
1395         name circ,
1396         XMLATTRIBUTES(
1397             CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1398             'tag:open-ils.org:U2@circ/' || id AS id,
1399             xact_start,
1400             due_date
1401         ),
1402         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,
1403         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
1404     )
1405     FROM action.circulation
1406     WHERE id = $1;
1407 $F$ LANGUAGE SQL STABLE;
1408
1409 CREATE OR REPLACE FUNCTION unapi.mmr_mra (
1410     obj_id BIGINT,
1411     format TEXT,
1412     ename TEXT,
1413     includes TEXT[],
1414     org TEXT,
1415     depth INT DEFAULT NULL,
1416     slimit HSTORE DEFAULT NULL,
1417     soffset HSTORE DEFAULT NULL,
1418     include_xmlns BOOL DEFAULT TRUE,
1419     pref_lib INT DEFAULT NULL
1420 ) RETURNS XML AS $F$
1421     SELECT  XMLELEMENT(
1422         name attributes,
1423         XMLATTRIBUTES(
1424             CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
1425             'tag:open-ils.org:U2@mmr/' || $1 AS metarecord
1426         ),
1427         (SELECT XMLAGG(foo.y)
1428           FROM (
1429             WITH sourcelist AS (
1430                 WITH aou AS (SELECT COALESCE(id, (evergreen.org_top()).id) AS id FROM actor.org_unit WHERE shortname = $5 LIMIT 1),
1431                      basevm AS (SELECT c_attrs FROM  asset.patron_default_visibility_mask()),
1432                      circvm AS (SELECT search.calculate_visibility_attribute_test('circ_lib', ARRAY_AGG(aoud.id)) AS mask
1433                                   FROM aou, LATERAL actor.org_unit_descendants(aou.id, $6) aoud)
1434                 SELECT  source
1435                   FROM  aou, circvm, basevm, metabib.metarecord_source_map mmsm
1436                   WHERE mmsm.metarecord = $1 AND (
1437                     EXISTS (
1438                         SELECT  1
1439                           FROM  circvm, basevm, asset.copy_vis_attr_cache acvac
1440                           WHERE acvac.vis_attr_vector @@ (basevm.c_attrs || '&' || circvm.mask)::query_int
1441                                 AND acvac.record = mmsm.source
1442                     )
1443                     OR EXISTS (SELECT 1 FROM evergreen.located_uris(source, aou.id, $10) LIMIT 1)
1444                     OR EXISTS (SELECT 1 FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = mmsm.source)
1445                 )
1446             )
1447             SELECT  cmra.aid,
1448                     XMLELEMENT(
1449                         name field,
1450                         XMLATTRIBUTES(
1451                             cmra.attr AS name,
1452                             cmra.value AS "coded-value",
1453                             cmra.aid AS "cvmid",
1454                             rad.composite,
1455                             rad.multi,
1456                             rad.filter,
1457                             rad.sorter,
1458                             cmra.source_list
1459                         ),
1460                         cmra.value
1461                     )
1462               FROM  (
1463                 SELECT DISTINCT aid, attr, value, STRING_AGG(x.id::TEXT, ',') AS source_list
1464                   FROM (
1465                     SELECT  v.source AS id,
1466                             c.id AS aid,
1467                             c.ctype AS attr,
1468                             c.code AS value
1469                       FROM  metabib.record_attr_vector_list v
1470                             JOIN config.coded_value_map c ON ( c.id = ANY( v.vlist ) )
1471                     ) AS x
1472                     JOIN sourcelist ON (x.id = sourcelist.source)
1473                     GROUP BY 1, 2, 3
1474                 ) AS cmra
1475                 JOIN config.record_attr_definition rad ON (cmra.attr = rad.name)
1476                 UNION ALL
1477             SELECT  umra.aid,
1478                     XMLELEMENT(
1479                         name field,
1480                         XMLATTRIBUTES(
1481                             umra.attr AS name,
1482                             rad.composite,
1483                             rad.multi,
1484                             rad.filter,
1485                             rad.sorter
1486                         ),
1487                         umra.value
1488                     )
1489               FROM  (
1490                 SELECT DISTINCT aid, attr, value
1491                   FROM (
1492                     SELECT  v.source AS id,
1493                             m.id AS aid,
1494                             m.attr AS attr,
1495                             m.value AS value
1496                       FROM  metabib.record_attr_vector_list v
1497                             JOIN metabib.uncontrolled_record_attr_value m ON ( m.id = ANY( v.vlist ) )
1498                     ) AS x
1499                     JOIN sourcelist ON (x.id = sourcelist.source)
1500                 ) AS umra
1501                 JOIN config.record_attr_definition rad ON (umra.attr = rad.name)
1502                 ORDER BY 1
1503
1504             )foo(id,y)
1505         )
1506     )
1507 $F$ LANGUAGE SQL STABLE;
1508
1509 CREATE OR REPLACE FUNCTION unapi.mmr_holdings_xml (
1510     mid BIGINT,
1511     ouid INT,
1512     org TEXT,
1513     depth INT DEFAULT NULL,
1514     includes TEXT[] DEFAULT NULL::TEXT[],
1515     slimit HSTORE DEFAULT NULL,
1516     soffset HSTORE DEFAULT NULL,
1517     include_xmlns BOOL DEFAULT TRUE,
1518     pref_lib INT DEFAULT NULL
1519 )
1520 RETURNS XML AS $F$
1521      SELECT  XMLELEMENT(
1522                  name holdings,
1523                  XMLATTRIBUTES(
1524                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
1525                     CASE WHEN ('mmr' = ANY ($5)) THEN 'tag:open-ils.org:U2@mmr/' || $1 || '/' || $3 ELSE NULL END AS id,
1526                     (SELECT metarecord_has_holdable_copy FROM asset.metarecord_has_holdable_copy($1)) AS has_holdable
1527                  ),
1528                  XMLELEMENT(
1529                      name counts,
1530                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
1531                          SELECT  XMLELEMENT(
1532                                      name count,
1533                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
1534                                  )::text
1535                            FROM  asset.opac_ou_metarecord_copy_count($2,  $1)
1536                                      UNION
1537                          SELECT  XMLELEMENT(
1538                                      name count,
1539                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
1540                                  )::text
1541                            FROM  asset.staff_ou_metarecord_copy_count($2, $1)
1542                                      UNION
1543                          SELECT  XMLELEMENT(
1544                                      name count,
1545                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
1546                                  )::text
1547                            FROM  asset.opac_ou_metarecord_copy_count($9,  $1)
1548                                      ORDER BY 1
1549                      )x)
1550                  ),
1551                  -- XXX monograph_parts and foreign_copies are skipped in MRs ... put them back some day?
1552                  XMLELEMENT(
1553                      name volumes,
1554                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
1555                         -- Physical copies
1556                         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
1557                         FROM evergreen.ranked_volumes((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $4, $6, $7, $9, $5) AS y
1558                         UNION ALL
1559                         -- Located URIs
1560                         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), uris.rank, name, label_sortkey
1561                         FROM evergreen.located_uris((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $9) AS uris
1562                      )x)
1563                  ),
1564                  CASE WHEN ('ssub' = ANY ($5)) THEN
1565                      XMLELEMENT(
1566                          name subscriptions,
1567                          (SELECT XMLAGG(ssub) FROM (
1568                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
1569                               FROM  serial.subscription
1570                               WHERE record_entry IN (SELECT source FROM metabib.metarecord_source_map WHERE metarecord = $1)
1571                         )x)
1572                      )
1573                  ELSE NULL END
1574              );
1575 $F$ LANGUAGE SQL STABLE;
1576
1577 CREATE OR REPLACE FUNCTION unapi.mmr (
1578     obj_id BIGINT,
1579     format TEXT,
1580     ename TEXT,
1581     includes TEXT[],
1582     org TEXT,
1583     depth INT DEFAULT NULL,
1584     slimit HSTORE DEFAULT NULL,
1585     soffset HSTORE DEFAULT NULL,
1586     include_xmlns BOOL DEFAULT TRUE,
1587     pref_lib INT DEFAULT NULL
1588 )
1589 RETURNS XML AS $F$
1590 DECLARE
1591     mmrec   metabib.metarecord%ROWTYPE;
1592     leadrec biblio.record_entry%ROWTYPE;
1593     subrec biblio.record_entry%ROWTYPE;
1594     layout  unapi.bre_output_layout%ROWTYPE;
1595     xfrm    config.xml_transform%ROWTYPE;
1596     ouid    INT;
1597     xml_buf TEXT; -- growing XML document
1598     tmp_xml TEXT; -- single-use XML string
1599     xml_frag TEXT; -- single-use XML fragment
1600     top_el  TEXT;
1601     output  XML;
1602     hxml    XML;
1603     axml    XML;
1604     subxml  XML; -- subordinate records elements
1605     sub_xpath TEXT; 
1606     parts   TEXT[]; 
1607 BEGIN
1608
1609     -- xpath for extracting bre.marc values from subordinate records 
1610     -- so they may be appended to the MARC of the master record prior
1611     -- to XSLT processing.
1612     -- subjects, isbn, issn, upc -- anything else?
1613     sub_xpath := 
1614       '//*[starts-with(@tag, "6") or @tag="020" or @tag="022" or @tag="024"]';
1615
1616     IF org = '-' OR org IS NULL THEN
1617         SELECT shortname INTO org FROM evergreen.org_top();
1618     END IF;
1619
1620     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
1621
1622     IF ouid IS NULL THEN
1623         RETURN NULL::XML;
1624     END IF;
1625
1626     SELECT INTO mmrec * FROM metabib.metarecord WHERE id = obj_id;
1627     IF NOT FOUND THEN
1628         RETURN NULL::XML;
1629     END IF;
1630
1631     -- TODO: aggregate holdings from constituent records
1632     IF format = 'holdings_xml' THEN -- the special case
1633         output := unapi.mmr_holdings_xml(
1634             obj_id, ouid, org, depth,
1635             evergreen.array_remove_item_by_value(includes,'holdings_xml'),
1636             slimit, soffset, include_xmlns, pref_lib);
1637         RETURN output;
1638     END IF;
1639
1640     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
1641
1642     IF layout.name IS NULL THEN
1643         RETURN NULL::XML;
1644     END IF;
1645
1646     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
1647
1648     SELECT INTO leadrec * FROM biblio.record_entry WHERE id = mmrec.master_record;
1649
1650     -- Grab distinct MVF for all records if requested
1651     IF ('mra' = ANY (includes)) THEN 
1652         axml := unapi.mmr_mra(obj_id,NULL,NULL,NULL,org,depth,NULL,NULL,TRUE,pref_lib);
1653     ELSE
1654         axml := NULL::XML;
1655     END IF;
1656
1657     xml_buf = leadrec.marc;
1658
1659     hxml := NULL::XML;
1660     IF ('holdings_xml' = ANY (includes)) THEN
1661         hxml := unapi.mmr_holdings_xml(
1662                     obj_id, ouid, org, depth,
1663                     evergreen.array_remove_item_by_value(includes,'holdings_xml'),
1664                     slimit, soffset, include_xmlns, pref_lib);
1665     END IF;
1666
1667     subxml := NULL::XML;
1668     parts := '{}'::TEXT[];
1669     FOR subrec IN SELECT bre.* FROM biblio.record_entry bre
1670          JOIN metabib.metarecord_source_map mmsm ON (mmsm.source = bre.id)
1671          JOIN metabib.metarecord mmr ON (mmr.id = mmsm.metarecord)
1672          WHERE mmr.id = obj_id AND NOT bre.deleted
1673          ORDER BY CASE WHEN bre.id = mmr.master_record THEN 0 ELSE bre.id END
1674          LIMIT COALESCE((slimit->'bre')::INT, 5) LOOP
1675
1676         IF subrec.id = leadrec.id THEN CONTINUE; END IF;
1677         -- Append choice data from the the non-lead records to the 
1678         -- the lead record document
1679
1680         parts := parts || xpath(sub_xpath, subrec.marc::XML)::TEXT[];
1681     END LOOP;
1682
1683     SELECT ARRAY_TO_STRING( ARRAY_AGG( DISTINCT p ), '' )::XML INTO subxml FROM UNNEST(parts) p;
1684
1685     -- append data from the subordinate records to the 
1686     -- main record document before applying the XSLT
1687
1688     IF subxml IS NOT NULL THEN 
1689         xml_buf := REGEXP_REPLACE(xml_buf, 
1690             '</record>(.*?)$', subxml || '</record>' || E'\\1');
1691     END IF;
1692
1693     IF format = 'marcxml' THEN
1694          -- If we're not using the prefixed namespace in 
1695          -- this record, then remove all declarations of it
1696         IF xml_buf !~ E'<marc:' THEN
1697            xml_buf := REGEXP_REPLACE(xml_buf, 
1698             ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
1699         END IF; 
1700     ELSE
1701         xml_buf := oils_xslt_process(xml_buf, xfrm.xslt)::XML;
1702     END IF;
1703
1704     -- update top_el to reflect the change in xml_buf, which may
1705     -- now be a different type of document (e.g. record -> mods)
1706     top_el := REGEXP_REPLACE(xml_buf, E'^.*?<((?:\\S+:)?' || 
1707         layout.holdings_element || ').*$', E'\\1');
1708
1709     IF axml IS NOT NULL THEN 
1710         xml_buf := REGEXP_REPLACE(xml_buf, 
1711             '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
1712     END IF;
1713
1714     IF hxml IS NOT NULL THEN
1715         xml_buf := REGEXP_REPLACE(xml_buf, 
1716             '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
1717     END IF;
1718
1719     IF ('mmr.unapi' = ANY (includes)) THEN 
1720         output := REGEXP_REPLACE(
1721             xml_buf,
1722             '</' || top_el || '>(.*?)',
1723             XMLELEMENT(
1724                 name abbr,
1725                 XMLATTRIBUTES(
1726                     'http://www.w3.org/1999/xhtml' AS xmlns,
1727                     'unapi-id' AS class,
1728                     'tag:open-ils.org:U2@mmr/' || obj_id || '/' || org AS title
1729                 )
1730             )::TEXT || '</' || top_el || E'>\\1'
1731         );
1732     ELSE
1733         output := xml_buf;
1734     END IF;
1735
1736     -- remove ignorable whitesace
1737     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
1738     RETURN output;
1739 END;
1740 $F$ LANGUAGE PLPGSQL STABLE;
1741
1742
1743 /*
1744
1745  -- Some test queries
1746
1747 SELECT unapi.memoize( 'bre', 1,'mods32','','{holdings_xml,acp}'::TEXT[], 'SYS1');
1748 SELECT unapi.memoize( 'bre', 1,'marcxml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
1749 SELECT unapi.memoize( 'bre', 1,'holdings_xml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
1750
1751 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>');
1752
1753 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>');
1754 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>');
1755 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>');
1756 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>');
1757
1758 SELECT unapi.biblio_record_entry_feed('{216}'::BIGINT[],'marcxml','{}'::TEXT[], 'BR1');
1759 EXPLAIN ANALYZE SELECT unapi.bre(216,'marcxml','record','{holdings_xml,bre.unapi}'::TEXT[], 'BR1');
1760 EXPLAIN ANALYZE SELECT unapi.bre(216,'holdings_xml','record','{}'::TEXT[], 'BR1');
1761 EXPLAIN ANALYZE SELECT unapi.holdings_xml(216,4,'BR1',2,'{bre}'::TEXT[]);
1762 EXPLAIN ANALYZE SELECT unapi.bre(216,'mods32','record','{}'::TEXT[], 'BR1');
1763
1764 -- Limit to 5 call numbers, 5 copies, with a preferred library of 4 (BR1), in SYS2 at a depth of 0
1765 EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,acnp,acns,bmp}','SYS2',0,'acn=>5,acp=>5',NULL,TRUE,4);
1766
1767 */
1768
1769 COMMIT;
1770