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