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