<?xml version="1.0" encoding="UTF-8"?><database name="egdb3_11_7" schema="vandelay" type="PostgreSQL - 15.10 (Ubuntu 15.10-1.pgdg24.04+1)">
   <sequences>
      <sequence increment="1" name="authority_attr_definition_id_seq" startValue="1"/>
      <sequence increment="1" name="authority_match_id_seq" startValue="1"/>
      <sequence increment="1" name="bib_attr_definition_id_seq" startValue="1"/>
      <sequence increment="1" name="bib_match_id_seq" startValue="1"/>
      <sequence increment="1" name="import_bib_trash_fields_id_seq" startValue="1"/>
      <sequence increment="1" name="import_bib_trash_group_id_seq" startValue="1"/>
      <sequence increment="1" name="import_item_attr_definition_id_seq" startValue="1"/>
      <sequence increment="1" name="import_item_id_seq" startValue="1"/>
      <sequence increment="1" name="match_set_id_seq" startValue="1"/>
      <sequence increment="1" name="match_set_point_id_seq" startValue="1"/>
      <sequence increment="1" name="match_set_quality_id_seq" startValue="1"/>
      <sequence increment="1" name="merge_profile_id_seq" startValue="1"/>
      <sequence increment="1" name="queue_id_seq" startValue="1"/>
      <sequence increment="1" name="queued_authority_record_attr_id_seq" startValue="1"/>
      <sequence increment="1" name="queued_bib_record_attr_id_seq" startValue="1"/>
      <sequence increment="1" name="queued_record_id_seq" startValue="1"/>
      <sequence increment="1" name="session_tracker_id_seq" startValue="1"/>
   </sequences>
   <tables>
      <table name="authority_attr_definition" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.authority_attr_definition_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="serial" typeCode="4">
            <child column="field" foreignKey="queued_authority_record_attr_field_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="queued_authority_record_attr"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="code" nullable="false" remarks="" size="2147483647" type="text" typeCode="12">
            <parent column="code" foreignKey="Implied Constraint" implied="true" onDeleteCascade="false" schema="vandelay" table="import_error"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="description" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="xpath" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="''::text" digits="0" id="4" name="remove" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="authority_attr_definition_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="authority_attr_definition_code_key" unique="true">
            <column ascending="true" name="code"/>
         </index>
      </table>
      <table name="authority_match" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.authority_match_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="queued_record" nullable="true" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="authority_match_queued_record_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="queued_authority_record"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="eg_record" nullable="true" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="authority_match_eg_record_fkey" implied="false" onDeleteCascade="false" schema="authority" table="record_entry"/>
         </column>
         <column autoUpdated="false" defaultValue="0" digits="0" id="3" name="quality" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="0" digits="0" id="4" name="match_score" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="authority_match_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
      </table>
      <table name="authority_queue" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.queue_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5">
            <child column="queue" foreignKey="queued_authority_record_queue_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="queued_authority_record"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="owner" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="name" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="false" digits="0" id="3" name="complete" nullable="false" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="match_set" nullable="true" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="'authority'::vandelay.authority_queue_queue_type" digits="0" id="5" name="queue_type" nullable="false" remarks="" size="2147483647" type="&quot;vandelay&quot;.&quot;authority_queue_queue_type&quot;" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="authority_queue_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="vand_authority_queue_name_once_per_owner_const" unique="true">
            <column ascending="true" name="owner"/>
            <column ascending="true" name="name"/>
            <column ascending="true" name="queue_type"/>
         </index>
      </table>
      <table name="bib_attr_definition" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.bib_attr_definition_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="serial" typeCode="4">
            <child column="field" foreignKey="queued_bib_record_attr_field_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="queued_bib_record_attr"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="code" nullable="false" remarks="" size="2147483647" type="text" typeCode="12">
            <parent column="code" foreignKey="Implied Constraint" implied="true" onDeleteCascade="false" schema="vandelay" table="import_error"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="description" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="xpath" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="''::text" digits="0" id="4" name="remove" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="bib_attr_definition_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="bib_attr_definition_code_key" unique="true">
            <column ascending="true" name="code"/>
         </index>
      </table>
      <table name="bib_match" numRows="53" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.bib_match_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="queued_record" nullable="true" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="bib_match_queued_record_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="queued_bib_record"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="eg_record" nullable="true" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="bib_match_eg_record_fkey" implied="false" onDeleteCascade="true" schema="biblio" table="record_entry"/>
         </column>
         <column autoUpdated="false" defaultValue="1" digits="0" id="3" name="quality" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="0" digits="0" id="4" name="match_score" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="bib_match_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="bib_match_queued_record_idx" unique="false">
            <column ascending="true" name="queued_record"/>
         </index>
      </table>
      <table name="bib_queue" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.queue_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5">
            <child column="queue" foreignKey="queued_bib_record_queue_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="queued_bib_record"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="owner" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="name" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="false" digits="0" id="3" name="complete" nullable="false" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="match_set" nullable="true" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="'bib'::vandelay.bib_queue_queue_type" digits="0" id="5" name="queue_type" nullable="false" remarks="" size="2147483647" type="&quot;vandelay&quot;.&quot;bib_queue_queue_type&quot;" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="6" name="item_attr_def" nullable="true" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="bib_queue_item_attr_def_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="import_item_attr_definition"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="7" name="match_bucket" nullable="true" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="match_bucket_fkey" implied="false" onDeleteCascade="false" schema="container" table="biblio_record_entry_bucket"/>
         </column>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="bib_queue_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="vand_bib_queue_name_once_per_owner_const" unique="true">
            <column ascending="true" name="owner"/>
            <column ascending="true" name="name"/>
            <column ascending="true" name="queue_type"/>
         </index>
      </table>
      <table name="import_bib_trash_fields" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.import_bib_trash_fields_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="grp" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="import_bib_trash_fields_grp_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="import_bib_trash_group"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="field" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="import_bib_trash_fields_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="vand_import_bib_trash_fields_once_per" unique="true">
            <column ascending="true" name="grp"/>
            <column ascending="true" name="field"/>
         </index>
      </table>
      <table name="import_bib_trash_group" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.import_bib_trash_group_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="serial" typeCode="4">
            <child column="grp" foreignKey="import_bib_trash_fields_grp_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="import_bib_trash_fields"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="owner" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="import_bib_trash_group_owner_fkey" implied="false" onDeleteCascade="false" schema="actor" table="org_unit"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="label" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="false" digits="0" id="3" name="always_apply" nullable="false" remarks="" size="1" type="bool" typeCode="-7"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="import_bib_trash_group_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="vand_import_bib_trash_grp_owner_label" unique="true">
            <column ascending="true" name="owner"/>
            <column ascending="true" name="label"/>
         </index>
      </table>
      <table name="import_error" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="code" nullable="false" remarks="" size="2147483647" type="text" typeCode="12">
            <child column="code" foreignKey="Implied Constraint" implied="true" onDeleteCascade="false" schema="vandelay" table="authority_attr_definition"/>
            <child column="code" foreignKey="Implied Constraint" implied="true" onDeleteCascade="false" schema="vandelay" table="bib_attr_definition"/>
            <child column="import_error" foreignKey="import_item_import_error_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="import_item"/>
            <child column="import_error" foreignKey="queued_authority_record_import_error_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="queued_authority_record"/>
            <child column="import_error" foreignKey="queued_bib_record_import_error_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="queued_bib_record"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="description" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="code" sequenceNumberInPK="1"/>
         <index name="import_error_pkey" unique="true">
            <column ascending="true" name="code"/>
         </index>
      </table>
      <table name="import_item" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.import_item_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="record" nullable="false" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="import_item_record_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="queued_bib_record"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="definition" nullable="false" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="import_item_definition_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="import_item_attr_definition"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="import_error" nullable="true" remarks="" size="2147483647" type="text" typeCode="12">
            <parent column="code" foreignKey="import_item_import_error_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="import_error"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="error_detail" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="5" name="imported_as" nullable="true" remarks="" size="19" type="int8" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="6" id="6" name="import_time" nullable="true" remarks="" size="35" type="timestamptz" typeCode="93"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="7" name="owning_lib" nullable="true" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="8" name="circ_lib" nullable="true" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="9" name="call_number" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="10" name="copy_number" nullable="true" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="11" name="status" nullable="true" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="12" name="location" nullable="true" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="13" name="circulate" nullable="true" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="14" name="deposit" nullable="true" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="2" id="15" name="deposit_amount" nullable="true" remarks="" size="8" type="numeric" typeCode="2"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="16" name="ref" nullable="true" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="17" name="holdable" nullable="true" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="2" id="18" name="price" nullable="true" remarks="" size="8" type="numeric" typeCode="2"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="19" name="barcode" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="20" name="circ_modifier" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="21" name="circ_as_type" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="22" name="alert_message" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="23" name="pub_note" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="24" name="priv_note" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="25" name="stat_cat_data" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="26" name="parts_data" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="27" name="opac_visible" nullable="true" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="28" name="internal_id" nullable="true" remarks="" size="19" type="int8" typeCode="-5"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="import_item_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="import_item_record_idx" unique="false">
            <column ascending="true" name="record"/>
         </index>
      </table>
      <table name="import_item_attr_definition" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.import_item_attr_definition_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5">
            <child column="item_attr_def" foreignKey="bib_queue_item_attr_def_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="bib_queue"/>
            <child column="definition" foreignKey="import_item_definition_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="import_item"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="owner" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="import_item_attr_definition_owner_fkey" implied="false" onDeleteCascade="true" schema="actor" table="org_unit"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="name" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="tag" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="false" digits="0" id="4" name="keep" nullable="false" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="5" name="owning_lib" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="6" name="circ_lib" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="7" name="call_number" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="8" name="copy_number" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="9" name="status" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="10" name="location" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="11" name="circulate" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="12" name="deposit" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="13" name="deposit_amount" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="14" name="ref" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="15" name="holdable" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="16" name="price" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="17" name="barcode" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="18" name="circ_modifier" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="19" name="circ_as_type" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="20" name="alert_message" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="21" name="opac_visible" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="22" name="pub_note_title" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="23" name="pub_note" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="24" name="priv_note_title" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="25" name="priv_note" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="26" name="internal_id" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="27" name="stat_cat_data" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="28" name="parts_data" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="import_item_attr_definition_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="vand_import_item_attr_def_idx" unique="true">
            <column ascending="true" name="owner"/>
            <column ascending="true" name="name"/>
         </index>
      </table>
      <table name="match_set" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.match_set_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="serial" typeCode="4">
            <child column="match_set" foreignKey="match_set_point_match_set_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="match_set_point"/>
            <child column="match_set" foreignKey="match_set_quality_match_set_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="match_set_quality"/>
            <child column="match_set" foreignKey="queue_match_set_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="queue"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="name" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="owner" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="match_set_owner_fkey" implied="false" onDeleteCascade="true" schema="actor" table="org_unit"/>
         </column>
         <column autoUpdated="false" defaultValue="'biblio'::text" digits="0" id="3" name="mtype" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="match_set_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="name_once_per_owner_mtype" unique="true">
            <column ascending="true" name="name"/>
            <column ascending="true" name="owner"/>
            <column ascending="true" name="mtype"/>
         </index>
      </table>
      <table name="match_set_point" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.match_set_point_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="serial" typeCode="4">
            <child column="parent" foreignKey="match_set_point_parent_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="match_set_point"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="match_set" nullable="true" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="match_set_point_match_set_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="match_set"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="parent" nullable="true" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="match_set_point_parent_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="match_set_point"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="bool_op" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="svf" nullable="true" remarks="" size="2147483647" type="text" typeCode="12">
            <parent column="name" foreignKey="match_set_point_svf_fkey" implied="false" onDeleteCascade="false" schema="config" table="record_attr_definition"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="5" name="tag" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="6" name="subfield" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="false" digits="0" id="7" name="negate" nullable="true" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="1" digits="0" id="8" name="quality" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="false" digits="0" id="9" name="heading" nullable="false" remarks="" size="1" type="bool" typeCode="-7"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="match_set_point_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <checkConstraint constraint="(((bool_op IS NULL) OR (bool_op = ANY (ARRAY['AND'::text, 'OR'::text, 'NOT'::text]))))" name="match_set_point_bool_op_check"/>
         <checkConstraint constraint="((((tag IS NOT NULL) AND (subfield IS NOT NULL)) OR (tag IS NULL)))" name="vmsp_need_a_subfield_with_a_tag"/>
         <checkConstraint constraint="((((tag IS NOT NULL) AND (svf IS NULL) AND (heading IS FALSE) AND (bool_op IS NULL)) OR ((tag IS NULL) AND (svf IS NOT NULL) AND (heading IS FALSE) AND (bool_op IS NULL)) OR ((tag IS NULL) AND (svf IS NULL) AND (heading IS TRUE) AND (bool_op IS NULL)) OR ((tag IS NULL) AND (svf IS NULL) AND (heading IS FALSE) AND (bool_op IS NOT NULL))))" name="vmsp_need_a_tag_or_a_ff_or_a_bo"/>
      </table>
      <table name="match_set_quality" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.match_set_quality_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="serial" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="match_set" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="match_set_quality_match_set_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="match_set"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="svf" nullable="true" remarks="" size="2147483647" type="text" typeCode="12">
            <parent column="name" foreignKey="match_set_quality_svf_fkey" implied="false" onDeleteCascade="false" schema="config" table="record_attr_definition"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="tag" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="subfield" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="5" name="value" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="1" digits="0" id="6" name="quality" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="match_set_quality_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="vmsq_def_once_per_set" unique="true">
            <column ascending="true" name="match_set"/>
            <column ascending="true" name="value"/>
         </index>
         <checkConstraint constraint="((((tag IS NOT NULL) AND (subfield IS NOT NULL)) OR (tag IS NULL)))" name="vmsq_need_a_subfield_with_a_tag"/>
         <checkConstraint constraint="((((tag IS NOT NULL) AND (svf IS NULL)) OR ((tag IS NULL) AND (svf IS NOT NULL))))" name="vmsq_need_a_tag_or_a_ff"/>
      </table>
      <table name="merge_profile" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.merge_profile_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="owner" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="merge_profile_owner_fkey" implied="false" onDeleteCascade="true" schema="actor" table="org_unit"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="name" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="add_spec" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="replace_spec" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="5" name="strip_spec" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="6" name="preserve_spec" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="false" digits="0" id="7" name="update_bib_source" nullable="false" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="false" digits="0" id="8" name="update_bib_editor" nullable="false" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="9" name="lwm_ratio" nullable="true" remarks="" size="0" type="numeric" typeCode="2"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="merge_profile_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="vand_merge_prof_owner_name_idx" unique="true">
            <column ascending="true" name="owner"/>
            <column ascending="true" name="name"/>
         </index>
         <checkConstraint constraint="(((preserve_spec IS NOT NULL) OR (replace_spec IS NOT NULL) OR ((preserve_spec IS NULL) AND (replace_spec IS NULL))))" name="add_replace_strip_or_preserve"/>
      </table>
      <table name="queue" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.queue_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="owner" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="queue_owner_fkey" implied="false" onDeleteCascade="false" schema="actor" table="usr"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="name" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="false" digits="0" id="3" name="complete" nullable="false" remarks="" size="1" type="bool" typeCode="-7"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="match_set" nullable="true" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="queue_match_set_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="match_set"/>
         </column>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="queue_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
      </table>
      <table name="queued_authority_record" numRows="97" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.queued_record_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5">
            <child column="queued_record" foreignKey="authority_match_queued_record_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="authority_match"/>
            <child column="record" foreignKey="queued_authority_record_attr_record_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="queued_authority_record_attr"/>
         </column>
         <column autoUpdated="false" defaultValue="now()" digits="6" id="1" name="create_time" nullable="false" remarks="" size="35" type="timestamptz" typeCode="93"/>
         <column autoUpdated="false" defaultValue="null" digits="6" id="2" name="import_time" nullable="true" remarks="" size="35" type="timestamptz" typeCode="93"/>
         <column autoUpdated="false" defaultValue="'import'::text" digits="0" id="3" name="purpose" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="marc" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="0" digits="0" id="5" name="quality" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="6" name="queue" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="queued_authority_record_queue_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="authority_queue"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="7" name="imported_as" nullable="true" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="queued_authority_record_imported_as_fkey" implied="false" onDeleteCascade="false" schema="authority" table="record_entry"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="8" name="import_error" nullable="true" remarks="" size="2147483647" type="text" typeCode="12">
            <parent column="code" foreignKey="queued_authority_record_import_error_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="import_error"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="9" name="error_detail" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="queued_authority_record_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="queued_authority_record_queue_idx" unique="false">
            <column ascending="true" name="queue"/>
         </index>
         <checkConstraint constraint="((purpose = ANY (ARRAY['import'::text, 'overlay'::text])))" name="queued_record_purpose_check"/>
      </table>
      <table name="queued_authority_record_attr" numRows="97" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.queued_authority_record_attr_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="record" nullable="false" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="queued_authority_record_attr_record_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="queued_authority_record"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="field" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="queued_authority_record_attr_field_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="authority_attr_definition"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="attr_value" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="queued_authority_record_attr_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="queued_authority_record_attr_record_idx" unique="false">
            <column ascending="true" name="record"/>
         </index>
      </table>
      <table name="queued_bib_record" numRows="147" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.queued_record_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5">
            <child column="queued_record" foreignKey="lineitem_queued_record_fkey" implied="false" onDeleteCascade="false" schema="acq" table="lineitem"/>
            <child column="queued_record" foreignKey="bib_match_queued_record_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="bib_match"/>
            <child column="record" foreignKey="import_item_record_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="import_item"/>
            <child column="record" foreignKey="queued_bib_record_attr_record_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="queued_bib_record_attr"/>
         </column>
         <column autoUpdated="false" defaultValue="now()" digits="6" id="1" name="create_time" nullable="false" remarks="" size="35" type="timestamptz" typeCode="93"/>
         <column autoUpdated="false" defaultValue="null" digits="6" id="2" name="import_time" nullable="true" remarks="" size="35" type="timestamptz" typeCode="93"/>
         <column autoUpdated="false" defaultValue="'import'::text" digits="0" id="3" name="purpose" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="marc" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="0" digits="0" id="5" name="quality" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="6" name="queue" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="queued_bib_record_queue_fkey" implied="false" onDeleteCascade="true" schema="vandelay" table="bib_queue"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="7" name="bib_source" nullable="true" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="queued_bib_record_bib_source_fkey" implied="false" onDeleteCascade="false" schema="config" table="bib_source"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="8" name="imported_as" nullable="true" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="queued_bib_record_imported_as_fkey" implied="false" onDeleteCascade="false" schema="biblio" table="record_entry"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="9" name="import_error" nullable="true" remarks="" size="2147483647" type="text" typeCode="12">
            <parent column="code" foreignKey="queued_bib_record_import_error_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="import_error"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="10" name="error_detail" nullable="true" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="queued_bib_record_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="queued_bib_record_queue_idx" unique="false">
            <column ascending="true" name="queue"/>
         </index>
         <checkConstraint constraint="((purpose = ANY (ARRAY['import'::text, 'overlay'::text])))" name="queued_record_purpose_check"/>
      </table>
      <table name="queued_bib_record_attr" numRows="120" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.queued_bib_record_attr_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="record" nullable="false" remarks="" size="19" type="int8" typeCode="-5">
            <parent column="id" foreignKey="queued_bib_record_attr_record_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="queued_bib_record"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="field" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="queued_bib_record_attr_field_fkey" implied="false" onDeleteCascade="false" schema="vandelay" table="bib_attr_definition"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="attr_value" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="queued_bib_record_attr_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="queued_bib_record_attr_record_idx" unique="false">
            <column ascending="true" name="record"/>
         </index>
      </table>
      <table name="queued_record" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.queued_record_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="now()" digits="6" id="1" name="create_time" nullable="false" remarks="" size="35" type="timestamptz" typeCode="93"/>
         <column autoUpdated="false" defaultValue="null" digits="6" id="2" name="import_time" nullable="true" remarks="" size="35" type="timestamptz" typeCode="93"/>
         <column autoUpdated="false" defaultValue="'import'::text" digits="0" id="3" name="purpose" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="marc" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="0" digits="0" id="5" name="quality" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="queued_record_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <checkConstraint constraint="((purpose = ANY (ARRAY['import'::text, 'overlay'::text])))" name="queued_record_purpose_check"/>
      </table>
      <table name="session_tracker" numRows="17" remarks="" schema="vandelay" type="TABLE">
         <column autoUpdated="true" defaultValue="nextval('vandelay.session_tracker_id_seq'::regclass)" digits="0" id="0" name="id" nullable="false" remarks="" size="19" type="bigserial" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="session_key" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="name" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="3" name="usr" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="session_tracker_usr_fkey" implied="false" onDeleteCascade="false" schema="actor" table="usr"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="4" name="workstation" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <parent column="id" foreignKey="session_tracker_workstation_fkey" implied="false" onDeleteCascade="true" schema="actor" table="workstation"/>
         </column>
         <column autoUpdated="false" defaultValue="'bib'::text" digits="0" id="5" name="record_type" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="6" name="queue" nullable="false" remarks="" size="19" type="int8" typeCode="-5"/>
         <column autoUpdated="false" defaultValue="now()" digits="6" id="7" name="create_time" nullable="false" remarks="" size="35" type="timestamptz" typeCode="93"/>
         <column autoUpdated="false" defaultValue="now()" digits="6" id="8" name="update_time" nullable="false" remarks="" size="35" type="timestamptz" typeCode="93"/>
         <column autoUpdated="false" defaultValue="'active'::text" digits="0" id="9" name="state" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="'enqueue'::text" digits="0" id="10" name="action_type" nullable="false" remarks="" size="2147483647" type="text" typeCode="12"/>
         <column autoUpdated="false" defaultValue="0" digits="0" id="11" name="total_actions" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <column autoUpdated="false" defaultValue="0" digits="0" id="12" name="actions_performed" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="session_tracker_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <checkConstraint constraint="((action_type = ANY (ARRAY['upload'::text, 'enqueue'::text, 'import'::text])))" name="vand_tracker_valid_action_type"/>
         <checkConstraint constraint="((record_type = ANY (ARRAY['bib'::text, 'authority'::text])))" name="vand_tracker_valid_record_type"/>
         <checkConstraint constraint="((state = ANY (ARRAY['active'::text, 'error'::text, 'complete'::text])))" name="vand_tracker_valid_state"/>
      </table>
   </tables>
   <routines>
      <routine dataAccess="MODIFIES" deterministic="false" name="_get_expr_push_jrow(node vandelay.match_set_point, tags_rstore hstore, auth_heading text)" returnType="void" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="node" type="vandelay.match_set_point"/>
            <parameter mode="IN" name="tags_rstore" type="hstore"/>
            <parameter mode="IN" name="auth_heading" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="_get_expr_push_qrow(node vandelay.match_set_point)" returnType="void" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[DECLARE
BEGIN
    INSERT INTO _vandelay_tmp_qrows (q) VALUES (node.id);
END;]]></definition>
         <parameters>
            <parameter mode="IN" name="node" type="vandelay.match_set_point"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="_get_expr_render_one(node vandelay.match_set_point)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="node" type="vandelay.match_set_point"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="_node_tag_comparisons(caseless boolean, op text, tags_rstore hstore, tagkey text)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="caseless" type="boolean"/>
            <parameter mode="IN" name="op" type="text"/>
            <parameter mode="IN" name="tags_rstore" type="hstore"/>
            <parameter mode="IN" name="tagkey" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="add_field(target_xml text, source_xml text, field text)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="sql"><![CDATA[SELECT vandelay.add_field( $1, $2, $3, 0 );]]></definition>
         <parameters>
            <parameter mode="IN" name="target_xml" type="text"/>
            <parameter mode="IN" name="source_xml" type="text"/>
            <parameter mode="IN" name="field" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="add_field(target_xml text, source_xml text, field text, force_add integer)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plperlu"><![CDATA[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 $match = $3;
            $match =~ s/^\s*//; $match =~ s/\s*$//;
            $fields{$field} = { sf => [ split('', $sf) ] };
            if ($match) {
                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} = { sf => $msf, re => 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})) {
                            next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
                        }
                        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;]]></definition>
         <parameters>
            <parameter mode="IN" name="target_xml" type="text"/>
            <parameter mode="IN" name="source_xml" type="text"/>
            <parameter mode="IN" name="field" type="text"/>
            <parameter mode="IN" name="force_add" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_authority_queue(queue_id bigint)" returnType="SETOF bigint" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="sql"><![CDATA[SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );]]></definition>
         <parameters>
            <parameter mode="IN" name="queue_id" type="bigint"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_authority_queue(queue_id bigint, merge_profile_id integer)" returnType="SETOF bigint" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="queue_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_authority_record(import_id bigint, merge_profile_id integer)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_authority_record_with_best(import_id bigint, merge_profile_id integer, lwm_ratio_value_p numeric)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
            <parameter mode="IN" name="lwm_ratio_value_p" type="numeric"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_bib_queue(queue_id bigint)" returnType="SETOF bigint" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="sql"><![CDATA[SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );]]></definition>
         <parameters>
            <parameter mode="IN" name="queue_id" type="bigint"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_bib_queue(queue_id bigint, merge_profile_id integer)" returnType="SETOF bigint" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="queue_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_bib_queue_with_best(import_id bigint, merge_profile_id integer)" returnType="SETOF bigint" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="sql"><![CDATA[SELECT vandelay.auto_overlay_bib_queue_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_bib_queue_with_best(queue_id bigint, merge_profile_id integer, lwm_ratio_value numeric)" returnType="SETOF bigint" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="queue_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
            <parameter mode="IN" name="lwm_ratio_value" type="numeric"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_bib_record(import_id bigint, merge_profile_id integer)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_bib_record_with_best(import_id bigint, merge_profile_id integer)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="sql"><![CDATA[SELECT vandelay.auto_overlay_bib_record_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_bib_record_with_best(import_id bigint, merge_profile_id integer, lwm_ratio_value_p numeric)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
            <parameter mode="IN" name="lwm_ratio_value_p" type="numeric"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="auto_overlay_org_unit_copies(import_id bigint, merge_profile_id integer, lwm_ratio_value_p numeric)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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) > 0 LOOP
        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;

        -- 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;
        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;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
            <parameter mode="IN" name="lwm_ratio_value_p" type="numeric"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="cleanup_authority_marc()" returnType="trigger" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="cleanup_bib_marc()" returnType="trigger" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="compile_profile(incoming_xml text)" returnType="vandelay.compile_profile" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="incoming_xml" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="extract_rec_attrs(xml text)" returnType="hstore" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="sql"><![CDATA[SELECT vandelay.extract_rec_attrs( $1, (SELECT ARRAY_AGG(name) FROM config.record_attr_definition));]]></definition>
         <parameters>
            <parameter mode="IN" name="xml" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="extract_rec_attrs(xml text, attr_defs text[])" returnType="hstore" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="xml" type="text"/>
            <parameter mode="IN" name="attr_defs" type="text[]"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="find_bib_tcn_data(xml text)" returnType="SETOF vandelay.tcn_data" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="xml" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="flatten_marc(marc text)" returnType="SETOF vandelay.flat_marc" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="marc" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="flatten_marc_hstore(record_xml text)" returnType="hstore" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="record_xml" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="flay_marc(text)" returnType="SETOF vandelay.flat_marc" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plperlu"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="get_expr_from_match_set(match_set_id integer, tags_rstore hstore)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[BEGIN
    RETURN vandelay.get_expr_from_match_set(
        match_set_id, tags_rstore, NULL);
END;]]></definition>
         <parameters>
            <parameter mode="IN" name="match_set_id" type="integer"/>
            <parameter mode="IN" name="tags_rstore" type="hstore"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="get_expr_from_match_set(match_set_id integer, tags_rstore hstore, auth_heading text)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="match_set_id" type="integer"/>
            <parameter mode="IN" name="tags_rstore" type="hstore"/>
            <parameter mode="IN" name="auth_heading" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="get_expr_from_match_set_point(node vandelay.match_set_point, tags_rstore hstore, auth_heading text)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="node" type="vandelay.match_set_point"/>
            <parameter mode="IN" name="tags_rstore" type="hstore"/>
            <parameter mode="IN" name="auth_heading" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="ingest_authority_marc()" returnType="trigger" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="ingest_bib_items()" returnType="trigger" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="ingest_bib_marc()" returnType="trigger" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="ingest_items(import_id bigint, attr_def_id bigint)" returnType="SETOF vandelay.import_item" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="attr_def_id" type="bigint"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="marc21_extract_all_fixed_fields(marc text, use_default boolean DEFAULT false)" returnType="SETOF biblio.record_ff_map" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="marc" type="text"/>
            <parameter mode="IN" name="use_default" type="boolean"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="marc21_extract_fixed_field(marc text, ff text, use_default boolean DEFAULT false)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="marc" type="text"/>
            <parameter mode="IN" name="ff" type="text"/>
            <parameter mode="IN" name="use_default" type="boolean"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="marc21_extract_fixed_field_list(marc text, ff text, use_default boolean DEFAULT false)" returnType="text[]" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="marc" type="text"/>
            <parameter mode="IN" name="ff" type="text"/>
            <parameter mode="IN" name="use_default" type="boolean"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="marc21_physical_characteristics(marc text)" returnType="SETOF biblio.marc21_physical_characteristics" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="marc" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="marc21_record_type(marc text)" returnType="config.marc21_rec_type_map" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="marc" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="match_authority_record()" returnType="trigger" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="match_bib_record()" returnType="trigger" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="match_set_test_authxml(match_set_id integer, record_xml text)" returnType="SETOF vandelay.match_set_test_result" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="match_set_id" type="integer"/>
            <parameter mode="IN" name="record_xml" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="match_set_test_marcxml(match_set_id integer, record_xml text, bucket_id integer)" returnType="SETOF vandelay.match_set_test_result" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="match_set_id" type="integer"/>
            <parameter mode="IN" name="record_xml" type="text"/>
            <parameter mode="IN" name="bucket_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="measure_auth_record_quality(xml text, match_set_id integer)" returnType="integer" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="xml" type="text"/>
            <parameter mode="IN" name="match_set_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="measure_record_quality(xml text, match_set_id integer)" returnType="integer" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="xml" type="text"/>
            <parameter mode="IN" name="match_set_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="merge_record_xml(target_marc text, template_marc text)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="target_marc" type="text"/>
            <parameter mode="IN" name="template_marc" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="merge_record_xml(target_xml text, source_xml text, add_rule text, replace_preserve_rule text, strip_rule text)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="sql"><![CDATA[SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);]]></definition>
         <parameters>
            <parameter mode="IN" name="target_xml" type="text"/>
            <parameter mode="IN" name="source_xml" type="text"/>
            <parameter mode="IN" name="add_rule" type="text"/>
            <parameter mode="IN" name="replace_preserve_rule" type="text"/>
            <parameter mode="IN" name="strip_rule" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="merge_record_xml_using_profile(incoming_marc text, existing_marc text, merge_profile_id bigint)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="incoming_marc" type="text"/>
            <parameter mode="IN" name="existing_marc" type="text"/>
            <parameter mode="IN" name="merge_profile_id" type="bigint"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="overlay_authority_record(import_id bigint, eg_id bigint, merge_profile_id integer)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="eg_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="overlay_bib_record(import_id bigint, eg_id bigint, merge_profile_id integer)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="import_id" type="bigint"/>
            <parameter mode="IN" name="eg_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="replace_field(target_xml text, source_xml text, field text)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plperlu"><![CDATA[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 $match = $3;
            $match =~ s/^\s*//; $match =~ s/\s*$//;
            $fields{$field} = { sf => [ split('', $sf) ] };
            if ($match) {
                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} = { sf => $msf, re => 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;
        for my $target_field ($target_r->field($f)) {

            # field spec contains a regex for this field.  Confirm field on 
            # target record matches the specified regex before replacing.
            if (exists($fields{$f}{match})) {
                next unless (grep { $_ =~ $fields{$f}{match}{re} } 
                    $target_field->subfield($fields{$f}{match}{sf}));
            }

            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;]]></definition>
         <parameters>
            <parameter mode="IN" name="target_xml" type="text"/>
            <parameter mode="IN" name="source_xml" type="text"/>
            <parameter mode="IN" name="field" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="strip_field(xml text, field text)" returnType="text" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plperlu"><![CDATA[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 = grep { $to_field->subfield($_) =~ $fields{$f}{match}{$_} } keys %{$fields{$f}{match}};
                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;]]></definition>
         <parameters>
            <parameter mode="IN" name="xml" type="text"/>
            <parameter mode="IN" name="field" type="text"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="template_overlay_bib_record(v_marc text, eg_id bigint)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="sql"><![CDATA[SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);]]></definition>
         <parameters>
            <parameter mode="IN" name="v_marc" type="text"/>
            <parameter mode="IN" name="eg_id" type="bigint"/>
         </parameters>
      </routine>
      <routine dataAccess="MODIFIES" deterministic="false" name="template_overlay_bib_record(v_marc text, eg_id bigint, merge_profile_id integer)" returnType="boolean" securityType="INVOKER" type="FUNCTION">
         <comment/>
         <definition language="plpgsql"><![CDATA[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;]]></definition>
         <parameters>
            <parameter mode="IN" name="v_marc" type="text"/>
            <parameter mode="IN" name="eg_id" type="bigint"/>
            <parameter mode="IN" name="merge_profile_id" type="integer"/>
         </parameters>
      </routine>
   </routines>
</database>
