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