LP#1053397: Add optional org filter to "has holdable copies", and use that in open...
[Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / ZZZZ.schema.unapi-mmr.sql
1 BEGIN;
2
3 DROP FUNCTION asset.record_has_holdable_copy (BIGINT);
4 CREATE FUNCTION asset.record_has_holdable_copy ( rid BIGINT, ou INT DEFAULT NULL) RETURNS BOOL AS $f$
5 BEGIN
6     PERFORM 1
7         FROM
8             asset.copy acp
9             JOIN asset.call_number acn ON acp.call_number = acn.id
10             JOIN asset.copy_location acpl ON acp.location = acpl.id
11             JOIN config.copy_status ccs ON acp.status = ccs.id
12         WHERE
13             acn.record = rid
14             AND acp.holdable = true
15             AND acpl.holdable = true
16             AND ccs.holdable = true
17             AND acp.deleted = false
18             AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
19         LIMIT 1;
20     IF FOUND THEN
21         RETURN true;
22     END IF;
23     RETURN FALSE;
24 END;
25 $f$ LANGUAGE PLPGSQL;
26
27 DROP FUNCTION asset.metarecord_has_holdable_copy (BIGINT);
28 CREATE FUNCTION asset.metarecord_has_holdable_copy ( rid BIGINT, ou INT DEFAULT NULL) RETURNS BOOL AS $f$
29 BEGIN
30     PERFORM 1
31         FROM
32             asset.copy acp
33             JOIN asset.call_number acn ON acp.call_number = acn.id
34             JOIN asset.copy_location acpl ON acp.location = acpl.id
35             JOIN config.copy_status ccs ON acp.status = ccs.id
36             JOIN metabib.metarecord_source_map mmsm ON acn.record = mmsm.source
37         WHERE
38             mmsm.metarecord = rid
39             AND acp.holdable = true
40             AND acpl.holdable = true
41             AND ccs.holdable = true
42             AND acp.deleted = false
43             AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
44         LIMIT 1;
45     IF FOUND THEN
46         RETURN true;
47     END IF;
48     RETURN FALSE;
49 END;
50 $f$ LANGUAGE PLPGSQL;
51
52 CREATE OR REPLACE FUNCTION asset.opac_ou_metarecord_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
53 DECLARE
54     ans RECORD;
55     trans INT;
56 BEGIN
57     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;
58
59     FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
60         RETURN QUERY
61         SELECT  ans.depth,
62                 ans.id,
63                 COUNT( av.id ),
64                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
65                 COUNT( av.id ),
66                 trans
67           FROM  
68                 actor.org_unit_descendants(ans.id) d
69                 JOIN asset.opac_visible_copies av ON (av.circ_lib = d.id)
70                 JOIN asset.copy cp ON (cp.id = av.copy_id)
71                 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record)
72           GROUP BY 1,2,6;
73
74         IF NOT FOUND THEN
75             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
76         END IF;
77
78     END LOOP;
79
80     RETURN;
81 END;
82 $f$ LANGUAGE PLPGSQL;
83
84 CREATE OR REPLACE FUNCTION asset.opac_lasso_metarecord_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
85 DECLARE
86     ans RECORD;
87     trans INT;
88 BEGIN
89     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;
90
91     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
92         RETURN QUERY
93         SELECT  -1,
94                 ans.id,
95                 COUNT( av.id ),
96                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
97                 COUNT( av.id ),
98                 trans
99           FROM
100                 actor.org_unit_descendants(ans.id) d
101                 JOIN asset.opac_visible_copies av ON (av.circ_lib = d.id)
102                 JOIN asset.copy cp ON (cp.id = av.copy_id)
103                 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record)
104           GROUP BY 1,2,6;
105
106         IF NOT FOUND THEN
107             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
108         END IF;
109
110     END LOOP;   
111                 
112     RETURN;     
113 END;            
114 $f$ LANGUAGE PLPGSQL;
115
116 CREATE OR REPLACE FUNCTION asset.staff_ou_metarecord_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
117 DECLARE         
118     ans RECORD; 
119     trans INT;
120 BEGIN
121     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;
122
123     FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
124         RETURN QUERY
125         SELECT  ans.depth,
126                 ans.id,
127                 COUNT( cp.id ),
128                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
129                 COUNT( cp.id ),
130                 trans
131           FROM
132                 actor.org_unit_descendants(ans.id) d
133                 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
134                 JOIN asset.call_number cn ON (cn.id = cp.call_number AND NOT cn.deleted)
135                 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = cn.record)
136           GROUP BY 1,2,6;
137
138         IF NOT FOUND THEN
139             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
140         END IF;
141
142     END LOOP;
143
144     RETURN;
145 END;
146 $f$ LANGUAGE PLPGSQL;
147
148 CREATE OR REPLACE FUNCTION asset.staff_lasso_metarecord_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
149 DECLARE
150     ans RECORD;
151     trans INT;
152 BEGIN
153     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;
154
155     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
156         RETURN QUERY
157         SELECT  -1,
158                 ans.id,
159                 COUNT( cp.id ),
160                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
161                 COUNT( cp.id ),
162                 trans
163           FROM
164                 actor.org_unit_descendants(ans.id) d
165                 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
166                 JOIN asset.call_number cn ON (cn.id = cp.call_number AND NOT cn.deleted)
167                 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = cn.record)
168           GROUP BY 1,2,6;
169
170         IF NOT FOUND THEN
171             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
172         END IF;
173
174     END LOOP;
175
176     RETURN;
177 END;
178 $f$ LANGUAGE PLPGSQL;
179
180 CREATE OR REPLACE FUNCTION unapi.mmr_mra (
181     obj_id BIGINT,
182     format TEXT,
183     ename TEXT,
184     includes TEXT[],
185     org TEXT,
186     depth INT DEFAULT NULL,
187     slimit HSTORE DEFAULT NULL,
188     soffset HSTORE DEFAULT NULL,
189     include_xmlns BOOL DEFAULT TRUE,
190     pref_lib INT DEFAULT NULL
191 ) RETURNS XML AS $F$
192     SELECT  XMLELEMENT(
193         name attributes,
194         XMLATTRIBUTES(
195             CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
196             'tag:open-ils.org:U2@mmr/' || $1 AS metarecord
197         ),
198         (SELECT XMLAGG(foo.y)
199           FROM (
200             SELECT  DISTINCT ON (COALESCE(cvm.id,uvm.id))
201                     COALESCE(cvm.id,uvm.id),
202                     XMLELEMENT(
203                         name field,
204                         XMLATTRIBUTES(
205                             mra.attr AS name,
206                             cvm.value AS "coded-value",
207                             cvm.id AS "cvmid",
208                             rad.composite,
209                             rad.multi,
210                             rad.filter,
211                             rad.sorter
212                         ),
213                         mra.value
214                     )
215               FROM  metabib.record_attr_flat mra
216                     JOIN config.record_attr_definition rad ON (mra.attr = rad.name)
217                     LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = mra.attr AND code = mra.value)
218                     LEFT JOIN metabib.uncontrolled_record_attr_value uvm ON (uvm.attr = mra.attr AND uvm.value = mra.value)
219               WHERE mra.id IN (
220                     WITH aou AS (SELECT COALESCE(id, (evergreen.org_top()).id) AS id 
221                         FROM actor.org_unit WHERE shortname = $5 LIMIT 1)
222                     SELECT source 
223                     FROM metabib.metarecord_source_map, aou
224                     WHERE metarecord = $1 AND (
225                         EXISTS (
226                             SELECT 1 FROM asset.opac_visible_copies 
227                             WHERE record = source AND circ_lib IN (
228                                 SELECT id FROM actor.org_unit_descendants(aou.id, $6)) 
229                             LIMIT 1
230                         )
231                         OR EXISTS (SELECT 1 FROM located_uris(source, aou.id, $10) LIMIT 1)
232                     )
233                 )
234               ORDER BY 1
235             )foo(id,y)
236         )
237     )
238 $F$ LANGUAGE SQL STABLE;
239
240 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
241     bibid BIGINT[],
242     ouid INT,
243     depth INT DEFAULT NULL,
244     slimit HSTORE DEFAULT NULL,
245     soffset HSTORE DEFAULT NULL,
246     pref_lib INT DEFAULT NULL,
247     includes TEXT[] DEFAULT NULL::TEXT[]
248 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
249     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
250         SELECT acn.id, aou.name, acn.label_sortkey,
251             evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
252             RANK() OVER w
253         FROM asset.call_number acn
254             JOIN asset.copy acp ON (acn.id = acp.call_number)
255             JOIN actor.org_unit_descendants( $2, COALESCE(
256                 $3, (
257                     SELECT depth
258                     FROM actor.org_unit_type aout
259                         INNER JOIN actor.org_unit ou ON ou_type = aout.id
260                     WHERE ou.id = $2
261                 ), $6)
262             ) AS aou ON (acp.circ_lib = aou.id)
263         WHERE acn.record = ANY ($1)
264             AND acn.deleted IS FALSE
265             AND acp.deleted IS FALSE
266             AND CASE WHEN ('exclude_invisible_acn' = ANY($7)) THEN
267                 EXISTS (
268                     SELECT 1
269                     FROM asset.opac_visible_copies
270                     WHERE copy_id = acp.id AND record = acn.record
271                 ) ELSE TRUE END
272         GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
273         WINDOW w AS (
274             ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
275         )
276     ) AS ua
277     GROUP BY ua.id, ua.name, ua.label_sortkey
278     ORDER BY rank, ua.name, ua.label_sortkey
279     LIMIT ($4 -> 'acn')::INT
280     OFFSET ($5 -> 'acn')::INT;
281 $$
282 LANGUAGE SQL STABLE;
283
284 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes
285     ( 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[] )
286     RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT)
287     AS $$ SELECT * FROM evergreen.ranked_volumes(ARRAY[$1],$2,$3,$4,$5,$6,$7) $$ LANGUAGE SQL STABLE;
288
289
290 CREATE OR REPLACE FUNCTION evergreen.located_uris (
291     bibid BIGINT[],
292     ouid INT,
293     pref_lib INT DEFAULT NULL
294 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
295     WITH all_orgs AS (SELECT COALESCE( enabled, FALSE ) AS flag FROM config.global_flag WHERE name = 'opac.located_uri.act_as_copy')
296     SELECT DISTINCT ON (id) * FROM (
297     SELECT acn.id, COALESCE(aou.name,aoud.name), acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
298       FROM asset.call_number acn
299            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number
300            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
301            LEFT JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
302            LEFT JOIN actor.org_unit_descendants( COALESCE($3, $2) ) aoud ON (acn.owning_lib = aoud.id),
303            all_orgs
304       WHERE acn.record = ANY ($1)
305           AND acn.deleted IS FALSE
306           AND auri.active IS TRUE
307           AND ((NOT all_orgs.flag AND aou.id IS NOT NULL) OR COALESCE(aou.id,aoud.id) IS NOT NULL)
308     UNION
309     SELECT acn.id, COALESCE(aou.name,aoud.name) AS name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
310       FROM asset.call_number acn
311            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number
312            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
313            LEFT JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
314            LEFT JOIN actor.org_unit_descendants( $2 ) aoud ON (acn.owning_lib = aoud.id),
315            all_orgs
316       WHERE acn.record = ANY ($1)
317           AND acn.deleted IS FALSE
318           AND auri.active IS TRUE
319           AND ((NOT all_orgs.flag AND aou.id IS NOT NULL) OR COALESCE(aou.id,aoud.id) IS NOT NULL))x
320     ORDER BY id, pref_ou DESC;
321 $$
322 LANGUAGE SQL STABLE;
323
324 CREATE OR REPLACE FUNCTION evergreen.located_uris ( bibid BIGINT, ouid INT, pref_lib INT DEFAULT NULL)
325     RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT)
326     AS $$ SELECT * FROM evergreen.located_uris(ARRAY[$1],$2,$3) $$ LANGUAGE SQL STABLE;
327
328
329 CREATE OR REPLACE FUNCTION unapi.mmr_holdings_xml (
330     mid BIGINT,
331     ouid INT,
332     org TEXT,
333     depth INT DEFAULT NULL,
334     includes TEXT[] DEFAULT NULL::TEXT[],
335     slimit HSTORE DEFAULT NULL,
336     soffset HSTORE DEFAULT NULL,
337     include_xmlns BOOL DEFAULT TRUE,
338     pref_lib INT DEFAULT NULL
339 )
340 RETURNS XML AS $F$
341      SELECT  XMLELEMENT(
342                  name holdings,
343                  XMLATTRIBUTES(
344                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
345                     CASE WHEN ('mmr' = ANY ($5)) THEN 'tag:open-ils.org:U2@mmr/' || $1 || '/' || $3 ELSE NULL END AS id,
346                     (SELECT metarecord_has_holdable_copy FROM asset.metarecord_has_holdable_copy($1)) AS has_holdable
347                  ),
348                  XMLELEMENT(
349                      name counts,
350                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
351                          SELECT  XMLELEMENT(
352                                      name count,
353                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
354                                  )::text
355                            FROM  asset.opac_ou_metarecord_copy_count($2,  $1)
356                                      UNION
357                          SELECT  XMLELEMENT(
358                                      name count,
359                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
360                                  )::text
361                            FROM  asset.staff_ou_metarecord_copy_count($2, $1)
362                                      UNION
363                          SELECT  XMLELEMENT(
364                                      name count,
365                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
366                                  )::text
367                            FROM  asset.opac_ou_metarecord_copy_count($9,  $1)
368                                      ORDER BY 1
369                      )x)
370                  ),
371                  -- XXX monograph_parts and foreign_copies are skipped in MRs ... put them back some day?
372                  XMLELEMENT(
373                      name volumes,
374                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
375                         -- Physical copies
376                         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
377                         FROM evergreen.ranked_volumes((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $4, $6, $7, $9, $5) AS y
378                         UNION ALL
379                         -- Located URIs
380                         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
381                         FROM evergreen.located_uris((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $9) AS uris
382                      )x)
383                  ),
384                  CASE WHEN ('ssub' = ANY ($5)) THEN
385                      XMLELEMENT(
386                          name subscriptions,
387                          (SELECT XMLAGG(ssub) FROM (
388                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
389                               FROM  serial.subscription
390                               WHERE record_entry IN (SELECT source FROM metabib.metarecord_source_map WHERE metarecord = $1)
391                         )x)
392                      )
393                  ELSE NULL END
394              );
395 $F$ LANGUAGE SQL STABLE;
396
397 CREATE OR REPLACE FUNCTION unapi.mmr (
398     obj_id BIGINT,
399     format TEXT,
400     ename TEXT,
401     includes TEXT[],
402     org TEXT,
403     depth INT DEFAULT NULL,
404     slimit HSTORE DEFAULT NULL,
405     soffset HSTORE DEFAULT NULL,
406     include_xmlns BOOL DEFAULT TRUE,
407     pref_lib INT DEFAULT NULL
408 )
409 RETURNS XML AS $F$
410 DECLARE
411     mmrec   metabib.metarecord%ROWTYPE;
412     leadrec biblio.record_entry%ROWTYPE;
413     subrec biblio.record_entry%ROWTYPE;
414     layout  unapi.bre_output_layout%ROWTYPE;
415     xfrm    config.xml_transform%ROWTYPE;
416     ouid    INT;
417     xml_buf TEXT; -- growing XML document
418     tmp_xml TEXT; -- single-use XML string
419     xml_frag TEXT; -- single-use XML fragment
420     top_el  TEXT;
421     output  XML;
422     hxml    XML;
423     axml    XML;
424     subxml  XML; -- subordinate records elements
425     sub_xpath TEXT; 
426     parts   TEXT[]; 
427 BEGIN
428
429     -- xpath for extracting bre.marc values from subordinate records 
430     -- so they may be appended to the MARC of the master record prior
431     -- to XSLT processing.
432     -- subjects, isbn, issn, upc -- anything else?
433     sub_xpath := 
434       '//*[starts-with(@tag, "6") or @tag="020" or @tag="022" or @tag="024"]';
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     SELECT INTO mmrec * FROM metabib.metarecord WHERE id = obj_id;
447     IF NOT FOUND THEN
448         RETURN NULL::XML;
449     END IF;
450
451     -- TODO: aggregate holdings from constituent records
452     IF format = 'holdings_xml' THEN -- the special case
453         output := unapi.mmr_holdings_xml(
454             obj_id, ouid, org, depth,
455             evergreen.array_remove_item_by_value(includes,'holdings_xml'),
456             slimit, soffset, include_xmlns);
457         RETURN output;
458     END IF;
459
460     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
461
462     IF layout.name IS NULL THEN
463         RETURN NULL::XML;
464     END IF;
465
466     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
467
468     SELECT INTO leadrec * FROM biblio.record_entry WHERE id = mmrec.master_record;
469
470     -- Grab distinct MVF for all records if requested
471     IF ('mra' = ANY (includes)) THEN 
472         axml := unapi.mmr_mra(obj_id,NULL,NULL,NULL,org,depth,NULL,NULL,TRUE,pref_lib);
473     ELSE
474         axml := NULL::XML;
475     END IF;
476
477     xml_buf = leadrec.marc;
478
479     hxml := NULL::XML;
480     IF ('holdings_xml' = ANY (includes)) THEN
481         hxml := unapi.mmr_holdings_xml(
482                     obj_id, ouid, org, depth,
483                     evergreen.array_remove_item_by_value(includes,'holdings_xml'),
484                     slimit, soffset, include_xmlns, pref_lib);
485     END IF;
486
487     subxml := NULL::XML;
488     parts := '{}'::TEXT[];
489     FOR subrec IN SELECT bre.* FROM biblio.record_entry bre
490          JOIN metabib.metarecord_source_map mmsm ON (mmsm.source = bre.id)
491          JOIN metabib.metarecord mmr ON (mmr.id = mmsm.metarecord)
492          WHERE mmr.id = obj_id
493          ORDER BY CASE WHEN bre.id = mmr.master_record THEN 0 ELSE bre.id END
494          LIMIT COALESCE((slimit->'bre')::INT, 5) LOOP
495
496         IF subrec.id = leadrec.id THEN CONTINUE; END IF;
497         -- Append choice data from the the non-lead records to the 
498         -- the lead record document
499
500         parts := parts || xpath(sub_xpath, subrec.marc::XML)::TEXT[];
501     END LOOP;
502
503     SELECT ARRAY_TO_STRING( ARRAY_AGG( DISTINCT p ), '' )::XML INTO subxml FROM UNNEST(parts) p;
504
505     -- append data from the subordinate records to the 
506     -- main record document before applying the XSLT
507
508     IF subxml IS NOT NULL THEN 
509         xml_buf := REGEXP_REPLACE(xml_buf, 
510             '</record>(.*?)$', subxml || '</record>' || E'\\1');
511     END IF;
512
513     IF format = 'marcxml' THEN
514          -- If we're not using the prefixed namespace in 
515          -- this record, then remove all declarations of it
516         IF xml_buf !~ E'<marc:' THEN
517            xml_buf := REGEXP_REPLACE(xml_buf, 
518             ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
519         END IF; 
520     ELSE
521         xml_buf := oils_xslt_process(xml_buf, xfrm.xslt)::XML;
522     END IF;
523
524     -- update top_el to reflect the change in xml_buf, which may
525     -- now be a different type of document (e.g. record -> mods)
526     top_el := REGEXP_REPLACE(xml_buf, E'^.*?<((?:\\S+:)?' || 
527         layout.holdings_element || ').*$', E'\\1');
528
529     IF axml IS NOT NULL THEN 
530         xml_buf := REGEXP_REPLACE(xml_buf, 
531             '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
532     END IF;
533
534     IF hxml IS NOT NULL THEN
535         xml_buf := REGEXP_REPLACE(xml_buf, 
536             '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
537     END IF;
538
539     IF ('mmr.unapi' = ANY (includes)) THEN 
540         output := REGEXP_REPLACE(
541             xml_buf,
542             '</' || top_el || '>(.*?)',
543             XMLELEMENT(
544                 name abbr,
545                 XMLATTRIBUTES(
546                     'http://www.w3.org/1999/xhtml' AS xmlns,
547                     'unapi-id' AS class,
548                     'tag:open-ils.org:U2@mmr/' || obj_id || '/' || org AS title
549                 )
550             )::TEXT || '</' || top_el || E'>\\1'
551         );
552     ELSE
553         output := xml_buf;
554     END IF;
555
556     -- remove ignorable whitesace
557     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
558     RETURN output;
559 END;
560 $F$ LANGUAGE PLPGSQL STABLE;
561
562
563
564 COMMIT;
565