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