]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/XXXX.function.builtin_array_remove.sql
LP#1778955: Remove our custom version of array_remove(anyarray,anyelement)
[working/Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / XXXX.function.builtin_array_remove.sql
1 BEGIN;
2
3 SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
4
5 CREATE OR REPLACE FUNCTION unapi.bre (
6     obj_id BIGINT,
7     format TEXT,
8     ename TEXT,
9     includes TEXT[],
10     org TEXT,
11     depth INT DEFAULT NULL,
12     slimit HSTORE DEFAULT NULL,
13     soffset HSTORE DEFAULT NULL,
14     include_xmlns BOOL DEFAULT TRUE,
15     pref_lib INT DEFAULT NULL
16 )
17 RETURNS XML AS $F$
18 DECLARE
19     me      biblio.record_entry%ROWTYPE;
20     layout  unapi.bre_output_layout%ROWTYPE;
21     xfrm    config.xml_transform%ROWTYPE;
22     ouid    INT;
23     tmp_xml TEXT;
24     top_el  TEXT;
25     output  XML;
26     hxml    XML;
27     axml    XML;
28     source  XML;
29 BEGIN
30
31     IF org = '-' OR org IS NULL THEN
32         SELECT shortname INTO org FROM evergreen.org_top();
33     END IF;
34
35     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
36
37     IF ouid IS NULL THEN
38         RETURN NULL::XML;
39     END IF;
40
41     IF format = 'holdings_xml' THEN -- the special case
42         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
43         RETURN output;
44     END IF;
45
46     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
47
48     IF layout.name IS NULL THEN
49         RETURN NULL::XML;
50     END IF;
51
52     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
53
54     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
55
56     -- grab bib_source, if any
57     IF ('cbs' = ANY (includes) AND me.source IS NOT NULL) THEN
58         source := unapi.cbs(me.source,NULL,NULL,NULL,NULL);
59     ELSE
60         source := NULL::XML;
61     END IF;
62
63     -- grab SVF if we need them
64     IF ('mra' = ANY (includes)) THEN 
65         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
66     ELSE
67         axml := NULL::XML;
68     END IF;
69
70     -- grab holdings if we need them
71     IF ('holdings_xml' = ANY (includes)) THEN 
72         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, array_remove(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
73     ELSE
74         hxml := NULL::XML;
75     END IF;
76
77
78     -- generate our item node
79
80
81     IF format = 'marcxml' THEN
82         tmp_xml := me.marc;
83         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
84            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
85         END IF; 
86     ELSE
87         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
88     END IF;
89
90     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
91
92     IF source IS NOT NULL THEN
93         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', source || '</' || top_el || E'>\\1');
94     END IF;
95
96     IF axml IS NOT NULL THEN 
97         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
98     END IF;
99
100     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
101         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
102     END IF;
103
104     IF ('bre.unapi' = ANY (includes)) THEN 
105         output := REGEXP_REPLACE(
106             tmp_xml,
107             '</' || top_el || '>(.*?)',
108             XMLELEMENT(
109                 name abbr,
110                 XMLATTRIBUTES(
111                     'http://www.w3.org/1999/xhtml' AS xmlns,
112                     'unapi-id' AS class,
113                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
114                 )
115             )::TEXT || '</' || top_el || E'>\\1'
116         );
117     ELSE
118         output := tmp_xml;
119     END IF;
120
121     IF ('bre.extern' = ANY (includes)) THEN 
122         output := REGEXP_REPLACE(
123             tmp_xml,
124             '</' || top_el || '>(.*?)',
125             XMLELEMENT(
126                 name extern,
127                 XMLATTRIBUTES(
128                     'http://open-ils.org/spec/biblio/v1' AS xmlns,
129                     me.creator AS creator,
130                     me.editor AS editor,
131                     me.create_date AS create_date,
132                     me.edit_date AS edit_date,
133                     me.quality AS quality,
134                     me.fingerprint AS fingerprint,
135                     me.tcn_source AS tcn_source,
136                     me.tcn_value AS tcn_value,
137                     me.owner AS owner,
138                     me.share_depth AS share_depth,
139                     me.active AS active,
140                     me.deleted AS deleted
141                 )
142             )::TEXT || '</' || top_el || E'>\\1'
143         );
144     ELSE
145         output := tmp_xml;
146     END IF;
147
148     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
149     RETURN output;
150 END;
151 $F$ LANGUAGE PLPGSQL STABLE;
152
153 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
154     bid BIGINT,
155     ouid INT,
156     org TEXT,
157     depth INT DEFAULT NULL,
158     includes TEXT[] DEFAULT NULL::TEXT[],
159     slimit HSTORE DEFAULT NULL,
160     soffset HSTORE DEFAULT NULL,
161     include_xmlns BOOL DEFAULT TRUE,
162     pref_lib INT DEFAULT NULL
163 )
164 RETURNS XML AS $F$
165      SELECT  XMLELEMENT(
166                  name holdings,
167                  XMLATTRIBUTES(
168                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
169                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id,
170                     (SELECT record_has_holdable_copy FROM asset.record_has_holdable_copy($1)) AS has_holdable
171                  ),
172                  XMLELEMENT(
173                      name counts,
174                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
175                          SELECT  XMLELEMENT(
176                                      name count,
177                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
178                                  )::text
179                            FROM  asset.opac_ou_record_copy_count($2,  $1)
180                                      UNION
181                          SELECT  XMLELEMENT(
182                                      name count,
183                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
184                                  )::text
185                            FROM  asset.staff_ou_record_copy_count($2, $1)
186                                      UNION
187                          SELECT  XMLELEMENT(
188                                      name count,
189                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
190                                  )::text
191                            FROM  asset.opac_ou_record_copy_count($9,  $1)
192                                      ORDER BY 1
193                      )x)
194                  ),
195                  CASE 
196                      WHEN ('bmp' = ANY ($5)) THEN
197                         XMLELEMENT(
198                             name monograph_parts,
199                             (SELECT XMLAGG(bmp) FROM (
200                                 SELECT  unapi.bmp( id, 'xml', 'monograph_part', array_remove( array_remove($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE)
201                                   FROM  biblio.monograph_part
202                                   WHERE NOT deleted AND record = $1
203                             )x)
204                         )
205                      ELSE NULL
206                  END,
207                  XMLELEMENT(
208                      name volumes,
209                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
210                         -- Physical copies
211                         SELECT  unapi.acn(y.id,'xml','volume',array_remove( array_remove($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
212                         FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9, $5) AS y
213                         UNION ALL
214                         -- Located URIs
215                         SELECT unapi.acn(uris.id,'xml','volume',array_remove( array_remove($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), uris.rank, name, label_sortkey
216                         FROM evergreen.located_uris($1, $2, $9) AS uris
217                      )x)
218                  ),
219                  CASE WHEN ('ssub' = ANY ($5)) THEN 
220                      XMLELEMENT(
221                          name subscriptions,
222                          (SELECT XMLAGG(ssub) FROM (
223                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
224                               FROM  serial.subscription
225                               WHERE record_entry = $1
226                         )x)
227                      )
228                  ELSE NULL END,
229                  CASE WHEN ('acp' = ANY ($5)) THEN 
230                      XMLELEMENT(
231                          name foreign_copies,
232                          (SELECT XMLAGG(acp) FROM (
233                             SELECT  unapi.acp(p.target_copy,'xml','copy',array_remove($5,'acp'), $3, $4, $6, $7, FALSE)
234                               FROM  biblio.peer_bib_copy_map p
235                                     JOIN asset.copy c ON (p.target_copy = c.id)
236                               WHERE NOT c.deleted AND p.peer_record = $1
237                             LIMIT ($6 -> 'acp')::INT
238                             OFFSET ($7 -> 'acp')::INT
239                         )x)
240                      )
241                  ELSE NULL END
242              );
243 $F$ LANGUAGE SQL STABLE;
244
245 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$
246         SELECT  XMLELEMENT(
247                     name subscription,
248                     XMLATTRIBUTES(
249                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
250                         'tag:open-ils.org:U2@ssub/' || id AS id,
251                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
252                         start_date AS start, end_date AS end, expected_date_offset
253                     ),
254                     CASE 
255                         WHEN ('sdist' = ANY ($4)) THEN
256                             XMLELEMENT( name distributions,
257                                 (SELECT XMLAGG(sdist) FROM (
258                                     SELECT  unapi.sdist( id, 'xml', 'distribution', array_remove($4,'ssub'), $5, $6, $7, $8, FALSE)
259                                       FROM  serial.distribution
260                                       WHERE subscription = ssub.id
261                                 )x)
262                             )
263                         ELSE NULL
264                     END
265                 )
266           FROM  serial.subscription ssub
267           WHERE id = $1
268           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
269 $F$ LANGUAGE SQL STABLE;
270
271 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$
272         SELECT  XMLELEMENT(
273                     name distribution,
274                     XMLATTRIBUTES(
275                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
276                         'tag:open-ils.org:U2@sdist/' || id AS id,
277                                 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
278                                     'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
279                         unit_label_prefix, label, unit_label_suffix, summary_method
280                     ),
281                     unapi.aou( holding_lib, $2, 'holding_lib', array_remove($4,'sdist'), $5, $6, $7, $8),
282                     CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) ELSE NULL END,
283                     CASE 
284                         WHEN ('sstr' = ANY ($4)) THEN
285                             XMLELEMENT( name streams,
286                                 (SELECT XMLAGG(sstr) FROM (
287                                     SELECT  unapi.sstr( id, 'xml', 'stream', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE)
288                                       FROM  serial.stream
289                                       WHERE distribution = sdist.id
290                                 )x)
291                             )
292                         ELSE NULL
293                     END,
294                     XMLELEMENT( name summaries,
295                         CASE 
296                             WHEN ('sbsum' = ANY ($4)) THEN
297                                 (SELECT XMLAGG(sbsum) FROM (
298                                     SELECT  unapi.sbsum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE)
299                                       FROM  serial.basic_summary
300                                       WHERE distribution = sdist.id
301                                 )x)
302                             ELSE NULL
303                         END,
304                         CASE 
305                             WHEN ('sisum' = ANY ($4)) THEN
306                                 (SELECT XMLAGG(sisum) FROM (
307                                     SELECT  unapi.sisum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE)
308                                       FROM  serial.index_summary
309                                       WHERE distribution = sdist.id
310                                 )x)
311                             ELSE NULL
312                         END,
313                         CASE 
314                             WHEN ('sssum' = ANY ($4)) THEN
315                                 (SELECT XMLAGG(sssum) FROM (
316                                     SELECT  unapi.sssum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE)
317                                       FROM  serial.supplement_summary
318                                       WHERE distribution = sdist.id
319                                 )x)
320                             ELSE NULL
321                         END
322                     )
323                 )
324           FROM  serial.distribution sdist
325           WHERE id = $1
326           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
327 $F$ LANGUAGE SQL STABLE;
328
329 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$
330     SELECT  XMLELEMENT(
331                 name stream,
332                 XMLATTRIBUTES(
333                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
334                     'tag:open-ils.org:U2@sstr/' || id AS id,
335                     routing_label
336                 ),
337                 CASE WHEN distribution IS NOT NULL AND ('sdist' = ANY ($4)) THEN unapi.sssum( distribution, 'xml', 'distribtion', array_remove($4,'sstr'), $5, $6, $7, $8, FALSE) ELSE NULL END,
338                 CASE 
339                     WHEN ('sitem' = ANY ($4)) THEN
340                         XMLELEMENT( name items,
341                             (SELECT XMLAGG(sitem) FROM (
342                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', array_remove($4,'sstr'), $5, $6, $7, $8, FALSE)
343                                   FROM  serial.item
344                                   WHERE stream = sstr.id
345                             )x)
346                         )
347                     ELSE NULL
348                 END
349             )
350       FROM  serial.stream sstr
351       WHERE id = $1
352       GROUP BY id, routing_label, distribution;
353 $F$ LANGUAGE SQL STABLE;
354
355 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$
356     SELECT  XMLELEMENT(
357                 name issuance,
358                 XMLATTRIBUTES(
359                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
360                     'tag:open-ils.org:U2@siss/' || id AS id,
361                     create_date, edit_date, label, date_published,
362                     holding_code, holding_type, holding_link_id
363                 ),
364                 CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', array_remove($4,'siss'), $5, $6, $7, $8, FALSE) ELSE NULL END,
365                 CASE 
366                     WHEN ('sitem' = ANY ($4)) THEN
367                         XMLELEMENT( name items,
368                             (SELECT XMLAGG(sitem) FROM (
369                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', array_remove($4,'siss'), $5, $6, $7, $8, FALSE)
370                                   FROM  serial.item
371                                   WHERE issuance = sstr.id
372                             )x)
373                         )
374                     ELSE NULL
375                 END
376             )
377       FROM  serial.issuance sstr
378       WHERE id = $1
379       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
380 $F$ LANGUAGE SQL STABLE;
381
382 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$
383         SELECT  XMLELEMENT(
384                     name serial_item,
385                     XMLATTRIBUTES(
386                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
387                         'tag:open-ils.org:U2@sitem/' || id AS id,
388                         'tag:open-ils.org:U2@siss/' || issuance AS issuance,
389                         date_expected, date_received
390                     ),
391                     CASE WHEN issuance IS NOT NULL AND ('siss' = ANY ($4)) THEN unapi.siss( issuance, $2, 'issuance', array_remove($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
392                     CASE WHEN stream IS NOT NULL AND ('sstr' = ANY ($4)) THEN unapi.sstr( stream, $2, 'stream', array_remove($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
393                     CASE WHEN unit IS NOT NULL AND ('sunit' = ANY ($4)) THEN unapi.sunit( unit, $2, 'serial_unit', array_remove($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
394                     CASE WHEN uri IS NOT NULL AND ('auri' = ANY ($4)) THEN unapi.auri( uri, $2, 'uri', array_remove($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END
395 --                    XMLELEMENT( name notes,
396 --                        CASE 
397 --                            WHEN ('acpn' = ANY ($4)) THEN
398 --                                (SELECT XMLAGG(acpn) FROM (
399 --                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', array_remove($4,'acp'), $5, $6, $7, $8)
400 --                                      FROM  asset.copy_note
401 --                                      WHERE owning_copy = cp.id AND pub
402 --                                )x)
403 --                            ELSE NULL
404 --                        END
405 --                    )
406                 )
407           FROM  serial.item sitem
408           WHERE id = $1;
409 $F$ LANGUAGE SQL STABLE;
410
411
412 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$
413     SELECT  XMLELEMENT(
414                 name serial_summary,
415                 XMLATTRIBUTES(
416                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
417                     'tag:open-ils.org:U2@sbsum/' || id AS id,
418                     'sssum' AS type, generated_coverage, textual_holdings, show_generated
419                 ),
420                 CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
421             )
422       FROM  serial.supplement_summary ssum
423       WHERE id = $1
424       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
425 $F$ LANGUAGE SQL STABLE;
426
427 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$
428     SELECT  XMLELEMENT(
429                 name serial_summary,
430                 XMLATTRIBUTES(
431                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
432                     'tag:open-ils.org:U2@sbsum/' || id AS id,
433                     'sbsum' AS type, generated_coverage, textual_holdings, show_generated
434                 ),
435                 CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
436             )
437       FROM  serial.basic_summary ssum
438       WHERE id = $1
439       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
440 $F$ LANGUAGE SQL STABLE;
441
442 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$
443     SELECT  XMLELEMENT(
444                 name serial_summary,
445                 XMLATTRIBUTES(
446                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
447                     'tag:open-ils.org:U2@sbsum/' || id AS id,
448                     'sisum' AS type, generated_coverage, textual_holdings, show_generated
449                 ),
450                 CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
451             )
452       FROM  serial.index_summary ssum
453       WHERE id = $1
454       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
455 $F$ LANGUAGE SQL STABLE;
456
457 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$
458         SELECT  XMLELEMENT(
459                     name monograph_part,
460                     XMLATTRIBUTES(
461                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
462                         'tag:open-ils.org:U2@bmp/' || id AS id,
463                         id AS ident,
464                         label,
465                         label_sortkey,
466                         'tag:open-ils.org:U2@bre/' || record AS record
467                     ),
468                     CASE 
469                         WHEN ('acp' = ANY ($4)) THEN
470                             XMLELEMENT( name copies,
471                                 (SELECT XMLAGG(acp) FROM (
472                                     SELECT  unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'bmp'), $5, $6, $7, $8, FALSE)
473                                       FROM  asset.copy cp
474                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
475                                       WHERE cpm.part = $1
476                                           AND cp.deleted IS FALSE
477                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
478                                       LIMIT ($7 -> 'acp')::INT
479                                       OFFSET ($8 -> 'acp')::INT
480
481                                 )x)
482                             )
483                         ELSE NULL
484                     END,
485                     CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', array_remove($4,'bmp'), $5, $6, $7, $8, FALSE) ELSE NULL END
486                 )
487           FROM  biblio.monograph_part
488           WHERE NOT deleted AND id = $1
489           GROUP BY id, label, label_sortkey, record;
490 $F$ LANGUAGE SQL STABLE;
491
492 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$
493         SELECT  XMLELEMENT(
494                     name copy,
495                     XMLATTRIBUTES(
496                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
497                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
498                         create_date, edit_date, copy_number, circulate, deposit,
499                         ref, holdable, deleted, deposit_amount, price, barcode,
500                         circ_modifier, circ_as_type, opac_visible, age_protect
501                     ),
502                     unapi.ccs( status, $2, 'status', array_remove($4,'acp'), $5, $6, $7, $8, FALSE),
503                     unapi.acl( location, $2, 'location', array_remove($4,'acp'), $5, $6, $7, $8, FALSE),
504                     unapi.aou( circ_lib, $2, 'circ_lib', array_remove($4,'acp'), $5, $6, $7, $8),
505                     unapi.aou( circ_lib, $2, 'circlib', array_remove($4,'acp'), $5, $6, $7, $8),
506                     CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
507                     CASE 
508                         WHEN ('acpn' = ANY ($4)) THEN
509                             XMLELEMENT( name copy_notes,
510                                 (SELECT XMLAGG(acpn) FROM (
511                                     SELECT  unapi.acpn( id, 'xml', 'copy_note', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
512                                       FROM  asset.copy_note
513                                       WHERE owning_copy = cp.id AND pub
514                                 )x)
515                             )
516                         ELSE NULL
517                     END,
518                     CASE 
519                         WHEN ('ascecm' = ANY ($4)) THEN
520                             XMLELEMENT( name statcats,
521                                 (SELECT XMLAGG(ascecm) FROM (
522                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
523                                       FROM  asset.stat_cat_entry_copy_map
524                                       WHERE owning_copy = cp.id
525                                 )x)
526                             )
527                         ELSE NULL
528                     END,
529                     CASE
530                         WHEN ('bre' = ANY ($4)) THEN
531                             XMLELEMENT( name foreign_records,
532                                 (SELECT XMLAGG(bre) FROM (
533                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
534                                       FROM  biblio.peer_bib_copy_map
535                                       WHERE target_copy = cp.id
536                                 )x)
537
538                             )
539                         ELSE NULL
540                     END,
541                     CASE 
542                         WHEN ('bmp' = ANY ($4)) THEN
543                             XMLELEMENT( name monograph_parts,
544                                 (SELECT XMLAGG(bmp) FROM (
545                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
546                                       FROM  asset.copy_part_map
547                                       WHERE target_copy = cp.id
548                                 )x)
549                             )
550                         ELSE NULL
551                     END,
552                     CASE 
553                         WHEN ('circ' = ANY ($4)) THEN
554                             XMLELEMENT( name current_circulation,
555                                 (SELECT XMLAGG(circ) FROM (
556                                     SELECT  unapi.circ( id, 'xml', 'circ', array_remove($4,'circ'), $5, $6, $7, $8, FALSE)
557                                       FROM  action.circulation
558                                       WHERE target_copy = cp.id
559                                             AND checkin_time IS NULL
560                                 )x)
561                             )
562                         ELSE NULL
563                     END
564                 )
565           FROM  asset.copy cp
566           WHERE id = $1
567               AND cp.deleted IS FALSE
568           GROUP BY id, status, location, circ_lib, call_number, create_date,
569               edit_date, copy_number, circulate, deposit, ref, holdable,
570               deleted, deposit_amount, price, barcode, circ_modifier,
571               circ_as_type, opac_visible, age_protect;
572 $F$ LANGUAGE SQL STABLE;
573
574 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$
575         SELECT  XMLELEMENT(
576                     name serial_unit,
577                     XMLATTRIBUTES(
578                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
579                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
580                         create_date, edit_date, copy_number, circulate, deposit,
581                         ref, holdable, deleted, deposit_amount, price, barcode,
582                         circ_modifier, circ_as_type, opac_visible, age_protect,
583                         status_changed_time, floating, mint_condition,
584                         detailed_contents, sort_key, summary_contents, cost 
585                     ),
586                     unapi.ccs( status, $2, 'status', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
587                     unapi.acl( location, $2, 'location', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
588                     unapi.aou( circ_lib, $2, 'circ_lib', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8),
589                     unapi.aou( circ_lib, $2, 'circlib', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8),
590                     CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
591                     XMLELEMENT( name copy_notes,
592                         CASE 
593                             WHEN ('acpn' = ANY ($4)) THEN
594                                 (SELECT XMLAGG(acpn) FROM (
595                                     SELECT  unapi.acpn( id, 'xml', 'copy_note', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE)
596                                       FROM  asset.copy_note
597                                       WHERE owning_copy = cp.id AND pub
598                                 )x)
599                             ELSE NULL
600                         END
601                     ),
602                     XMLELEMENT( name statcats,
603                         CASE 
604                             WHEN ('ascecm' = ANY ($4)) THEN
605                                 (SELECT XMLAGG(ascecm) FROM (
606                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
607                                       FROM  asset.stat_cat_entry_copy_map
608                                       WHERE owning_copy = cp.id
609                                 )x)
610                             ELSE NULL
611                         END
612                     ),
613                     XMLELEMENT( name foreign_records,
614                         CASE
615                             WHEN ('bre' = ANY ($4)) THEN
616                                 (SELECT XMLAGG(bre) FROM (
617                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
618                                       FROM  biblio.peer_bib_copy_map
619                                       WHERE target_copy = cp.id
620                                 )x)
621                             ELSE NULL
622                         END
623                     ),
624                     CASE 
625                         WHEN ('bmp' = ANY ($4)) THEN
626                             XMLELEMENT( name monograph_parts,
627                                 (SELECT XMLAGG(bmp) FROM (
628                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
629                                       FROM  asset.copy_part_map
630                                       WHERE target_copy = cp.id
631                                 )x)
632                             )
633                         ELSE NULL
634                     END,
635                     CASE 
636                         WHEN ('circ' = ANY ($4)) THEN
637                             XMLELEMENT( name current_circulation,
638                                 (SELECT XMLAGG(circ) FROM (
639                                     SELECT  unapi.circ( id, 'xml', 'circ', array_remove($4,'circ'), $5, $6, $7, $8, FALSE)
640                                       FROM  action.circulation
641                                       WHERE target_copy = cp.id
642                                             AND checkin_time IS NULL
643                                 )x)
644                             )
645                         ELSE NULL
646                     END
647                 )
648           FROM  serial.unit cp
649           WHERE id = $1
650               AND cp.deleted IS FALSE
651           GROUP BY id, status, location, circ_lib, call_number, create_date,
652               edit_date, copy_number, circulate, floating, mint_condition,
653               deposit, ref, holdable, deleted, deposit_amount, price,
654               barcode, circ_modifier, circ_as_type, opac_visible,
655               status_changed_time, detailed_contents, sort_key,
656               summary_contents, cost, age_protect;
657 $F$ LANGUAGE SQL STABLE;
658
659 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$
660         SELECT  XMLELEMENT(
661                     name volume,
662                     XMLATTRIBUTES(
663                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
664                         'tag:open-ils.org:U2@acn/' || acn.id AS id,
665                         acn.id AS vol_id, o.shortname AS lib,
666                         o.opac_visible AS opac_visible,
667                         deleted, label, label_sortkey, label_class, record
668                     ),
669                     unapi.aou( owning_lib, $2, 'owning_lib', array_remove($4,'acn'), $5, $6, $7, $8),
670                     CASE 
671                         WHEN ('acp' = ANY ($4)) THEN
672                             CASE WHEN $6 IS NOT NULL THEN
673                                 XMLELEMENT( name copies,
674                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
675                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'acn'), $5, $6, $7, $8, FALSE),
676                                             evergreen.rank_cp(cp) AS rank_avail
677                                           FROM  asset.copy cp
678                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
679                                           WHERE cp.call_number = acn.id
680                                               AND cp.deleted IS FALSE
681                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
682                                           LIMIT ($7 -> 'acp')::INT
683                                           OFFSET ($8 -> 'acp')::INT
684                                     )x)
685                                 )
686                             ELSE
687                                 XMLELEMENT( name copies,
688                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
689                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'acn'), $5, $6, $7, $8, FALSE),
690                                             evergreen.rank_cp(cp) AS rank_avail
691                                           FROM  asset.copy cp
692                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
693                                           WHERE cp.call_number = acn.id
694                                               AND cp.deleted IS FALSE
695                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
696                                           LIMIT ($7 -> 'acp')::INT
697                                           OFFSET ($8 -> 'acp')::INT
698                                     )x)
699                                 )
700                             END
701                         ELSE NULL
702                     END,
703                     XMLELEMENT(
704                         name uris,
705                         (SELECT XMLAGG(auri) FROM (SELECT unapi.auri(uri,'xml','uri', array_remove($4,'acn'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE call_number = acn.id)x)
706                     ),
707                     unapi.acnp( acn.prefix, 'marcxml', 'prefix', array_remove($4,'acn'), $5, $6, $7, $8, FALSE),
708                     unapi.acns( acn.suffix, 'marcxml', 'suffix', array_remove($4,'acn'), $5, $6, $7, $8, FALSE),
709                     CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', array_remove($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END
710                 ) AS x
711           FROM  asset.call_number acn
712                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
713           WHERE acn.id = $1
714               AND acn.deleted IS FALSE
715           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
716 $F$ LANGUAGE SQL STABLE;
717
718 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$
719         SELECT  XMLELEMENT(
720                     name uri,
721                     XMLATTRIBUTES(
722                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
723                         'tag:open-ils.org:U2@auri/' || uri.id AS id,
724                         use_restriction,
725                         href,
726                         label
727                     ),
728                     CASE 
729                         WHEN ('acn' = ANY ($4)) THEN
730                             XMLELEMENT( name copies,
731                                 (SELECT XMLAGG(acn) FROM (SELECT unapi.acn( call_number, 'xml', 'copy', array_remove($4,'auri'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE uri = uri.id)x)
732                             )
733                         ELSE NULL
734                     END
735                 ) AS x
736           FROM  asset.uri uri
737           WHERE uri.id = $1
738           GROUP BY uri.id, use_restriction, href, label;
739 $F$ LANGUAGE SQL STABLE;
740
741 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$
742     SELECT XMLELEMENT(
743         name circ,
744         XMLATTRIBUTES(
745             CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
746             'tag:open-ils.org:U2@circ/' || id AS id,
747             xact_start,
748             due_date
749         ),
750         CASE WHEN ('aou' = ANY ($4)) THEN unapi.aou( circ_lib, $2, 'circ_lib', array_remove($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END,
751         CASE WHEN ('acp' = ANY ($4)) THEN unapi.acp( circ_lib, $2, 'target_copy', array_remove($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END
752     )
753     FROM action.circulation
754     WHERE id = $1;
755 $F$ LANGUAGE SQL STABLE;
756
757 CREATE OR REPLACE FUNCTION unapi.mmr_holdings_xml (
758     mid BIGINT,
759     ouid INT,
760     org TEXT,
761     depth INT DEFAULT NULL,
762     includes TEXT[] DEFAULT NULL::TEXT[],
763     slimit HSTORE DEFAULT NULL,
764     soffset HSTORE DEFAULT NULL,
765     include_xmlns BOOL DEFAULT TRUE,
766     pref_lib INT DEFAULT NULL
767 )
768 RETURNS XML AS $F$
769      SELECT  XMLELEMENT(
770                  name holdings,
771                  XMLATTRIBUTES(
772                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
773                     CASE WHEN ('mmr' = ANY ($5)) THEN 'tag:open-ils.org:U2@mmr/' || $1 || '/' || $3 ELSE NULL END AS id,
774                     (SELECT metarecord_has_holdable_copy FROM asset.metarecord_has_holdable_copy($1)) AS has_holdable
775                  ),
776                  XMLELEMENT(
777                      name counts,
778                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
779                          SELECT  XMLELEMENT(
780                                      name count,
781                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
782                                  )::text
783                            FROM  asset.opac_ou_metarecord_copy_count($2,  $1)
784                                      UNION
785                          SELECT  XMLELEMENT(
786                                      name count,
787                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
788                                  )::text
789                            FROM  asset.staff_ou_metarecord_copy_count($2, $1)
790                                      UNION
791                          SELECT  XMLELEMENT(
792                                      name count,
793                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
794                                  )::text
795                            FROM  asset.opac_ou_metarecord_copy_count($9,  $1)
796                                      ORDER BY 1
797                      )x)
798                  ),
799                  -- XXX monograph_parts and foreign_copies are skipped in MRs ... put them back some day?
800                  XMLELEMENT(
801                      name volumes,
802                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
803                         -- Physical copies
804                         SELECT  unapi.acn(y.id,'xml','volume',array_remove( array_remove($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
805                         FROM evergreen.ranked_volumes((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $4, $6, $7, $9, $5) AS y
806                         UNION ALL
807                         -- Located URIs
808                         SELECT unapi.acn(uris.id,'xml','volume',array_remove( array_remove($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), uris.rank, name, label_sortkey
809                         FROM evergreen.located_uris((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $9) AS uris
810                      )x)
811                  ),
812                  CASE WHEN ('ssub' = ANY ($5)) THEN
813                      XMLELEMENT(
814                          name subscriptions,
815                          (SELECT XMLAGG(ssub) FROM (
816                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
817                               FROM  serial.subscription
818                               WHERE record_entry IN (SELECT source FROM metabib.metarecord_source_map WHERE metarecord = $1)
819                         )x)
820                      )
821                  ELSE NULL END
822              );
823 $F$ LANGUAGE SQL STABLE;
824
825 CREATE OR REPLACE FUNCTION unapi.mmr (
826     obj_id BIGINT,
827     format TEXT,
828     ename TEXT,
829     includes TEXT[],
830     org TEXT,
831     depth INT DEFAULT NULL,
832     slimit HSTORE DEFAULT NULL,
833     soffset HSTORE DEFAULT NULL,
834     include_xmlns BOOL DEFAULT TRUE,
835     pref_lib INT DEFAULT NULL
836 )
837 RETURNS XML AS $F$
838 DECLARE
839     mmrec   metabib.metarecord%ROWTYPE;
840     leadrec biblio.record_entry%ROWTYPE;
841     subrec biblio.record_entry%ROWTYPE;
842     layout  unapi.bre_output_layout%ROWTYPE;
843     xfrm    config.xml_transform%ROWTYPE;
844     ouid    INT;
845     xml_buf TEXT; -- growing XML document
846     tmp_xml TEXT; -- single-use XML string
847     xml_frag TEXT; -- single-use XML fragment
848     top_el  TEXT;
849     output  XML;
850     hxml    XML;
851     axml    XML;
852     subxml  XML; -- subordinate records elements
853     sub_xpath TEXT; 
854     parts   TEXT[]; 
855 BEGIN
856
857     -- xpath for extracting bre.marc values from subordinate records 
858     -- so they may be appended to the MARC of the master record prior
859     -- to XSLT processing.
860     -- subjects, isbn, issn, upc -- anything else?
861     sub_xpath := 
862       '//*[starts-with(@tag, "6") or @tag="020" or @tag="022" or @tag="024"]';
863
864     IF org = '-' OR org IS NULL THEN
865         SELECT shortname INTO org FROM evergreen.org_top();
866     END IF;
867
868     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
869
870     IF ouid IS NULL THEN
871         RETURN NULL::XML;
872     END IF;
873
874     SELECT INTO mmrec * FROM metabib.metarecord WHERE id = obj_id;
875     IF NOT FOUND THEN
876         RETURN NULL::XML;
877     END IF;
878
879     -- TODO: aggregate holdings from constituent records
880     IF format = 'holdings_xml' THEN -- the special case
881         output := unapi.mmr_holdings_xml(
882             obj_id, ouid, org, depth,
883             array_remove(includes,'holdings_xml'),
884             slimit, soffset, include_xmlns, pref_lib);
885         RETURN output;
886     END IF;
887
888     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
889
890     IF layout.name IS NULL THEN
891         RETURN NULL::XML;
892     END IF;
893
894     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
895
896     SELECT INTO leadrec * FROM biblio.record_entry WHERE id = mmrec.master_record;
897
898     -- Grab distinct MVF for all records if requested
899     IF ('mra' = ANY (includes)) THEN 
900         axml := unapi.mmr_mra(obj_id,NULL,NULL,NULL,org,depth,NULL,NULL,TRUE,pref_lib);
901     ELSE
902         axml := NULL::XML;
903     END IF;
904
905     xml_buf = leadrec.marc;
906
907     hxml := NULL::XML;
908     IF ('holdings_xml' = ANY (includes)) THEN
909         hxml := unapi.mmr_holdings_xml(
910                     obj_id, ouid, org, depth,
911                     array_remove(includes,'holdings_xml'),
912                     slimit, soffset, include_xmlns, pref_lib);
913     END IF;
914
915     subxml := NULL::XML;
916     parts := '{}'::TEXT[];
917     FOR subrec IN SELECT bre.* FROM biblio.record_entry bre
918          JOIN metabib.metarecord_source_map mmsm ON (mmsm.source = bre.id)
919          JOIN metabib.metarecord mmr ON (mmr.id = mmsm.metarecord)
920          WHERE mmr.id = obj_id AND NOT bre.deleted
921          ORDER BY CASE WHEN bre.id = mmr.master_record THEN 0 ELSE bre.id END
922          LIMIT COALESCE((slimit->'bre')::INT, 5) LOOP
923
924         IF subrec.id = leadrec.id THEN CONTINUE; END IF;
925         -- Append choice data from the the non-lead records to the 
926         -- the lead record document
927
928         parts := parts || xpath(sub_xpath, subrec.marc::XML)::TEXT[];
929     END LOOP;
930
931     SELECT ARRAY_TO_STRING( ARRAY_AGG( DISTINCT p ), '' )::XML INTO subxml FROM UNNEST(parts) p;
932
933     -- append data from the subordinate records to the 
934     -- main record document before applying the XSLT
935
936     IF subxml IS NOT NULL THEN 
937         xml_buf := REGEXP_REPLACE(xml_buf, 
938             '</record>(.*?)$', subxml || '</record>' || E'\\1');
939     END IF;
940
941     IF format = 'marcxml' THEN
942          -- If we're not using the prefixed namespace in 
943          -- this record, then remove all declarations of it
944         IF xml_buf !~ E'<marc:' THEN
945            xml_buf := REGEXP_REPLACE(xml_buf, 
946             ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
947         END IF; 
948     ELSE
949         xml_buf := oils_xslt_process(xml_buf, xfrm.xslt)::XML;
950     END IF;
951
952     -- update top_el to reflect the change in xml_buf, which may
953     -- now be a different type of document (e.g. record -> mods)
954     top_el := REGEXP_REPLACE(xml_buf, E'^.*?<((?:\\S+:)?' || 
955         layout.holdings_element || ').*$', E'\\1');
956
957     IF axml IS NOT NULL THEN 
958         xml_buf := REGEXP_REPLACE(xml_buf, 
959             '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
960     END IF;
961
962     IF hxml IS NOT NULL THEN
963         xml_buf := REGEXP_REPLACE(xml_buf, 
964             '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
965     END IF;
966
967     IF ('mmr.unapi' = ANY (includes)) THEN 
968         output := REGEXP_REPLACE(
969             xml_buf,
970             '</' || top_el || '>(.*?)',
971             XMLELEMENT(
972                 name abbr,
973                 XMLATTRIBUTES(
974                     'http://www.w3.org/1999/xhtml' AS xmlns,
975                     'unapi-id' AS class,
976                     'tag:open-ils.org:U2@mmr/' || obj_id || '/' || org AS title
977                 )
978             )::TEXT || '</' || top_el || E'>\\1'
979         );
980     ELSE
981         output := xml_buf;
982     END IF;
983
984     -- remove ignorable whitesace
985     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
986     RETURN output;
987 END;
988 $F$ LANGUAGE PLPGSQL STABLE;
989
990 CREATE OR REPLACE FUNCTION authority.extract_headings(marc TEXT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$
991 DECLARE
992     idx         authority.heading_field%ROWTYPE;
993     xfrm        config.xml_transform%ROWTYPE;
994     prev_xfrm   TEXT;
995     transformed_xml TEXT;
996     heading_node    TEXT;
997     heading_node_list   TEXT[];
998     component_node    TEXT;
999     component_node_list   TEXT[];
1000     raw_text    TEXT;
1001     normalized_text    TEXT;
1002     normalizer  RECORD;
1003     curr_text   TEXT;
1004     joiner      TEXT;
1005     type_value  TEXT;
1006     base_thesaurus TEXT := NULL;
1007     output_row  authority.heading;
1008 BEGIN
1009
1010     -- Loop over the indexing entries
1011     FOR idx IN SELECT * FROM authority.heading_field WHERE restrict IS NULL OR id = ANY (restrict) ORDER BY format LOOP
1012
1013         output_row.field   := idx.id;
1014         output_row.type    := idx.heading_type;
1015         output_row.purpose := idx.heading_purpose;
1016
1017         joiner := COALESCE(idx.joiner, ' ');
1018
1019         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
1020
1021         -- See if we can skip the XSLT ... it's expensive
1022         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
1023             -- Can't skip the transform
1024             IF xfrm.xslt <> '---' THEN
1025                 transformed_xml := oils_xslt_process(marc, xfrm.xslt);
1026             ELSE
1027                 transformed_xml := marc;
1028             END IF;
1029
1030             prev_xfrm := xfrm.name;
1031         END IF;
1032
1033         IF idx.thesaurus_xpath IS NOT NULL THEN
1034             base_thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), '');
1035         END IF;
1036
1037         heading_node_list := oils_xpath( idx.heading_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
1038
1039         FOR heading_node IN SELECT x FROM unnest(heading_node_list) AS x LOOP
1040
1041             CONTINUE WHEN heading_node !~ E'^\\s*<';
1042
1043             output_row.variant_type := NULL;
1044             output_row.related_type := NULL;
1045             output_row.thesaurus    := NULL;
1046             output_row.heading      := NULL;
1047
1048             IF idx.heading_purpose = 'variant' AND idx.type_xpath IS NOT NULL THEN
1049                 type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), '');
1050                 BEGIN
1051                     output_row.variant_type := type_value;
1052                 EXCEPTION WHEN invalid_text_representation THEN
1053                     RAISE NOTICE 'Do not recognize variant heading type %', type_value;
1054                 END;
1055             END IF;
1056             IF idx.heading_purpose = 'related' AND idx.type_xpath IS NOT NULL THEN
1057                 type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), '');
1058                 BEGIN
1059                     output_row.related_type := type_value;
1060                 EXCEPTION WHEN invalid_text_representation THEN
1061                     RAISE NOTICE 'Do not recognize related heading type %', type_value;
1062                 END;
1063             END IF;
1064  
1065             IF idx.thesaurus_override_xpath IS NOT NULL THEN
1066                 output_row.thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_override_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), '');
1067             END IF;
1068             IF output_row.thesaurus IS NULL THEN
1069                 output_row.thesaurus := base_thesaurus;
1070             END IF;
1071
1072             raw_text := NULL;
1073
1074             -- now iterate over components of heading
1075             component_node_list := oils_xpath( idx.component_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
1076             FOR component_node IN SELECT x FROM unnest(component_node_list) AS x LOOP
1077             -- XXX much of this should be moved into oils_xpath_string...
1078                 curr_text := ARRAY_TO_STRING(array_remove(array_remove(
1079                     oils_xpath( '//text()', -- get the content of all the nodes within the main selected node
1080                         REGEXP_REPLACE( component_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space
1081                     ), ' '), ''),  -- throw away morally empty (bankrupt?) strings
1082                     joiner
1083                 );
1084
1085                 CONTINUE WHEN curr_text IS NULL OR curr_text = '';
1086
1087                 IF raw_text IS NOT NULL THEN
1088                     raw_text := raw_text || joiner;
1089                 END IF;
1090
1091                 raw_text := COALESCE(raw_text,'') || curr_text;
1092             END LOOP;
1093
1094             IF raw_text IS NOT NULL THEN
1095                 output_row.heading := raw_text;
1096                 normalized_text := raw_text;
1097
1098                 FOR normalizer IN
1099                     SELECT  n.func AS func,
1100                             n.param_count AS param_count,
1101                             m.params AS params
1102                     FROM  config.index_normalizer n
1103                             JOIN authority.heading_field_norm_map m ON (m.norm = n.id)
1104                     WHERE m.field = idx.id
1105                     ORDER BY m.pos LOOP
1106             
1107                         EXECUTE 'SELECT ' || normalizer.func || '(' ||
1108                             quote_literal( normalized_text ) ||
1109                             CASE
1110                                 WHEN normalizer.param_count > 0
1111                                     THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
1112                                     ELSE ''
1113                                 END ||
1114                             ')' INTO normalized_text;
1115             
1116                 END LOOP;
1117             
1118                 output_row.normalized_heading := normalized_text;
1119             
1120                 RETURN NEXT output_row;
1121             END IF;
1122         END LOOP;
1123
1124     END LOOP;
1125 END;
1126 $func$ LANGUAGE PLPGSQL;
1127
1128 CREATE OR REPLACE FUNCTION authority.extract_headings(rid BIGINT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$
1129 DECLARE
1130     auth        authority.record_entry%ROWTYPE;
1131     output_row  authority.heading;
1132 BEGIN
1133     -- Get the record
1134     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
1135
1136     RETURN QUERY SELECT * FROM authority.extract_headings(auth.marc, restrict);
1137 END;
1138 $func$ LANGUAGE PLPGSQL;
1139
1140 COMMIT;
1141
1142     sort_value          TEXT
1143 );
1144
1145 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry (
1146     rid BIGINT,
1147     default_joiner TEXT,
1148     field_types TEXT[],
1149     only_fields INT[]
1150 ) RETURNS SETOF metabib.field_entry_template AS $func$
1151 DECLARE
1152     bib     biblio.record_entry%ROWTYPE;
1153     idx     config.metabib_field%ROWTYPE;
1154     xfrm        config.xml_transform%ROWTYPE;
1155     prev_xfrm   TEXT;
1156     transformed_xml TEXT;
1157     xml_node    TEXT;
1158     xml_node_list   TEXT[];
1159     facet_text  TEXT;
1160     display_text TEXT;
1161     browse_text TEXT;
1162     sort_value  TEXT;
1163     raw_text    TEXT;
1164     curr_text   TEXT;
1165     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
1166     authority_text TEXT;
1167     authority_link BIGINT;
1168     output_row  metabib.field_entry_template%ROWTYPE;
1169     process_idx BOOL;
1170 BEGIN
1171
1172     -- Start out with no field-use bools set
1173     output_row.browse_field = FALSE;
1174     output_row.facet_field = FALSE;
1175     output_row.display_field = FALSE;
1176     output_row.search_field = FALSE;
1177
1178     -- Get the record
1179     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
1180
1181     -- Loop over the indexing entries
1182     FOR idx IN SELECT * FROM config.metabib_field WHERE id = ANY (only_fields) ORDER BY format LOOP
1183         CONTINUE WHEN idx.xpath IS NULL OR idx.xpath = ''; -- pure virtual field
1184
1185         process_idx := FALSE;
1186         IF idx.display_field AND 'display' = ANY (field_types) THEN process_idx = TRUE; END IF;
1187         IF idx.browse_field AND 'browse' = ANY (field_types) THEN process_idx = TRUE; END IF;
1188         IF idx.search_field AND 'search' = ANY (field_types) THEN process_idx = TRUE; END IF;
1189         IF idx.facet_field AND 'facet' = ANY (field_types) THEN process_idx = TRUE; END IF;
1190         CONTINUE WHEN process_idx = FALSE; -- disabled for all types
1191
1192         joiner := COALESCE(idx.joiner, default_joiner);
1193
1194         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
1195
1196         -- See if we can skip the XSLT ... it's expensive
1197         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
1198             -- Can't skip the transform
1199             IF xfrm.xslt <> '---' THEN
1200                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
1201             ELSE
1202                 transformed_xml := bib.marc;
1203             END IF;
1204
1205             prev_xfrm := xfrm.name;
1206         END IF;
1207
1208         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
1209
1210         raw_text := NULL;
1211         FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP
1212             CONTINUE WHEN xml_node !~ E'^\\s*<';
1213
1214             -- XXX much of this should be moved into oils_xpath_string...
1215             curr_text := ARRAY_TO_STRING(array_remove(array_remove(
1216                 oils_xpath( '//text()', -- get the content of all the nodes within the main selected node
1217                     REGEXP_REPLACE( xml_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space
1218                 ), ' '), ''),  -- throw away morally empty (bankrupt?) strings
1219                 joiner
1220             );
1221
1222             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
1223
1224             IF raw_text IS NOT NULL THEN
1225                 raw_text := raw_text || joiner;
1226             END IF;
1227
1228             raw_text := COALESCE(raw_text,'') || curr_text;
1229
1230             -- autosuggest/metabib.browse_entry
1231             IF idx.browse_field THEN
1232
1233                 IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN
1234                     browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
1235                 ELSE
1236                     browse_text := curr_text;
1237                 END IF;
1238
1239                 IF idx.browse_sort_xpath IS NOT NULL AND
1240                     idx.browse_sort_xpath <> '' THEN
1241
1242                     sort_value := oils_xpath_string(
1243                         idx.browse_sort_xpath, xml_node, joiner,
1244                         ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
1245                     );
1246                 ELSE
1247                     sort_value := browse_text;
1248                 END IF;
1249
1250                 output_row.field_class = idx.field_class;
1251                 output_row.field = idx.id;
1252                 output_row.source = rid;
1253                 output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g'));
1254                 output_row.sort_value :=
1255                     public.naco_normalize(sort_value);
1256
1257                 output_row.authority := NULL;
1258
1259                 IF idx.authority_xpath IS NOT NULL AND idx.authority_xpath <> '' THEN
1260                     authority_text := oils_xpath_string(
1261                         idx.authority_xpath, xml_node, joiner,
1262                         ARRAY[
1263                             ARRAY[xfrm.prefix, xfrm.namespace_uri],
1264                             ARRAY['xlink','http://www.w3.org/1999/xlink']
1265                         ]
1266                     );
1267
1268                     IF authority_text ~ '^\d+$' THEN
1269                         authority_link := authority_text::BIGINT;
1270                         PERFORM * FROM authority.record_entry WHERE id = authority_link;
1271                         IF FOUND THEN
1272                             output_row.authority := authority_link;
1273                         END IF;
1274                     END IF;
1275
1276                 END IF;
1277
1278                 output_row.browse_field = TRUE;
1279                 -- Returning browse rows with search_field = true for search+browse
1280                 -- configs allows us to retain granularity of being able to search
1281                 -- browse fields with "starts with" type operators (for example, for
1282                 -- titles of songs in music albums)
1283                 IF idx.search_field THEN
1284                     output_row.search_field = TRUE;
1285                 END IF;
1286                 RETURN NEXT output_row;
1287                 output_row.browse_field = FALSE;
1288                 output_row.search_field = FALSE;
1289                 output_row.sort_value := NULL;
1290             END IF;
1291
1292             -- insert raw node text for faceting
1293             IF idx.facet_field THEN
1294
1295                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
1296                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
1297                 ELSE
1298                     facet_text := curr_text;
1299                 END IF;
1300
1301                 output_row.field_class = idx.field_class;
1302                 output_row.field = -1 * idx.id;
1303                 output_row.source = rid;
1304                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
1305
1306                 output_row.facet_field = TRUE;
1307                 RETURN NEXT output_row;
1308                 output_row.facet_field = FALSE;
1309             END IF;
1310
1311             -- insert raw node text for display
1312             IF idx.display_field THEN
1313
1314                 IF idx.display_xpath IS NOT NULL AND idx.display_xpath <> '' THEN
1315                     display_text := oils_xpath_string( idx.display_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
1316                 ELSE
1317                     display_text := curr_text;
1318                 END IF;
1319
1320                 output_row.field_class = idx.field_class;
1321                 output_row.field = -1 * idx.id;
1322                 output_row.source = rid;
1323                 output_row.value = BTRIM(REGEXP_REPLACE(display_text, E'\\s+', ' ', 'g'));
1324
1325                 output_row.display_field = TRUE;
1326                 RETURN NEXT output_row;
1327                 output_row.display_field = FALSE;
1328             END IF;
1329
1330         END LOOP;
1331
1332         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
1333
1334         -- insert combined node text for searching
1335         IF idx.search_field THEN
1336             output_row.field_class = idx.field_class;
1337             output_row.field = idx.id;
1338             output_row.source = rid;
1339             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
1340
1341             output_row.search_field = TRUE;
1342             RETURN NEXT output_row;
1343             output_row.search_field = FALSE;
1344         END IF;
1345
1346     END LOOP;
1347
1348 END;
1349 $func$ LANGUAGE PLPGSQL;
1350
1351 -- Shall we drop it?
1352 -- DROP FUNCTION evergreen.array_remove_item_by_value(ANYARRAY, ANYELEMENT)
1353
1354 COMMIT;
1355