vandelay Functions
This page documents all 54 function(s) in the vandelay schema.
Function Index
| Function | Return Type | Language | Volatility | Security |
|---|---|---|---|---|
_get_expr_push_jrow(node vandelay.match_set_point, tags_r…) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
_node_tag_comparisons(caseless boolean, op text, tags_rstor…) |
|
plpgsql |
VOLATILE |
|
|
sql |
VOLATILE |
||
|
plperlu |
VOLATILE |
||
|
sql |
VOLATILE |
||
auto_overlay_authority_queue(queue_id bigint, merge_profile_id int…) |
|
plpgsql |
VOLATILE |
|
auto_overlay_authority_record(import_id bigint, merge_profile_id in…) |
|
plpgsql |
VOLATILE |
|
auto_overlay_authority_record_with_best(import_id bigint, merge_profile_id in…) |
|
plpgsql |
VOLATILE |
|
|
sql |
VOLATILE |
||
auto_overlay_bib_queue(queue_id bigint, merge_profile_id int…) |
|
plpgsql |
VOLATILE |
|
auto_overlay_bib_queue_with_best(import_id bigint, merge_profile_id in…) |
|
sql |
VOLATILE |
|
auto_overlay_bib_queue_with_best(queue_id bigint, merge_profile_id int…) |
|
plpgsql |
VOLATILE |
|
auto_overlay_bib_record(import_id bigint, merge_profile_id in…) |
|
plpgsql |
VOLATILE |
|
auto_overlay_bib_record_with_best(import_id bigint, merge_profile_id in…) |
|
sql |
VOLATILE |
|
auto_overlay_bib_record_with_best(import_id bigint, merge_profile_id in…) |
|
plpgsql |
VOLATILE |
|
auto_overlay_org_unit_copies(import_id bigint, merge_profile_id in…) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
sql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plperlu |
VOLATILE |
||
get_expr_from_match_set(match_set_id integer, tags_rstore hstore) |
|
plpgsql |
VOLATILE |
|
get_expr_from_match_set(match_set_id integer, tags_rstore hst…) |
|
plpgsql |
VOLATILE |
|
get_expr_from_match_set_point(node vandelay.match_set_point, tags_r…) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
marc21_extract_all_fixed_fields(marc text, use_default boolean DEFAUL…) |
|
plpgsql |
VOLATILE |
|
marc21_extract_fixed_field(marc text, ff text, use_default boole…) |
|
plpgsql |
VOLATILE |
|
marc21_extract_fixed_field_list(marc text, ff text, use_default boole…) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
match_set_test_authxml(match_set_id integer, record_xml text) |
|
plpgsql |
VOLATILE |
|
match_set_test_marcxml(match_set_id integer, record_xml text…) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
sql |
VOLATILE |
||
merge_record_xml_using_profile(incoming_marc text, existing_marc tex…) |
|
plpgsql |
VOLATILE |
|
overlay_authority_record(import_id bigint, eg_id bigint, merge…) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
plperlu |
VOLATILE |
||
|
plperlu |
VOLATILE |
||
|
sql |
VOLATILE |
||
template_overlay_bib_record(v_marc text, eg_id bigint, merge_prof…) |
|
plpgsql |
VOLATILE |
_get_expr_push_jrow
Signature: vandelay._get_expr_push_jrow(node vandelay.match_set_point, tags_rstore hstore, auth_heading text)
Returns: void
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
jrow TEXT;
my_alias TEXT;
op TEXT;
tagkey TEXT;
caseless BOOL;
jrow_count INT;
my_using TEXT;
my_join TEXT;
rec_table TEXT;
BEGIN
-- remember $1 is tags_rstore, and $2 is svf_rstore
-- a non-NULL auth_heading means we're matching authority records
IF auth_heading IS NOT NULL THEN
rec_table := 'authority.full_rec';
ELSE
rec_table := 'metabib.full_rec';
END IF;
caseless := FALSE;
SELECT COUNT(*) INTO jrow_count FROM _vandelay_tmp_jrows;
IF jrow_count > 0 THEN
my_using := ' USING (record)';
my_join := 'FULL OUTER JOIN';
ELSE
my_using := '';
my_join := 'FROM';
END IF;
IF node.tag IS NOT NULL THEN
caseless := (node.tag IN ('020', '022', '024'));
tagkey := node.tag;
IF node.subfield IS NOT NULL THEN
tagkey := tagkey || node.subfield;
END IF;
END IF;
IF node.negate THEN
IF caseless THEN
op := 'NOT LIKE';
ELSE
op := '<>';
END IF;
ELSE
IF caseless THEN
op := 'LIKE';
ELSE
op := '=';
END IF;
END IF;
my_alias := 'n' || node.id::TEXT;
jrow := my_join || ' (SELECT *, ';
IF node.tag IS NOT NULL THEN
jrow := jrow || node.quality ||
' AS quality FROM ' || rec_table || ' mfr WHERE mfr.tag = ''' ||
node.tag || '''';
IF node.subfield IS NOT NULL THEN
jrow := jrow || ' AND mfr.subfield = ''' ||
node.subfield || '''';
END IF;
jrow := jrow || ' AND (';
jrow := jrow || vandelay._node_tag_comparisons(caseless, op, tags_rstore, tagkey);
jrow := jrow || ')) ' || my_alias || my_using || E'\n';
ELSE -- svf
IF auth_heading IS NOT NULL THEN -- authority record
IF node.heading AND auth_heading <> '' THEN
jrow := jrow || 'id AS record, ' || node.quality ||
' AS quality FROM authority.record_entry are ' ||
' WHERE are.heading = ''' || auth_heading || '''';
jrow := jrow || ') ' || my_alias || my_using || E'\n';
END IF;
ELSE -- bib record
jrow := jrow || 'id AS record, ' || node.quality ||
' AS quality FROM metabib.record_attr_flat mraf WHERE mraf.attr = ''' ||
node.svf || ''' AND mraf.value ' || op || ' $2->''' || node.svf || ''') ' ||
my_alias || my_using || E'\n';
END IF;
END IF;
INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
END;
_get_expr_push_qrow
Signature: vandelay._get_expr_push_qrow(node vandelay.match_set_point)
Returns: void
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
BEGIN
INSERT INTO _vandelay_tmp_qrows (q) VALUES (node.id);
END;
_get_expr_render_one
Signature: vandelay._get_expr_render_one(node vandelay.match_set_point)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
s TEXT;
BEGIN
IF node.bool_op IS NOT NULL THEN
RETURN node.bool_op;
ELSE
RETURN '(n' || node.id::TEXT || '.id IS NOT NULL)';
END IF;
END;
_node_tag_comparisons
Signature: vandelay._node_tag_comparisons(caseless boolean, op text, tags_rstore hstore, tagkey text)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
result TEXT;
i INT;
vals TEXT[];
BEGIN
i := 1;
vals := tags_rstore->tagkey;
result := '';
WHILE TRUE LOOP
IF i > 1 THEN
IF vals[i] IS NULL THEN
EXIT;
ELSE
result := result || ' OR ';
END IF;
END IF;
IF caseless THEN
result := result || 'LOWER(mfr.value) ' || op;
ELSE
result := result || 'mfr.value ' || op;
END IF;
result := result || ' ' || COALESCE('''' || vals[i] || '''', 'NULL');
IF vals[i] IS NULL THEN
EXIT;
END IF;
i := i + 1;
END LOOP;
RETURN result;
END;
add_field
Signature: vandelay.add_field(target_xml text, source_xml text, field text)
Returns: text
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT vandelay.add_field( $1, $2, $3, 0 );
add_field
Signature: vandelay.add_field(target_xml text, source_xml text, field text, force_add integer)
Returns: text
Language |
plperlu |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
use MARC::Record;
use MARC::File::XML (BinaryEncoding => 'UTF-8');
use MARC::Charset;
use strict;
MARC::Charset->assume_unicode(1);
my $target_xml = shift;
my $source_xml = shift;
my $field_spec = shift;
my $force_add = shift || 0;
my $target_r = MARC::Record->new_from_xml( $target_xml );
my $source_r = MARC::Record->new_from_xml( $source_xml );
return $target_xml unless ($target_r && $source_r);
my @field_list = split(',', $field_spec);
my %fields;
for my $f (@field_list) {
$f =~ s/^\s*//; $f =~ s/\s*$//;
if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
my $field = $1;
$field =~ s/\s+//;
my $sf = $2;
$sf =~ s/\s+//;
my $matches = $3;
$matches =~ s/^\s*//; $matches =~ s/\s*$//;
$fields{$field} = { sf => [ split('', $sf) ] };
if ($matches) {
for my $match (split('&&', $matches)) {
$match =~ s/^\s*//; $match =~ s/\s*$//;
my ($msf,$mre) = split('~', $match);
if (length($msf) > 0 and length($mre) > 0) {
$msf =~ s/^\s*//; $msf =~ s/\s*$//;
$mre =~ s/^\s*//; $mre =~ s/\s*$//;
$fields{$field}{match}{$msf} = qr/$mre/;
}
}
}
}
}
for my $f ( keys %fields) {
if ( @{$fields{$f}{sf}} ) {
for my $from_field ($source_r->field( $f )) {
my @tos = $target_r->field( $f );
if (!@tos) {
next if (exists($fields{$f}{match}) and !$force_add);
my @new_fields = map { $_->clone } $source_r->field( $f );
$target_r->insert_fields_ordered( @new_fields );
} else {
for my $to_field (@tos) {
if (exists($fields{$f}{match})) {
my @match_list;
for my $match_key_sf_code ( keys %{$fields{$f}{match}} ) {
# We loop here because there might be multiple SFs, such as multiple
# $0s in an authority controlled datafield, where one has the EG-special
# format, and others are links to external heading data.
for my $sf_content ($to_field->subfield($match_key_sf_code)) {
if ($sf_content =~ $fields{$f}{match}{$match_key_sf_code}) {
push @match_list, $sf_content;
}
}
}
next unless (scalar(@match_list) >= scalar(keys %{$fields{$f}{match}}));
}
for my $old_sf ($from_field->subfields) {
$to_field->add_subfields( @$old_sf ) if grep(/$$old_sf[0]/,@{$fields{$f}{sf}});
}
}
}
}
} else {
my @new_fields = map { $_->clone } $source_r->field( $f );
$target_r->insert_fields_ordered( @new_fields );
}
}
$target_xml = $target_r->as_xml_record;
$target_xml =~ s/^<\?.+?\?>$//mo;
$target_xml =~ s/\n//sgo;
$target_xml =~ s/>\s+</></sgo;
return $target_xml;
auto_overlay_authority_queue
Signature: vandelay.auto_overlay_authority_queue(queue_id bigint)
Returns: SETOF bigint
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
auto_overlay_authority_queue
Signature: vandelay.auto_overlay_authority_queue(queue_id bigint, merge_profile_id integer)
Returns: SETOF bigint
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
queued_record vandelay.queued_authority_record%ROWTYPE;
BEGIN
FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
RETURN NEXT queued_record.id;
END IF;
END LOOP;
RETURN;
END;
auto_overlay_authority_record
Signature: vandelay.auto_overlay_authority_record(import_id bigint, merge_profile_id integer)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
eg_id BIGINT;
match_count INT;
BEGIN
SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
IF match_count <> 1 THEN
-- RAISE NOTICE 'not an exact match';
RETURN FALSE;
END IF;
SELECT m.eg_record INTO eg_id
FROM vandelay.authority_match m
WHERE m.queued_record = import_id
LIMIT 1;
IF eg_id IS NULL THEN
RETURN FALSE;
END IF;
RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
END;
auto_overlay_authority_record_with_best
Signature: vandelay.auto_overlay_authority_record_with_best(import_id bigint, merge_profile_id integer, lwm_ratio_value_p numeric)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
eg_id BIGINT;
lwm_ratio_value NUMERIC;
BEGIN
lwm_ratio_value := COALESCE(lwm_ratio_value_p, 0.0);
PERFORM * FROM vandelay.queued_authority_record WHERE import_time IS NOT NULL AND id = import_id;
IF FOUND THEN
-- RAISE NOTICE 'already imported, cannot auto-overlay'
RETURN FALSE;
END IF;
SELECT m.eg_record INTO eg_id
FROM vandelay.authority_match m
JOIN vandelay.queued_authority_record qr ON (m.queued_record = qr.id)
JOIN vandelay.authority_queue q ON (qr.queue = q.id)
JOIN authority.record_entry r ON (r.id = m.eg_record)
WHERE m.queued_record = import_id
AND qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC >= lwm_ratio_value
ORDER BY m.match_score DESC, -- required match score
qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC DESC, -- quality tie breaker
m.id -- when in doubt, use the first match
LIMIT 1;
IF eg_id IS NULL THEN
-- RAISE NOTICE 'incoming record is not of high enough quality';
RETURN FALSE;
END IF;
RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
END;
auto_overlay_bib_queue
Signature: vandelay.auto_overlay_bib_queue(queue_id bigint)
Returns: SETOF bigint
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
auto_overlay_bib_queue
Signature: vandelay.auto_overlay_bib_queue(queue_id bigint, merge_profile_id integer)
Returns: SETOF bigint
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
queued_record vandelay.queued_bib_record%ROWTYPE;
BEGIN
FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
RETURN NEXT queued_record.id;
END IF;
END LOOP;
RETURN;
END;
auto_overlay_bib_queue_with_best
Signature: vandelay.auto_overlay_bib_queue_with_best(import_id bigint, merge_profile_id integer)
Returns: SETOF bigint
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT vandelay.auto_overlay_bib_queue_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;
auto_overlay_bib_queue_with_best
Signature: vandelay.auto_overlay_bib_queue_with_best(queue_id bigint, merge_profile_id integer, lwm_ratio_value numeric)
Returns: SETOF bigint
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
queued_record vandelay.queued_bib_record%ROWTYPE;
BEGIN
FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
IF vandelay.auto_overlay_bib_record_with_best( queued_record.id, merge_profile_id, lwm_ratio_value ) THEN
RETURN NEXT queued_record.id;
END IF;
END LOOP;
RETURN;
END;
auto_overlay_bib_record
Signature: vandelay.auto_overlay_bib_record(import_id bigint, merge_profile_id integer)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
eg_id BIGINT;
match_count INT;
BEGIN
PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
IF FOUND THEN
-- RAISE NOTICE 'already imported, cannot auto-overlay'
RETURN FALSE;
END IF;
SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
IF match_count <> 1 THEN
-- RAISE NOTICE 'not an exact match';
RETURN FALSE;
END IF;
-- Check that the one match is on the first 901c
SELECT m.eg_record INTO eg_id
FROM vandelay.queued_bib_record q
JOIN vandelay.bib_match m ON (m.queued_record = q.id)
WHERE q.id = import_id
AND m.eg_record = oils_xpath_string('//*[@tag="901"]/*[@code="c"][1]',marc)::BIGINT;
IF NOT FOUND THEN
-- RAISE NOTICE 'not a 901c match';
RETURN FALSE;
END IF;
RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
END;
auto_overlay_bib_record_with_best
Signature: vandelay.auto_overlay_bib_record_with_best(import_id bigint, merge_profile_id integer)
Returns: boolean
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT vandelay.auto_overlay_bib_record_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;
auto_overlay_bib_record_with_best
Signature: vandelay.auto_overlay_bib_record_with_best(import_id bigint, merge_profile_id integer, lwm_ratio_value_p numeric)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
eg_id BIGINT;
lwm_ratio_value NUMERIC;
BEGIN
lwm_ratio_value := COALESCE(lwm_ratio_value_p, 0.0);
PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
IF FOUND THEN
-- RAISE NOTICE 'already imported, cannot auto-overlay'
RETURN FALSE;
END IF;
SELECT m.eg_record INTO eg_id
FROM vandelay.bib_match m
JOIN vandelay.queued_bib_record qr ON (m.queued_record = qr.id)
JOIN vandelay.bib_queue q ON (qr.queue = q.id)
JOIN biblio.record_entry r ON (r.id = m.eg_record)
WHERE m.queued_record = import_id
AND qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC >= lwm_ratio_value
ORDER BY m.match_score DESC, -- required match score
qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC DESC, -- quality tie breaker
m.id -- when in doubt, use the first match
LIMIT 1;
IF eg_id IS NULL THEN
-- RAISE NOTICE 'incoming record is not of high enough quality';
RETURN FALSE;
END IF;
RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
END;
auto_overlay_org_unit_copies
Signature: vandelay.auto_overlay_org_unit_copies(import_id bigint, merge_profile_id integer, lwm_ratio_value_p numeric)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
eg_id BIGINT;
match_count INT;
rec vandelay.bib_match%ROWTYPE;
v_owning_lib INT;
scope_org INT;
scope_orgs INT[];
copy_count INT := 0;
max_copy_count INT := 0;
BEGIN
PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
IF FOUND THEN
-- RAISE NOTICE 'already imported, cannot auto-overlay'
RETURN FALSE;
END IF;
-- Gather all the owning libs for our import items.
-- These are our initial scope_orgs.
SELECT ARRAY_AGG(DISTINCT owning_lib) INTO scope_orgs
FROM vandelay.import_item
WHERE record = import_id;
WHILE CARDINALITY(scope_orgs) IS NOT NULL LOOP
EXIT WHEN CARDINALITY(scope_orgs) = 0;
FOR scope_org IN SELECT * FROM UNNEST(scope_orgs) LOOP
-- For each match, get a count of all copies at descendants of our scope org.
FOR rec IN SELECT * FROM vandelay.bib_match AS vbm
WHERE queued_record = import_id
ORDER BY vbm.eg_record DESC
LOOP
SELECT COUNT(acp.id) INTO copy_count
FROM asset.copy AS acp
INNER JOIN asset.call_number AS acn
ON acp.call_number = acn.id
WHERE acn.owning_lib IN (SELECT id FROM
actor.org_unit_descendants(scope_org))
AND acn.record = rec.eg_record
AND acp.deleted = FALSE;
IF copy_count > max_copy_count THEN
max_copy_count := copy_count;
eg_id := rec.eg_record;
END IF;
END LOOP;
END LOOP;
EXIT WHEN eg_id IS NOT NULL;
-- If no matching bibs had holdings, gather our next set of orgs to check, and iterate.
IF max_copy_count = 0 THEN
SELECT ARRAY_AGG(DISTINCT parent_ou) INTO scope_orgs
FROM actor.org_unit
WHERE id IN (SELECT * FROM UNNEST(scope_orgs))
AND parent_ou IS NOT NULL;
EXIT WHEN CARDINALITY(scope_orgs) IS NULL;
END IF;
END LOOP;
IF eg_id IS NULL THEN
-- Could not determine best match via copy count
-- fall back to default best match
IF (SELECT * FROM vandelay.auto_overlay_bib_record_with_best( import_id, merge_profile_id, lwm_ratio_value_p )) THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END IF;
RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
END;
cleanup_authority_marc
Signature: vandelay.cleanup_authority_marc()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
RETURN NEW;
END IF;
DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
IF TG_OP = 'UPDATE' THEN
RETURN NEW;
END IF;
RETURN OLD;
END;
cleanup_bib_marc
Signature: vandelay.cleanup_bib_marc()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
RETURN NEW;
END IF;
DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
DELETE FROM vandelay.import_item WHERE record = OLD.id;
IF TG_OP = 'UPDATE' THEN
RETURN NEW;
END IF;
RETURN OLD;
END;
compile_profile
Signature: vandelay.compile_profile(incoming_xml text)
Returns: vandelay.compile_profile
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
output vandelay.compile_profile%ROWTYPE;
profile vandelay.merge_profile%ROWTYPE;
profile_tmpl TEXT;
profile_tmpl_owner TEXT;
add_rule TEXT := '';
strip_rule TEXT := '';
replace_rule TEXT := '';
preserve_rule TEXT := '';
BEGIN
profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
SELECT p.* INTO profile
FROM vandelay.merge_profile p
JOIN actor.org_unit u ON (u.id = p.owner)
WHERE p.name = profile_tmpl
AND u.shortname = profile_tmpl_owner;
IF profile.id IS NOT NULL THEN
add_rule := COALESCE(profile.add_spec,'');
strip_rule := COALESCE(profile.strip_spec,'');
replace_rule := COALESCE(profile.replace_spec,'');
preserve_rule := COALESCE(profile.preserve_spec,'');
END IF;
END IF;
add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
output.add_rule := BTRIM(add_rule,',');
output.replace_rule := BTRIM(replace_rule,',');
output.strip_rule := BTRIM(strip_rule,',');
output.preserve_rule := BTRIM(preserve_rule,',');
RETURN output;
END;
extract_rec_attrs
Signature: vandelay.extract_rec_attrs(xml text)
Returns: hstore
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT vandelay.extract_rec_attrs( $1, (SELECT ARRAY_AGG(name) FROM config.record_attr_definition));
extract_rec_attrs
Signature: vandelay.extract_rec_attrs(xml text, attr_defs text[])
Returns: hstore
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
transformed_xml TEXT;
prev_xfrm TEXT;
normalizer RECORD;
xfrm config.xml_transform%ROWTYPE;
attr_value TEXT;
new_attrs HSTORE := ''::HSTORE;
attr_def config.record_attr_definition%ROWTYPE;
BEGIN
FOR attr_def IN SELECT * FROM config.record_attr_definition WHERE name IN (SELECT * FROM UNNEST(attr_defs)) ORDER BY format LOOP
IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
SELECT STRING_AGG(x.value, COALESCE(attr_def.joiner,' ')) INTO attr_value
FROM vandelay.flatten_marc(xml) AS x
WHERE x.tag LIKE attr_def.tag
AND CASE
WHEN attr_def.sf_list IS NOT NULL
THEN POSITION(x.subfield IN attr_def.sf_list) > 0
ELSE TRUE
END
GROUP BY x.tag
ORDER BY x.tag
LIMIT 1;
ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
attr_value := vandelay.marc21_extract_fixed_field(xml, attr_def.fixed_field);
ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.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(xml,xfrm.xslt);
ELSE
transformed_xml := xml;
END IF;
prev_xfrm := xfrm.name;
END IF;
IF xfrm.name IS NULL THEN
-- just grab the marcxml (empty) transform
SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
prev_xfrm := xfrm.name;
END IF;
attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
SELECT m.value::TEXT INTO attr_value
FROM vandelay.marc21_physical_characteristics(xml) v
JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
WHERE v.subfield = attr_def.phys_char_sf
LIMIT 1; -- Just in case ...
END IF;
-- apply index normalizers to attr_value
FOR normalizer IN
SELECT n.func AS func,
n.param_count AS param_count,
m.params AS params
FROM config.index_normalizer n
JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
WHERE attr = attr_def.name
ORDER BY m.pos LOOP
EXECUTE 'SELECT ' || normalizer.func || '(' ||
quote_nullable( attr_value ) ||
CASE
WHEN normalizer.param_count > 0
THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
ELSE ''
END ||
')' INTO attr_value;
END LOOP;
-- Add the new value to the hstore
new_attrs := new_attrs || hstore( attr_def.name, attr_value );
END LOOP;
RETURN new_attrs;
END;
find_bib_tcn_data
Signature: vandelay.find_bib_tcn_data(xml text)
Returns: SETOF vandelay.tcn_data
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
eg_tcn TEXT;
eg_tcn_source TEXT;
output vandelay.tcn_data%ROWTYPE;
BEGIN
-- 001/003
eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
eg_tcn_source := 'System Local';
END IF;
PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn AND NOT deleted;
IF NOT FOUND THEN
output.used := FALSE;
ELSE
output.used := TRUE;
END IF;
output.tcn := eg_tcn;
output.tcn_source := eg_tcn_source;
RETURN NEXT output;
END IF;
-- 901 ab
eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
eg_tcn_source := 'System Local';
END IF;
PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn AND NOT deleted;
IF NOT FOUND THEN
output.used := FALSE;
ELSE
output.used := TRUE;
END IF;
output.tcn := eg_tcn;
output.tcn_source := eg_tcn_source;
RETURN NEXT output;
END IF;
-- 039 ab
eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
eg_tcn_source := 'System Local';
END IF;
PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn AND NOT deleted;
IF NOT FOUND THEN
output.used := FALSE;
ELSE
output.used := TRUE;
END IF;
output.tcn := eg_tcn;
output.tcn_source := eg_tcn_source;
RETURN NEXT output;
END IF;
-- 020 a
eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
eg_tcn_source := 'ISBN';
PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn AND NOT deleted;
IF NOT FOUND THEN
output.used := FALSE;
ELSE
output.used := TRUE;
END IF;
output.tcn := eg_tcn;
output.tcn_source := eg_tcn_source;
RETURN NEXT output;
END IF;
-- 022 a
eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
eg_tcn_source := 'ISSN';
PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn AND NOT deleted;
IF NOT FOUND THEN
output.used := FALSE;
ELSE
output.used := TRUE;
END IF;
output.tcn := eg_tcn;
output.tcn_source := eg_tcn_source;
RETURN NEXT output;
END IF;
-- 010 a
eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
eg_tcn_source := 'LCCN';
PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn AND NOT deleted;
IF NOT FOUND THEN
output.used := FALSE;
ELSE
output.used := TRUE;
END IF;
output.tcn := eg_tcn;
output.tcn_source := eg_tcn_source;
RETURN NEXT output;
END IF;
-- 035 a
eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
eg_tcn_source := 'System Legacy';
PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn AND NOT deleted;
IF NOT FOUND THEN
output.used := FALSE;
ELSE
output.used := TRUE;
END IF;
output.tcn := eg_tcn;
output.tcn_source := eg_tcn_source;
RETURN NEXT output;
END IF;
RETURN;
END;
flatten_marc
Signature: vandelay.flatten_marc(marc text)
Returns: SETOF vandelay.flat_marc
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
output vandelay.flat_marc%ROWTYPE;
field RECORD;
BEGIN
FOR field IN SELECT * FROM vandelay.flay_marc( marc ) LOOP
output.ind1 := field.ind1;
output.ind2 := field.ind2;
output.tag := field.tag;
output.subfield := field.subfield;
IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
output.value := naco_normalize(field.value, field.subfield);
ELSE
output.value := field.value;
END IF;
CONTINUE WHEN output.value IS NULL;
RETURN NEXT output;
END LOOP;
END;
flatten_marc_hstore
Signature: vandelay.flatten_marc_hstore(record_xml text)
Returns: hstore
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
RETURN (SELECT
HSTORE(
ARRAY_AGG(tag || (COALESCE(subfield, ''))),
ARRAY_AGG(value)
)
FROM (
SELECT tag, subfield, ARRAY_AGG(value)::TEXT AS value
FROM (SELECT tag,
subfield,
CASE WHEN tag = '020' THEN -- caseless -- isbn
LOWER((SELECT REGEXP_MATCHES(value,$$^(\S{10,17})$$))[1] || '%')
WHEN tag = '022' THEN -- caseless -- issn
LOWER((SELECT REGEXP_MATCHES(value,$$^(\S{4}[- ]?\S{4})$$))[1] || '%')
WHEN tag = '024' THEN -- caseless -- upc (other)
LOWER(value || '%')
ELSE
value
END AS value
FROM vandelay.flatten_marc(record_xml)) x
GROUP BY tag, subfield ORDER BY tag, subfield
) subquery
);
END;
flay_marc
Signature: vandelay.flay_marc(text)
Returns: SETOF vandelay.flat_marc
Language |
plperlu |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
use MARC::Record;
use MARC::File::XML (BinaryEncoding => 'UTF-8');
use MARC::Charset;
use strict;
MARC::Charset->assume_unicode(1);
my $xml = shift;
my $r = MARC::Record->new_from_xml( $xml );
return_next( { tag => 'LDR', value => $r->leader } );
for my $f ( $r->fields ) {
if ($f->is_control_field) {
return_next({ tag => $f->tag, value => $f->data });
} else {
for my $s ($f->subfields) {
return_next({
tag => $f->tag,
ind1 => $f->indicator(1),
ind2 => $f->indicator(2),
subfield => $s->[0],
value => $s->[1]
});
if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
my $trim = $f->indicator(2) || 0;
return_next({
tag => 'tnf',
ind1 => $f->indicator(1),
ind2 => $f->indicator(2),
subfield => 'a',
value => substr( $s->[1], $trim )
});
}
}
}
}
return undef;
get_expr_from_match_set
Signature: vandelay.get_expr_from_match_set(match_set_id integer, tags_rstore hstore)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
RETURN vandelay.get_expr_from_match_set(
match_set_id, tags_rstore, NULL);
END;
get_expr_from_match_set
Signature: vandelay.get_expr_from_match_set(match_set_id integer, tags_rstore hstore, auth_heading text)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
root vandelay.match_set_point;
BEGIN
SELECT * INTO root FROM vandelay.match_set_point
WHERE parent IS NULL AND match_set = match_set_id;
RETURN vandelay.get_expr_from_match_set_point(
root, tags_rstore, auth_heading);
END;
get_expr_from_match_set_point
Signature: vandelay.get_expr_from_match_set_point(node vandelay.match_set_point, tags_rstore hstore, auth_heading text)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
q TEXT;
i INTEGER;
this_op TEXT;
children INTEGER[];
child vandelay.match_set_point;
BEGIN
SELECT ARRAY_AGG(id) INTO children FROM vandelay.match_set_point
WHERE parent = node.id;
IF ARRAY_LENGTH(children, 1) > 0 THEN
this_op := vandelay._get_expr_render_one(node);
q := '(';
i := 1;
WHILE children[i] IS NOT NULL LOOP
SELECT * INTO child FROM vandelay.match_set_point
WHERE id = children[i];
IF i > 1 THEN
q := q || ' ' || this_op || ' ';
END IF;
i := i + 1;
q := q || vandelay.get_expr_from_match_set_point(
child, tags_rstore, auth_heading);
END LOOP;
q := q || ')';
RETURN q;
ELSIF node.bool_op IS NULL THEN
PERFORM vandelay._get_expr_push_qrow(node);
PERFORM vandelay._get_expr_push_jrow(node, tags_rstore, auth_heading);
RETURN vandelay._get_expr_render_one(node);
ELSE
RETURN '';
END IF;
END;
ingest_authority_marc
Signature: vandelay.ingest_authority_marc()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
value TEXT;
atype TEXT;
adef RECORD;
BEGIN
IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
RETURN NEW;
END IF;
FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
IF (value IS NOT NULL AND value <> '') THEN
INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
END IF;
END LOOP;
RETURN NULL;
END;
ingest_bib_items
Signature: vandelay.ingest_bib_items()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
attr_def BIGINT;
item_data vandelay.import_item%ROWTYPE;
BEGIN
IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
RETURN NEW;
END IF;
SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
INSERT INTO vandelay.import_item (
record,
definition,
owning_lib,
circ_lib,
call_number,
copy_number,
status,
location,
circulate,
deposit,
deposit_amount,
ref,
holdable,
price,
barcode,
circ_modifier,
circ_as_type,
alert_message,
pub_note,
priv_note,
internal_id,
opac_visible,
stat_cat_data,
parts_data,
import_error,
error_detail
) VALUES (
NEW.id,
item_data.definition,
item_data.owning_lib,
item_data.circ_lib,
item_data.call_number,
item_data.copy_number,
item_data.status,
item_data.location,
item_data.circulate,
item_data.deposit,
item_data.deposit_amount,
item_data.ref,
item_data.holdable,
item_data.price,
item_data.barcode,
item_data.circ_modifier,
item_data.circ_as_type,
item_data.alert_message,
item_data.pub_note,
item_data.priv_note,
item_data.internal_id,
item_data.opac_visible,
item_data.stat_cat_data,
item_data.parts_data,
item_data.import_error,
item_data.error_detail
);
END LOOP;
RETURN NULL;
END;
ingest_bib_marc
Signature: vandelay.ingest_bib_marc()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
value TEXT;
atype TEXT;
adef RECORD;
BEGIN
IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
RETURN NEW;
END IF;
FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
IF (value IS NOT NULL AND value <> '') THEN
INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
END IF;
END LOOP;
RETURN NULL;
END;
ingest_items
Signature: vandelay.ingest_items(import_id bigint, attr_def_id bigint)
Returns: SETOF vandelay.import_item
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
owning_lib TEXT;
circ_lib TEXT;
call_number TEXT;
copy_number TEXT;
status TEXT;
location TEXT;
circulate TEXT;
deposit TEXT;
deposit_amount TEXT;
ref TEXT;
holdable TEXT;
price TEXT;
barcode TEXT;
circ_modifier TEXT;
circ_as_type TEXT;
alert_message TEXT;
opac_visible TEXT;
pub_note TEXT;
priv_note TEXT;
internal_id TEXT;
stat_cat_data TEXT;
parts_data TEXT;
attr_def RECORD;
tmp_attr_set RECORD;
attr_set vandelay.import_item%ROWTYPE;
xpaths TEXT[];
tmp_str TEXT;
BEGIN
SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
IF FOUND THEN
attr_set.definition := attr_def.id;
-- Build the combined XPath
owning_lib :=
CASE
WHEN attr_def.owning_lib IS NULL THEN 'null()'
WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@code="' || attr_def.owning_lib || '"]'
ELSE '//*' || attr_def.owning_lib
END;
circ_lib :=
CASE
WHEN attr_def.circ_lib IS NULL THEN 'null()'
WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@code="' || attr_def.circ_lib || '"]'
ELSE '//*' || attr_def.circ_lib
END;
call_number :=
CASE
WHEN attr_def.call_number IS NULL THEN 'null()'
WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@code="' || attr_def.call_number || '"]'
ELSE '//*' || attr_def.call_number
END;
copy_number :=
CASE
WHEN attr_def.copy_number IS NULL THEN 'null()'
WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@code="' || attr_def.copy_number || '"]'
ELSE '//*' || attr_def.copy_number
END;
status :=
CASE
WHEN attr_def.status IS NULL THEN 'null()'
WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@code="' || attr_def.status || '"]'
ELSE '//*' || attr_def.status
END;
location :=
CASE
WHEN attr_def.location IS NULL THEN 'null()'
WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@code="' || attr_def.location || '"]'
ELSE '//*' || attr_def.location
END;
circulate :=
CASE
WHEN attr_def.circulate IS NULL THEN 'null()'
WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@code="' || attr_def.circulate || '"]'
ELSE '//*' || attr_def.circulate
END;
deposit :=
CASE
WHEN attr_def.deposit IS NULL THEN 'null()'
WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@code="' || attr_def.deposit || '"]'
ELSE '//*' || attr_def.deposit
END;
deposit_amount :=
CASE
WHEN attr_def.deposit_amount IS NULL THEN 'null()'
WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@code="' || attr_def.deposit_amount || '"]'
ELSE '//*' || attr_def.deposit_amount
END;
ref :=
CASE
WHEN attr_def.ref IS NULL THEN 'null()'
WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@code="' || attr_def.ref || '"]'
ELSE '//*' || attr_def.ref
END;
holdable :=
CASE
WHEN attr_def.holdable IS NULL THEN 'null()'
WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@code="' || attr_def.holdable || '"]'
ELSE '//*' || attr_def.holdable
END;
price :=
CASE
WHEN attr_def.price IS NULL THEN 'null()'
WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@code="' || attr_def.price || '"]'
ELSE '//*' || attr_def.price
END;
barcode :=
CASE
WHEN attr_def.barcode IS NULL THEN 'null()'
WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@code="' || attr_def.barcode || '"]'
ELSE '//*' || attr_def.barcode
END;
circ_modifier :=
CASE
WHEN attr_def.circ_modifier IS NULL THEN 'null()'
WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@code="' || attr_def.circ_modifier || '"]'
ELSE '//*' || attr_def.circ_modifier
END;
circ_as_type :=
CASE
WHEN attr_def.circ_as_type IS NULL THEN 'null()'
WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@code="' || attr_def.circ_as_type || '"]'
ELSE '//*' || attr_def.circ_as_type
END;
alert_message :=
CASE
WHEN attr_def.alert_message IS NULL THEN 'null()'
WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@code="' || attr_def.alert_message || '"]'
ELSE '//*' || attr_def.alert_message
END;
opac_visible :=
CASE
WHEN attr_def.opac_visible IS NULL THEN 'null()'
WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@code="' || attr_def.opac_visible || '"]'
ELSE '//*' || attr_def.opac_visible
END;
pub_note :=
CASE
WHEN attr_def.pub_note IS NULL THEN 'null()'
WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@code="' || attr_def.pub_note || '"]'
ELSE '//*' || attr_def.pub_note
END;
priv_note :=
CASE
WHEN attr_def.priv_note IS NULL THEN 'null()'
WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@code="' || attr_def.priv_note || '"]'
ELSE '//*' || attr_def.priv_note
END;
internal_id :=
CASE
WHEN attr_def.internal_id IS NULL THEN 'null()'
WHEN LENGTH( attr_def.internal_id ) = 1 THEN '//*[@code="' || attr_def.internal_id || '"]'
ELSE '//*' || attr_def.internal_id
END;
stat_cat_data :=
CASE
WHEN attr_def.stat_cat_data IS NULL THEN 'null()'
WHEN LENGTH( attr_def.stat_cat_data ) = 1 THEN '//*[@code="' || attr_def.stat_cat_data || '"]'
ELSE '//*' || attr_def.stat_cat_data
END;
parts_data :=
CASE
WHEN attr_def.parts_data IS NULL THEN 'null()'
WHEN LENGTH( attr_def.parts_data ) = 1 THEN '//*[@code="' || attr_def.parts_data || '"]'
ELSE '//*' || attr_def.parts_data
END;
xpaths := ARRAY[owning_lib, circ_lib, call_number, copy_number, status, location, circulate,
deposit, deposit_amount, ref, holdable, price, barcode, circ_modifier, circ_as_type,
alert_message, pub_note, priv_note, internal_id, stat_cat_data, parts_data, opac_visible];
FOR tmp_attr_set IN
SELECT *
FROM oils_xpath_tag_to_table( (SELECT marc FROM vandelay.queued_bib_record WHERE id = import_id), attr_def.tag, xpaths)
AS t( ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, internal_id TEXT,
stat_cat_data TEXT, parts_data TEXT, opac_vis TEXT )
LOOP
attr_set.import_error := NULL;
attr_set.error_detail := NULL;
attr_set.deposit_amount := NULL;
attr_set.copy_number := NULL;
attr_set.price := NULL;
attr_set.circ_modifier := NULL;
attr_set.location := NULL;
attr_set.barcode := NULL;
attr_set.call_number := NULL;
IF tmp_attr_set.pr != '' THEN
tmp_str = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
IF tmp_str = '' THEN
attr_set.import_error := 'import.item.invalid.price';
attr_set.error_detail := tmp_attr_set.pr; -- original value
RETURN NEXT attr_set; CONTINUE;
END IF;
attr_set.price := tmp_str::NUMERIC(8,2);
END IF;
IF tmp_attr_set.dep_amount != '' THEN
tmp_str = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
IF tmp_str = '' THEN
attr_set.import_error := 'import.item.invalid.deposit_amount';
attr_set.error_detail := tmp_attr_set.dep_amount;
RETURN NEXT attr_set; CONTINUE;
END IF;
attr_set.deposit_amount := tmp_str::NUMERIC(8,2);
END IF;
IF tmp_attr_set.cnum != '' THEN
tmp_str = REGEXP_REPLACE(tmp_attr_set.cnum, E'[^0-9]', '', 'g');
IF tmp_str = '' THEN
attr_set.import_error := 'import.item.invalid.copy_number';
attr_set.error_detail := tmp_attr_set.cnum;
RETURN NEXT attr_set; CONTINUE;
END IF;
attr_set.copy_number := tmp_str::INT;
END IF;
IF tmp_attr_set.ol != '' THEN
SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
IF NOT FOUND THEN
attr_set.import_error := 'import.item.invalid.owning_lib';
attr_set.error_detail := tmp_attr_set.ol;
RETURN NEXT attr_set; CONTINUE;
END IF;
END IF;
IF tmp_attr_set.clib != '' THEN
SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
IF NOT FOUND THEN
attr_set.import_error := 'import.item.invalid.circ_lib';
attr_set.error_detail := tmp_attr_set.clib;
RETURN NEXT attr_set; CONTINUE;
END IF;
END IF;
IF tmp_attr_set.cs != '' THEN
SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
IF NOT FOUND THEN
attr_set.import_error := 'import.item.invalid.status';
attr_set.error_detail := tmp_attr_set.cs;
RETURN NEXT attr_set; CONTINUE;
END IF;
END IF;
IF COALESCE(tmp_attr_set.circ_mod, '') = '' THEN
-- no circ mod defined, see if we should apply a default
SELECT INTO attr_set.circ_modifier TRIM(BOTH '"' FROM value)
FROM actor.org_unit_ancestor_setting(
'vandelay.item.circ_modifier.default',
attr_set.owning_lib
);
-- make sure the value from the org setting is still valid
PERFORM 1 FROM config.circ_modifier WHERE code = attr_set.circ_modifier;
IF NOT FOUND THEN
attr_set.import_error := 'import.item.invalid.circ_modifier';
attr_set.error_detail := tmp_attr_set.circ_mod;
RETURN NEXT attr_set; CONTINUE;
END IF;
ELSE
SELECT code INTO attr_set.circ_modifier FROM config.circ_modifier WHERE code = tmp_attr_set.circ_mod;
IF NOT FOUND THEN
attr_set.import_error := 'import.item.invalid.circ_modifier';
attr_set.error_detail := tmp_attr_set.circ_mod;
RETURN NEXT attr_set; CONTINUE;
END IF;
END IF;
IF tmp_attr_set.circ_as != '' THEN
SELECT code INTO attr_set.circ_as_type FROM config.coded_value_map WHERE ctype = 'item_type' AND code = tmp_attr_set.circ_as;
IF NOT FOUND THEN
attr_set.import_error := 'import.item.invalid.circ_as_type';
attr_set.error_detail := tmp_attr_set.circ_as;
RETURN NEXT attr_set; CONTINUE;
END IF;
END IF;
IF COALESCE(tmp_attr_set.cl, '') = '' THEN
-- no location specified, see if we should apply a default
SELECT INTO attr_set.location TRIM(BOTH '"' FROM value)
FROM actor.org_unit_ancestor_setting(
'vandelay.item.copy_location.default',
attr_set.owning_lib
);
-- make sure the value from the org setting is still valid
PERFORM 1 FROM asset.copy_location
WHERE id = attr_set.location AND NOT deleted;
IF NOT FOUND THEN
attr_set.import_error := 'import.item.invalid.location';
attr_set.error_detail := tmp_attr_set.cs;
RETURN NEXT attr_set; CONTINUE;
END IF;
ELSE
-- search up the org unit tree for a matching copy location
WITH RECURSIVE anscestor_depth AS (
SELECT ou.id,
out.depth AS depth,
ou.parent_ou
FROM actor.org_unit ou
JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
WHERE ou.id = COALESCE(attr_set.owning_lib, attr_set.circ_lib)
UNION ALL
SELECT ou.id,
out.depth,
ou.parent_ou
FROM actor.org_unit ou
JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
) SELECT cpl.id INTO attr_set.location
FROM anscestor_depth a
JOIN asset.copy_location cpl ON (cpl.owning_lib = a.id)
WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl)
AND NOT cpl.deleted
ORDER BY a.depth DESC
LIMIT 1;
IF NOT FOUND THEN
attr_set.import_error := 'import.item.invalid.location';
attr_set.error_detail := tmp_attr_set.cs;
RETURN NEXT attr_set; CONTINUE;
END IF;
END IF;
attr_set.circulate :=
LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
attr_set.deposit :=
LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
attr_set.holdable :=
LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
attr_set.opac_visible :=
LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
attr_set.ref :=
LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
attr_set.call_number := tmp_attr_set.cn; -- TEXT
attr_set.barcode := tmp_attr_set.bc; -- TEXT,
attr_set.alert_message := tmp_attr_set.amessage; -- TEXT,
attr_set.pub_note := tmp_attr_set.note; -- TEXT,
attr_set.priv_note := tmp_attr_set.pnote; -- TEXT,
attr_set.alert_message := tmp_attr_set.amessage; -- TEXT,
attr_set.internal_id := tmp_attr_set.internal_id::BIGINT;
attr_set.stat_cat_data := tmp_attr_set.stat_cat_data; -- TEXT,
attr_set.parts_data := tmp_attr_set.parts_data; -- TEXT,
RETURN NEXT attr_set;
END LOOP;
END IF;
RETURN;
END;
marc21_extract_all_fixed_fields
Signature: vandelay.marc21_extract_all_fixed_fields(marc text, use_default boolean DEFAULT false)
Returns: SETOF biblio.record_ff_map
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
tag_data TEXT;
rtype TEXT;
ff_pos RECORD;
output biblio.record_ff_map%ROWTYPE;
BEGIN
rtype := (vandelay.marc21_record_type( marc )).code;
FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
output.ff_name := ff_pos.fixed_field;
output.ff_value := NULL;
IF ff_pos.tag = 'ldr' THEN
output.ff_value := oils_xpath_string('//*[local-name()="leader"]', marc);
IF output.ff_value IS NOT NULL THEN
output.ff_value := SUBSTRING( output.ff_value, ff_pos.start_pos + 1, ff_pos.length );
RETURN NEXT output;
output.ff_value := NULL;
END IF;
ELSE
FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
output.ff_value := SUBSTRING( tag_data, ff_pos.start_pos + 1, ff_pos.length );
CONTINUE WHEN output.ff_value IS NULL AND NOT use_default;
IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
RETURN NEXT output;
output.ff_value := NULL;
END LOOP;
END IF;
END LOOP;
RETURN;
END;
marc21_extract_fixed_field
Signature: vandelay.marc21_extract_fixed_field(marc text, ff text, use_default boolean DEFAULT false)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
rtype TEXT;
ff_pos RECORD;
tag_data RECORD;
val TEXT;
BEGIN
rtype := (vandelay.marc21_record_type( marc )).code;
FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
IF ff_pos.tag = 'ldr' THEN
val := oils_xpath_string('//*[local-name()="leader"]', marc);
IF val IS NOT NULL THEN
val := SUBSTRING( val, ff_pos.start_pos + 1, ff_pos.length );
RETURN val;
END IF;
ELSE
FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
RETURN val;
END LOOP;
END IF;
CONTINUE WHEN NOT use_default;
val := REPEAT( ff_pos.default_val, ff_pos.length );
RETURN val;
END LOOP;
RETURN NULL;
END;
marc21_extract_fixed_field_list
Signature: vandelay.marc21_extract_fixed_field_list(marc text, ff text, use_default boolean DEFAULT false)
Returns: text[]
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
rtype TEXT;
ff_pos RECORD;
tag_data RECORD;
val TEXT;
collection TEXT[] := '{}'::TEXT[];
BEGIN
rtype := (vandelay.marc21_record_type( marc )).code;
FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
IF ff_pos.tag = 'ldr' THEN
val := oils_xpath_string('//*[local-name()="leader"]', marc);
IF val IS NOT NULL THEN
val := SUBSTRING( val, ff_pos.start_pos + 1, ff_pos.length );
collection := collection || val;
END IF;
ELSE
FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
collection := collection || val;
END LOOP;
END IF;
CONTINUE WHEN NOT use_default;
CONTINUE WHEN ARRAY_UPPER(collection, 1) > 0;
val := REPEAT( ff_pos.default_val, ff_pos.length );
collection := collection || val;
END LOOP;
RETURN collection;
END;
marc21_physical_characteristics
Signature: vandelay.marc21_physical_characteristics(marc text)
Returns: SETOF biblio.marc21_physical_characteristics
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
rowid INT := 0;
_007 TEXT;
ptype config.marc21_physical_characteristic_type_map%ROWTYPE;
psf config.marc21_physical_characteristic_subfield_map%ROWTYPE;
pval config.marc21_physical_characteristic_value_map%ROWTYPE;
retval biblio.marc21_physical_characteristics%ROWTYPE;
BEGIN
FOR _007 IN SELECT oils_xpath_string('//*', value) FROM UNNEST(oils_xpath('//*[@tag="007"]', marc)) x(value) LOOP
IF _007 IS NOT NULL AND _007 <> '' THEN
SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
IF ptype.ptype_key IS NOT NULL THEN
FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
SELECT * INTO pval FROM config.marc21_physical_characteristic_value_map WHERE ptype_subfield = psf.id AND value = SUBSTRING( _007, psf.start_pos + 1, psf.length );
IF pval.id IS NOT NULL THEN
rowid := rowid + 1;
retval.id := rowid;
retval.ptype := ptype.ptype_key;
retval.subfield := psf.id;
retval.value := pval.id;
RETURN NEXT retval;
END IF;
END LOOP;
END IF;
END IF;
END LOOP;
RETURN;
END;
marc21_record_type
Signature: vandelay.marc21_record_type(marc text)
Returns: config.marc21_rec_type_map
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
ldr TEXT;
tval TEXT;
tval_rec RECORD;
bval TEXT;
bval_rec RECORD;
retval config.marc21_rec_type_map%ROWTYPE;
BEGIN
ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
IF ldr IS NULL OR ldr = '' THEN
SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
RETURN retval;
END IF;
SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
-- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
IF retval.code IS NULL THEN
SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
END IF;
RETURN retval;
END;
match_authority_record
Signature: vandelay.match_authority_record()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
incoming_existing_id TEXT;
test_result vandelay.match_set_test_result%ROWTYPE;
tmp_rec BIGINT;
match_set INT;
BEGIN
IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
RETURN NEW;
END IF;
DELETE FROM vandelay.authority_match WHERE queued_record = NEW.id;
SELECT q.match_set INTO match_set FROM vandelay.authority_queue q WHERE q.id = NEW.queue;
IF match_set IS NOT NULL THEN
NEW.quality := vandelay.measure_auth_record_quality( NEW.marc, match_set );
END IF;
-- Perfect matches on 901$c exit early with a match with high quality.
incoming_existing_id :=
oils_xpath_string('//*[@tag="901"]/*[@code="c"][1]', NEW.marc);
IF incoming_existing_id IS NOT NULL AND incoming_existing_id != '' THEN
SELECT id INTO tmp_rec FROM authority.record_entry WHERE id = incoming_existing_id::bigint;
IF tmp_rec IS NOT NULL THEN
INSERT INTO vandelay.authority_match (queued_record, eg_record, match_score, quality)
SELECT
NEW.id,
b.id,
9999,
-- note: no match_set means quality==0
vandelay.measure_auth_record_quality( b.marc, match_set )
FROM authority.record_entry b
WHERE id = incoming_existing_id::bigint;
END IF;
END IF;
IF match_set IS NULL THEN
RETURN NEW;
END IF;
FOR test_result IN SELECT * FROM
vandelay.match_set_test_authxml(match_set, NEW.marc) LOOP
INSERT INTO vandelay.authority_match ( queued_record, eg_record, match_score, quality )
SELECT
NEW.id,
test_result.record,
test_result.quality,
vandelay.measure_auth_record_quality( b.marc, match_set )
FROM authority.record_entry b
WHERE id = test_result.record;
END LOOP;
RETURN NEW;
END;
match_bib_record
Signature: vandelay.match_bib_record()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
incoming_existing_id TEXT;
test_result vandelay.match_set_test_result%ROWTYPE;
tmp_rec BIGINT;
match_set INT;
match_bucket INT;
BEGIN
IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
RETURN NEW;
END IF;
DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
SELECT q.match_set INTO match_set FROM vandelay.bib_queue q WHERE q.id = NEW.queue;
IF match_set IS NOT NULL THEN
NEW.quality := vandelay.measure_record_quality( NEW.marc, match_set );
END IF;
-- Perfect matches on 901$c exit early with a match with high quality.
incoming_existing_id :=
oils_xpath_string('//*[@tag="901"]/*[@code="c"][1]', NEW.marc);
IF incoming_existing_id IS NOT NULL AND incoming_existing_id != '' THEN
SELECT id INTO tmp_rec FROM biblio.record_entry WHERE id = incoming_existing_id::bigint;
IF tmp_rec IS NOT NULL THEN
INSERT INTO vandelay.bib_match (queued_record, eg_record, match_score, quality)
SELECT
NEW.id,
b.id,
9999,
-- note: no match_set means quality==0
vandelay.measure_record_quality( b.marc, match_set )
FROM biblio.record_entry b
WHERE id = incoming_existing_id::bigint;
END IF;
END IF;
IF match_set IS NULL THEN
RETURN NEW;
END IF;
SELECT q.match_bucket INTO match_bucket FROM vandelay.bib_queue q WHERE q.id = NEW.queue;
FOR test_result IN SELECT * FROM
vandelay.match_set_test_marcxml(match_set, NEW.marc, match_bucket) LOOP
INSERT INTO vandelay.bib_match ( queued_record, eg_record, match_score, quality )
SELECT
NEW.id,
test_result.record,
test_result.quality,
vandelay.measure_record_quality( b.marc, match_set )
FROM biblio.record_entry b
WHERE id = test_result.record;
END LOOP;
RETURN NEW;
END;
match_set_test_authxml
Signature: vandelay.match_set_test_authxml(match_set_id integer, record_xml text)
Returns: SETOF vandelay.match_set_test_result
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
tags_rstore HSTORE;
heading TEXT;
coal TEXT;
joins TEXT;
query_ TEXT;
wq TEXT;
qvalue INTEGER;
rec RECORD;
BEGIN
tags_rstore := vandelay.flatten_marc_hstore(record_xml);
SELECT normalize_heading INTO heading
FROM authority.normalize_heading(record_xml);
CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
-- generate the where clause and return that directly (into wq), and as
-- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
wq := vandelay.get_expr_from_match_set(
match_set_id, tags_rstore, heading);
query_ := 'SELECT DISTINCT(record), ';
-- qrows table is for the quality bits we add to the SELECT clause
SELECT STRING_AGG(
'COALESCE(n' || q::TEXT || '.quality, 0)', ' + '
) INTO coal FROM _vandelay_tmp_qrows;
-- our query string so far is the SELECT clause and the inital FROM.
-- no JOINs yet nor the WHERE clause
query_ := query_ || coal || ' AS quality ' || E'\n';
-- jrows table is for the joins we must make (and the real text conditions)
SELECT STRING_AGG(j, E'\n') INTO joins
FROM _vandelay_tmp_jrows;
-- add those joins and the where clause to our query.
query_ := query_ || joins || E'\n';
query_ := query_ || 'JOIN authority.record_entry are ON (are.id = record) '
|| 'WHERE ' || wq || ' AND not are.deleted';
-- this will return rows of record,quality
FOR rec IN EXECUTE query_ USING tags_rstore LOOP
RETURN NEXT rec;
END LOOP;
DROP TABLE _vandelay_tmp_qrows;
DROP TABLE _vandelay_tmp_jrows;
RETURN;
END;
match_set_test_marcxml
Signature: vandelay.match_set_test_marcxml(match_set_id integer, record_xml text, bucket_id integer)
Returns: SETOF vandelay.match_set_test_result
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
tags_rstore HSTORE;
svf_rstore HSTORE;
coal TEXT;
joins TEXT;
query_ TEXT;
wq TEXT;
qvalue INTEGER;
rec RECORD;
BEGIN
tags_rstore := vandelay.flatten_marc_hstore(record_xml);
svf_rstore := vandelay.extract_rec_attrs(record_xml);
CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
-- generate the where clause and return that directly (into wq), and as
-- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
wq := vandelay.get_expr_from_match_set(match_set_id, tags_rstore);
query_ := 'SELECT DISTINCT(record), ';
-- qrows table is for the quality bits we add to the SELECT clause
SELECT STRING_AGG(
'COALESCE(n' || q::TEXT || '.quality, 0)', ' + '
) INTO coal FROM _vandelay_tmp_qrows;
-- our query string so far is the SELECT clause and the inital FROM.
-- no JOINs yet nor the WHERE clause
query_ := query_ || coal || ' AS quality ' || E'\n';
-- jrows table is for the joins we must make (and the real text conditions)
SELECT STRING_AGG(j, E'\n') INTO joins
FROM _vandelay_tmp_jrows;
-- add those joins and the where clause to our query.
query_ := query_ || joins || E'\n';
-- join the record bucket
IF bucket_id IS NOT NULL THEN
query_ := query_ || 'JOIN container.biblio_record_entry_bucket_item ' ||
'brebi ON (brebi.target_biblio_record_entry = record ' ||
'AND brebi.bucket = ' || bucket_id || E')\n';
END IF;
query_ := query_ || 'JOIN biblio.record_entry bre ON (bre.id = record) ' || 'WHERE ' || wq || ' AND not bre.deleted';
-- this will return rows of record,quality
FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
RETURN NEXT rec;
END LOOP;
DROP TABLE _vandelay_tmp_qrows;
DROP TABLE _vandelay_tmp_jrows;
RETURN;
END;
measure_auth_record_quality
Signature: vandelay.measure_auth_record_quality(xml text, match_set_id integer)
Returns: integer
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
out_q INT := 0;
rvalue TEXT;
test vandelay.match_set_quality%ROWTYPE;
BEGIN
FOR test IN SELECT * FROM vandelay.match_set_quality
WHERE match_set = match_set_id LOOP
IF test.tag IS NOT NULL THEN
FOR rvalue IN SELECT value FROM vandelay.flatten_marc( xml )
WHERE tag = test.tag AND subfield = test.subfield LOOP
IF test.value = rvalue THEN
out_q := out_q + test.quality;
END IF;
END LOOP;
END IF;
END LOOP;
RETURN out_q;
END;
measure_record_quality
Signature: vandelay.measure_record_quality(xml text, match_set_id integer)
Returns: integer
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
out_q INT := 0;
rvalue TEXT;
test vandelay.match_set_quality%ROWTYPE;
BEGIN
FOR test IN SELECT * FROM vandelay.match_set_quality WHERE match_set = match_set_id LOOP
IF test.tag IS NOT NULL THEN
FOR rvalue IN SELECT value FROM vandelay.flatten_marc( xml ) WHERE tag = test.tag AND subfield = test.subfield LOOP
IF test.value = rvalue THEN
out_q := out_q + test.quality;
END IF;
END LOOP;
ELSE
IF test.value = vandelay.extract_rec_attrs(xml, ARRAY[test.svf]) -> test.svf THEN
out_q := out_q + test.quality;
END IF;
END IF;
END LOOP;
RETURN out_q;
END;
merge_record_xml
Signature: vandelay.merge_record_xml(target_marc text, template_marc text)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
dyn_profile vandelay.compile_profile%ROWTYPE;
replace_rule TEXT;
tmp_marc TEXT;
trgt_marc TEXT;
tmpl_marc TEXT;
match_count INT;
BEGIN
IF target_marc IS NULL OR template_marc IS NULL THEN
-- RAISE NOTICE 'no marc for target or template record';
RETURN NULL;
END IF;
dyn_profile := vandelay.compile_profile( template_marc );
IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
-- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
RETURN NULL;
END IF;
IF dyn_profile.replace_rule = '' AND dyn_profile.preserve_rule = '' AND dyn_profile.add_rule = '' AND dyn_profile.strip_rule = '' THEN
--Since we have nothing to do, just return what we were given.
RETURN target_marc;
ELSIF dyn_profile.replace_rule <> '' THEN
trgt_marc = target_marc;
tmpl_marc = template_marc;
replace_rule = dyn_profile.replace_rule;
ELSE
tmp_marc = target_marc;
trgt_marc = template_marc;
tmpl_marc = tmp_marc;
replace_rule = dyn_profile.preserve_rule;
END IF;
RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
END;
merge_record_xml
Signature: vandelay.merge_record_xml(target_xml text, source_xml text, add_rule text, replace_preserve_rule text, strip_rule text)
Returns: text
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
merge_record_xml_using_profile
Signature: vandelay.merge_record_xml_using_profile(incoming_marc text, existing_marc text, merge_profile_id bigint)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
merge_profile vandelay.merge_profile%ROWTYPE;
dyn_profile vandelay.compile_profile%ROWTYPE;
target_marc TEXT;
source_marc TEXT;
replace_rule TEXT;
match_count INT;
BEGIN
IF existing_marc IS NULL OR incoming_marc IS NULL THEN
-- RAISE NOTICE 'no marc for source or target records';
RETURN NULL;
END IF;
IF merge_profile_id IS NOT NULL THEN
SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
IF FOUND THEN
dyn_profile.add_rule := COALESCE(merge_profile.add_spec,'');
dyn_profile.strip_rule := COALESCE(merge_profile.strip_spec,'');
dyn_profile.replace_rule := COALESCE(merge_profile.replace_spec,'');
dyn_profile.preserve_rule := COALESCE(merge_profile.preserve_spec,'');
ELSE
-- RAISE NOTICE 'merge profile not found';
RETURN NULL;
END IF;
ELSE
-- RAISE NOTICE 'no merge profile specified';
RETURN NULL;
END IF;
IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
-- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
RETURN NULL;
END IF;
IF dyn_profile.replace_rule = '' AND dyn_profile.preserve_rule = '' AND dyn_profile.add_rule = '' AND dyn_profile.strip_rule = '' THEN
-- Since we have nothing to do, just return a target record as is
RETURN existing_marc;
ELSIF dyn_profile.preserve_rule <> '' THEN
source_marc = existing_marc;
target_marc = incoming_marc;
replace_rule = dyn_profile.preserve_rule;
ELSE
source_marc = incoming_marc;
target_marc = existing_marc;
replace_rule = dyn_profile.replace_rule;
END IF;
RETURN vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
END;
overlay_authority_record
Signature: vandelay.overlay_authority_record(import_id bigint, eg_id bigint, merge_profile_id integer)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
merge_profile vandelay.merge_profile%ROWTYPE;
dyn_profile vandelay.compile_profile%ROWTYPE;
editor_string TEXT;
new_editor INT;
new_edit_date TIMESTAMPTZ;
source_marc TEXT;
target_marc TEXT;
eg_marc_row authority.record_entry%ROWTYPE;
eg_marc TEXT;
v_marc TEXT;
replace_rule TEXT;
match_count INT;
update_query TEXT;
BEGIN
SELECT * INTO eg_marc_row
FROM authority.record_entry b
JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
LIMIT 1;
SELECT q.marc INTO v_marc
FROM vandelay.queued_record q
JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
LIMIT 1;
eg_marc := eg_marc_row.marc;
IF eg_marc IS NULL OR v_marc IS NULL THEN
-- RAISE NOTICE 'no marc for vandelay or authority record';
RETURN FALSE;
END IF;
-- Extract the editor string before any modification to the vandelay
-- MARC occur.
editor_string :=
(oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
-- If an editor value can be found, update the authority record
-- editor and edit_date values.
IF editor_string IS NOT NULL AND editor_string <> '' THEN
-- Vandelay.pm sets the value to 'usrname' when needed.
SELECT id INTO new_editor
FROM actor.usr WHERE usrname = editor_string;
IF new_editor IS NULL THEN
SELECT usr INTO new_editor
FROM actor.card WHERE barcode = editor_string;
END IF;
IF new_editor IS NOT NULL THEN
new_edit_date := NOW();
ELSE -- No valid editor, use current values
new_editor = eg_marc_row.editor;
new_edit_date = eg_marc_row.edit_date;
END IF;
ELSE
new_editor = eg_marc_row.editor;
new_edit_date = eg_marc_row.edit_date;
END IF;
dyn_profile := vandelay.compile_profile( v_marc );
IF merge_profile_id IS NOT NULL THEN
SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
IF FOUND THEN
dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
END IF;
END IF;
IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
-- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
RETURN FALSE;
END IF;
IF dyn_profile.replace_rule = '' AND dyn_profile.preserve_rule = '' AND dyn_profile.add_rule = '' AND dyn_profile.strip_rule = '' THEN
--Since we have nothing to do, just return a NOOP "we did it"
RETURN TRUE;
ELSIF dyn_profile.replace_rule <> '' THEN
source_marc = v_marc;
target_marc = eg_marc;
replace_rule = dyn_profile.replace_rule;
ELSE
source_marc = eg_marc;
target_marc = v_marc;
replace_rule = dyn_profile.preserve_rule;
END IF;
UPDATE authority.record_entry
SET marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule ),
editor = new_editor,
edit_date = new_edit_date
WHERE id = eg_id;
IF NOT FOUND THEN
-- Import/merge failed. Nothing left to do.
RETURN FALSE;
END IF;
-- Authority record successfully merged / imported.
-- Update the vandelay record to show the successful import.
UPDATE vandelay.queued_authority_record
SET imported_as = eg_id,
import_time = NOW()
WHERE id = import_id;
RETURN TRUE;
END;
overlay_bib_record
Signature: vandelay.overlay_bib_record(import_id bigint, eg_id bigint, merge_profile_id integer)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
editor_string TEXT;
editor_id INT;
v_marc TEXT;
v_bib_source INT;
update_fields TEXT[];
update_query TEXT;
update_bib_source BOOL;
update_bib_editor BOOL;
BEGIN
SELECT q.marc, q.bib_source INTO v_marc, v_bib_source
FROM vandelay.queued_bib_record q
JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
LIMIT 1;
IF v_marc IS NULL THEN
-- RAISE NOTICE 'no marc for vandelay or bib record';
RETURN FALSE;
END IF;
IF NOT vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
-- no update happened, get outta here.
RETURN FALSE;
END IF;
UPDATE vandelay.queued_bib_record
SET imported_as = eg_id,
import_time = NOW()
WHERE id = import_id;
SELECT q.update_bib_source INTO update_bib_source
FROM vandelay.merge_profile q where q.id = merge_profile_Id;
IF update_bib_source AND v_bib_source IS NOT NULL THEN
update_fields := ARRAY_APPEND(update_fields, 'source = ' || v_bib_source);
END IF;
SELECT q.update_bib_editor INTO update_bib_editor
FROM vandelay.merge_profile q where q.id = merge_profile_Id;
IF update_bib_editor THEN
editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
IF editor_string IS NOT NULL AND editor_string <> '' THEN
SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
IF editor_id IS NULL THEN
SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
END IF;
IF editor_id IS NOT NULL THEN
--only update the edit date if we have a valid editor
update_fields := ARRAY_APPEND(
update_fields, 'editor = ' || editor_id || ', edit_date = NOW()');
END IF;
END IF;
END IF;
IF ARRAY_LENGTH(update_fields, 1) > 0 THEN
update_query := 'UPDATE biblio.record_entry SET ' ||
ARRAY_TO_STRING(update_fields, ',') || ' WHERE id = ' || eg_id || ';';
EXECUTE update_query;
END IF;
RETURN TRUE;
END;
replace_field
Signature: vandelay.replace_field(target_xml text, source_xml text, field text)
Returns: text
Language |
plperlu |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
use strict;
use MARC::Record;
use MARC::Field;
use MARC::File::XML (BinaryEncoding => 'UTF-8');
use MARC::Charset;
MARC::Charset->assume_unicode(1);
my $target_xml = shift;
my $source_xml = shift;
my $field_spec = shift;
my $target_r = MARC::Record->new_from_xml($target_xml);
my $source_r = MARC::Record->new_from_xml($source_xml);
return $target_xml unless $target_r && $source_r;
# Extract the field_spec components into MARC tags, subfields,
# and regex matches. Copied wholesale from vandelay.strip_field()
my @field_list = split(',', $field_spec);
my %fields;
for my $f (@field_list) {
$f =~ s/^\s*//; $f =~ s/\s*$//;
if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
my $field = $1;
$field =~ s/\s+//;
my $sf = $2;
$sf =~ s/\s+//;
my $matches = $3;
$matches =~ s/^\s*//; $matches =~ s/\s*$//;
$fields{$field} = { sf => [ split('', $sf) ] };
if ($matches) {
for my $match (split('&&', $matches)) {
$match =~ s/^\s*//; $match =~ s/\s*$//;
my ($msf,$mre) = split('~', $match);
if (length($msf) > 0 and length($mre) > 0) {
$msf =~ s/^\s*//; $msf =~ s/\s*$//;
$mre =~ s/^\s*//; $mre =~ s/\s*$//;
$fields{$field}{match}{$msf} = qr/$mre/;
}
}
}
}
}
# Returns a flat list of subfield (code, value, code, value, ...)
# suitable for adding to a MARC::Field.
sub generate_replacement_subfields {
my ($source_field, $target_field, @controlled_subfields) = @_;
# Performing a wholesale field replacment.
# Use the entire source field as-is.
return map {$_->[0], $_->[1]} $source_field->subfields
unless @controlled_subfields;
my @new_subfields;
# Iterate over all target field subfields:
# 1. Keep uncontrolled subfields as is.
# 2. Replace values for controlled subfields when a
# replacement value exists on the source record.
# 3. Delete values for controlled subfields when no
# replacement value exists on the source record.
for my $target_sf ($target_field->subfields) {
my $subfield = $target_sf->[0];
my $target_val = $target_sf->[1];
if (grep {$_ eq $subfield} @controlled_subfields) {
if (my $source_val = $source_field->subfield($subfield)) {
# We have a replacement value
push(@new_subfields, $subfield, $source_val);
} else {
# no replacement value for controlled subfield, drop it.
}
} else {
# Field is not controlled. Copy it over as-is.
push(@new_subfields, $subfield, $target_val);
}
}
# Iterate over all subfields in the source field and back-fill
# any values that exist only in the source field. Insert these
# subfields in the same relative position they exist in the
# source field.
my @seen_subfields;
for my $source_sf ($source_field->subfields) {
my $subfield = $source_sf->[0];
my $source_val = $source_sf->[1];
push(@seen_subfields, $subfield);
# target field already contains this subfield,
# so it would have been addressed above.
next if $target_field->subfield($subfield);
# Ignore uncontrolled subfields.
next unless grep {$_ eq $subfield} @controlled_subfields;
# Adding a new subfield. Find its relative position and add
# it to the list under construction. Work backwards from
# the list of already seen subfields to find the best slot.
my $done = 0;
for my $seen_sf (reverse(@seen_subfields)) {
my $idx = @new_subfields;
for my $new_sf (reverse(@new_subfields)) {
$idx--;
next if $idx % 2 == 1; # sf codes are in the even slots
if ($new_subfields[$idx] eq $seen_sf) {
splice(@new_subfields, $idx + 2, 0, $subfield, $source_val);
$done = 1;
last;
}
}
last if $done;
}
# if no slot was found, add to the end of the list.
push(@new_subfields, $subfield, $source_val) unless $done;
}
return @new_subfields;
}
# MARC tag loop
for my $f (keys %fields) {
my $tag_idx = -1;
my @target_fields = $target_r->field($f);
if (!@target_fields and !defined($fields{$f}{match})) {
# we will just add the source fields
# unless they require a target match.
my @add_these = map { $_->clone } $source_r->field($f);
$target_r->insert_fields_ordered( @add_these );
}
for my $target_field (@target_fields) { # This will not run when the above "if" does.
# field spec contains a regex for this field. Confirm field on
# target record matches the specified regex before replacing.
if (exists($fields{$f}{match})) {
my @match_list;
for my $match_key_sf_code ( keys %{$fields{$f}{match}} ) {
# We loop here because there might be multiple SFs, such as multiple
# $0s in an authority controlled datafield, where one has the EG-special
# format, and others are links to external heading data.
for my $sf_content ($target_field->subfield($match_key_sf_code)) {
if ($sf_content =~ $fields{$f}{match}{$match_key_sf_code}) {
push @match_list, $sf_content;
}
}
}
next unless (scalar(@match_list) >= scalar(keys %{$fields{$f}{match}}));
}
my @new_subfields;
my @controlled_subfields = @{$fields{$f}{sf}};
# If the target record has multiple matching bib fields,
# replace them from matching fields on the source record
# in a predictable order to avoid replacing with them with
# same source field repeatedly.
my @source_fields = $source_r->field($f);
my $source_field = $source_fields[++$tag_idx];
if (!$source_field && @controlled_subfields) {
# When there are more target fields than source fields
# and we are replacing values for subfields and not
# performing wholesale field replacment, use the last
# available source field as the input for all remaining
# target fields.
$source_field = $source_fields[$#source_fields];
}
if (!$source_field) {
# No source field exists. Delete all affected target
# data. This is a little bit counterintuitive, but is
# backwards compatible with the previous version of this
# function which first deleted all affected data, then
# replaced values where possible.
if (@controlled_subfields) {
$target_field->delete_subfield($_) for @controlled_subfields;
} else {
$target_r->delete_field($target_field);
}
next;
}
my @new_subfields = generate_replacement_subfields(
$source_field, $target_field, @controlled_subfields);
# Build the replacement field from scratch.
my $replacement_field = MARC::Field->new(
$target_field->tag,
$target_field->indicator(1),
$target_field->indicator(2),
@new_subfields
);
$target_field->replace_with($replacement_field);
}
}
$target_xml = $target_r->as_xml_record;
$target_xml =~ s/^<\?.+?\?>$//mo;
$target_xml =~ s/\n//sgo;
$target_xml =~ s/>\s+</></sgo;
return $target_xml;
strip_field
Signature: vandelay.strip_field(xml text, field text)
Returns: text
Language |
plperlu |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
use MARC::Record;
use MARC::File::XML (BinaryEncoding => 'UTF-8');
use MARC::Charset;
use strict;
MARC::Charset->assume_unicode(1);
my $xml = shift;
my $r = MARC::Record->new_from_xml( $xml );
return $xml unless ($r);
my $field_spec = shift;
my @field_list = split(',', $field_spec);
my %fields;
for my $f (@field_list) {
$f =~ s/^\s*//; $f =~ s/\s*$//;
if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
my $field = $1;
$field =~ s/\s+//;
my $sf = $2;
$sf =~ s/\s+//;
my $matches = $3;
$matches =~ s/^\s*//; $matches =~ s/\s*$//;
$fields{$field} = { sf => [ split('', $sf) ] };
if ($matches) {
for my $match (split('&&', $matches)) {
$match =~ s/^\s*//; $match =~ s/\s*$//;
my ($msf,$mre) = split('~', $match);
if (length($msf) > 0 and length($mre) > 0) {
$msf =~ s/^\s*//; $msf =~ s/\s*$//;
$mre =~ s/^\s*//; $mre =~ s/\s*$//;
$fields{$field}{match}{$msf} = qr/$mre/;
}
}
}
}
}
for my $f ( keys %fields) {
for my $to_field ($r->field( $f )) {
if (exists($fields{$f}{match})) {
my @match_list;
for my $match_key_sf_code ( keys %{$fields{$f}{match}} ) {
# We loop here because there might be multiple SFs, such as multiple
# $0s in an authority controlled datafield, where one has the EG-special
# format, and others are links to external heading data.
for my $sf_content ($to_field->subfield($match_key_sf_code)) {
if ($sf_content =~ $fields{$f}{match}{$match_key_sf_code}) {
push @match_list, $sf_content;
}
}
}
next unless (scalar(@match_list) >= scalar(keys %{$fields{$f}{match}}));
}
if ( @{$fields{$f}{sf}} ) {
$to_field->delete_subfield(code => $fields{$f}{sf});
} else {
$r->delete_field( $to_field );
}
}
}
$xml = $r->as_xml_record;
$xml =~ s/^<\?.+?\?>$//mo;
$xml =~ s/\n//sgo;
$xml =~ s/>\s+</></sgo;
return $xml;
template_overlay_bib_record
Signature: vandelay.template_overlay_bib_record(v_marc text, eg_id bigint)
Returns: boolean
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
template_overlay_bib_record
Signature: vandelay.template_overlay_bib_record(v_marc text, eg_id bigint, merge_profile_id integer)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
merge_profile vandelay.merge_profile%ROWTYPE;
dyn_profile vandelay.compile_profile%ROWTYPE;
editor_string TEXT;
editor_id INT;
source_marc TEXT;
target_marc TEXT;
eg_marc TEXT;
replace_rule TEXT;
match_count INT;
BEGIN
SELECT b.marc INTO eg_marc
FROM biblio.record_entry b
WHERE b.id = eg_id
LIMIT 1;
IF eg_marc IS NULL OR v_marc IS NULL THEN
-- RAISE NOTICE 'no marc for template or bib record';
RETURN FALSE;
END IF;
dyn_profile := vandelay.compile_profile( v_marc );
IF merge_profile_id IS NOT NULL THEN
SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
IF FOUND THEN
dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
END IF;
END IF;
IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
-- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
RETURN FALSE;
END IF;
IF dyn_profile.replace_rule = '' AND dyn_profile.preserve_rule = '' AND dyn_profile.add_rule = '' AND dyn_profile.strip_rule = '' THEN
--Since we have nothing to do, just return a NOOP "we did it"
RETURN TRUE;
ELSIF dyn_profile.replace_rule <> '' THEN
source_marc = v_marc;
target_marc = eg_marc;
replace_rule = dyn_profile.replace_rule;
ELSE
source_marc = eg_marc;
target_marc = v_marc;
replace_rule = dyn_profile.preserve_rule;
END IF;
UPDATE biblio.record_entry
SET marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
WHERE id = eg_id;
IF NOT FOUND THEN
-- RAISE NOTICE 'update of biblio.record_entry failed';
RETURN FALSE;
END IF;
RETURN TRUE;
END;