From: Galen Charlton Date: Wed, 2 Jun 2021 15:24:00 +0000 (-0400) Subject: LP#1778955: stamp schema update X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=d4f5a2effd634089071eb820b02fd8e76225b389;p=contrib%2FConifer.git LP#1778955: stamp schema update Signed-off-by: Galen Charlton --- diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql index c9f0fb10dc..fd619611ff 100644 --- a/Open-ILS/src/sql/Pg/002.schema.config.sql +++ b/Open-ILS/src/sql/Pg/002.schema.config.sql @@ -92,7 +92,7 @@ CREATE TRIGGER no_overlapping_deps BEFORE INSERT OR UPDATE ON config.db_patch_dependencies FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates'); -INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1263', :eg_version); -- berick/csharp/gmcharlt +INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1264', :eg_version); -- mrylander/sandbergja/rhamby/gmcharlt CREATE TABLE config.bib_source ( id SERIAL PRIMARY KEY, diff --git a/Open-ILS/src/sql/Pg/upgrade/1264.function.builtin_array_remove.sql b/Open-ILS/src/sql/Pg/upgrade/1264.function.builtin_array_remove.sql new file mode 100644 index 0000000000..70bbe6a3fa --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/1264.function.builtin_array_remove.sql @@ -0,0 +1,1350 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('1264', :eg_version); + +CREATE OR REPLACE FUNCTION unapi.bre ( + obj_id BIGINT, + format TEXT, + ename TEXT, + includes TEXT[], + org TEXT, + depth INT DEFAULT NULL, + slimit HSTORE DEFAULT NULL, + soffset HSTORE DEFAULT NULL, + include_xmlns BOOL DEFAULT TRUE, + pref_lib INT DEFAULT NULL +) +RETURNS XML AS $F$ +DECLARE + me biblio.record_entry%ROWTYPE; + layout unapi.bre_output_layout%ROWTYPE; + xfrm config.xml_transform%ROWTYPE; + ouid INT; + tmp_xml TEXT; + top_el TEXT; + output XML; + hxml XML; + axml XML; + source XML; +BEGIN + + IF org = '-' OR org IS NULL THEN + SELECT shortname INTO org FROM evergreen.org_top(); + END IF; + + SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org; + + IF ouid IS NULL THEN + RETURN NULL::XML; + END IF; + + IF format = 'holdings_xml' THEN -- the special case + output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns); + RETURN output; + END IF; + + SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format; + + IF layout.name IS NULL THEN + RETURN NULL::XML; + END IF; + + SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform; + + SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id; + + -- grab bib_source, if any + IF ('cbs' = ANY (includes) AND me.source IS NOT NULL) THEN + source := unapi.cbs(me.source,NULL,NULL,NULL,NULL); + ELSE + source := NULL::XML; + END IF; + + -- grab SVF if we need them + IF ('mra' = ANY (includes)) THEN + axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL); + ELSE + axml := NULL::XML; + END IF; + + -- grab holdings if we need them + IF ('holdings_xml' = ANY (includes)) THEN + hxml := unapi.holdings_xml(obj_id, ouid, org, depth, array_remove(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib); + ELSE + hxml := NULL::XML; + END IF; + + + -- generate our item node + + + IF format = 'marcxml' THEN + tmp_xml := me.marc; + IF tmp_xml !~ E'(.*?)$', source || '\\1'); + END IF; + + IF axml IS NOT NULL THEN + tmp_xml := REGEXP_REPLACE(tmp_xml, '(.*?)$', axml || '\\1'); + END IF; + + IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position? + tmp_xml := REGEXP_REPLACE(tmp_xml, '(.*?)$', hxml || '\\1'); + END IF; + + IF ('bre.unapi' = ANY (includes)) THEN + output := REGEXP_REPLACE( + tmp_xml, + '(.*?)', + XMLELEMENT( + name abbr, + XMLATTRIBUTES( + 'http://www.w3.org/1999/xhtml' AS xmlns, + 'unapi-id' AS class, + 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title + ) + )::TEXT || '\\1' + ); + ELSE + output := tmp_xml; + END IF; + + IF ('bre.extern' = ANY (includes)) THEN + output := REGEXP_REPLACE( + tmp_xml, + '(.*?)', + XMLELEMENT( + name extern, + XMLATTRIBUTES( + 'http://open-ils.org/spec/biblio/v1' AS xmlns, + me.creator AS creator, + me.editor AS editor, + me.create_date AS create_date, + me.edit_date AS edit_date, + me.quality AS quality, + me.fingerprint AS fingerprint, + me.tcn_source AS tcn_source, + me.tcn_value AS tcn_value, + me.owner AS owner, + me.share_depth AS share_depth, + me.active AS active, + me.deleted AS deleted + ) + )::TEXT || '\\1' + ); + ELSE + output := tmp_xml; + END IF; + + output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML; + RETURN output; +END; +$F$ LANGUAGE PLPGSQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.holdings_xml ( + bid BIGINT, + ouid INT, + org TEXT, + depth INT DEFAULT NULL, + includes TEXT[] DEFAULT NULL::TEXT[], + slimit HSTORE DEFAULT NULL, + soffset HSTORE DEFAULT NULL, + include_xmlns BOOL DEFAULT TRUE, + pref_lib INT DEFAULT NULL +) +RETURNS XML AS $F$ + SELECT XMLELEMENT( + name holdings, + XMLATTRIBUTES( + CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id, + (SELECT record_has_holdable_copy FROM asset.record_has_holdable_copy($1)) AS has_holdable + ), + XMLELEMENT( + name counts, + (SELECT XMLAGG(XMLELEMENT::XML) FROM ( + SELECT XMLELEMENT( + name count, + XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) + )::text + FROM asset.opac_ou_record_copy_count($2, $1) + UNION + SELECT XMLELEMENT( + name count, + XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) + )::text + FROM asset.staff_ou_record_copy_count($2, $1) + UNION + SELECT XMLELEMENT( + name count, + XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) + )::text + FROM asset.opac_ou_record_copy_count($9, $1) + ORDER BY 1 + )x) + ), + CASE + WHEN ('bmp' = ANY ($5)) THEN + XMLELEMENT( + name monograph_parts, + (SELECT XMLAGG(bmp) FROM ( + SELECT unapi.bmp( id, 'xml', 'monograph_part', array_remove( array_remove($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE) + FROM biblio.monograph_part + WHERE NOT deleted AND record = $1 + )x) + ) + ELSE NULL + END, + XMLELEMENT( + name volumes, + (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM ( + -- Physical copies + 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 + FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9, $5) AS y + UNION ALL + -- Located URIs + 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 + FROM evergreen.located_uris($1, $2, $9) AS uris + )x) + ), + CASE WHEN ('ssub' = ANY ($5)) THEN + XMLELEMENT( + name subscriptions, + (SELECT XMLAGG(ssub) FROM ( + SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE) + FROM serial.subscription + WHERE record_entry = $1 + )x) + ) + ELSE NULL END, + CASE WHEN ('acp' = ANY ($5)) THEN + XMLELEMENT( + name foreign_copies, + (SELECT XMLAGG(acp) FROM ( + SELECT unapi.acp(p.target_copy,'xml','copy',array_remove($5,'acp'), $3, $4, $6, $7, FALSE) + FROM biblio.peer_bib_copy_map p + JOIN asset.copy c ON (p.target_copy = c.id) + WHERE NOT c.deleted AND p.peer_record = $1 + LIMIT ($6 -> 'acp')::INT + OFFSET ($7 -> 'acp')::INT + )x) + ) + ELSE NULL END + ); +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name subscription, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@ssub/' || id AS id, + 'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib, + start_date AS start, end_date AS end, expected_date_offset + ), + CASE + WHEN ('sdist' = ANY ($4)) THEN + XMLELEMENT( name distributions, + (SELECT XMLAGG(sdist) FROM ( + SELECT unapi.sdist( id, 'xml', 'distribution', array_remove($4,'ssub'), $5, $6, $7, $8, FALSE) + FROM serial.distribution + WHERE subscription = ssub.id + )x) + ) + ELSE NULL + END + ) + FROM serial.subscription ssub + WHERE id = $1 + GROUP BY id, start_date, end_date, expected_date_offset, owning_lib; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name distribution, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@sdist/' || id AS id, + 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number, + 'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number, + unit_label_prefix, label, unit_label_suffix, summary_method + ), + unapi.aou( holding_lib, $2, 'holding_lib', array_remove($4,'sdist'), $5, $6, $7, $8), + 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, + CASE + WHEN ('sstr' = ANY ($4)) THEN + XMLELEMENT( name streams, + (SELECT XMLAGG(sstr) FROM ( + SELECT unapi.sstr( id, 'xml', 'stream', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) + FROM serial.stream + WHERE distribution = sdist.id + )x) + ) + ELSE NULL + END, + XMLELEMENT( name summaries, + CASE + WHEN ('sbsum' = ANY ($4)) THEN + (SELECT XMLAGG(sbsum) FROM ( + SELECT unapi.sbsum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) + FROM serial.basic_summary + WHERE distribution = sdist.id + )x) + ELSE NULL + END, + CASE + WHEN ('sisum' = ANY ($4)) THEN + (SELECT XMLAGG(sisum) FROM ( + SELECT unapi.sisum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) + FROM serial.index_summary + WHERE distribution = sdist.id + )x) + ELSE NULL + END, + CASE + WHEN ('sssum' = ANY ($4)) THEN + (SELECT XMLAGG(sssum) FROM ( + SELECT unapi.sssum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) + FROM serial.supplement_summary + WHERE distribution = sdist.id + )x) + ELSE NULL + END + ) + ) + FROM serial.distribution sdist + WHERE id = $1 + GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name stream, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@sstr/' || id AS id, + routing_label + ), + 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, + CASE + WHEN ('sitem' = ANY ($4)) THEN + XMLELEMENT( name items, + (SELECT XMLAGG(sitem) FROM ( + SELECT unapi.sitem( id, 'xml', 'serial_item', array_remove($4,'sstr'), $5, $6, $7, $8, FALSE) + FROM serial.item + WHERE stream = sstr.id + )x) + ) + ELSE NULL + END + ) + FROM serial.stream sstr + WHERE id = $1 + GROUP BY id, routing_label, distribution; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name issuance, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@siss/' || id AS id, + create_date, edit_date, label, date_published, + holding_code, holding_type, holding_link_id + ), + 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, + CASE + WHEN ('sitem' = ANY ($4)) THEN + XMLELEMENT( name items, + (SELECT XMLAGG(sitem) FROM ( + SELECT unapi.sitem( id, 'xml', 'serial_item', array_remove($4,'siss'), $5, $6, $7, $8, FALSE) + FROM serial.item + WHERE issuance = sstr.id + )x) + ) + ELSE NULL + END + ) + FROM serial.issuance sstr + WHERE id = $1 + GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name serial_item, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@sitem/' || id AS id, + 'tag:open-ils.org:U2@siss/' || issuance AS issuance, + date_expected, date_received + ), + 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, + 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, + 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, + 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 +-- XMLELEMENT( name notes, +-- CASE +-- WHEN ('acpn' = ANY ($4)) THEN +-- (SELECT XMLAGG(acpn) FROM ( +-- SELECT unapi.acpn( id, 'xml', 'copy_note', array_remove($4,'acp'), $5, $6, $7, $8) +-- FROM asset.copy_note +-- WHERE owning_copy = cp.id AND pub +-- )x) +-- ELSE NULL +-- END +-- ) + ) + FROM serial.item sitem + WHERE id = $1; +$F$ LANGUAGE SQL STABLE; + + +CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name serial_summary, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@sbsum/' || id AS id, + 'sssum' AS type, generated_coverage, textual_holdings, show_generated + ), + CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END + ) + FROM serial.supplement_summary ssum + WHERE id = $1 + GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name serial_summary, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@sbsum/' || id AS id, + 'sbsum' AS type, generated_coverage, textual_holdings, show_generated + ), + CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END + ) + FROM serial.basic_summary ssum + WHERE id = $1 + GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name serial_summary, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@sbsum/' || id AS id, + 'sisum' AS type, generated_coverage, textual_holdings, show_generated + ), + CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END + ) + FROM serial.index_summary ssum + WHERE id = $1 + GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name monograph_part, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@bmp/' || id AS id, + id AS ident, + label, + label_sortkey, + 'tag:open-ils.org:U2@bre/' || record AS record + ), + CASE + WHEN ('acp' = ANY ($4)) THEN + XMLELEMENT( name copies, + (SELECT XMLAGG(acp) FROM ( + SELECT unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'bmp'), $5, $6, $7, $8, FALSE) + FROM asset.copy cp + JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id) + WHERE cpm.part = $1 + AND cp.deleted IS FALSE + ORDER BY COALESCE(cp.copy_number,0), cp.barcode + LIMIT ($7 -> 'acp')::INT + OFFSET ($8 -> 'acp')::INT + + )x) + ) + ELSE NULL + END, + CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', array_remove($4,'bmp'), $5, $6, $7, $8, FALSE) ELSE NULL END + ) + FROM biblio.monograph_part + WHERE NOT deleted AND id = $1 + GROUP BY id, label, label_sortkey, record; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name copy, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id, + create_date, edit_date, copy_number, circulate, deposit, + ref, holdable, deleted, deposit_amount, price, barcode, + circ_modifier, circ_as_type, opac_visible, age_protect + ), + unapi.ccs( status, $2, 'status', array_remove($4,'acp'), $5, $6, $7, $8, FALSE), + unapi.acl( location, $2, 'location', array_remove($4,'acp'), $5, $6, $7, $8, FALSE), + unapi.aou( circ_lib, $2, 'circ_lib', array_remove($4,'acp'), $5, $6, $7, $8), + unapi.aou( circ_lib, $2, 'circlib', array_remove($4,'acp'), $5, $6, $7, $8), + 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, + CASE + WHEN ('acpn' = ANY ($4)) THEN + XMLELEMENT( name copy_notes, + (SELECT XMLAGG(acpn) FROM ( + SELECT unapi.acpn( id, 'xml', 'copy_note', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) + FROM asset.copy_note + WHERE owning_copy = cp.id AND pub + )x) + ) + ELSE NULL + END, + CASE + WHEN ('ascecm' = ANY ($4)) THEN + XMLELEMENT( name statcats, + (SELECT XMLAGG(ascecm) FROM ( + SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) + FROM asset.stat_cat_entry_copy_map + WHERE owning_copy = cp.id + )x) + ) + ELSE NULL + END, + CASE + WHEN ('bre' = ANY ($4)) THEN + XMLELEMENT( name foreign_records, + (SELECT XMLAGG(bre) FROM ( + SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE) + FROM biblio.peer_bib_copy_map + WHERE target_copy = cp.id + )x) + + ) + ELSE NULL + END, + CASE + WHEN ('bmp' = ANY ($4)) THEN + XMLELEMENT( name monograph_parts, + (SELECT XMLAGG(bmp) FROM ( + SELECT unapi.bmp( part, 'xml', 'monograph_part', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) + FROM asset.copy_part_map + WHERE target_copy = cp.id + )x) + ) + ELSE NULL + END, + CASE + WHEN ('circ' = ANY ($4)) THEN + XMLELEMENT( name current_circulation, + (SELECT XMLAGG(circ) FROM ( + SELECT unapi.circ( id, 'xml', 'circ', array_remove($4,'circ'), $5, $6, $7, $8, FALSE) + FROM action.circulation + WHERE target_copy = cp.id + AND checkin_time IS NULL + )x) + ) + ELSE NULL + END + ) + FROM asset.copy cp + WHERE id = $1 + AND cp.deleted IS FALSE + 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, age_protect; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name serial_unit, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id, + create_date, edit_date, copy_number, circulate, deposit, + ref, holdable, deleted, deposit_amount, price, barcode, + circ_modifier, circ_as_type, opac_visible, age_protect, + status_changed_time, floating, mint_condition, + detailed_contents, sort_key, summary_contents, cost + ), + unapi.ccs( status, $2, 'status', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE), + unapi.acl( location, $2, 'location', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE), + unapi.aou( circ_lib, $2, 'circ_lib', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8), + unapi.aou( circ_lib, $2, 'circlib', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8), + 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, + XMLELEMENT( name copy_notes, + CASE + WHEN ('acpn' = ANY ($4)) THEN + (SELECT XMLAGG(acpn) FROM ( + SELECT unapi.acpn( id, 'xml', 'copy_note', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE) + FROM asset.copy_note + WHERE owning_copy = cp.id AND pub + )x) + ELSE NULL + END + ), + XMLELEMENT( name statcats, + CASE + WHEN ('ascecm' = ANY ($4)) THEN + (SELECT XMLAGG(ascecm) FROM ( + SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) + FROM asset.stat_cat_entry_copy_map + WHERE owning_copy = cp.id + )x) + ELSE NULL + END + ), + XMLELEMENT( name foreign_records, + CASE + WHEN ('bre' = ANY ($4)) THEN + (SELECT XMLAGG(bre) FROM ( + SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE) + FROM biblio.peer_bib_copy_map + WHERE target_copy = cp.id + )x) + ELSE NULL + END + ), + CASE + WHEN ('bmp' = ANY ($4)) THEN + XMLELEMENT( name monograph_parts, + (SELECT XMLAGG(bmp) FROM ( + SELECT unapi.bmp( part, 'xml', 'monograph_part', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) + FROM asset.copy_part_map + WHERE target_copy = cp.id + )x) + ) + ELSE NULL + END, + CASE + WHEN ('circ' = ANY ($4)) THEN + XMLELEMENT( name current_circulation, + (SELECT XMLAGG(circ) FROM ( + SELECT unapi.circ( id, 'xml', 'circ', array_remove($4,'circ'), $5, $6, $7, $8, FALSE) + FROM action.circulation + WHERE target_copy = cp.id + AND checkin_time IS NULL + )x) + ) + ELSE NULL + END + ) + FROM serial.unit cp + WHERE id = $1 + AND cp.deleted IS FALSE + GROUP BY id, status, location, circ_lib, call_number, create_date, + edit_date, copy_number, circulate, floating, mint_condition, + 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, age_protect; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name volume, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@acn/' || acn.id AS id, + acn.id AS vol_id, o.shortname AS lib, + o.opac_visible AS opac_visible, + deleted, label, label_sortkey, label_class, record + ), + unapi.aou( owning_lib, $2, 'owning_lib', array_remove($4,'acn'), $5, $6, $7, $8), + CASE + WHEN ('acp' = ANY ($4)) THEN + CASE WHEN $6 IS NOT NULL THEN + XMLELEMENT( name copies, + (SELECT XMLAGG(acp ORDER BY rank_avail) FROM ( + SELECT unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'acn'), $5, $6, $7, $8, FALSE), + evergreen.rank_cp(cp) AS rank_avail + FROM asset.copy cp + JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id) + WHERE cp.call_number = acn.id + AND cp.deleted IS FALSE + ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode + LIMIT ($7 -> 'acp')::INT + OFFSET ($8 -> 'acp')::INT + )x) + ) + ELSE + XMLELEMENT( name copies, + (SELECT XMLAGG(acp ORDER BY rank_avail) FROM ( + SELECT unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'acn'), $5, $6, $7, $8, FALSE), + evergreen.rank_cp(cp) AS rank_avail + FROM asset.copy cp + JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id) + WHERE cp.call_number = acn.id + AND cp.deleted IS FALSE + ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode + LIMIT ($7 -> 'acp')::INT + OFFSET ($8 -> 'acp')::INT + )x) + ) + END + ELSE NULL + END, + XMLELEMENT( + name uris, + (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) + ), + unapi.acnp( acn.prefix, 'marcxml', 'prefix', array_remove($4,'acn'), $5, $6, $7, $8, FALSE), + unapi.acns( acn.suffix, 'marcxml', 'suffix', array_remove($4,'acn'), $5, $6, $7, $8, FALSE), + CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', array_remove($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END + ) AS x + FROM asset.call_number acn + JOIN actor.org_unit o ON (o.id = acn.owning_lib) + WHERE acn.id = $1 + AND acn.deleted IS FALSE + GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name uri, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@auri/' || uri.id AS id, + use_restriction, + href, + label + ), + CASE + WHEN ('acn' = ANY ($4)) THEN + XMLELEMENT( name copies, + (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) + ) + ELSE NULL + END + ) AS x + FROM asset.uri uri + WHERE uri.id = $1 + GROUP BY uri.id, use_restriction, href, label; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.circ (obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ + SELECT XMLELEMENT( + name circ, + XMLATTRIBUTES( + CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + 'tag:open-ils.org:U2@circ/' || id AS id, + xact_start, + due_date + ), + 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, + 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 + ) + FROM action.circulation + WHERE id = $1; +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.mmr_holdings_xml ( + mid BIGINT, + ouid INT, + org TEXT, + depth INT DEFAULT NULL, + includes TEXT[] DEFAULT NULL::TEXT[], + slimit HSTORE DEFAULT NULL, + soffset HSTORE DEFAULT NULL, + include_xmlns BOOL DEFAULT TRUE, + pref_lib INT DEFAULT NULL +) +RETURNS XML AS $F$ + SELECT XMLELEMENT( + name holdings, + XMLATTRIBUTES( + CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, + CASE WHEN ('mmr' = ANY ($5)) THEN 'tag:open-ils.org:U2@mmr/' || $1 || '/' || $3 ELSE NULL END AS id, + (SELECT metarecord_has_holdable_copy FROM asset.metarecord_has_holdable_copy($1)) AS has_holdable + ), + XMLELEMENT( + name counts, + (SELECT XMLAGG(XMLELEMENT::XML) FROM ( + SELECT XMLELEMENT( + name count, + XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) + )::text + FROM asset.opac_ou_metarecord_copy_count($2, $1) + UNION + SELECT XMLELEMENT( + name count, + XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) + )::text + FROM asset.staff_ou_metarecord_copy_count($2, $1) + UNION + SELECT XMLELEMENT( + name count, + XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) + )::text + FROM asset.opac_ou_metarecord_copy_count($9, $1) + ORDER BY 1 + )x) + ), + -- XXX monograph_parts and foreign_copies are skipped in MRs ... put them back some day? + XMLELEMENT( + name volumes, + (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM ( + -- Physical copies + 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 + FROM evergreen.ranked_volumes((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $4, $6, $7, $9, $5) AS y + UNION ALL + -- Located URIs + 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 + FROM evergreen.located_uris((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $9) AS uris + )x) + ), + CASE WHEN ('ssub' = ANY ($5)) THEN + XMLELEMENT( + name subscriptions, + (SELECT XMLAGG(ssub) FROM ( + SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE) + FROM serial.subscription + WHERE record_entry IN (SELECT source FROM metabib.metarecord_source_map WHERE metarecord = $1) + )x) + ) + ELSE NULL END + ); +$F$ LANGUAGE SQL STABLE; + +CREATE OR REPLACE FUNCTION unapi.mmr ( + obj_id BIGINT, + format TEXT, + ename TEXT, + includes TEXT[], + org TEXT, + depth INT DEFAULT NULL, + slimit HSTORE DEFAULT NULL, + soffset HSTORE DEFAULT NULL, + include_xmlns BOOL DEFAULT TRUE, + pref_lib INT DEFAULT NULL +) +RETURNS XML AS $F$ +DECLARE + mmrec metabib.metarecord%ROWTYPE; + leadrec biblio.record_entry%ROWTYPE; + subrec biblio.record_entry%ROWTYPE; + layout unapi.bre_output_layout%ROWTYPE; + xfrm config.xml_transform%ROWTYPE; + ouid INT; + xml_buf TEXT; -- growing XML document + tmp_xml TEXT; -- single-use XML string + xml_frag TEXT; -- single-use XML fragment + top_el TEXT; + output XML; + hxml XML; + axml XML; + subxml XML; -- subordinate records elements + sub_xpath TEXT; + parts TEXT[]; +BEGIN + + -- xpath for extracting bre.marc values from subordinate records + -- so they may be appended to the MARC of the master record prior + -- to XSLT processing. + -- subjects, isbn, issn, upc -- anything else? + sub_xpath := + '//*[starts-with(@tag, "6") or @tag="020" or @tag="022" or @tag="024"]'; + + IF org = '-' OR org IS NULL THEN + SELECT shortname INTO org FROM evergreen.org_top(); + END IF; + + SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org; + + IF ouid IS NULL THEN + RETURN NULL::XML; + END IF; + + SELECT INTO mmrec * FROM metabib.metarecord WHERE id = obj_id; + IF NOT FOUND THEN + RETURN NULL::XML; + END IF; + + -- TODO: aggregate holdings from constituent records + IF format = 'holdings_xml' THEN -- the special case + output := unapi.mmr_holdings_xml( + obj_id, ouid, org, depth, + array_remove(includes,'holdings_xml'), + slimit, soffset, include_xmlns, pref_lib); + RETURN output; + END IF; + + SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format; + + IF layout.name IS NULL THEN + RETURN NULL::XML; + END IF; + + SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform; + + SELECT INTO leadrec * FROM biblio.record_entry WHERE id = mmrec.master_record; + + -- Grab distinct MVF for all records if requested + IF ('mra' = ANY (includes)) THEN + axml := unapi.mmr_mra(obj_id,NULL,NULL,NULL,org,depth,NULL,NULL,TRUE,pref_lib); + ELSE + axml := NULL::XML; + END IF; + + xml_buf = leadrec.marc; + + hxml := NULL::XML; + IF ('holdings_xml' = ANY (includes)) THEN + hxml := unapi.mmr_holdings_xml( + obj_id, ouid, org, depth, + array_remove(includes,'holdings_xml'), + slimit, soffset, include_xmlns, pref_lib); + END IF; + + subxml := NULL::XML; + parts := '{}'::TEXT[]; + FOR subrec IN SELECT bre.* FROM biblio.record_entry bre + JOIN metabib.metarecord_source_map mmsm ON (mmsm.source = bre.id) + JOIN metabib.metarecord mmr ON (mmr.id = mmsm.metarecord) + WHERE mmr.id = obj_id AND NOT bre.deleted + ORDER BY CASE WHEN bre.id = mmr.master_record THEN 0 ELSE bre.id END + LIMIT COALESCE((slimit->'bre')::INT, 5) LOOP + + IF subrec.id = leadrec.id THEN CONTINUE; END IF; + -- Append choice data from the the non-lead records to the + -- the lead record document + + parts := parts || xpath(sub_xpath, subrec.marc::XML)::TEXT[]; + END LOOP; + + SELECT ARRAY_TO_STRING( ARRAY_AGG( DISTINCT p ), '' )::XML INTO subxml FROM UNNEST(parts) p; + + -- append data from the subordinate records to the + -- main record document before applying the XSLT + + IF subxml IS NOT NULL THEN + xml_buf := REGEXP_REPLACE(xml_buf, + '(.*?)$', subxml || '' || E'\\1'); + END IF; + + IF format = 'marcxml' THEN + -- If we're not using the prefixed namespace in + -- this record, then remove all declarations of it + IF xml_buf !~ E' mods) + top_el := REGEXP_REPLACE(xml_buf, E'^.*?<((?:\\S+:)?' || + layout.holdings_element || ').*$', E'\\1'); + + IF axml IS NOT NULL THEN + xml_buf := REGEXP_REPLACE(xml_buf, + '(.*?)$', axml || '\\1'); + END IF; + + IF hxml IS NOT NULL THEN + xml_buf := REGEXP_REPLACE(xml_buf, + '(.*?)$', hxml || '\\1'); + END IF; + + IF ('mmr.unapi' = ANY (includes)) THEN + output := REGEXP_REPLACE( + xml_buf, + '(.*?)', + XMLELEMENT( + name abbr, + XMLATTRIBUTES( + 'http://www.w3.org/1999/xhtml' AS xmlns, + 'unapi-id' AS class, + 'tag:open-ils.org:U2@mmr/' || obj_id || '/' || org AS title + ) + )::TEXT || '\\1' + ); + ELSE + output := xml_buf; + END IF; + + -- remove ignorable whitesace + output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML; + RETURN output; +END; +$F$ LANGUAGE PLPGSQL STABLE; + +CREATE OR REPLACE FUNCTION authority.extract_headings(marc TEXT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$ +DECLARE + idx authority.heading_field%ROWTYPE; + xfrm config.xml_transform%ROWTYPE; + prev_xfrm TEXT; + transformed_xml TEXT; + heading_node TEXT; + heading_node_list TEXT[]; + component_node TEXT; + component_node_list TEXT[]; + raw_text TEXT; + normalized_text TEXT; + normalizer RECORD; + curr_text TEXT; + joiner TEXT; + type_value TEXT; + base_thesaurus TEXT := NULL; + output_row authority.heading; +BEGIN + + -- Loop over the indexing entries + FOR idx IN SELECT * FROM authority.heading_field WHERE restrict IS NULL OR id = ANY (restrict) ORDER BY format LOOP + + output_row.field := idx.id; + output_row.type := idx.heading_type; + output_row.purpose := idx.heading_purpose; + + joiner := COALESCE(idx.joiner, ' '); + + SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format; + + -- See if we can skip the XSLT ... it's expensive + IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN + -- Can't skip the transform + IF xfrm.xslt <> '---' THEN + transformed_xml := oils_xslt_process(marc, xfrm.xslt); + ELSE + transformed_xml := marc; + END IF; + + prev_xfrm := xfrm.name; + END IF; + + IF idx.thesaurus_xpath IS NOT NULL THEN + base_thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + END IF; + + heading_node_list := oils_xpath( idx.heading_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + + FOR heading_node IN SELECT x FROM unnest(heading_node_list) AS x LOOP + + CONTINUE WHEN heading_node !~ E'^\\s*<'; + + output_row.variant_type := NULL; + output_row.related_type := NULL; + output_row.thesaurus := NULL; + output_row.heading := NULL; + + IF idx.heading_purpose = 'variant' AND idx.type_xpath IS NOT NULL THEN + type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + BEGIN + output_row.variant_type := type_value; + EXCEPTION WHEN invalid_text_representation THEN + RAISE NOTICE 'Do not recognize variant heading type %', type_value; + END; + END IF; + IF idx.heading_purpose = 'related' AND idx.type_xpath IS NOT NULL THEN + type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + BEGIN + output_row.related_type := type_value; + EXCEPTION WHEN invalid_text_representation THEN + RAISE NOTICE 'Do not recognize related heading type %', type_value; + END; + END IF; + + IF idx.thesaurus_override_xpath IS NOT NULL THEN + output_row.thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_override_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + END IF; + IF output_row.thesaurus IS NULL THEN + output_row.thesaurus := base_thesaurus; + END IF; + + raw_text := NULL; + + -- now iterate over components of heading + component_node_list := oils_xpath( idx.component_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + FOR component_node IN SELECT x FROM unnest(component_node_list) AS x LOOP + -- XXX much of this should be moved into oils_xpath_string... + curr_text := ARRAY_TO_STRING(array_remove(array_remove( + oils_xpath( '//text()', -- get the content of all the nodes within the main selected node + REGEXP_REPLACE( component_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space + ), ' '), ''), -- throw away morally empty (bankrupt?) strings + joiner + ); + + CONTINUE WHEN curr_text IS NULL OR curr_text = ''; + + IF raw_text IS NOT NULL THEN + raw_text := raw_text || joiner; + END IF; + + raw_text := COALESCE(raw_text,'') || curr_text; + END LOOP; + + IF raw_text IS NOT NULL THEN + output_row.heading := raw_text; + normalized_text := raw_text; + + FOR normalizer IN + SELECT n.func AS func, + n.param_count AS param_count, + m.params AS params + FROM config.index_normalizer n + JOIN authority.heading_field_norm_map m ON (m.norm = n.id) + WHERE m.field = idx.id + ORDER BY m.pos LOOP + + EXECUTE 'SELECT ' || normalizer.func || '(' || + quote_literal( normalized_text ) || + CASE + WHEN normalizer.param_count > 0 + THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'') + ELSE '' + END || + ')' INTO normalized_text; + + END LOOP; + + output_row.normalized_heading := normalized_text; + + RETURN NEXT output_row; + END IF; + END LOOP; + + END LOOP; +END; +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION authority.extract_headings(rid BIGINT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$ +DECLARE + auth authority.record_entry%ROWTYPE; + output_row authority.heading; +BEGIN + -- Get the record + SELECT INTO auth * FROM authority.record_entry WHERE id = rid; + + RETURN QUERY SELECT * FROM authority.extract_headings(auth.marc, restrict); +END; +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( + rid BIGINT, + default_joiner TEXT, + field_types TEXT[], + only_fields INT[] +) RETURNS SETOF metabib.field_entry_template AS $func$ +DECLARE + bib biblio.record_entry%ROWTYPE; + idx config.metabib_field%ROWTYPE; + xfrm config.xml_transform%ROWTYPE; + prev_xfrm TEXT; + transformed_xml TEXT; + xml_node TEXT; + xml_node_list TEXT[]; + facet_text TEXT; + display_text TEXT; + browse_text TEXT; + sort_value TEXT; + raw_text TEXT; + curr_text TEXT; + joiner TEXT := default_joiner; -- XXX will index defs supply a joiner? + authority_text TEXT; + authority_link BIGINT; + output_row metabib.field_entry_template%ROWTYPE; + process_idx BOOL; +BEGIN + + -- Start out with no field-use bools set + output_row.browse_field = FALSE; + output_row.facet_field = FALSE; + output_row.display_field = FALSE; + output_row.search_field = FALSE; + + -- Get the record + SELECT INTO bib * FROM biblio.record_entry WHERE id = rid; + + -- Loop over the indexing entries + FOR idx IN SELECT * FROM config.metabib_field WHERE id = ANY (only_fields) ORDER BY format LOOP + CONTINUE WHEN idx.xpath IS NULL OR idx.xpath = ''; -- pure virtual field + + process_idx := FALSE; + IF idx.display_field AND 'display' = ANY (field_types) THEN process_idx = TRUE; END IF; + IF idx.browse_field AND 'browse' = ANY (field_types) THEN process_idx = TRUE; END IF; + IF idx.search_field AND 'search' = ANY (field_types) THEN process_idx = TRUE; END IF; + IF idx.facet_field AND 'facet' = ANY (field_types) THEN process_idx = TRUE; END IF; + CONTINUE WHEN process_idx = FALSE; -- disabled for all types + + joiner := COALESCE(idx.joiner, default_joiner); + + SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format; + + -- See if we can skip the XSLT ... it's expensive + IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN + -- Can't skip the transform + IF xfrm.xslt <> '---' THEN + transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt); + ELSE + transformed_xml := bib.marc; + END IF; + + prev_xfrm := xfrm.name; + END IF; + + xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + + raw_text := NULL; + FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP + CONTINUE WHEN xml_node !~ E'^\\s*<'; + + -- XXX much of this should be moved into oils_xpath_string... + curr_text := ARRAY_TO_STRING(array_remove(array_remove( + oils_xpath( '//text()', -- get the content of all the nodes within the main selected node + REGEXP_REPLACE( xml_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space + ), ' '), ''), -- throw away morally empty (bankrupt?) strings + joiner + ); + + CONTINUE WHEN curr_text IS NULL OR curr_text = ''; + + IF raw_text IS NOT NULL THEN + raw_text := raw_text || joiner; + END IF; + + raw_text := COALESCE(raw_text,'') || curr_text; + + -- autosuggest/metabib.browse_entry + IF idx.browse_field THEN + + IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN + browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + ELSE + browse_text := curr_text; + END IF; + + IF idx.browse_sort_xpath IS NOT NULL AND + idx.browse_sort_xpath <> '' THEN + + sort_value := oils_xpath_string( + idx.browse_sort_xpath, xml_node, joiner, + ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] + ); + ELSE + sort_value := browse_text; + END IF; + + output_row.field_class = idx.field_class; + output_row.field = idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g')); + output_row.sort_value := + public.naco_normalize(sort_value); + + output_row.authority := NULL; + + IF idx.authority_xpath IS NOT NULL AND idx.authority_xpath <> '' THEN + authority_text := oils_xpath_string( + idx.authority_xpath, xml_node, joiner, + ARRAY[ + ARRAY[xfrm.prefix, xfrm.namespace_uri], + ARRAY['xlink','http://www.w3.org/1999/xlink'] + ] + ); + + IF authority_text ~ '^\d+$' THEN + authority_link := authority_text::BIGINT; + PERFORM * FROM authority.record_entry WHERE id = authority_link; + IF FOUND THEN + output_row.authority := authority_link; + END IF; + END IF; + + END IF; + + output_row.browse_field = TRUE; + -- Returning browse rows with search_field = true for search+browse + -- configs allows us to retain granularity of being able to search + -- browse fields with "starts with" type operators (for example, for + -- titles of songs in music albums) + IF idx.search_field THEN + output_row.search_field = TRUE; + END IF; + RETURN NEXT output_row; + output_row.browse_field = FALSE; + output_row.search_field = FALSE; + output_row.sort_value := NULL; + END IF; + + -- insert raw node text for faceting + IF idx.facet_field THEN + + IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN + facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + ELSE + facet_text := curr_text; + END IF; + + output_row.field_class = idx.field_class; + output_row.field = -1 * idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g')); + + output_row.facet_field = TRUE; + RETURN NEXT output_row; + output_row.facet_field = FALSE; + END IF; + + -- insert raw node text for display + IF idx.display_field THEN + + IF idx.display_xpath IS NOT NULL AND idx.display_xpath <> '' THEN + display_text := oils_xpath_string( idx.display_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + ELSE + display_text := curr_text; + END IF; + + output_row.field_class = idx.field_class; + output_row.field = -1 * idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(display_text, E'\\s+', ' ', 'g')); + + output_row.display_field = TRUE; + RETURN NEXT output_row; + output_row.display_field = FALSE; + END IF; + + END LOOP; + + CONTINUE WHEN raw_text IS NULL OR raw_text = ''; + + -- insert combined node text for searching + IF idx.search_field THEN + output_row.field_class = idx.field_class; + output_row.field = idx.id; + output_row.source = rid; + output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g')); + + output_row.search_field = TRUE; + RETURN NEXT output_row; + output_row.search_field = FALSE; + END IF; + + END LOOP; + +END; +$func$ LANGUAGE PLPGSQL; + +-- We no longer need the custom function +DROP FUNCTION evergreen.array_remove_item_by_value(ANYARRAY, ANYELEMENT); + +COMMIT; + diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.function.builtin_array_remove.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.function.builtin_array_remove.sql deleted file mode 100644 index f1f5e60f12..0000000000 --- a/Open-ILS/src/sql/Pg/upgrade/XXXX.function.builtin_array_remove.sql +++ /dev/null @@ -1,1350 +0,0 @@ -BEGIN; - -SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); - -CREATE OR REPLACE FUNCTION unapi.bre ( - obj_id BIGINT, - format TEXT, - ename TEXT, - includes TEXT[], - org TEXT, - depth INT DEFAULT NULL, - slimit HSTORE DEFAULT NULL, - soffset HSTORE DEFAULT NULL, - include_xmlns BOOL DEFAULT TRUE, - pref_lib INT DEFAULT NULL -) -RETURNS XML AS $F$ -DECLARE - me biblio.record_entry%ROWTYPE; - layout unapi.bre_output_layout%ROWTYPE; - xfrm config.xml_transform%ROWTYPE; - ouid INT; - tmp_xml TEXT; - top_el TEXT; - output XML; - hxml XML; - axml XML; - source XML; -BEGIN - - IF org = '-' OR org IS NULL THEN - SELECT shortname INTO org FROM evergreen.org_top(); - END IF; - - SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org; - - IF ouid IS NULL THEN - RETURN NULL::XML; - END IF; - - IF format = 'holdings_xml' THEN -- the special case - output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns); - RETURN output; - END IF; - - SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format; - - IF layout.name IS NULL THEN - RETURN NULL::XML; - END IF; - - SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform; - - SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id; - - -- grab bib_source, if any - IF ('cbs' = ANY (includes) AND me.source IS NOT NULL) THEN - source := unapi.cbs(me.source,NULL,NULL,NULL,NULL); - ELSE - source := NULL::XML; - END IF; - - -- grab SVF if we need them - IF ('mra' = ANY (includes)) THEN - axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL); - ELSE - axml := NULL::XML; - END IF; - - -- grab holdings if we need them - IF ('holdings_xml' = ANY (includes)) THEN - hxml := unapi.holdings_xml(obj_id, ouid, org, depth, array_remove(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib); - ELSE - hxml := NULL::XML; - END IF; - - - -- generate our item node - - - IF format = 'marcxml' THEN - tmp_xml := me.marc; - IF tmp_xml !~ E'(.*?)$', source || '\\1'); - END IF; - - IF axml IS NOT NULL THEN - tmp_xml := REGEXP_REPLACE(tmp_xml, '(.*?)$', axml || '\\1'); - END IF; - - IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position? - tmp_xml := REGEXP_REPLACE(tmp_xml, '(.*?)$', hxml || '\\1'); - END IF; - - IF ('bre.unapi' = ANY (includes)) THEN - output := REGEXP_REPLACE( - tmp_xml, - '(.*?)', - XMLELEMENT( - name abbr, - XMLATTRIBUTES( - 'http://www.w3.org/1999/xhtml' AS xmlns, - 'unapi-id' AS class, - 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title - ) - )::TEXT || '\\1' - ); - ELSE - output := tmp_xml; - END IF; - - IF ('bre.extern' = ANY (includes)) THEN - output := REGEXP_REPLACE( - tmp_xml, - '(.*?)', - XMLELEMENT( - name extern, - XMLATTRIBUTES( - 'http://open-ils.org/spec/biblio/v1' AS xmlns, - me.creator AS creator, - me.editor AS editor, - me.create_date AS create_date, - me.edit_date AS edit_date, - me.quality AS quality, - me.fingerprint AS fingerprint, - me.tcn_source AS tcn_source, - me.tcn_value AS tcn_value, - me.owner AS owner, - me.share_depth AS share_depth, - me.active AS active, - me.deleted AS deleted - ) - )::TEXT || '\\1' - ); - ELSE - output := tmp_xml; - END IF; - - output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML; - RETURN output; -END; -$F$ LANGUAGE PLPGSQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.holdings_xml ( - bid BIGINT, - ouid INT, - org TEXT, - depth INT DEFAULT NULL, - includes TEXT[] DEFAULT NULL::TEXT[], - slimit HSTORE DEFAULT NULL, - soffset HSTORE DEFAULT NULL, - include_xmlns BOOL DEFAULT TRUE, - pref_lib INT DEFAULT NULL -) -RETURNS XML AS $F$ - SELECT XMLELEMENT( - name holdings, - XMLATTRIBUTES( - CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id, - (SELECT record_has_holdable_copy FROM asset.record_has_holdable_copy($1)) AS has_holdable - ), - XMLELEMENT( - name counts, - (SELECT XMLAGG(XMLELEMENT::XML) FROM ( - SELECT XMLELEMENT( - name count, - XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) - )::text - FROM asset.opac_ou_record_copy_count($2, $1) - UNION - SELECT XMLELEMENT( - name count, - XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) - )::text - FROM asset.staff_ou_record_copy_count($2, $1) - UNION - SELECT XMLELEMENT( - name count, - XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) - )::text - FROM asset.opac_ou_record_copy_count($9, $1) - ORDER BY 1 - )x) - ), - CASE - WHEN ('bmp' = ANY ($5)) THEN - XMLELEMENT( - name monograph_parts, - (SELECT XMLAGG(bmp) FROM ( - SELECT unapi.bmp( id, 'xml', 'monograph_part', array_remove( array_remove($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE) - FROM biblio.monograph_part - WHERE NOT deleted AND record = $1 - )x) - ) - ELSE NULL - END, - XMLELEMENT( - name volumes, - (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM ( - -- Physical copies - 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 - FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9, $5) AS y - UNION ALL - -- Located URIs - 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 - FROM evergreen.located_uris($1, $2, $9) AS uris - )x) - ), - CASE WHEN ('ssub' = ANY ($5)) THEN - XMLELEMENT( - name subscriptions, - (SELECT XMLAGG(ssub) FROM ( - SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE) - FROM serial.subscription - WHERE record_entry = $1 - )x) - ) - ELSE NULL END, - CASE WHEN ('acp' = ANY ($5)) THEN - XMLELEMENT( - name foreign_copies, - (SELECT XMLAGG(acp) FROM ( - SELECT unapi.acp(p.target_copy,'xml','copy',array_remove($5,'acp'), $3, $4, $6, $7, FALSE) - FROM biblio.peer_bib_copy_map p - JOIN asset.copy c ON (p.target_copy = c.id) - WHERE NOT c.deleted AND p.peer_record = $1 - LIMIT ($6 -> 'acp')::INT - OFFSET ($7 -> 'acp')::INT - )x) - ) - ELSE NULL END - ); -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name subscription, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@ssub/' || id AS id, - 'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib, - start_date AS start, end_date AS end, expected_date_offset - ), - CASE - WHEN ('sdist' = ANY ($4)) THEN - XMLELEMENT( name distributions, - (SELECT XMLAGG(sdist) FROM ( - SELECT unapi.sdist( id, 'xml', 'distribution', array_remove($4,'ssub'), $5, $6, $7, $8, FALSE) - FROM serial.distribution - WHERE subscription = ssub.id - )x) - ) - ELSE NULL - END - ) - FROM serial.subscription ssub - WHERE id = $1 - GROUP BY id, start_date, end_date, expected_date_offset, owning_lib; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name distribution, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@sdist/' || id AS id, - 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number, - 'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number, - unit_label_prefix, label, unit_label_suffix, summary_method - ), - unapi.aou( holding_lib, $2, 'holding_lib', array_remove($4,'sdist'), $5, $6, $7, $8), - 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, - CASE - WHEN ('sstr' = ANY ($4)) THEN - XMLELEMENT( name streams, - (SELECT XMLAGG(sstr) FROM ( - SELECT unapi.sstr( id, 'xml', 'stream', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) - FROM serial.stream - WHERE distribution = sdist.id - )x) - ) - ELSE NULL - END, - XMLELEMENT( name summaries, - CASE - WHEN ('sbsum' = ANY ($4)) THEN - (SELECT XMLAGG(sbsum) FROM ( - SELECT unapi.sbsum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) - FROM serial.basic_summary - WHERE distribution = sdist.id - )x) - ELSE NULL - END, - CASE - WHEN ('sisum' = ANY ($4)) THEN - (SELECT XMLAGG(sisum) FROM ( - SELECT unapi.sisum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) - FROM serial.index_summary - WHERE distribution = sdist.id - )x) - ELSE NULL - END, - CASE - WHEN ('sssum' = ANY ($4)) THEN - (SELECT XMLAGG(sssum) FROM ( - SELECT unapi.sssum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) - FROM serial.supplement_summary - WHERE distribution = sdist.id - )x) - ELSE NULL - END - ) - ) - FROM serial.distribution sdist - WHERE id = $1 - GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name stream, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@sstr/' || id AS id, - routing_label - ), - 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, - CASE - WHEN ('sitem' = ANY ($4)) THEN - XMLELEMENT( name items, - (SELECT XMLAGG(sitem) FROM ( - SELECT unapi.sitem( id, 'xml', 'serial_item', array_remove($4,'sstr'), $5, $6, $7, $8, FALSE) - FROM serial.item - WHERE stream = sstr.id - )x) - ) - ELSE NULL - END - ) - FROM serial.stream sstr - WHERE id = $1 - GROUP BY id, routing_label, distribution; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name issuance, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@siss/' || id AS id, - create_date, edit_date, label, date_published, - holding_code, holding_type, holding_link_id - ), - 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, - CASE - WHEN ('sitem' = ANY ($4)) THEN - XMLELEMENT( name items, - (SELECT XMLAGG(sitem) FROM ( - SELECT unapi.sitem( id, 'xml', 'serial_item', array_remove($4,'siss'), $5, $6, $7, $8, FALSE) - FROM serial.item - WHERE issuance = sstr.id - )x) - ) - ELSE NULL - END - ) - FROM serial.issuance sstr - WHERE id = $1 - GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name serial_item, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@sitem/' || id AS id, - 'tag:open-ils.org:U2@siss/' || issuance AS issuance, - date_expected, date_received - ), - 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, - 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, - 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, - 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 --- XMLELEMENT( name notes, --- CASE --- WHEN ('acpn' = ANY ($4)) THEN --- (SELECT XMLAGG(acpn) FROM ( --- SELECT unapi.acpn( id, 'xml', 'copy_note', array_remove($4,'acp'), $5, $6, $7, $8) --- FROM asset.copy_note --- WHERE owning_copy = cp.id AND pub --- )x) --- ELSE NULL --- END --- ) - ) - FROM serial.item sitem - WHERE id = $1; -$F$ LANGUAGE SQL STABLE; - - -CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name serial_summary, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@sbsum/' || id AS id, - 'sssum' AS type, generated_coverage, textual_holdings, show_generated - ), - CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END - ) - FROM serial.supplement_summary ssum - WHERE id = $1 - GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name serial_summary, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@sbsum/' || id AS id, - 'sbsum' AS type, generated_coverage, textual_holdings, show_generated - ), - CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END - ) - FROM serial.basic_summary ssum - WHERE id = $1 - GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name serial_summary, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@sbsum/' || id AS id, - 'sisum' AS type, generated_coverage, textual_holdings, show_generated - ), - CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END - ) - FROM serial.index_summary ssum - WHERE id = $1 - GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name monograph_part, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@bmp/' || id AS id, - id AS ident, - label, - label_sortkey, - 'tag:open-ils.org:U2@bre/' || record AS record - ), - CASE - WHEN ('acp' = ANY ($4)) THEN - XMLELEMENT( name copies, - (SELECT XMLAGG(acp) FROM ( - SELECT unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'bmp'), $5, $6, $7, $8, FALSE) - FROM asset.copy cp - JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id) - WHERE cpm.part = $1 - AND cp.deleted IS FALSE - ORDER BY COALESCE(cp.copy_number,0), cp.barcode - LIMIT ($7 -> 'acp')::INT - OFFSET ($8 -> 'acp')::INT - - )x) - ) - ELSE NULL - END, - CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', array_remove($4,'bmp'), $5, $6, $7, $8, FALSE) ELSE NULL END - ) - FROM biblio.monograph_part - WHERE NOT deleted AND id = $1 - GROUP BY id, label, label_sortkey, record; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name copy, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id, - create_date, edit_date, copy_number, circulate, deposit, - ref, holdable, deleted, deposit_amount, price, barcode, - circ_modifier, circ_as_type, opac_visible, age_protect - ), - unapi.ccs( status, $2, 'status', array_remove($4,'acp'), $5, $6, $7, $8, FALSE), - unapi.acl( location, $2, 'location', array_remove($4,'acp'), $5, $6, $7, $8, FALSE), - unapi.aou( circ_lib, $2, 'circ_lib', array_remove($4,'acp'), $5, $6, $7, $8), - unapi.aou( circ_lib, $2, 'circlib', array_remove($4,'acp'), $5, $6, $7, $8), - 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, - CASE - WHEN ('acpn' = ANY ($4)) THEN - XMLELEMENT( name copy_notes, - (SELECT XMLAGG(acpn) FROM ( - SELECT unapi.acpn( id, 'xml', 'copy_note', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) - FROM asset.copy_note - WHERE owning_copy = cp.id AND pub - )x) - ) - ELSE NULL - END, - CASE - WHEN ('ascecm' = ANY ($4)) THEN - XMLELEMENT( name statcats, - (SELECT XMLAGG(ascecm) FROM ( - SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) - FROM asset.stat_cat_entry_copy_map - WHERE owning_copy = cp.id - )x) - ) - ELSE NULL - END, - CASE - WHEN ('bre' = ANY ($4)) THEN - XMLELEMENT( name foreign_records, - (SELECT XMLAGG(bre) FROM ( - SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE) - FROM biblio.peer_bib_copy_map - WHERE target_copy = cp.id - )x) - - ) - ELSE NULL - END, - CASE - WHEN ('bmp' = ANY ($4)) THEN - XMLELEMENT( name monograph_parts, - (SELECT XMLAGG(bmp) FROM ( - SELECT unapi.bmp( part, 'xml', 'monograph_part', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) - FROM asset.copy_part_map - WHERE target_copy = cp.id - )x) - ) - ELSE NULL - END, - CASE - WHEN ('circ' = ANY ($4)) THEN - XMLELEMENT( name current_circulation, - (SELECT XMLAGG(circ) FROM ( - SELECT unapi.circ( id, 'xml', 'circ', array_remove($4,'circ'), $5, $6, $7, $8, FALSE) - FROM action.circulation - WHERE target_copy = cp.id - AND checkin_time IS NULL - )x) - ) - ELSE NULL - END - ) - FROM asset.copy cp - WHERE id = $1 - AND cp.deleted IS FALSE - 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, age_protect; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name serial_unit, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id, - create_date, edit_date, copy_number, circulate, deposit, - ref, holdable, deleted, deposit_amount, price, barcode, - circ_modifier, circ_as_type, opac_visible, age_protect, - status_changed_time, floating, mint_condition, - detailed_contents, sort_key, summary_contents, cost - ), - unapi.ccs( status, $2, 'status', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE), - unapi.acl( location, $2, 'location', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE), - unapi.aou( circ_lib, $2, 'circ_lib', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8), - unapi.aou( circ_lib, $2, 'circlib', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8), - 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, - XMLELEMENT( name copy_notes, - CASE - WHEN ('acpn' = ANY ($4)) THEN - (SELECT XMLAGG(acpn) FROM ( - SELECT unapi.acpn( id, 'xml', 'copy_note', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE) - FROM asset.copy_note - WHERE owning_copy = cp.id AND pub - )x) - ELSE NULL - END - ), - XMLELEMENT( name statcats, - CASE - WHEN ('ascecm' = ANY ($4)) THEN - (SELECT XMLAGG(ascecm) FROM ( - SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) - FROM asset.stat_cat_entry_copy_map - WHERE owning_copy = cp.id - )x) - ELSE NULL - END - ), - XMLELEMENT( name foreign_records, - CASE - WHEN ('bre' = ANY ($4)) THEN - (SELECT XMLAGG(bre) FROM ( - SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE) - FROM biblio.peer_bib_copy_map - WHERE target_copy = cp.id - )x) - ELSE NULL - END - ), - CASE - WHEN ('bmp' = ANY ($4)) THEN - XMLELEMENT( name monograph_parts, - (SELECT XMLAGG(bmp) FROM ( - SELECT unapi.bmp( part, 'xml', 'monograph_part', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) - FROM asset.copy_part_map - WHERE target_copy = cp.id - )x) - ) - ELSE NULL - END, - CASE - WHEN ('circ' = ANY ($4)) THEN - XMLELEMENT( name current_circulation, - (SELECT XMLAGG(circ) FROM ( - SELECT unapi.circ( id, 'xml', 'circ', array_remove($4,'circ'), $5, $6, $7, $8, FALSE) - FROM action.circulation - WHERE target_copy = cp.id - AND checkin_time IS NULL - )x) - ) - ELSE NULL - END - ) - FROM serial.unit cp - WHERE id = $1 - AND cp.deleted IS FALSE - GROUP BY id, status, location, circ_lib, call_number, create_date, - edit_date, copy_number, circulate, floating, mint_condition, - 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, age_protect; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name volume, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@acn/' || acn.id AS id, - acn.id AS vol_id, o.shortname AS lib, - o.opac_visible AS opac_visible, - deleted, label, label_sortkey, label_class, record - ), - unapi.aou( owning_lib, $2, 'owning_lib', array_remove($4,'acn'), $5, $6, $7, $8), - CASE - WHEN ('acp' = ANY ($4)) THEN - CASE WHEN $6 IS NOT NULL THEN - XMLELEMENT( name copies, - (SELECT XMLAGG(acp ORDER BY rank_avail) FROM ( - SELECT unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'acn'), $5, $6, $7, $8, FALSE), - evergreen.rank_cp(cp) AS rank_avail - FROM asset.copy cp - JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id) - WHERE cp.call_number = acn.id - AND cp.deleted IS FALSE - ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode - LIMIT ($7 -> 'acp')::INT - OFFSET ($8 -> 'acp')::INT - )x) - ) - ELSE - XMLELEMENT( name copies, - (SELECT XMLAGG(acp ORDER BY rank_avail) FROM ( - SELECT unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'acn'), $5, $6, $7, $8, FALSE), - evergreen.rank_cp(cp) AS rank_avail - FROM asset.copy cp - JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id) - WHERE cp.call_number = acn.id - AND cp.deleted IS FALSE - ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode - LIMIT ($7 -> 'acp')::INT - OFFSET ($8 -> 'acp')::INT - )x) - ) - END - ELSE NULL - END, - XMLELEMENT( - name uris, - (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) - ), - unapi.acnp( acn.prefix, 'marcxml', 'prefix', array_remove($4,'acn'), $5, $6, $7, $8, FALSE), - unapi.acns( acn.suffix, 'marcxml', 'suffix', array_remove($4,'acn'), $5, $6, $7, $8, FALSE), - CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', array_remove($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END - ) AS x - FROM asset.call_number acn - JOIN actor.org_unit o ON (o.id = acn.owning_lib) - WHERE acn.id = $1 - AND acn.deleted IS FALSE - GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name uri, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@auri/' || uri.id AS id, - use_restriction, - href, - label - ), - CASE - WHEN ('acn' = ANY ($4)) THEN - XMLELEMENT( name copies, - (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) - ) - ELSE NULL - END - ) AS x - FROM asset.uri uri - WHERE uri.id = $1 - GROUP BY uri.id, use_restriction, href, label; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.circ (obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ - SELECT XMLELEMENT( - name circ, - XMLATTRIBUTES( - CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - 'tag:open-ils.org:U2@circ/' || id AS id, - xact_start, - due_date - ), - 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, - 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 - ) - FROM action.circulation - WHERE id = $1; -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.mmr_holdings_xml ( - mid BIGINT, - ouid INT, - org TEXT, - depth INT DEFAULT NULL, - includes TEXT[] DEFAULT NULL::TEXT[], - slimit HSTORE DEFAULT NULL, - soffset HSTORE DEFAULT NULL, - include_xmlns BOOL DEFAULT TRUE, - pref_lib INT DEFAULT NULL -) -RETURNS XML AS $F$ - SELECT XMLELEMENT( - name holdings, - XMLATTRIBUTES( - CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns, - CASE WHEN ('mmr' = ANY ($5)) THEN 'tag:open-ils.org:U2@mmr/' || $1 || '/' || $3 ELSE NULL END AS id, - (SELECT metarecord_has_holdable_copy FROM asset.metarecord_has_holdable_copy($1)) AS has_holdable - ), - XMLELEMENT( - name counts, - (SELECT XMLAGG(XMLELEMENT::XML) FROM ( - SELECT XMLELEMENT( - name count, - XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) - )::text - FROM asset.opac_ou_metarecord_copy_count($2, $1) - UNION - SELECT XMLELEMENT( - name count, - XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) - )::text - FROM asset.staff_ou_metarecord_copy_count($2, $1) - UNION - SELECT XMLELEMENT( - name count, - XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow) - )::text - FROM asset.opac_ou_metarecord_copy_count($9, $1) - ORDER BY 1 - )x) - ), - -- XXX monograph_parts and foreign_copies are skipped in MRs ... put them back some day? - XMLELEMENT( - name volumes, - (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM ( - -- Physical copies - 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 - FROM evergreen.ranked_volumes((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $4, $6, $7, $9, $5) AS y - UNION ALL - -- Located URIs - 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 - FROM evergreen.located_uris((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $9) AS uris - )x) - ), - CASE WHEN ('ssub' = ANY ($5)) THEN - XMLELEMENT( - name subscriptions, - (SELECT XMLAGG(ssub) FROM ( - SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE) - FROM serial.subscription - WHERE record_entry IN (SELECT source FROM metabib.metarecord_source_map WHERE metarecord = $1) - )x) - ) - ELSE NULL END - ); -$F$ LANGUAGE SQL STABLE; - -CREATE OR REPLACE FUNCTION unapi.mmr ( - obj_id BIGINT, - format TEXT, - ename TEXT, - includes TEXT[], - org TEXT, - depth INT DEFAULT NULL, - slimit HSTORE DEFAULT NULL, - soffset HSTORE DEFAULT NULL, - include_xmlns BOOL DEFAULT TRUE, - pref_lib INT DEFAULT NULL -) -RETURNS XML AS $F$ -DECLARE - mmrec metabib.metarecord%ROWTYPE; - leadrec biblio.record_entry%ROWTYPE; - subrec biblio.record_entry%ROWTYPE; - layout unapi.bre_output_layout%ROWTYPE; - xfrm config.xml_transform%ROWTYPE; - ouid INT; - xml_buf TEXT; -- growing XML document - tmp_xml TEXT; -- single-use XML string - xml_frag TEXT; -- single-use XML fragment - top_el TEXT; - output XML; - hxml XML; - axml XML; - subxml XML; -- subordinate records elements - sub_xpath TEXT; - parts TEXT[]; -BEGIN - - -- xpath for extracting bre.marc values from subordinate records - -- so they may be appended to the MARC of the master record prior - -- to XSLT processing. - -- subjects, isbn, issn, upc -- anything else? - sub_xpath := - '//*[starts-with(@tag, "6") or @tag="020" or @tag="022" or @tag="024"]'; - - IF org = '-' OR org IS NULL THEN - SELECT shortname INTO org FROM evergreen.org_top(); - END IF; - - SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org; - - IF ouid IS NULL THEN - RETURN NULL::XML; - END IF; - - SELECT INTO mmrec * FROM metabib.metarecord WHERE id = obj_id; - IF NOT FOUND THEN - RETURN NULL::XML; - END IF; - - -- TODO: aggregate holdings from constituent records - IF format = 'holdings_xml' THEN -- the special case - output := unapi.mmr_holdings_xml( - obj_id, ouid, org, depth, - array_remove(includes,'holdings_xml'), - slimit, soffset, include_xmlns, pref_lib); - RETURN output; - END IF; - - SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format; - - IF layout.name IS NULL THEN - RETURN NULL::XML; - END IF; - - SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform; - - SELECT INTO leadrec * FROM biblio.record_entry WHERE id = mmrec.master_record; - - -- Grab distinct MVF for all records if requested - IF ('mra' = ANY (includes)) THEN - axml := unapi.mmr_mra(obj_id,NULL,NULL,NULL,org,depth,NULL,NULL,TRUE,pref_lib); - ELSE - axml := NULL::XML; - END IF; - - xml_buf = leadrec.marc; - - hxml := NULL::XML; - IF ('holdings_xml' = ANY (includes)) THEN - hxml := unapi.mmr_holdings_xml( - obj_id, ouid, org, depth, - array_remove(includes,'holdings_xml'), - slimit, soffset, include_xmlns, pref_lib); - END IF; - - subxml := NULL::XML; - parts := '{}'::TEXT[]; - FOR subrec IN SELECT bre.* FROM biblio.record_entry bre - JOIN metabib.metarecord_source_map mmsm ON (mmsm.source = bre.id) - JOIN metabib.metarecord mmr ON (mmr.id = mmsm.metarecord) - WHERE mmr.id = obj_id AND NOT bre.deleted - ORDER BY CASE WHEN bre.id = mmr.master_record THEN 0 ELSE bre.id END - LIMIT COALESCE((slimit->'bre')::INT, 5) LOOP - - IF subrec.id = leadrec.id THEN CONTINUE; END IF; - -- Append choice data from the the non-lead records to the - -- the lead record document - - parts := parts || xpath(sub_xpath, subrec.marc::XML)::TEXT[]; - END LOOP; - - SELECT ARRAY_TO_STRING( ARRAY_AGG( DISTINCT p ), '' )::XML INTO subxml FROM UNNEST(parts) p; - - -- append data from the subordinate records to the - -- main record document before applying the XSLT - - IF subxml IS NOT NULL THEN - xml_buf := REGEXP_REPLACE(xml_buf, - '(.*?)$', subxml || '' || E'\\1'); - END IF; - - IF format = 'marcxml' THEN - -- If we're not using the prefixed namespace in - -- this record, then remove all declarations of it - IF xml_buf !~ E' mods) - top_el := REGEXP_REPLACE(xml_buf, E'^.*?<((?:\\S+:)?' || - layout.holdings_element || ').*$', E'\\1'); - - IF axml IS NOT NULL THEN - xml_buf := REGEXP_REPLACE(xml_buf, - '(.*?)$', axml || '\\1'); - END IF; - - IF hxml IS NOT NULL THEN - xml_buf := REGEXP_REPLACE(xml_buf, - '(.*?)$', hxml || '\\1'); - END IF; - - IF ('mmr.unapi' = ANY (includes)) THEN - output := REGEXP_REPLACE( - xml_buf, - '(.*?)', - XMLELEMENT( - name abbr, - XMLATTRIBUTES( - 'http://www.w3.org/1999/xhtml' AS xmlns, - 'unapi-id' AS class, - 'tag:open-ils.org:U2@mmr/' || obj_id || '/' || org AS title - ) - )::TEXT || '\\1' - ); - ELSE - output := xml_buf; - END IF; - - -- remove ignorable whitesace - output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML; - RETURN output; -END; -$F$ LANGUAGE PLPGSQL STABLE; - -CREATE OR REPLACE FUNCTION authority.extract_headings(marc TEXT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$ -DECLARE - idx authority.heading_field%ROWTYPE; - xfrm config.xml_transform%ROWTYPE; - prev_xfrm TEXT; - transformed_xml TEXT; - heading_node TEXT; - heading_node_list TEXT[]; - component_node TEXT; - component_node_list TEXT[]; - raw_text TEXT; - normalized_text TEXT; - normalizer RECORD; - curr_text TEXT; - joiner TEXT; - type_value TEXT; - base_thesaurus TEXT := NULL; - output_row authority.heading; -BEGIN - - -- Loop over the indexing entries - FOR idx IN SELECT * FROM authority.heading_field WHERE restrict IS NULL OR id = ANY (restrict) ORDER BY format LOOP - - output_row.field := idx.id; - output_row.type := idx.heading_type; - output_row.purpose := idx.heading_purpose; - - joiner := COALESCE(idx.joiner, ' '); - - SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format; - - -- See if we can skip the XSLT ... it's expensive - IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN - -- Can't skip the transform - IF xfrm.xslt <> '---' THEN - transformed_xml := oils_xslt_process(marc, xfrm.xslt); - ELSE - transformed_xml := marc; - END IF; - - prev_xfrm := xfrm.name; - END IF; - - IF idx.thesaurus_xpath IS NOT NULL THEN - base_thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); - END IF; - - heading_node_list := oils_xpath( idx.heading_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); - - FOR heading_node IN SELECT x FROM unnest(heading_node_list) AS x LOOP - - CONTINUE WHEN heading_node !~ E'^\\s*<'; - - output_row.variant_type := NULL; - output_row.related_type := NULL; - output_row.thesaurus := NULL; - output_row.heading := NULL; - - IF idx.heading_purpose = 'variant' AND idx.type_xpath IS NOT NULL THEN - type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); - BEGIN - output_row.variant_type := type_value; - EXCEPTION WHEN invalid_text_representation THEN - RAISE NOTICE 'Do not recognize variant heading type %', type_value; - END; - END IF; - IF idx.heading_purpose = 'related' AND idx.type_xpath IS NOT NULL THEN - type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); - BEGIN - output_row.related_type := type_value; - EXCEPTION WHEN invalid_text_representation THEN - RAISE NOTICE 'Do not recognize related heading type %', type_value; - END; - END IF; - - IF idx.thesaurus_override_xpath IS NOT NULL THEN - output_row.thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_override_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); - END IF; - IF output_row.thesaurus IS NULL THEN - output_row.thesaurus := base_thesaurus; - END IF; - - raw_text := NULL; - - -- now iterate over components of heading - component_node_list := oils_xpath( idx.component_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); - FOR component_node IN SELECT x FROM unnest(component_node_list) AS x LOOP - -- XXX much of this should be moved into oils_xpath_string... - curr_text := ARRAY_TO_STRING(array_remove(array_remove( - oils_xpath( '//text()', -- get the content of all the nodes within the main selected node - REGEXP_REPLACE( component_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space - ), ' '), ''), -- throw away morally empty (bankrupt?) strings - joiner - ); - - CONTINUE WHEN curr_text IS NULL OR curr_text = ''; - - IF raw_text IS NOT NULL THEN - raw_text := raw_text || joiner; - END IF; - - raw_text := COALESCE(raw_text,'') || curr_text; - END LOOP; - - IF raw_text IS NOT NULL THEN - output_row.heading := raw_text; - normalized_text := raw_text; - - FOR normalizer IN - SELECT n.func AS func, - n.param_count AS param_count, - m.params AS params - FROM config.index_normalizer n - JOIN authority.heading_field_norm_map m ON (m.norm = n.id) - WHERE m.field = idx.id - ORDER BY m.pos LOOP - - EXECUTE 'SELECT ' || normalizer.func || '(' || - quote_literal( normalized_text ) || - CASE - WHEN normalizer.param_count > 0 - THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'') - ELSE '' - END || - ')' INTO normalized_text; - - END LOOP; - - output_row.normalized_heading := normalized_text; - - RETURN NEXT output_row; - END IF; - END LOOP; - - END LOOP; -END; -$func$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION authority.extract_headings(rid BIGINT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$ -DECLARE - auth authority.record_entry%ROWTYPE; - output_row authority.heading; -BEGIN - -- Get the record - SELECT INTO auth * FROM authority.record_entry WHERE id = rid; - - RETURN QUERY SELECT * FROM authority.extract_headings(auth.marc, restrict); -END; -$func$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( - rid BIGINT, - default_joiner TEXT, - field_types TEXT[], - only_fields INT[] -) RETURNS SETOF metabib.field_entry_template AS $func$ -DECLARE - bib biblio.record_entry%ROWTYPE; - idx config.metabib_field%ROWTYPE; - xfrm config.xml_transform%ROWTYPE; - prev_xfrm TEXT; - transformed_xml TEXT; - xml_node TEXT; - xml_node_list TEXT[]; - facet_text TEXT; - display_text TEXT; - browse_text TEXT; - sort_value TEXT; - raw_text TEXT; - curr_text TEXT; - joiner TEXT := default_joiner; -- XXX will index defs supply a joiner? - authority_text TEXT; - authority_link BIGINT; - output_row metabib.field_entry_template%ROWTYPE; - process_idx BOOL; -BEGIN - - -- Start out with no field-use bools set - output_row.browse_field = FALSE; - output_row.facet_field = FALSE; - output_row.display_field = FALSE; - output_row.search_field = FALSE; - - -- Get the record - SELECT INTO bib * FROM biblio.record_entry WHERE id = rid; - - -- Loop over the indexing entries - FOR idx IN SELECT * FROM config.metabib_field WHERE id = ANY (only_fields) ORDER BY format LOOP - CONTINUE WHEN idx.xpath IS NULL OR idx.xpath = ''; -- pure virtual field - - process_idx := FALSE; - IF idx.display_field AND 'display' = ANY (field_types) THEN process_idx = TRUE; END IF; - IF idx.browse_field AND 'browse' = ANY (field_types) THEN process_idx = TRUE; END IF; - IF idx.search_field AND 'search' = ANY (field_types) THEN process_idx = TRUE; END IF; - IF idx.facet_field AND 'facet' = ANY (field_types) THEN process_idx = TRUE; END IF; - CONTINUE WHEN process_idx = FALSE; -- disabled for all types - - joiner := COALESCE(idx.joiner, default_joiner); - - SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format; - - -- See if we can skip the XSLT ... it's expensive - IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN - -- Can't skip the transform - IF xfrm.xslt <> '---' THEN - transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt); - ELSE - transformed_xml := bib.marc; - END IF; - - prev_xfrm := xfrm.name; - END IF; - - xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); - - raw_text := NULL; - FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP - CONTINUE WHEN xml_node !~ E'^\\s*<'; - - -- XXX much of this should be moved into oils_xpath_string... - curr_text := ARRAY_TO_STRING(array_remove(array_remove( - oils_xpath( '//text()', -- get the content of all the nodes within the main selected node - REGEXP_REPLACE( xml_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space - ), ' '), ''), -- throw away morally empty (bankrupt?) strings - joiner - ); - - CONTINUE WHEN curr_text IS NULL OR curr_text = ''; - - IF raw_text IS NOT NULL THEN - raw_text := raw_text || joiner; - END IF; - - raw_text := COALESCE(raw_text,'') || curr_text; - - -- autosuggest/metabib.browse_entry - IF idx.browse_field THEN - - IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN - browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); - ELSE - browse_text := curr_text; - END IF; - - IF idx.browse_sort_xpath IS NOT NULL AND - idx.browse_sort_xpath <> '' THEN - - sort_value := oils_xpath_string( - idx.browse_sort_xpath, xml_node, joiner, - ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] - ); - ELSE - sort_value := browse_text; - END IF; - - output_row.field_class = idx.field_class; - output_row.field = idx.id; - output_row.source = rid; - output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g')); - output_row.sort_value := - public.naco_normalize(sort_value); - - output_row.authority := NULL; - - IF idx.authority_xpath IS NOT NULL AND idx.authority_xpath <> '' THEN - authority_text := oils_xpath_string( - idx.authority_xpath, xml_node, joiner, - ARRAY[ - ARRAY[xfrm.prefix, xfrm.namespace_uri], - ARRAY['xlink','http://www.w3.org/1999/xlink'] - ] - ); - - IF authority_text ~ '^\d+$' THEN - authority_link := authority_text::BIGINT; - PERFORM * FROM authority.record_entry WHERE id = authority_link; - IF FOUND THEN - output_row.authority := authority_link; - END IF; - END IF; - - END IF; - - output_row.browse_field = TRUE; - -- Returning browse rows with search_field = true for search+browse - -- configs allows us to retain granularity of being able to search - -- browse fields with "starts with" type operators (for example, for - -- titles of songs in music albums) - IF idx.search_field THEN - output_row.search_field = TRUE; - END IF; - RETURN NEXT output_row; - output_row.browse_field = FALSE; - output_row.search_field = FALSE; - output_row.sort_value := NULL; - END IF; - - -- insert raw node text for faceting - IF idx.facet_field THEN - - IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN - facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); - ELSE - facet_text := curr_text; - END IF; - - output_row.field_class = idx.field_class; - output_row.field = -1 * idx.id; - output_row.source = rid; - output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g')); - - output_row.facet_field = TRUE; - RETURN NEXT output_row; - output_row.facet_field = FALSE; - END IF; - - -- insert raw node text for display - IF idx.display_field THEN - - IF idx.display_xpath IS NOT NULL AND idx.display_xpath <> '' THEN - display_text := oils_xpath_string( idx.display_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); - ELSE - display_text := curr_text; - END IF; - - output_row.field_class = idx.field_class; - output_row.field = -1 * idx.id; - output_row.source = rid; - output_row.value = BTRIM(REGEXP_REPLACE(display_text, E'\\s+', ' ', 'g')); - - output_row.display_field = TRUE; - RETURN NEXT output_row; - output_row.display_field = FALSE; - END IF; - - END LOOP; - - CONTINUE WHEN raw_text IS NULL OR raw_text = ''; - - -- insert combined node text for searching - IF idx.search_field THEN - output_row.field_class = idx.field_class; - output_row.field = idx.id; - output_row.source = rid; - output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g')); - - output_row.search_field = TRUE; - RETURN NEXT output_row; - output_row.search_field = FALSE; - END IF; - - END LOOP; - -END; -$func$ LANGUAGE PLPGSQL; - --- We no longer need the custom function -DROP FUNCTION evergreen.array_remove_item_by_value(ANYARRAY, ANYELEMENT); - -COMMIT; -