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