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