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