]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/0510.schema.search_path.sql
Stamped upgrade script for "lp 823496: do not fail to index personal names that have...
[Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / 0510.schema.search_path.sql
1 BEGIN;
2
3 INSERT INTO config.upgrade_log (version) VALUES ('0510'); -- miker
4
5 SELECT evergreen.change_db_setting('search_path', ARRAY['evergreen','public','pg_catalog']);
6
7 -- Fix function breakage due to short search path
8 CREATE OR REPLACE FUNCTION evergreen.force_unicode_normal_form(string TEXT, form TEXT) RETURNS TEXT AS $func$
9 use Unicode::Normalize 'normalize';
10 return normalize($_[1],$_[0]); # reverse the params
11 $func$ LANGUAGE PLPERLU;
12
13 CREATE OR REPLACE FUNCTION evergreen.facet_force_nfc() RETURNS TRIGGER AS $$
14 BEGIN
15     NEW.value := force_unicode_normal_form(NEW.value,'NFC');
16     RETURN NEW;
17 END;
18 $$ LANGUAGE PLPGSQL;
19
20 DROP TRIGGER facet_force_nfc_tgr ON metabib.facet_entry;
21
22 CREATE TRIGGER facet_force_nfc_tgr
23     BEFORE UPDATE OR INSERT ON metabib.facet_entry
24     FOR EACH ROW EXECUTE PROCEDURE evergreen.facet_force_nfc();
25
26 DROP FUNCTION IF EXISTS public.force_unicode_normal_form (TEXT,TEXT);
27 DROP FUNCTION IF EXISTS public.facet_force_nfc ();
28
29 CREATE OR REPLACE FUNCTION evergreen.xml_escape(str TEXT) RETURNS text AS $$
30     SELECT REPLACE(REPLACE(REPLACE($1,
31        '&', '&'),
32        '<', '&lt;'),
33        '>', '&gt;');
34 $$ LANGUAGE SQL IMMUTABLE;
35
36 CREATE OR REPLACE FUNCTION evergreen.maintain_901 () RETURNS TRIGGER AS $func$
37 DECLARE
38     use_id_for_tcn BOOLEAN;
39 BEGIN
40     -- Remove any existing 901 fields before we insert the authoritative one
41     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield[^>]*?tag="901".+?</datafield>', '', 'g');
42
43     IF TG_TABLE_SCHEMA = 'biblio' THEN
44         -- Set TCN value to record ID?
45         SELECT enabled FROM config.global_flag INTO use_id_for_tcn
46             WHERE name = 'cat.bib.use_id_for_tcn';
47
48         IF use_id_for_tcn = 't' THEN
49             NEW.tcn_value := NEW.id;
50         END IF;
51
52         NEW.marc := REGEXP_REPLACE(
53             NEW.marc,
54             E'(</(?:[^:]*?:)?record>)',
55             E'<datafield tag="901" ind1=" " ind2=" ">' ||
56                 '<subfield code="a">' || evergreen.xml_escape(NEW.tcn_value) || E'</subfield>' ||
57                 '<subfield code="b">' || evergreen.xml_escape(NEW.tcn_source) || E'</subfield>' ||
58                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
59                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
60                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
61                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
62              E'</datafield>\\1'
63         );
64     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
65         NEW.marc := REGEXP_REPLACE(
66             NEW.marc,
67             E'(</(?:[^:]*?:)?record>)',
68             E'<datafield tag="901" ind1=" " ind2=" ">' ||
69                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
70                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
71              E'</datafield>\\1'
72         );
73     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
74         NEW.marc := REGEXP_REPLACE(
75             NEW.marc,
76             E'(</(?:[^:]*?:)?record>)',
77             E'<datafield tag="901" ind1=" " ind2=" ">' ||
78                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
79                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
80                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
81                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
82              E'</datafield>\\1'
83         );
84     ELSE
85         NEW.marc := REGEXP_REPLACE(
86             NEW.marc,
87             E'(</(?:[^:]*?:)?record>)',
88             E'<datafield tag="901" ind1=" " ind2=" ">' ||
89                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
90                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
91              E'</datafield>\\1'
92         );
93     END IF;
94
95     RETURN NEW;
96 END;
97 $func$ LANGUAGE PLPGSQL;
98
99 DROP TRIGGER b_maintain_901 ON biblio.record_entry;
100 DROP TRIGGER b_maintain_901 ON authority.record_entry;
101 DROP TRIGGER b_maintain_901 ON serial.record_entry;
102
103 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
104 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
105 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
106
107 DROP FUNCTION IF EXISTS public.maintain_901 ();
108
109 ------ Backporting note: 2.1+ only beyond here --------
110
111 DROP SCHEMA IF EXISTS unapi CASCADE;
112 CREATE SCHEMA unapi;
113
114 CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT) RETURNS anyarray AS $$ SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2; $$ LANGUAGE SQL;
115
116 CREATE TABLE unapi.bre_output_layout (
117     name                TEXT    PRIMARY KEY,
118     transform           TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
119     mime_type           TEXT    NOT NULL,
120     feed_top            TEXT    NOT NULL,
121     holdings_element    TEXT,
122     title_element       TEXT,
123     description_element TEXT,
124     creator_element     TEXT,
125     update_ts_element   TEXT
126 );
127
128 INSERT INTO unapi.bre_output_layout
129     (name,           transform, mime_type,              holdings_element, feed_top,         title_element, description_element, creator_element, update_ts_element)
130         VALUES
131     ('holdings_xml', NULL,      'application/xml',      NULL,             'hxml',           NULL,          NULL,                NULL,            NULL),
132     ('marcxml',      'marcxml', 'application/marc+xml', 'record',         'collection',     NULL,          NULL,                NULL,            NULL),
133     ('mods32',       'mods32',  'application/mods+xml', 'mods',           'modsCollection', NULL,          NULL,                NULL,            NULL)
134 ;
135
136 -- Dummy functions, so we can create the real ones out of order
137 CREATE OR REPLACE FUNCTION unapi.aou    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
138 CREATE OR REPLACE FUNCTION unapi.acnp   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
139 CREATE OR REPLACE FUNCTION unapi.acns   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
140 CREATE OR REPLACE FUNCTION unapi.acn    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
141 CREATE OR REPLACE FUNCTION unapi.ssub   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
142 CREATE OR REPLACE FUNCTION unapi.sdist  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
143 CREATE OR REPLACE FUNCTION unapi.sstr   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
144 CREATE OR REPLACE FUNCTION unapi.sitem  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
145 CREATE OR REPLACE FUNCTION unapi.sunit  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
146 CREATE OR REPLACE FUNCTION unapi.sisum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
147 CREATE OR REPLACE FUNCTION unapi.sbsum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
148 CREATE OR REPLACE FUNCTION unapi.sssum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
149 CREATE OR REPLACE FUNCTION unapi.siss   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
150 CREATE OR REPLACE FUNCTION unapi.auri   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
151 CREATE OR REPLACE FUNCTION unapi.acp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
152 CREATE OR REPLACE FUNCTION unapi.acpn   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
153 CREATE OR REPLACE FUNCTION unapi.acl    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
154 CREATE OR REPLACE FUNCTION unapi.ccs    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
155 CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
156 CREATE OR REPLACE FUNCTION unapi.bre    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
157 CREATE OR REPLACE FUNCTION unapi.bmp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
158
159 CREATE OR REPLACE FUNCTION unapi.holdings_xml ( bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
160 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
161
162 CREATE OR REPLACE FUNCTION unapi.memoize (classname TEXT, obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
163 DECLARE
164     key     TEXT;
165     output  XML;
166 BEGIN
167     key :=
168         'id'        || COALESCE(obj_id::TEXT,'') ||
169         'format'    || COALESCE(format::TEXT,'') ||
170         'ename'     || COALESCE(ename::TEXT,'') ||
171         'includes'  || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
172         'org'       || COALESCE(org::TEXT,'') ||
173         'depth'     || COALESCE(depth::TEXT,'') ||
174         'slimit'    || COALESCE(slimit::TEXT,'') ||
175         'soffset'   || COALESCE(soffset::TEXT,'') ||
176         'include_xmlns'   || COALESCE(include_xmlns::TEXT,'');
177     -- RAISE NOTICE 'memoize key: %', key;
178
179     key := MD5(key);
180     -- RAISE NOTICE 'memoize hash: %', key;
181
182     -- XXX cache logic ... memcached? table?
183
184     EXECUTE $$SELECT unapi.$$ || classname || $$( $1, $2, $3, $4, $5, $6, $7, $8, $9);$$ INTO output USING obj_id, format, ename, includes, org, depth, slimit, soffset, include_xmlns;
185     RETURN output;
186 END;
187 $F$ LANGUAGE PLPGSQL;
188
189 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$
190 DECLARE
191     layout          unapi.bre_output_layout%ROWTYPE;
192     transform       config.xml_transform%ROWTYPE;
193     item_format     TEXT;
194     tmp_xml         TEXT;
195     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
196     ouid            INT;
197     element_list    TEXT[];
198 BEGIN
199
200     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
201     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
202
203     IF layout.name IS NULL THEN
204         RETURN NULL::XML;
205     END IF;
206
207     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
208     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
209
210     -- Gather the bib xml
211     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
212
213     IF layout.title_element IS NOT NULL THEN
214         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title, include_xmlns;
215     END IF;
216
217     IF layout.description_element IS NOT NULL THEN
218         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description, include_xmlns;
219     END IF;
220
221     IF layout.creator_element IS NOT NULL THEN
222         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator, include_xmlns;
223     END IF;
224
225     IF layout.update_ts_element IS NOT NULL THEN
226         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts, include_xmlns;
227     END IF;
228
229     IF unapi_url IS NOT NULL THEN
230         EXECUTE $$SELECT XMLCONCAT( XMLELEMENT( name link, XMLATTRIBUTES( 'http://www.w3.org/1999/xhtml' AS xmlns, 'unapi-server' AS rel, $1 AS href, 'unapi' AS title)), $2)$$ INTO tmp_xml USING unapi_url, tmp_xml::XML;
231     END IF;
232
233     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
234
235     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
236     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
237         EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, include_xmlns;
238     END LOOP;
239
240     RETURN tmp_xml::XML;
241 END;
242 $F$ LANGUAGE PLPGSQL;
243
244 CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
245 DECLARE
246     me      biblio.record_entry%ROWTYPE;
247     layout  unapi.bre_output_layout%ROWTYPE;
248     xfrm    config.xml_transform%ROWTYPE;
249     ouid    INT;
250     tmp_xml TEXT;
251     top_el  TEXT;
252     output  XML;
253     hxml    XML;
254 BEGIN
255
256     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
257
258     IF ouid IS NULL THEN
259         RETURN NULL::XML;
260     END IF;
261
262     IF format = 'holdings_xml' THEN -- the special case
263         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
264         RETURN output;
265     END IF;
266
267     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
268
269     IF layout.name IS NULL THEN
270         RETURN NULL::XML;
271     END IF;
272
273     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
274
275     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
276
277     -- grab hodlings if we need them
278     IF ('holdings_xml' = ANY (includes)) THEN 
279         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
280     ELSE
281         hxml := NULL::XML;
282     END IF;
283
284
285     -- generate our item node
286
287
288     IF format = 'marcxml' THEN
289         tmp_xml := me.marc;
290         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
291            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
292         END IF; 
293     ELSE
294         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
295     END IF;
296
297     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
298
299     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
300         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
301     END IF;
302
303     IF ('bre.unapi' = ANY (includes)) THEN 
304         output := REGEXP_REPLACE(
305             tmp_xml,
306             '</' || top_el || '>(.*?)',
307             XMLELEMENT(
308                 name abbr,
309                 XMLATTRIBUTES(
310                     'http://www.w3.org/1999/xhtml' AS xmlns,
311                     'unapi-id' AS class,
312                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
313                 )
314             )::TEXT || '</' || top_el || E'>\\1'
315         );
316     ELSE
317         output := tmp_xml;
318     END IF;
319
320     RETURN output;
321 END;
322 $F$ LANGUAGE PLPGSQL;
323
324 CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$
325      SELECT  XMLELEMENT(
326                  name holdings,
327                  XMLATTRIBUTES(
328                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
329                     CASE WHEN ('bre' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
330                  ),
331                  XMLELEMENT(
332                      name counts,
333                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
334                          SELECT  XMLELEMENT(
335                                      name count,
336                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
337                                  )::text
338                            FROM  asset.opac_ou_record_copy_count($2,  $1)
339                                      UNION
340                          SELECT  XMLELEMENT(
341                                      name count,
342                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
343                                  )::text
344                            FROM  asset.staff_ou_record_copy_count($2, $1)
345                                      ORDER BY 1
346                      )x)
347                  ),
348                  CASE 
349                      WHEN ('bmp' = ANY ($5)) THEN
350                         XMLELEMENT( name monograph_parts,
351                             XMLAGG((SELECT unapi.bmp( id, 'xml', 'monograph_part', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE) FROM biblio.monograph_part WHERE record = $1))
352                         )
353                      ELSE NULL
354                  END,
355                  CASE WHEN ('acn' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN 
356                      XMLELEMENT(
357                          name volumes,
358                          (SELECT XMLAGG(acn) FROM (
359                             SELECT  unapi.acn(acn.id,'xml','volume', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value('{acn,auri}'::TEXT[] || $5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE)
360                               FROM  asset.call_number acn
361                               WHERE acn.record = $1
362                                     AND EXISTS (
363                                         SELECT  1
364                                           FROM  asset.copy acp
365                                                 JOIN actor.org_unit_descendants(
366                                                     $2,
367                                                     (COALESCE(
368                                                         $4,
369                                                         (SELECT aout.depth
370                                                           FROM  actor.org_unit_type aout
371                                                                 JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
372                                                         )
373                                                     ))
374                                                 ) aoud ON (acp.circ_lib = aoud.id)
375                                           LIMIT 1
376                                     )
377                               ORDER BY label_sortkey
378                               LIMIT $6
379                               OFFSET $7
380                          )x)
381                      )
382                  ELSE NULL END,
383                  CASE WHEN ('ssub' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN 
384                      XMLELEMENT(
385                          name subscriptions,
386                          (SELECT XMLAGG(ssub) FROM (
387                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
388                               FROM  serial.subscription
389                               WHERE record_entry = $1
390                         )x)
391                      )
392                  ELSE NULL END
393              );
394 $F$ LANGUAGE SQL;
395
396 CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
397         SELECT  XMLELEMENT(
398                     name subscription,
399                     XMLATTRIBUTES(
400                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
401                         'tag:open-ils.org:U2@ssub/' || id AS id,
402                         start_date AS start, end_date AS end, expected_date_offset
403                     ),
404                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8),
405                     XMLELEMENT( name distributions,
406                         CASE 
407                             WHEN ('sdist' = ANY ($4)) THEN
408                                 XMLAGG((SELECT unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE) FROM serial.distribution WHERE subscription = ssub.id))
409                             ELSE NULL
410                         END
411                     )
412                 )
413           FROM  serial.subscription ssub
414           WHERE id = $1
415           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
416 $F$ LANGUAGE SQL;
417
418 CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
419         SELECT  XMLELEMENT(
420                     name distribution,
421                     XMLATTRIBUTES(
422                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
423                         'tag:open-ils.org:U2@sdist/' || id AS id,
424                         'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
425                         'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
426                         unit_label_prefix, label, unit_label_suffix, summary_method
427                     ),
428                     unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
429                     CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) ELSE NULL END,
430                     XMLELEMENT( name streams,
431                         CASE 
432                             WHEN ('sstr' = ANY ($4)) THEN
433                                 XMLAGG((SELECT unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) FROM serial.stream WHERE distribution = sdist.id))
434                             ELSE NULL
435                         END
436                     ),
437                     XMLELEMENT( name summaries,
438                         CASE 
439                             WHEN ('ssum' = ANY ($4)) THEN
440                                 XMLAGG((SELECT unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) FROM serial.basic_summary WHERE distribution = sdist.id))
441                             ELSE NULL
442                         END,
443                         CASE 
444                             WHEN ('ssum' = ANY ($4)) THEN
445                                 XMLAGG((SELECT unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) FROM serial.index_summary WHERE distribution = sdist.id))
446                             ELSE NULL
447                         END,
448                         CASE 
449                             WHEN ('ssum' = ANY ($4)) THEN
450                                 XMLAGG((SELECT unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) FROM serial.supplement_summary WHERE distribution = sdist.id))
451                             ELSE NULL
452                         END
453                     )
454                 )
455           FROM  serial.distribution sdist
456           WHERE id = $1
457           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
458 $F$ LANGUAGE SQL;
459
460 CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
461     SELECT  XMLELEMENT(
462                 name stream,
463                 XMLATTRIBUTES(
464                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
465                     'tag:open-ils.org:U2@sstr/' || id AS id,
466                     routing_label
467                 ),
468                 CASE WHEN distribution IS NOT NULL AND ('sdist' = ANY ($4)) THEN unapi.sssum( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE) ELSE NULL END,
469                 XMLELEMENT( name items,
470                     CASE 
471                         WHEN ('sitem' = ANY ($4)) THEN
472                             XMLAGG((SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE) FROM serial.item WHERE stream = sstr.id))
473                         ELSE NULL
474                     END
475                 )
476             )
477       FROM  serial.stream sstr
478       WHERE id = $1
479       GROUP BY id, routing_label, distribution;
480 $F$ LANGUAGE SQL;
481
482 CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
483     SELECT  XMLELEMENT(
484                 name issuance,
485                 XMLATTRIBUTES(
486                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
487                     'tag:open-ils.org:U2@siss/' || id AS id,
488                     create_date, edit_date, label, date_published,
489                     holding_code, holding_type, holding_link_id
490                 ),
491                 CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE) ELSE NULL END,
492                 XMLELEMENT( name items,
493                     CASE 
494                         WHEN ('sitem' = ANY ($4)) THEN
495                             XMLAGG((SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE) FROM serial.item WHERE issuance = sstr.id))
496                         ELSE NULL
497                     END
498                 )
499             )
500       FROM  serial.issuance sstr
501       WHERE id = $1
502       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
503 $F$ LANGUAGE SQL;
504
505 CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
506         SELECT  XMLELEMENT(
507                     name serial_item,
508                     XMLATTRIBUTES(
509                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
510                         'tag:open-ils.org:U2@sitem/' || id AS id,
511                         'tag:open-ils.org:U2@siss/' || issuance AS issuance,
512                         date_expected, date_received
513                     ),
514                     CASE WHEN issuance IS NOT NULL AND ('siss' = ANY ($4)) THEN unapi.siss( issuance, $2, 'issuance', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
515                     CASE WHEN stream IS NOT NULL AND ('sstr' = ANY ($4)) THEN unapi.sstr( stream, $2, 'stream', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
516                     CASE WHEN unit IS NOT NULL AND ('sunit' = ANY ($4)) THEN unapi.sunit( stream, $2, 'serial_unit', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
517                     CASE WHEN uri IS NOT NULL AND ('auri' = ANY ($4)) THEN unapi.auri( uri, $2, 'uri', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END
518 --                    XMLELEMENT( name notes,
519 --                        CASE 
520 --                            WHEN ('acpn' = ANY ($4)) THEN
521 --                                XMLAGG((SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8) FROM asset.copy_note WHERE owning_copy = cp.id AND pub))
522 --                            ELSE NULL
523 --                        END
524 --                    )
525                 )
526           FROM  serial.item sitem
527           WHERE id = $1;
528 $F$ LANGUAGE SQL;
529
530
531 CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
532     SELECT  XMLELEMENT(
533                 name serial_summary,
534                 XMLATTRIBUTES(
535                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
536                     'tag:open-ils.org:U2@sbsum/' || id AS id,
537                     'sssum' AS type, generated_coverage, textual_holdings, show_generated
538                 ),
539                 CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
540             )
541       FROM  serial.supplement_summary ssum
542       WHERE id = $1
543       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
544 $F$ LANGUAGE SQL;
545
546 CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
547     SELECT  XMLELEMENT(
548                 name serial_summary,
549                 XMLATTRIBUTES(
550                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
551                     'tag:open-ils.org:U2@sbsum/' || id AS id,
552                     'sbsum' AS type, generated_coverage, textual_holdings, show_generated
553                 ),
554                 CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
555             )
556       FROM  serial.basic_summary ssum
557       WHERE id = $1
558       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
559 $F$ LANGUAGE SQL;
560
561 CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
562     SELECT  XMLELEMENT(
563                 name serial_summary,
564                 XMLATTRIBUTES(
565                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
566                     'tag:open-ils.org:U2@sbsum/' || id AS id,
567                     'sisum' AS type, generated_coverage, textual_holdings, show_generated
568                 ),
569                 CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
570             )
571       FROM  serial.index_summary ssum
572       WHERE id = $1
573       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
574 $F$ LANGUAGE SQL;
575
576
577 CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
578 DECLARE
579     output XML;
580 BEGIN
581     IF ename = 'circlib' THEN
582         SELECT  XMLELEMENT(
583                     name circlib,
584                     XMLATTRIBUTES(
585                         'http://open-ils.org/spec/actors/v1' AS xmlns,
586                         id AS ident
587                     ),
588                     name
589                 ) INTO output
590           FROM  actor.org_unit aou
591           WHERE id = obj_id;
592     ELSE
593         EXECUTE $$SELECT  XMLELEMENT(
594                     name $$ || ename || $$,
595                     XMLATTRIBUTES(
596                         'http://open-ils.org/spec/actors/v1' AS xmlns,
597                         'tag:open-ils.org:U2@aou/' || id AS id,
598                         shortname, name, opac_visible
599                     )
600                 )
601           FROM  actor.org_unit aou
602          WHERE id = $1 $$ INTO output USING obj_id;
603     END IF;
604
605     RETURN output;
606
607 END;
608 $F$ LANGUAGE PLPGSQL;
609
610 CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
611     SELECT  XMLELEMENT(
612                 name location,
613                 XMLATTRIBUTES(
614                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
615                     id AS ident
616                 ),
617                 name
618             )
619       FROM  asset.copy_location
620       WHERE id = $1;
621 $F$ LANGUAGE SQL;
622
623 CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
624     SELECT  XMLELEMENT(
625                 name status,
626                 XMLATTRIBUTES(
627                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
628                     id AS ident
629                 ),
630                 name
631             )
632       FROM  config.copy_status
633       WHERE id = $1;
634 $F$ LANGUAGE SQL;
635
636 CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
637         SELECT  XMLELEMENT(
638                     name copy_note,
639                     XMLATTRIBUTES(
640                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
641                         create_date AS date,
642                         title
643                     ),
644                     value
645                 )
646           FROM  asset.copy_note
647           WHERE id = $1;
648 $F$ LANGUAGE SQL;
649
650 CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
651         SELECT  XMLELEMENT(
652                     name statcat,
653                     XMLATTRIBUTES(
654                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
655                         sc.name,
656                         sc.opac_visible
657                     ),
658                     asce.value
659                 )
660           FROM  asset.stat_cat_entry asce
661                 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
662           WHERE asce.id = $1;
663 $F$ LANGUAGE SQL;
664
665 CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
666         SELECT  XMLELEMENT(
667                     name monograph_part,
668                     XMLATTRIBUTES(
669                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
670                         'tag:open-ils.org:U2@bmp/' || id AS id,
671                         id AS ident,
672                         label,
673                         label_sortkey,
674                         'tag:open-ils.org:U2@bre/' || record AS record
675                     ),
676                     CASE 
677                         WHEN ('acp' = ANY ($4)) THEN
678                             XMLELEMENT( name copies,
679                                 (SELECT XMLAGG(acp) FROM (
680                                     SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
681                                       FROM  asset.copy cp
682                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
683                                       WHERE cpm.part = $1
684                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
685                                       LIMIT $7
686                                       OFFSET $8
687                                 )x)
688                             )
689                         ELSE NULL
690                     END,
691                     CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE) ELSE NULL END
692                 )
693           FROM  biblio.monograph_part
694           WHERE id = $1
695           GROUP BY id, label, label_sortkey, record;
696 $F$ LANGUAGE SQL;
697
698 CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
699         SELECT  XMLELEMENT(
700                     name copy,
701                     XMLATTRIBUTES(
702                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
703                         'tag:open-ils.org:U2@acp/' || id AS id,
704                         create_date, edit_date, copy_number, circulate, deposit,
705                         ref, holdable, deleted, deposit_amount, price, barcode,
706                         circ_modifier, circ_as_type, opac_visible
707                     ),
708                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
709                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
710                     unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
711                     unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
712                     CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
713                     XMLELEMENT( name copy_notes,
714                         CASE 
715                             WHEN ('acpn' = ANY ($4)) THEN
716                                 XMLAGG((SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.copy_note WHERE owning_copy = cp.id AND pub))
717                             ELSE NULL
718                         END
719                     ),
720                     XMLELEMENT( name statcats,
721                         CASE 
722                             WHEN ('ascecm' = ANY ($4)) THEN
723                                 XMLAGG((SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.stat_cat_entry_copy_map WHERE owning_copy = cp.id))
724                             ELSE NULL
725                         END
726                     ),
727                     CASE 
728                         WHEN ('bmp' = ANY ($4)) THEN
729                             XMLELEMENT( name monograph_parts,
730                                 XMLAGG((SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.copy_part_map WHERE target_copy = cp.id))
731                             )
732                         ELSE NULL
733                     END
734                 )
735           FROM  asset.copy cp
736           WHERE id = $1
737           GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible;
738 $F$ LANGUAGE SQL;
739
740 CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
741         SELECT  XMLELEMENT(
742                     name serial_unit,
743                     XMLATTRIBUTES(
744                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
745                         'tag:open-ils.org:U2@acp/' || id AS id,
746                         create_date, edit_date, copy_number, circulate, deposit,
747                         ref, holdable, deleted, deposit_amount, price, barcode,
748                         circ_modifier, circ_as_type, opac_visible, status_changed_time,
749                         floating, mint_condition, detailed_contents, sort_key, summary_contents, cost 
750                     ),
751                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
752                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
753                     unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8),
754                     unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8),
755                     CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
756                     XMLELEMENT( name copy_notes,
757                         CASE 
758                             WHEN ('acpn' = ANY ($4)) THEN
759                                 XMLAGG((SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE) FROM asset.copy_note WHERE owning_copy = cp.id AND pub))
760                             ELSE NULL
761                         END
762                     ),
763                     XMLELEMENT( name statcats,
764                         CASE 
765                             WHEN ('ascecm' = ANY ($4)) THEN
766                                 XMLAGG((SELECT unapi.acpn( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE) FROM asset.stat_cat_entry_copy_map WHERE owning_copy = cp.id))
767                             ELSE NULL
768                         END
769                     )
770                 )
771           FROM  serial.unit cp
772           WHERE id = $1
773           GROUP BY  id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, floating, mint_condition,
774                     deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible, status_changed_time, detailed_contents, sort_key, summary_contents, cost;
775 $F$ LANGUAGE SQL;
776
777 CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
778         SELECT  XMLELEMENT(
779                     name volume,
780                     XMLATTRIBUTES(
781                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
782                         'tag:open-ils.org:U2@acn/' || acn.id AS id,
783                         o.shortname AS lib,
784                         o.opac_visible AS opac_visible,
785                         deleted, label, label_sortkey, label_class, record
786                     ),
787                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
788                     XMLELEMENT( name copies,
789                         CASE 
790                             WHEN ('acp' = ANY ($4)) THEN
791                                 (SELECT XMLAGG(acp) FROM (
792                                     SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
793                                       FROM  asset.copy cp
794                                             JOIN actor.org_unit_descendants(
795                                                 (SELECT id FROM actor.org_unit WHERE shortname = $5),
796                                                 (COALESCE($6,(SELECT aout.depth FROM actor.org_unit_type aout JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.shortname = $5))))
797                                             ) aoud ON (cp.circ_lib = aoud.id)
798                                       WHERE cp.call_number = acn.id
799                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
800                                       LIMIT $7
801                                       OFFSET $8
802                                 )x)
803                             ELSE NULL
804                         END
805                     ),
806                     XMLELEMENT(
807                         name uris,
808                         (SELECT XMLAGG(auri) FROM (SELECT unapi.auri(uri,'xml','uri', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE call_number = acn.id)x)
809                     ),
810                     CASE WHEN ('acnp' = ANY ($4)) THEN unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END,
811                     CASE WHEN ('acns' = ANY ($4)) THEN unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END,
812                     CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END
813                 ) AS x
814           FROM  asset.call_number acn
815                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
816           WHERE acn.id = $1
817           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
818 $F$ LANGUAGE SQL;
819
820 CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
821         SELECT  XMLELEMENT(
822                     name call_number_prefix,
823                     XMLATTRIBUTES(
824                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
825                         id AS ident,
826                         label,
827                         label_sortkey
828                     ),
829                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acnp'), $5, $6, $7, $8)
830                 )
831           FROM  asset.call_number_prefix
832           WHERE id = $1;
833 $F$ LANGUAGE SQL;
834
835 CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
836         SELECT  XMLELEMENT(
837                     name call_number_suffix,
838                     XMLATTRIBUTES(
839                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
840                         id AS ident,
841                         label,
842                         label_sortkey
843                     ),
844                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acns'), $5, $6, $7, $8)
845                 )
846           FROM  asset.call_number_suffix
847           WHERE id = $1;
848 $F$ LANGUAGE SQL;
849
850 CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
851         SELECT  XMLELEMENT(
852                     name volume,
853                     XMLATTRIBUTES(
854                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
855                         'tag:open-ils.org:U2@auri/' || uri.id AS id,
856                         use_restriction,
857                         href,
858                         label
859                     ),
860                     XMLELEMENT( name copies,
861                         CASE 
862                             WHEN ('acn' = ANY ($4)) THEN
863                                 (SELECT XMLAGG(acn) FROM (SELECT unapi.acn( call_number, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'auri'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE uri = uri.id)x)
864                             ELSE NULL
865                         END
866                     )
867                 ) AS x
868           FROM  asset.uri uri
869           WHERE uri.id = $1
870           GROUP BY uri.id, use_restriction, href, label;
871 $F$ LANGUAGE SQL;
872
873 DROP FUNCTION IF EXISTS public.array_remove_item_by_value(ANYARRAY,ANYELEMENT);
874
875 CREATE OR REPLACE FUNCTION evergreen.lpad_number_substrings( TEXT, TEXT, INT ) RETURNS TEXT AS $$
876     my $string = shift;
877     my $pad = shift;
878     my $len = shift;
879     my $find = $len - 1;
880
881     while ($string =~ /(?:^|\D)(\d{1,$find})(?:$|\D)/) {
882         my $padded = $1;
883         $padded = $pad x ($len - length($padded)) . $padded;
884         $string =~ s/$1/$padded/sg;
885     }
886
887     return $string;
888 $$ LANGUAGE PLPERLU;
889
890 CREATE OR REPLACE FUNCTION biblio.normalize_biblio_monograph_part_sortkey () RETURNS TRIGGER AS $$
891 BEGIN
892     NEW.label_sortkey := REGEXP_REPLACE(
893         evergreen.lpad_number_substrings(
894             naco_normalize(NEW.label),
895             '0',
896             10
897         ),
898         E'\\s+',
899         '',
900         'g'
901     );
902     RETURN NEW;
903 END;
904 $$ LANGUAGE PLPGSQL;
905
906 CREATE OR REPLACE FUNCTION asset.normalize_affix_sortkey () RETURNS TRIGGER AS $$
907 BEGIN
908     NEW.label_sortkey := REGEXP_REPLACE(
909         evergreen.lpad_number_substrings(
910             naco_normalize(NEW.label),
911             '0',
912             10
913         ),
914         E'\\s+',
915         '',
916         'g'
917     );
918     RETURN NEW;
919 END;
920 $$ LANGUAGE PLPGSQL;
921
922 DROP FUNCTION IF EXISTS public.lpad_number_substrings(TEXT,TEXT,INT);
923
924 COMMIT;
925