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